88 lines
4.5 KiB
JavaScript
88 lines
4.5 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.detectDynamicExports = detectDynamicExports;
|
|
const debug = require('debug')('expo:babel:exports');
|
|
// A babel pass to detect the usage of `module.exports` or `exports` in a module for use in
|
|
// export all expansion passes during tree shaking.
|
|
function detectDynamicExports(api) {
|
|
const { types: t } = api;
|
|
return {
|
|
name: 'expo-detect-dynamic-exports',
|
|
pre(file) {
|
|
assertExpoMetadata(file.metadata);
|
|
file.metadata.hasCjsExports = false;
|
|
},
|
|
visitor: {
|
|
// Any usage of `module.exports` or `exports` will mark the module as non-static.
|
|
// module.exports.a = 1;
|
|
// exports.a = 1;
|
|
CallExpression(path, state) {
|
|
assertExpoMetadata(state.file.metadata);
|
|
if (state.file.metadata.hasCjsExports)
|
|
return;
|
|
const callee = path.node.callee;
|
|
if (
|
|
// Object.assign(...)
|
|
t.isMemberExpression(callee) &&
|
|
t.isIdentifier(callee.object, { name: 'Object' }) &&
|
|
t.isIdentifier(callee.property, { name: 'assign' }) &&
|
|
// Allow `Object.assign(module.exports)` since it does nothing. Must have a second argument.
|
|
path.node.arguments.length > 1) {
|
|
const isModuleExports = t.isMemberExpression(path.node.arguments[0]) &&
|
|
t.isIdentifier(path.node.arguments[0].object, { name: 'module' }) &&
|
|
// Second argument is `exports` or 'exports'
|
|
// .exports
|
|
(t.isIdentifier(path.node.arguments[0].property, { name: 'exports' }) ||
|
|
// ['exports']
|
|
(t.isStringLiteral(path.node.arguments[0].property) &&
|
|
path.node.arguments[0].property.value === 'exports'));
|
|
// NOTE: Cannot match `['exp' + 'orts']`. We'd need to run after minification to match that confidently.
|
|
// Check for Object.assign(module.exports, ...), Object.assign(exports, ...)
|
|
if (
|
|
// module.exports
|
|
isModuleExports ||
|
|
// exports
|
|
t.isIdentifier(path.node.arguments[0], { name: 'exports' })) {
|
|
debug('Found Object.assign to module.exports or exports at ' + path.node.loc?.start.line);
|
|
state.file.metadata.hasCjsExports = true;
|
|
}
|
|
}
|
|
},
|
|
AssignmentExpression(path, state) {
|
|
assertExpoMetadata(state.file.metadata);
|
|
if (state.file.metadata.hasCjsExports)
|
|
return;
|
|
const left = path.node.left;
|
|
// Detect module.exports.foo = ... or exports.foo = ...
|
|
if ((t.isMemberExpression(left) &&
|
|
((t.isIdentifier(left.object, { name: 'module' }) &&
|
|
(t.isIdentifier(left.property, { name: 'exports' }) ||
|
|
(t.isStringLiteral(left.property) && left.property.value === 'exports'))) ||
|
|
t.isIdentifier(left.object, { name: 'exports' }))) ||
|
|
('object' in left &&
|
|
t.isMemberExpression(left.object) &&
|
|
t.isIdentifier(left.object.object, { name: 'module' }) &&
|
|
(t.isIdentifier(left.object.property, { name: 'exports' }) ||
|
|
(t.isStringLiteral(left.object.property) &&
|
|
left.object.property.value === 'exports')))) {
|
|
debug('Found assignment to module.exports or exports at ' + path.node.loc?.start.line);
|
|
state.file.metadata.hasCjsExports = true;
|
|
}
|
|
else if (t.isIdentifier(left, { name: 'exports' }) &&
|
|
path.scope.hasGlobal('exports') &&
|
|
// Ensure left is not defined in any scope
|
|
!path.scope.hasBinding('exports')) {
|
|
debug('Found assignment to exports at ' + path.node.loc?.start.line);
|
|
state.file.metadata.hasCjsExports = true;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
}
|
|
function assertExpoMetadata(metadata) {
|
|
if (metadata && typeof metadata === 'object') {
|
|
return;
|
|
}
|
|
throw new Error('Expected Babel state.file.metadata to be an object');
|
|
}
|