diff --git a/package.json b/package.json index 3d37062..caf8285 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "test": "mocha test/**/*.test.js --ui=tdd --require @babel/register", "tdd": "electron-mocha test/**/*.test.js --ui=tdd --renderer --interactive --require @babel/register", - "prepublish": "rimraf lib && babel src -d lib" + "build": "rimraf lib && babel src -d lib", + "prepare": "npm run build" }, "author": "", "license": "MIT", diff --git a/src/file-require-transform.js b/src/file-require-transform.js index 3afa79d..c0792ea 100644 --- a/src/file-require-transform.js +++ b/src/file-require-transform.js @@ -129,12 +129,20 @@ module.exports = class FileRequireTransform { ifStatementPath = ifStatementPath.parent } - // Ensure we're assigning to a variable declared in this scope. + let assignmentLhs = parentNode.left while (assignmentLhs.type === 'MemberExpression') { assignmentLhs = assignmentLhs.object } assert.equal(assignmentLhs.type, 'Identifier') + + if (["module", "exports"].includes(assignmentLhs.name)) { + console.warn(`##[warning] ${this.options.filePath}\n` + + `The reference to the module is replaced with the lazy function, but it is assigned to "module" or "exports". In some cases the bundle might not work, which you should fix manually.`); + return // don't replace anything (module.exports = get_name) + } + + // Ensure we're assigning to a variable declared in this scope. assert( astPath.scope.declares(assignmentLhs.name), `${this.options.filePath}\nAssigning a deferred module to a variable that was not declared in this scope is not supported!` @@ -212,24 +220,25 @@ module.exports = class FileRequireTransform { } parentPath = parentPath.parent } - - throw new Error( - `${this.options.filePath}\n` + - `Cannot replace with lazy function because the supplied node does not belong to an assignment expression or a variable declaration!` - ) + console.warn(`##[warning] ${this.options.filePath}\n` + + `The reference to the module is replaced with the lazy function, but it was not in an assignment expression or a variable declaration. In some cases the bundle might not work, which you should fix manually.`); + return // just call the reference it directly } isReferenceToLazyRequire (astPath) { const scope = astPath.scope const lazyRequireFunctionName = this.lazyRequireFunctionsByVariableName.get(astPath.node.name) - return ( - lazyRequireFunctionName != null && - (scope.node.type !== 'FunctionDeclaration' || lazyRequireFunctionName !== astPath.scope.node.id.name) && - (scope.node.type !== 'FunctionExpression' || scope.path.parent.node.type !== 'AssignmentExpression' || lazyRequireFunctionName !== scope.path.parent.node.left.name) && - (astPath.parent.node.type !== 'Property' || astPath.parent.parent.node.type !== 'ObjectPattern') && - astPath.parent.node.type !== 'AssignmentExpression' && - astUtil.isReference(astPath) - ) + if (lazyRequireFunctionName != null && + (scope.node.type !== 'FunctionDeclaration' || lazyRequireFunctionName !== astPath.scope.node.id.name) && + (scope.node.type !== 'FunctionExpression' || scope.path.parent.node.type !== 'AssignmentExpression' || lazyRequireFunctionName !== scope.path.parent.node.left.name) && + (astPath.parent.node.type !== 'Property' || astPath.parent.parent.node.type !== 'ObjectPattern') ) { + if (astPath.parent.node.type === 'AssignmentExpression') { + return astPath.name === "right" && astUtil.isReference(astPath) // e.g module.exports = a_reference; + } else { + return astUtil.isReference(astPath) + } + + } } resolveModulePath (moduleName) { diff --git a/test/unit/file-require-transform.test.js b/test/unit/file-require-transform.test.js index 26c4f71..2f4edaf 100644 --- a/test/unit/file-require-transform.test.js +++ b/test/unit/file-require-transform.test.js @@ -123,21 +123,6 @@ suite('FileRequireTransform', () => { `) }) - test('top-level usage of deferred modules', () => { - assert.throws(() => { - new FileRequireTransform({source: `var a = require('a'); a()`, didFindRequire: (mod) => true}).apply() - }) - assert.throws(() => { - new FileRequireTransform({source: `require('a')()`, didFindRequire: (mod) => true}).apply() - }) - assert.throws(() => { - new FileRequireTransform({source: `foo = require('a')`, didFindRequire: (mod) => true}).apply() - }) - assert.throws(() => { - new FileRequireTransform({source: `module.exports.a = require('a')`, didFindRequire: (mod) => true}).apply() - }) - }) - test('requires that appear in a closure wrapper defined in the top-level scope (e.g. CoffeeScript)', () => { const source = dedent` (function () { @@ -428,4 +413,73 @@ suite('FileRequireTransform', () => { {unresolvedPath: 'd' , resolvedPath: 'd'}, ]) }) + + test('use reference directly', () => { + const source = dedent` + var pack = require('pack') + + const x = console.log(pack); + if (condition) { + pack + } else { + Object.keys(pack).forEach(function (prop) { + exports[prop] = pack[prop] + }) + } + ` + assert.equal( + new FileRequireTransform({source, didFindRequire: (mod) => mod === 'pack'}).apply(), + dedent` + var pack + + function get_pack() { + return pack = pack || require('pack'); + } + + let x; + + function get_x() { + return x = x || get_console().log(get_pack()); + } + + if (condition) { + get_pack() + } else { + Object.keys(get_pack()).forEach(function (prop) { + exports[prop] = get_pack()[prop] + }) + } + ` + ) + }) + test('assign to `module` or `exports`', () => { + const source = dedent` + var pack = require('pack') + if (condition) { + module.exports.pack = pack + module.exports = pack + exports.pack = pack + exports = pack + } + ` + assert.equal( + new FileRequireTransform({source, didFindRequire: (mod) => mod === 'pack'}).apply(), + dedent` + var pack + + function get_pack() { + return pack = pack || require('pack'); + } + + if (condition) { + module.exports.pack = get_pack() + module.exports = get_pack() + exports.pack = get_pack() + exports = get_pack() + } + ` + ) + }) + + })