babel-plugin-transform-amd-system-wrapper
Version:
Wraps AMD scripts into System.registerDynamic(...
462 lines (398 loc) • 22 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = function (_ref) {
var t = _ref.types;
var buildTemplate = (0, _babelTemplate2.default)('\n SYSTEM_GLOBAL.registerDynamic(MODULE_ID, [DEPS], false, BODY);\n ');
var buildFactory = (0, _babelTemplate2.default)('\n (function($__require, $__exports, $__module) {\n MODULE_URI\n BODY;\n })\n ');
var buildFactoryEs = (0, _babelTemplate2.default)('\n (function($__require, $__exports, $__module) {\n var $__moduleValue = (function() {\n MODULE_URI\n BODY;\n })() || $__module.exports;\n Object.defineProperty($__moduleValue, \'__esModule\', {\n value: true\n });\n return $__moduleValue;\n });\n ');
var buildFactoryTypeCheck = (0, _babelTemplate2.default)('\n FACTORY_DECLARATION\n if (typeof FACTORY_REFERENCE === TYPE) {\n return FACTORY_CALL;\n } else {\n return FACTORY_REFERENCE;\n }\n ');
var buildFactoryExpressionDeclaration = (0, _babelTemplate2.default)('\n var $__factory = FACTORY_EXPRESSION;\n ');
var buildModuleURIBinding = (0, _babelTemplate2.default)('\n $__module.uri = $__module.id;\n ');
return {
pre: function pre() {
this.amdDeps = [];
this.anonDefine = false;
this.anonDefineIndex = -1;
this.anonNamed = false;
this.bundleDefines = [];
},
post: function post() {
// Save filtered amd dependencies in the file metadata
this.file.metadata.amdDeps = this.amdDeps;
this.file.metadata.anonNamed = this.anonNamed;
},
visitor: {
CallExpression: {
enter: function enter(path, _ref2) {
var _this = this;
var _ref2$opts = _ref2.opts,
opts = _ref2$opts === undefined ? {} : _ref2$opts;
var that = this;
// don't perform nested transformations
if (this.outerModuleDefinition) {
return;
}
var callee = path.node.callee;
var args = path.node.arguments;
// match define(function(require) {})
// match define(['dep1'], function(dep1) {})
// match define('moduleName', function(dep1) {})
// match define('moduleName', ['dep1'], function(dep1) {})
if (t.isIdentifier(callee, {
name: 'define'
}) && !path.scope.hasBinding('define') && args.length >= 1 && args.length <= 3) {
// remember the outer `define()` to avoid transforming the inner ones
this.outerModuleDefinition = path;
if (opts.filterMode) {
var name = void 0,
depArg = -1;
if (t.isStringLiteral(args[0]) || args[1] && t.isArrayExpression(args[1])) {
name = args[0].value || true; // if args[0] is a string use it's literal value, else use 'true'
if (args[1] && t.isArrayExpression(args[1])) {
depArg = 1; // deps array is at args index 1
}
} else if (t.isArrayExpression(args[0])) {
depArg = 0; // anonymous define => deps array is at args index 0
}
var factoryArg = name && depArg === -1 ? 1 : depArg + 1; // If no deps array is present, the factory must be at index 1. Else it must be at index depArg + 1.
if (!args[factoryArg]) {
return; // if no factory param is present, do nothing.
}
// note the define index
// so we know which one to name for the second pass
if (!this.anonDefine || this.anonNamed) this.anonDefineIndex++;
var parseamdDeps = false;
// anonymous define
if (!name) {
if (this.anonDefine && !this.anonNamed) throw new Error('Multiple anonymous defines.');
this.anonDefine = true;
this.anonNamed = false;
parseamdDeps = true;
}
// named define
else {
if (typeof name !== 'boolean') {
this.bundleDefines.push(name);
this.amdDeps.splice(this.amdDeps.indexOf(name), 1);
}
if (!this.anonDefine && this.anonDefineIndex === 0 && typeof name !== 'boolean') {
this.anonDefine = true;
this.anonNamed = true;
parseamdDeps = true;
} else if (this.anonDefine && this.anonNamed) {
this.anonDefine = false;
this.anonNamed = false;
this.amdDeps = [];
}
}
if (!parseamdDeps) {
return;
}
if (depArg !== -1 && args[depArg].elements) {
args[depArg].elements.forEach(function (dep) {
if (['require', 'exports', 'module'].indexOf(dep.value) != -1) return;
if (that.bundleDefines.indexOf(dep.value) != -1) return;
that.amdDeps.push(dep.value);
});
} else if (depArg === -1 && t.isFunctionExpression(args[factoryArg])) {
(function () {
var cjsFactory = args[factoryArg],
fnParameters = args[factoryArg].params,
requireParam = fnParameters[0];
if (requireParam) {
var filterAMDamdDeps = {
// Visitor to determine all required dependencies if no this.amdDeps array is provided but a factory function with present `require` param.
CallExpression: function CallExpression(path) {
var callee = path.node.callee;
var args = path.node.arguments;
// Get function scope of defineFactory where param is used
if (path.scope.bindings[requireParam.name] && path.scope.bindings[requireParam.name].identifier === requireParam && t.isIdentifier(callee, {
name: requireParam.name
})) {
that.amdDeps.push(args[0].value);
}
}
};
path.traverse(filterAMDamdDeps);
}
})();
}
} else {
var callParams;
var requireParamIndex;
(function () {
var _deps;
var moduleName = null,
deps = [],
factoryArg = null,
isExportsInDeps = false,
isModuleInDepsOrInFactoryParam = false;
if (args.length === 1) {
// first param is factory object/function
factoryArg = args[0];
// parse factory params
if (t.isFunctionExpression(factoryArg)) {
factoryArg.params.forEach(function (factoryParam) {
if (t.isIdentifier(factoryParam) && factoryParam.name === 'module') {
isModuleInDepsOrInFactoryParam = true;
}
});
}
} else if (args.length === 2) {
// first param is either module name or dependency array
if (t.isStringLiteral(args[0])) {
moduleName = args[0];
} else if (t.isArrayExpression(args[0])) {
deps = args[0];
}
// second param is factory object/function
factoryArg = args[1];
} else {
// first param is module name
moduleName = args[0];
// second param is dependency array
deps = args[1];
// third param is factory object/function
factoryArg = args[2];
}
// Call params used for the define factories wrapped in IIFEs
callParams = [];
requireParamIndex = -1;
if (t.isArrayExpression(deps)) {
// Test if 'exports' exists as dependency: define(['exports'], function(exports) {})
isExportsInDeps = deps.elements.filter(function (param) {
return param.value === 'exports';
}).length > 0;
isModuleInDepsOrInFactoryParam = deps.elements.filter(function (param) {
return param.value === 'module';
}).length > 0;
deps.elements.forEach(function (param) {
if (['require', 'module', 'exports'].indexOf(param.value) !== -1) {
// Add all special identifiers in it's correct order and bind the to the param identifiers of the System.registerDynamic factory.
callParams.push(t.identifier('$__' + param.value));
if (param.value === 'require') {
// Save the index of the factory's require param.
requireParamIndex = callParams.length - 1;
}
} else {
callParams.push(t.callExpression(t.identifier('$__require'), [param]));
}
});
// Removal of all special identifiers from the dependency list, which are provided by the System.registerDynamic's factory.
deps = deps.elements.filter(function (param) {
return ['require', 'module', 'exports'].indexOf(param.value) === -1;
});
}
var newDeps = [];
// Handle CommonJS-style factories
if (deps.length === 0 && t.isFunctionExpression(factoryArg)) {
var detectThisAssignments = {
// Visitor to determine all assignments to `this` members in a cjs factory function. If so, `exports` must be used as `thisBindingExpression`.
ThisExpression: function ThisExpression(path) {
var currentPath = path;
// Traverse upwards until an assignment expression is reached
while (!t.isAssignmentExpression(currentPath.node) && currentPath.parentPath) {
currentPath = currentPath.parentPath;
}
if (t.isAssignmentExpression(currentPath.node)) {
var leftAssignmentOperand = currentPath.node.left;
if (t.isMemberExpression(leftAssignmentOperand)) {
leftAssignmentOperand = leftAssignmentOperand.object;
// Get base operand of member expression
while (leftAssignmentOperand.object) {
leftAssignmentOperand = leftAssignmentOperand.object;
}
}
}
}
};
// We need to traverse over the complete path, since we didn't get the path of the cjs factory here.
// TODO: Find a way to provide path of the cjs factory!!!
path.traverse(detectThisAssignments, {});
// Iterate over each param of the cjs factory
factoryArg.params.forEach(function (param, index) {
switch (index) {
// require
case 0:
var filterAMDDeps = {
// Visitor to determine all required dependencies if no DEPS array is provided but a factory function with present require param.
CallExpression: function CallExpression(path) {
var callee = path.node.callee;
var args = path.node.arguments;
// Get function scope of defineFactory where param is used
if (path.scope.bindings[param.name] && path.scope.bindings[param.name].identifier === param && t.isIdentifier(callee, {
name: param.name
})) {
newDeps.push(args[0]);
}
}
};
// We need to traverse over the complete path, since we didn't get the path of defineFactore here
// TODO: Find a way to provide path of the cjs factory!!!
path.traverse(filterAMDDeps, {});
// Push $__require to call params if used as factory default param
callParams.push(t.identifier('$__require'));
break;
// exports
case 1:
// Push $__require to call params if used as factory default param
callParams.push(t.identifier('$__exports'));
// Boolean flag which indicates that ```exports``` is present as factory param
isExportsInDeps = true;
break;
// module
case 2:
// Push $__require to call params if used as factory default param
callParams.push(t.identifier('$__module'));
break;
}
});
}
if (requireParamIndex !== -1 && t.isFunctionExpression(factoryArg)) {
var remapRequiredDeps = {
CallExpression: function CallExpression(path) {
var callee = path.node.callee;
var args = path.node.arguments;
if (t.isIdentifier(callee, {
name: factoryArg.params[requireParamIndex] && factoryArg.params[requireParamIndex].name
}) && t.isStringLiteral(args[0])) {
if (typeof opts.map === 'function') {
args[0].value = opts.map(args[0].value);
}
}
}
};
path.traverse(remapRequiredDeps, {});
}
var factoryReferenceIdentifier = void 0,
factoryAsExpressionDeclaration = void 0;
var thisBindingExpression = isExportsInDeps ? t.identifier('$__exports') : t.thisExpression();
if (t.isFunctionExpression(factoryArg)) {
// If factory is passed as function argument
var call = t.memberExpression(t.parenthesizedExpression(factoryArg), t.identifier('call'));
factoryArg = t.callExpression(call, [thisBindingExpression].concat(_toConsumableArray(callParams)));
} else if (t.isIdentifier(factoryArg)) {
// If factory is passed as identifier argument
factoryReferenceIdentifier = t.identifier(factoryArg.name);
var _call = t.memberExpression(factoryArg, t.identifier('call'));
factoryArg = t.callExpression(_call, [thisBindingExpression].concat(_toConsumableArray(callParams)));
} else if (!t.isObjectExpression(factoryArg)) {
// If factory is passed as expression argument
factoryReferenceIdentifier = t.identifier('$__factory');
factoryAsExpressionDeclaration = buildFactoryExpressionDeclaration({
FACTORY_EXPRESSION: t.parenthesizedExpression(factoryArg)
});
var _call2 = t.memberExpression(factoryReferenceIdentifier, t.identifier('call'));
factoryArg = t.callExpression(_call2, [thisBindingExpression].concat(_toConsumableArray(callParams)));
}
var factoryTypeTestNeeded = false;
if ((!moduleName || moduleName && deps.length === 0) && factoryReferenceIdentifier) {
// Wraps the factory in a ```if (typeof factory === 'function') {}``` test only a factory reference is present
factoryTypeTestNeeded = true;
factoryArg = buildFactoryTypeCheck({
FACTORY_REFERENCE: factoryReferenceIdentifier,
FACTORY_CALL: factoryArg,
TYPE: t.stringLiteral('function'),
FACTORY_DECLARATION: factoryAsExpressionDeclaration || null
});
}
var esModule = opts.esModule;
if (esModule) {
path.traverse({
StringLiteral: function StringLiteral(path) {
if (this.functionDepth < 2 && path.node.value === '__esModule') {
esModule = false;
path.stop();
}
},
MemberExpression: function MemberExpression(path) {
if (this.functionDepth < 2 && path.node.property.name === '__esModule') {
esModule = false;
path.stop();
}
},
ObjectProperty: function ObjectProperty(path) {
if (this.functionDepth < 2 && path.node.key.name === '__esModule') {
esModule = false;
path.stop();
}
},
Scope: {
enter: function enter(path) {
if (t.isFunction(path.scope.block)) this.functionDepth++;
if (this.functionDepth > 2) path.skip();
},
exit: function exit(path) {
if (t.isFunction(path.scope.block)) this.functionDepth--;
}
}
}, { functionDepth: 0 });
}
var factory = (esModule ? buildFactoryEs : buildFactory)({
MODULE_URI: isModuleInDepsOrInFactoryParam ? buildModuleURIBinding() : null,
BODY: factoryTypeTestNeeded ? factoryArg : t.returnStatement(factoryArg)
});
// Concat with required depencies array which contains string literals if factory default params are used
deps = (_deps = deps).concat.apply(_deps, newDeps);
if (opts.deps) {
// Concat all dependencies passed via the plugin's' options.
deps = deps.concat(opts.deps.map(function (dep) {
return t.stringLiteral(dep);
}).filter(function (loadDep) {
// Filter out already contained dependencies
return deps.filter(function (dep) {
return dep.value === loadDep.value;
}).length === 0;
}));
}
// Map dependencies using the dependency mapping function hook
if (typeof opts.map === 'function') {
deps.forEach(function (e) {
e.value = opts.map(e.value);
});
}
var moduleId = _this.getModuleName();
var systemRegister = buildTemplate({
SYSTEM_GLOBAL: opts.systemGlobal && t.identifier(opts.systemGlobal) || t.identifier('System'),
MODULE_ID: !opts.anonNamed && moduleName || moduleId && t.stringLiteral(moduleId),
DEPS: [].concat(_toConsumableArray(deps)),
BODY: factory
});
path.replaceWith(systemRegister);
})();
}
}
},
exit: function exit(path) {
// are we inside a `define()`?
if (this.outerModuleDefinition === path) {
this.outerModuleDefinition = null;
}
}
},
MemberExpression: function MemberExpression(path, _ref3) {
var opts = _ref3.opts;
if (!opts.filterMode) {
// Replace `define.amd` with `true` if it's used inside a logical expression,
// and replace `typeof define.amd` with `'object'`
if (t.isIdentifier(path.node.object, { name: 'define' }) && (t.isIdentifier(path.node.property, { name: 'amd' }) || t.isStringLiteral(path.node.property, { value: 'amd' })) && !path.scope.hasBinding('define') && path.parentPath) {
if (t.isLogicalExpression(path.parentPath)) path.replaceWith(t.booleanLiteral(true));else if (t.isUnaryExpression(path.parentPath) && path.parentPath.node.operator === 'typeof') path.parentPath.replaceWith(t.stringLiteral('object'));
}
}
},
UnaryExpression: function UnaryExpression(path, _ref4) {
var opts = _ref4.opts;
if (!opts.filterMode) {
// Replace `typeof define` if it's used inside a unary expression.
if (!path.scope.hasBinding('define') && path.node.operator === 'typeof' && path.node.argument.name === 'define') path.replaceWith(t.stringLiteral('function'));
}
}
}
};
};
var _babelTemplate = require('babel-template');
var _babelTemplate2 = _interopRequireDefault(_babelTemplate);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }