effector
Version:
Business logic with ease
997 lines (984 loc) • 34.6 kB
JavaScript
const REGION_NAME = '_internalHMRRegion';
function addFileNameIdentifier(addLoc, enableFileName, t, path, state) {
if (addLoc && !state.fileNameIdentifier) {
const fileName = enableFileName ? stripRoot(state.file.opts.root || '', state.filename || '', false) : '';
const fileNameIdentifier = path.scope.generateUidIdentifier('_effectorFileName');
// babel bug https://github.com/babel/babel/issues/9496
if (path.hub) {
const scope = path.hub.getScope();
if (scope) {
scope.push({
id: fileNameIdentifier,
init: t.stringLiteral(fileName)
});
}
}
state.fileNameIdentifier = fileNameIdentifier;
}
}
function addImport(t, path, method, importNamesMap) {
if (importNamesMap[method] !== null) {
return importNamesMap[method];
}
const programPath = findProgramPath(path);
const uid = programPath.scope.generateUidIdentifier(method);
const specifier = t.importSpecifier(uid, t.identifier(method));
if (importNamesMap.importDeclaration === null) {
const importDeclaration = t.importDeclaration([specifier], t.stringLiteral('effector'));
const [importPath] = programPath.unshiftContainer('body', importDeclaration);
importNamesMap.importDeclaration = importDeclaration;
importNamesMap.importDeclarationPath = importPath;
} else {
importNamesMap.importDeclaration.specifiers.push(specifier);
}
importNamesMap[method] = uid.name;
return uid.name;
}
function getImportedName(t, node) {
return t.isIdentifier(node.imported) ? node.imported.name : node.imported.value;
}
function applyMethodParsers(methodParsers, path, state, name) {
for (let i = 0; i < methodParsers.length; i++) {
const {
flag,
set,
fn
} = methodParsers[i];
if (flag && set.has(name)) {
fn(path, state, name, findCandidateNameForExpression(path));
}
}
}
function findCandidateNameForExpression(path) {
let id;
path.find(path => {
if (path.isAssignmentExpression()) {
id = path.node.left;
} else if (path.isObjectProperty()) {
id = path.node.key;
} else if (path.isVariableDeclarator()) {
id = path.node.id;
} else if (path.isStatement()) {
// we've hit a statement, we should stop crawling up
return true;
}
// we've got an id! no need to continue
if (id) return true;
return false;
});
return id;
}
function makeTrace(fileNameIdentifier, lineNumber, columnNumber, t) {
const fileLineLiteral = t.numericLiteral(lineNumber != null ? lineNumber : -1);
const fileColumnLiteral = t.numericLiteral(columnNumber != null ? columnNumber : -1);
const fileProperty = property(t, 'file', fileNameIdentifier);
const lineProperty = property(t, 'line', fileLineLiteral);
const columnProperty = property(t, 'column', fileColumnLiteral);
return t.objectExpression([fileProperty, lineProperty, columnProperty]);
}
function findLocArgs(path) {
let loc;
let args;
path.find(path => {
if (path.isCallExpression()) {
var _path$node$loc;
args = path.node.arguments;
loc = (_path$node$loc = path.node.loc) === null || _path$node$loc === void 0 ? void 0 : _path$node$loc.start;
return true;
}
return false;
});
return {
args,
loc
};
}
function setRestoreNameAfter(path, state, nameNodeId, t, {
addLoc,
addNames,
debugSids
}, checkBindingName) {
const displayName = nameNodeId ? nameNodeId.name : '';
if (isLocalVariable(path, checkBindingName)) return;
const {
args,
loc
} = findLocArgs(path);
if (args) {
if (!args[0]) return;
if (!args[1]) return;
const oldConfig = args[2];
const configExpr = args[2] = t.objectExpression([]);
const stableID = stringProperty(t, 'sid', generateStableID(state.file.opts.root, state.filename, displayName, loc.line, loc.column, debugSids));
if (oldConfig) {
configExpr.properties.push(property(t, 'and', oldConfig));
}
if (addLoc) {
const locProp = property(t, 'loc', makeTrace(state.fileNameIdentifier, loc.line, loc.column, t));
configExpr.properties.push(locProp);
}
if (displayName && addNames) {
configExpr.properties.push(stringProperty(t, 'name', displayName));
}
configExpr.properties.push(stableID);
}
}
function setStoreNameAfter(path, state, nameNodeId, t, {
addLoc,
addNames,
debugSids
}, fillFirstArg, checkBindingName) {
const displayName = nameNodeId ? nameNodeId.name : '';
if (isLocalVariable(path, checkBindingName)) return;
const {
args,
loc
} = findLocArgs(path);
if (args) {
if (!args[0]) {
if (!fillFirstArg) return;
args[0] = t.nullLiteral();
}
const oldConfig = args[1];
const configExpr = args[1] = t.objectExpression([]);
const stableID = stringProperty(t, 'sid', generateStableID(state.file.opts.root, state.filename, displayName, loc.line, loc.column, debugSids));
if (oldConfig) {
configExpr.properties.push(property(t, 'and', oldConfig));
}
if (addLoc) {
const locProp = property(t, 'loc', makeTrace(state.fileNameIdentifier, loc.line, loc.column, t));
configExpr.properties.push(locProp);
}
if (displayName && addNames) {
configExpr.properties.push(stringProperty(t, 'name', displayName));
}
configExpr.properties.push(stableID);
}
}
function isLocalVariable(path, checkBindingName) {
if (!checkBindingName) return false;
const binding = path.scope.getBinding(checkBindingName);
if (binding) return binding.kind !== 'module';
return false;
}
function setConfigForConfMethod(path, state, nameNodeId, t, {
addLoc,
addNames,
debugSids
}, singleArgument, checkBindingName, allowEmptyArguments) {
const displayName = nameNodeId ? nameNodeId.name : '';
if (isLocalVariable(path, checkBindingName)) return;
const {
args,
loc
} = findLocArgs(path);
if (args) {
if (!args[0] && !allowEmptyArguments) return;
const commonArgs = singleArgument ? args[0] : t.arrayExpression(args.slice());
args.length = 0;
const configExpr = t.objectExpression([]);
const stableID = stringProperty(t, 'sid', generateStableID(state.file.opts.root, state.filename, displayName, loc.line, loc.column, debugSids));
if (addLoc) {
const locProp = property(t, 'loc', makeTrace(state.fileNameIdentifier, loc.line, loc.column, t));
configExpr.properties.push(locProp);
}
if (displayName && addNames) {
configExpr.properties.push(stringProperty(t, 'name', displayName));
}
configExpr.properties.push(stableID);
args[0] = t.objectExpression([property(t, 'and', commonArgs), property(t, 'or', configExpr)]);
}
}
function setEventNameAfter(path, state, nameNodeId, t, {
addLoc,
addNames,
debugSids
}, checkBindingName) {
const displayName = nameNodeId ? nameNodeId.name : '';
if (isLocalVariable(path, checkBindingName)) return;
const {
args,
loc
} = findLocArgs(path);
if (args) {
const firstArgument = args[0];
if (!firstArgument) {
if (displayName) {
args[0] = t.stringLiteral(displayName);
}
}
const oldConfig = args[1];
const configExpr = args[firstArgument ? 1 : 0] = t.objectExpression([]);
const stableID = stringProperty(t, 'sid', generateStableID(state.file.opts.root, state.filename, displayName, loc.line, loc.column, debugSids));
if (oldConfig) {
configExpr.properties.push(property(t, 'and', oldConfig));
}
if (addLoc) {
const locProp = property(t, 'loc', makeTrace(state.fileNameIdentifier, loc.line, loc.column, t));
configExpr.properties.push(locProp);
}
if (displayName && addNames) {
configExpr.properties.push(stringProperty(t, 'name', displayName));
}
configExpr.properties.push(stableID);
}
}
function rememberLocalName(importedName, localName, creatorsList) {
for (const creators of creatorsList) {
if (creators.has(importedName)) {
creators.add(localName);
return;
}
}
}
function normalizeSource(source, rootPath, state) {
let normalizedSource = source;
if (normalizedSource.startsWith('.')) {
const {
resolve,
parse
} = require('path');
const currentFile = state.filename || '';
const {
dir
} = parse(currentFile);
const resolvedImport = resolve(dir, normalizedSource);
normalizedSource = stripRoot(rootPath, resolvedImport, true);
}
return stripExtension(normalizedSource);
}
function stripExtension(path) {
const {
extname
} = require('path');
const ext = extname(path);
if (ext.length > 0) {
path = path.slice(0, -ext.length);
}
return path;
}
function stripRoot(babelRoot, fileName, omitFirstSlash) {
const {
sep,
normalize
} = require('path');
const rawPath = (fileName || '').replace(babelRoot || '', '');
let normalizedSeq = normalize(rawPath).split(sep);
if (omitFirstSlash && normalizedSeq.length > 0 && normalizedSeq[0] === '') {
normalizedSeq = normalizedSeq.slice(1);
}
const normalizedPath = normalizedSeq.join('/');
return normalizedPath;
}
/**
* "foo src/index.js [12,30]"
*/
function generateStableID(babelRoot, fileName, varName, line, column, debugSids) {
const normalizedPath = stripRoot(babelRoot, fileName, false);
const appendix = debugSids ? `:${normalizedPath}:${varName}` : '';
return hashCode(`${varName} ${normalizedPath} [${line}, ${column}]`) + appendix;
}
function hashCode(s) {
let h = 0;
let i = 0;
if (s.length > 0) while (i < s.length) h = (h << 5) - h + s.charCodeAt(i++) | 0;
return h.toString(36);
}
function property(t, field, content) {
return t.objectProperty(t.identifier(field), content);
}
function stringProperty(t, field, value) {
return property(t, field, t.stringLiteral(value));
}
function findProgramPath(path) {
return path.find(path => path.isProgram());
}
const defaultFactories = ['@farfetched/core', '@effector/reflect', '@effector/reflect/ssr', '@effector/reflect/scope', 'atomic-router', '@withease/factories', 'effector-action', 'patronum' // there is also custom handling for patronum/{method} imports
];
function normalizeOptions(options) {
const defaults = options.noDefaults ? {
store: [],
event: [],
effect: [],
domain: [],
restore: [],
combine: [],
sample: [],
forward: [],
guard: [],
attach: [],
split: [],
createApi: [],
merge: [],
domainMethods: {
store: [],
event: [],
effect: [],
domain: []
},
reactMethods: {
createGate: []
},
factories: []
} : {
store: ['createStore'],
event: ['createEvent'],
effect: ['createEffect'],
domain: ['createDomain'],
restore: ['restore'],
combine: ['combine'],
sample: ['sample'],
forward: ['forward'],
guard: ['guard'],
attach: ['attach'],
split: ['split'],
createApi: ['createApi'],
merge: ['merge'],
domainMethods: {
store: ['store', 'createStore'],
event: ['event', 'createEvent'],
effect: ['effect', 'createEffect'],
domain: ['domain', 'createDomain']
},
reactMethods: {
createGate: ['createGate']
},
factories: defaultFactories
};
const fullConfig = readConfigFlags({
options,
properties: {
reactSsr: false,
transformLegacyDomainMethods: true,
forceScope: false,
filename: true,
stores: true,
events: true,
effects: true,
domains: true,
restores: true,
combines: true,
samples: true,
forwards: true,
guards: true,
attaches: true,
splits: true,
apis: true,
merges: true,
gates: true
},
result: {
importName: new Set(options.importName ? Array.isArray(options.importName) ? options.importName : [options.importName] : ['effector', 'effector/compat', 'effector-root', 'effector-root/compat', 'effector-logger', 'trail/runtime', '@effector/effector']),
importReactNames: {
ssr: new Set(readReactImportOption(options, 'ssr', ['effector-react/scope', 'effector-react/ssr'])),
nossr: new Set(readReactImportOption(options, 'nossr', ['effector-react', 'effector-react/compat']))
},
storeCreators: new Set(options.storeCreators || defaults.store),
eventCreators: new Set(options.eventCreators || defaults.event),
effectCreators: new Set(options.effectCreators || defaults.effect),
domainCreators: new Set(options.domainCreators || defaults.domain),
restoreCreators: new Set(options.restoreCreators || defaults.restore),
combineCreators: new Set(options.combineCreators || defaults.combine),
sampleCreators: new Set(options.sampleCreators || defaults.sample),
forwardCreators: new Set(options.forwardCreators || defaults.forward),
guardCreators: new Set(options.guardCreators || defaults.guard),
attachCreators: new Set(options.attachCreators || defaults.attach),
splitCreators: new Set(options.splitCreators || defaults.split),
apiCreators: new Set(options.apiCreators || defaults.createApi),
mergeCreators: new Set(options.mergeCreators || defaults.merge),
domainMethods: readConfigShape(options.domainMethods, defaults.domainMethods),
reactMethods: readConfigShape(options.reactMethods, defaults.reactMethods),
factories: [...(options.factories || []), ...defaults.factories].map(stripExtension),
addLoc: Boolean(options.addLoc),
debugSids: Boolean(options.debugSids),
forceScope: Boolean(options.forceScope),
hmr: options.hmr === 'none' ? false : options.hmr || false,
addNames: typeof options.addNames !== 'undefined' ? Boolean(options.addNames) : true
}
});
const creatorsList = [fullConfig.storeCreators, fullConfig.eventCreators, fullConfig.effectCreators, fullConfig.domainCreators, fullConfig.restoreCreators, fullConfig.combineCreators, fullConfig.sampleCreators, fullConfig.forwardCreators, fullConfig.guardCreators, fullConfig.attachCreators, fullConfig.splitCreators, fullConfig.apiCreators, fullConfig.mergeCreators];
return {
fullConfig,
creatorsList
};
}
function readReactImportOption(options, name, defaults) {
if (!options || !options.importReactNames) return defaults;
const {
importReactNames
} = options;
const value = importReactNames[name];
if (value) {
if (Array.isArray(value)) return value;else return [value];
}
return defaults;
}
function readConfigShape(shape = {}, defaults) {
const result = {};
for (const key in defaults) {
result[key] = readConfigArray(shape[key], defaults[key]);
}
return result;
}
function readConfigArray(array, defaults) {
return new Set(array || defaults);
}
function readConfigFlags({
options,
properties,
result
}) {
for (const property in properties) {
if (property in options) {
result[property] = Boolean(options[property]);
} else {
result[property] = properties[property];
}
}
return result;
}
function getMethodParsers(t, smallConfig, {
stores,
events,
effects,
domains,
restores,
combines,
samples,
forwards,
guards,
attaches,
splits,
apis,
merges,
gates,
storeCreators,
eventCreators,
effectCreators,
domainCreators,
restoreCreators,
combineCreators,
sampleCreators,
forwardCreators,
guardCreators,
attachCreators,
splitCreators,
apiCreators,
mergeCreators,
domainMethods,
reactMethods
}) {
const methodParsers = [{
flag: stores,
set: storeCreators,
fn: (path, state, name, id) => setStoreNameAfter(path, state, id, t, smallConfig, false, name)
}, {
flag: events,
set: eventCreators,
fn: (path, state, name, id) => setEventNameAfter(path, state, id, t, smallConfig, name)
}, {
flag: effects,
set: effectCreators,
fn: (path, state, name, id) => setEventNameAfter(path, state, id, t, smallConfig, name)
}, {
flag: domains,
set: domainCreators,
fn: (path, state, name, id) => setEventNameAfter(path, state, id, t, smallConfig, name)
}, {
flag: restores,
set: restoreCreators,
fn: (path, state, name, id) => setRestoreNameAfter(path, state, id, t, smallConfig, name)
}, {
flag: combines,
set: combineCreators,
fn: (path, state, name, id) => setConfigForConfMethod(path, state, id, t, smallConfig, false, name)
}, {
flag: samples,
set: sampleCreators,
fn: (path, state, name, id) => setConfigForConfMethod(path, state, id, t, smallConfig, false, name)
}, {
flag: forwards,
set: forwardCreators,
fn: (path, state, name, id) => setConfigForConfMethod(path, state, id, t, smallConfig, true, name)
}, {
flag: guards,
set: guardCreators,
fn: (path, state, name, id) => setConfigForConfMethod(path, state, id, t, smallConfig, false, name)
}, {
flag: attaches,
set: attachCreators,
fn: (path, state, name, id) => setConfigForConfMethod(path, state, id, t, smallConfig, true, name)
}, {
flag: splits,
set: splitCreators,
fn: (path, state, name, id) => setConfigForConfMethod(path, state, null, t, smallConfig, false, name)
}, {
flag: apis,
set: apiCreators,
fn: (path, state, name, id) => setConfigForConfMethod(path, state, null, t, smallConfig, false, name)
}, {
flag: merges,
set: mergeCreators,
fn: (path, state, name, id) => setStoreNameAfter(path, state, id, t, smallConfig, false, name)
}];
const domainMethodParsers = [{
flag: stores,
set: domainMethods.store,
fn: (path, state, name, id) => setStoreNameAfter(path, state, id, t, smallConfig, false)
}, {
flag: events,
set: domainMethods.event,
fn: (path, state, name, id) => setEventNameAfter(path, state, id, t, smallConfig)
}, {
flag: effects,
set: domainMethods.effect,
fn: (path, state, name, id) => setEventNameAfter(path, state, id, t, smallConfig)
}, {
flag: domains,
set: domainMethods.domain,
fn: (path, state, name, id) => setEventNameAfter(path, state, id, t, smallConfig)
}];
const reactMethodParsers = [{
flag: gates,
set: reactMethods.createGate,
fn: (path, state, name, id) => setConfigForConfMethod(path, state, id, t, smallConfig, false, name, true)
}];
return {
methodParsers,
domainMethodParsers,
reactMethodParsers
};
}
function transformReactHooks(t, name, reactMethodParsers, forceScope, path, state) {
applyMethodParsers(reactMethodParsers, path, state, name);
transformReactForceScope(t, name, forceScope, path, state);
}
function transformReactForceScope(t, name, forceScope, path, state) {
if (!forceScope || !state.effector_forceScopeSpecifiers.has(name)) return;
const binding = path.scope.getBinding(name);
if (!binding || !t.isImportSpecifier(binding.path.node)) return;
const hookName = getImportedName(t, binding.path.node);
switch (hookName) {
case 'useEvent':
case 'useStore':
case 'useUnit':
{
if (!hasSecondArgument(path)) {
pushForceScopeToConfig(t, path);
}
break;
}
case 'useList':
{
if (!hasThirdArgument(path)) {
pushForceScopeToConfig(t, path);
}
break;
}
case 'useGate':
{
if (!hasThirdArgument(path)) {
// If no props passed
if (!hasSecondArgument(path)) {
path.node.arguments.push(t.objectExpression([]));
}
// Add forceScope: true
pushForceScopeToConfig(t, path);
}
break;
}
case 'useStoreMap':
{
if (hasSecondArgument(path)) {
const config = convertArgumentsToConfig(t, path, ['store', 'fn']);
path.node.arguments = [config];
// Add keys: []
config.properties.push(property(t, 'keys', t.arrayExpression()));
// Add forceScope: true
config.properties.push(property(t, 'forceScope', t.booleanLiteral(true)));
} else {
const firstArg = path.node.arguments[0];
if (t.isObjectExpression(firstArg) && !hasPropertyInConfig(t, firstArg, 'forceScope')) {
firstArg.properties.push(property(t, 'forceScope', t.booleanLiteral(true)));
}
}
break;
}
}
}
function hasSecondArgument(path) {
return path.node.arguments.length >= 2;
}
function pushForceScopeToConfig(t, path) {
path.node.arguments.push(t.objectExpression([property(t, 'forceScope', t.booleanLiteral(true))]));
}
function hasThirdArgument(path) {
return path.node.arguments.length >= 3;
}
function convertArgumentsToConfig(t, path, argumentsAsKeys) {
const objectProperties = argumentsAsKeys.map((keyName, index) => t.objectProperty(t.identifier(keyName), t.cloneNode(path.node.arguments[index])));
return t.objectExpression(objectProperties);
}
function hasPropertyInConfig(t, configNode, propertyName) {
return configNode.properties.some(property => !t.isSpreadElement(property) && t.isIdentifier(property.key) && property.key.name === propertyName);
}
function transformFactory(t, path, state, factoriesUsed, debugSids, addLoc, addNames, factoryTemplate) {
const node = path.node;
const isTaggedTemplate = t.isTaggedTemplateExpression(node);
const callee = isTaggedTemplate ? node.tag : node.callee;
if (!t.isIdentifier(callee)) return;
const name = callee.name;
if (!factoriesUsed || node.effector_isFactory || !state.effector_factoryMap.has(name)) {
return;
}
const withFactoryImportName = addImport(t, path, 'withFactory', state.effector_importNames);
const {
importedName
} = state.effector_factoryMap.get(name);
node.effector_isFactory = true;
const idNode = findCandidateNameForExpression(path);
const resultName = idNode ? idNode.name : '';
const nodeFinder = isTaggedTemplate ? t.isTaggedTemplateExpression : t.isCallExpression;
let loc;
path.find(path => {
if (nodeFinder(path.node)) {
if (path.node.loc) {
loc = path.node.loc.start;
}
return true;
}
return false;
});
const sid = generateStableID(state.file.opts.root, state.filename, resultName, loc.line, loc.column, debugSids);
const factoryConfig = {
SID: JSON.stringify(sid),
FN: node,
FACTORY: withFactoryImportName
};
if (addLoc || addNames) {
factoryConfig.NAME = JSON.stringify(!resultName || resultName === '' ? 'none' : resultName);
factoryConfig.METHOD = JSON.stringify(importedName);
}
if (addLoc) {
factoryConfig.LOC = makeTrace(state.fileNameIdentifier, loc.line, loc.column, t);
}
path.replaceWith(factoryTemplate(factoryConfig));
}
function processLegacyDomainHooks(t, domainMethodParsers, transformLegacyDomainMethods, path, state) {
if (transformLegacyDomainMethods && t.isMemberExpression(path.node.callee) && 'name' in path.node.callee.property) {
applyMethodParsers(domainMethodParsers, path, state, path.node.callee.property.name);
}
}
const DEFAULT_WATCHED_CALLS = ['map', 'filter', 'filterMap', 'subscribe', 'on', 'watch', 'reset', 'prepend'];
const SUPPORTED_NODES = ['FunctionDeclaration', 'ArrowFunctionExpression', 'ExportDefaultDeclaration', 'ClassDeclaration'];
function transformHmr(t, path, factories, importNamesMap, hmrMode, createHMRRegion, createWithRegion, createHotCode) {
const watchedFactories = new Set(DEFAULT_WATCHED_CALLS);
path.traverse({
ImportDeclaration(declaration) {
const source = declaration.node.source.value;
const specifiers = declaration.node.specifiers.map(specifier => specifier.local.name);
if (!['effector', ...factories].includes(source)) {
return;
}
for (const specifier of specifiers) {
watchedFactories.add(specifier);
}
if (source !== 'effector') {
return;
}
},
CallExpression(path) {
applyHMRTransform(t, path, path.node.callee, watchedFactories, importNamesMap, hmrMode, createHMRRegion, createWithRegion, createHotCode);
},
TaggedTemplateExpression(path) {
applyHMRTransform(t, path, path.node.tag, watchedFactories, importNamesMap, hmrMode, createHMRRegion, createWithRegion, createHotCode);
}
});
}
function applyHMRTransform(t, path, callee, watchedFactories, importNamesMap, hmrMode, createHMRRegion, createWithRegion, createHotCode) {
if (!isSupportHMR(path, callee, watchedFactories)) {
return;
}
if (!importNamesMap.hmrRegionInserted) {
const createNodeName = addImport(t, path, 'createNode', importNamesMap);
const regionNode = createHMRRegion({
CREATE_NODE: createNodeName,
REGION_NAME
});
/** guaranteed to be initialized after addImport call */
importNamesMap.importDeclarationPath.insertAfter(regionNode);
importNamesMap.hmrRegionInserted = true;
}
if (!importNamesMap.hmrCodeInserted) {
const hotCode = createHotCode(importNamesMap, path, hmrMode);
const programPath = findProgramPath(path);
programPath.pushContainer('body', hotCode);
importNamesMap.hmrCodeInserted = true;
}
path.replaceWith(createWithRegion({
WITH_REGION: addImport(t, path, 'withRegion', importNamesMap),
REGION_NAME,
FN: path.node
}));
}
function isSupportHMR(path, callee, watchedFactories) {
var _property;
/** calls like `[foo]()`, who the hell even does that? */
if (!('name' in callee) && !(callee.property && callee.property.name)) {
return false;
}
const name = callee.name || ((_property = callee.property) === null || _property === void 0 ? void 0 : _property.name);
if (!watchedFactories.has(name)) {
return false;
}
return !path.findParent(parent => parent && SUPPORTED_NODES.includes(parent.node.type));
}
function createFactoryTemplate(template, addLoc, addNames) {
let factoryTemplate = null;
return args => {
if (!factoryTemplate) {
factoryTemplate = template(addLoc ? 'FACTORY({sid: SID,fn:()=>FN,name:NAME,method:METHOD,loc:LOC})' : addNames ? 'FACTORY({sid: SID,fn:()=>FN,name:NAME,method:METHOD})' : 'FACTORY({sid: SID,fn:()=>FN})');
}
return factoryTemplate(args);
};
}
function createWithRegionTemplate(template) {
let withRegionTemplate = null;
return args => {
if (!withRegionTemplate) {
withRegionTemplate = template('WITH_REGION(REGION_NAME, () => FN)');
}
return withRegionTemplate(args);
};
}
function createHMRRegionTemplate(template) {
let hmrRegionTemplate = null;
return args => {
if (!hmrRegionTemplate) {
hmrRegionTemplate = template('const REGION_NAME = CREATE_NODE({regional: true})');
}
return hmrRegionTemplate(args);
};
}
function createHotCodeTemplate(template, t) {
let hotProperty;
let hotCodeTemplate = null;
return (importNamesMap, declaration, hmrMode) => {
if (!hotProperty) {
hotProperty = template.expression.ast(hmrMode === 'cjs' ? 'module.hot' : '(import.meta.hot || import.meta.webpackHot)');
}
if (!hotCodeTemplate) {
hotCodeTemplate = template(`
if (HOT_PROPERTY) {
HOT_PROPERTY.dispose(() => CLEAR_NODE(REGION_NAME));
} else {
console.warn('[effector hmr] HMR is not available in current environment.');
}
`);
}
return hotCodeTemplate({
HOT_PROPERTY: hotProperty,
CLEAR_NODE: addImport(t, declaration, 'clearNode', importNamesMap),
REGION_NAME
});
};
}
function effectorBabelPlugin(babel, options = {}) {
const {
fullConfig,
creatorsList
} = normalizeOptions(options);
const {
addNames,
addLoc,
debugSids,
transformLegacyDomainMethods,
hmr,
filename: enableFileName,
reactMethods,
factories,
importName,
importReactNames,
reactSsr,
forceScope
} = fullConfig;
if (reactSsr) {
console.error('[effector/babel-plugin]: reactSsr option is deprecated, use imports from "effector-react" without aliases or /scope');
}
const factoriesUsed = factories.length > 0;
const hasRelativeFactories = factories.some(fab => fab.startsWith('./') || fab.startsWith('../'));
const smallConfig = {
addLoc,
addNames,
debugSids
};
const {
types: t,
template
} = babel;
const factoryTemplate = createFactoryTemplate(template, addLoc, addNames);
const hmrRegionTemplate = createHMRRegionTemplate(template);
const withRegionTemplate = createWithRegionTemplate(template);
const hotCodeTemplate = createHotCodeTemplate(template, t);
const {
methodParsers,
reactMethodParsers,
domainMethodParsers
} = getMethodParsers(t, smallConfig, fullConfig);
const importVisitor = {
ImportDeclaration(path, state) {
const source = path.node.source.value;
const specifiers = path.node.specifiers;
if (importName.has(source)) {
for (let i = 0; i < specifiers.length; i++) {
const s = specifiers[i];
if (!t.isImportSpecifier(s)) continue;
const importedName = getImportedName(t, s);
const localName = s.local.name;
if (importedName === localName) continue;
rememberLocalName(importedName, localName, creatorsList);
}
} else if (importReactNames.ssr.has(source) || importReactNames.nossr.has(source)) {
for (let i = 0; i < specifiers.length; i++) {
const s = specifiers[i];
if (!t.isImportSpecifier(s)) continue;
const importedName = getImportedName(t, s);
const localName = s.local.name;
// effector-react/scope already forced scope
if (importReactNames.nossr.has(source)) {
this.effector_forceScopeSpecifiers.add(localName);
}
if (importedName === localName) continue;
if (reactMethods.createGate.has(importedName)) {
reactMethods.createGate.add(localName);
}
}
} else {
for (let i = 0; i < specifiers.length; i++) {
const s = specifiers[i];
if (t.isImportNamespaceSpecifier(s)) continue;
const localName = s.local.name;
if (creatorsList.some(set => set.has(localName))) {
this.effector_ignoredImports.add(localName);
}
}
}
if (reactSsr) {
if (source === 'effector-react' || source === 'effector-react/compat') {
path.node.source.value = 'effector-react/scope';
}
}
if (factoriesUsed) {
const rootPath = state.file.opts.root || '';
if (!this.effector_factoryPaths) {
if (hasRelativeFactories) {
const {
resolve
} = require('path');
this.effector_factoryPaths = factories.map(fab => {
if (fab.startsWith('./') || fab.startsWith('../')) {
const resolvedFab = resolve(rootPath, fab);
return stripRoot(rootPath, resolvedFab, true);
}
return fab;
});
} else {
this.effector_factoryPaths = factories;
}
}
const normalizedSource = normalizeSource(source, rootPath, state);
if (this.effector_factoryPaths.includes(normalizedSource) ||
// custom handling for patronum/{method} imports
normalizedSource.startsWith('patronum/')) {
this.effector_needFactoryImport = true;
for (let i = 0; i < specifiers.length; i++) {
const s = specifiers[i];
if (t.isImportNamespaceSpecifier(s)) continue;
const importedName = t.isImportDefaultSpecifier(s) ? 'default' : getImportedName(t, s);
const localName = s.local.name;
this.effector_factoryMap.set(localName, {
localName,
importedName,
source: normalizedSource
});
}
}
}
}
};
const plugin = {
name: 'effector/babel-plugin',
pre() {
this.effector_ignoredImports = new Set();
this.effector_forceScopeSpecifiers = new Set();
this.effector_factoryMap = new Map();
this.effector_importNames = {
withFactory: null,
clearNode: null,
createNode: null,
withRegion: null,
importDeclaration: null,
importDeclarationPath: null,
hmrRegionInserted: false,
hmrCodeInserted: false
};
},
post() {
this.effector_ignoredImports.clear();
this.effector_factoryMap.clear();
this.effector_needFactoryImport = false;
this.effector_importNames = {
withFactory: null,
clearNode: null,
createNode: null,
withRegion: null,
importDeclaration: null,
importDeclarationPath: null,
hmrRegionInserted: false,
hmrCodeInserted: false
};
if (this.effector_factoryPaths) {
delete this.effector_factoryPaths;
}
},
visitor: {
Program: {
enter(path, state) {
if (hmr) {
var _state$file$opts$call;
const hmrMode = hmr === 'cjs' ? 'cjs' : hmr === 'es' ? 'es' : !!((_state$file$opts$call = state.file.opts.caller) !== null && _state$file$opts$call !== void 0 && _state$file$opts$call.supportsStaticESM) ? 'es' : 'cjs';
transformHmr(t, path, factories, this.effector_importNames, hmrMode, hmrRegionTemplate, withRegionTemplate, hotCodeTemplate);
}
path.traverse(importVisitor, state);
}
},
CallExpression(path, state) {
addFileNameIdentifier(addLoc, enableFileName, t, path, state);
processLegacyDomainHooks(t, domainMethodParsers, transformLegacyDomainMethods, path, state);
const name = getNonIgnoredCalee(t, path, state);
if (name) {
transformReactHooks(t, name, reactMethodParsers, forceScope, path, state);
processEffectorCommonMethods(name, methodParsers, path, state);
}
transformFactory(t, path, state, factoriesUsed, debugSids, addLoc, addNames, factoryTemplate);
},
TaggedTemplateExpression(path, state) {
addFileNameIdentifier(addLoc, enableFileName, t, path, state);
transformFactory(t, path, state, factoriesUsed, debugSids, addLoc, addNames, factoryTemplate);
}
}
};
return plugin;
}
function getNonIgnoredCalee(t, path, state) {
if (!t.isIdentifier(path.node.callee)) return null;
const name = path.node.callee.name;
if (state.effector_ignoredImports.has(name)) return null;
return name;
}
function processEffectorCommonMethods(name, methodParsers, path, state) {
applyMethodParsers(methodParsers, path, state, name);
}
module.exports = effectorBabelPlugin;
//# sourceMappingURL=babel-plugin.js.map
;