diff --git a/lib/JavascriptModulesPlugin.js b/lib/JavascriptModulesPlugin.js index 2040becdf..b31e966ea 100644 --- a/lib/JavascriptModulesPlugin.js +++ b/lib/JavascriptModulesPlugin.js @@ -11,13 +11,15 @@ class JavascriptModulesPlugin { compiler.hooks.compilation.tap("JavascriptModulesPlugin", (compilation, { normalModuleFactory }) => { - const createParser = () => { - return new Parser(); - }; - - normalModuleFactory.hooks.createParser.for("javascript/auto").tap("JavascriptModulesPlugin", createParser); - normalModuleFactory.hooks.createParser.for("javascript/dynamic").tap("JavascriptModulesPlugin", createParser); - normalModuleFactory.hooks.createParser.for("javascript/esm").tap("JavascriptModulesPlugin", createParser); + normalModuleFactory.hooks.createParser.for("javascript/auto").tap("JavascriptModulesPlugin", options => { + return new Parser(options, "auto"); + }); + normalModuleFactory.hooks.createParser.for("javascript/dynamic").tap("JavascriptModulesPlugin", options => { + return new Parser(options, "script"); + }); + normalModuleFactory.hooks.createParser.for("javascript/esm").tap("JavascriptModulesPlugin", options => { + return new Parser(options, "module"); + }); }); } } diff --git a/lib/NormalModuleFactory.js b/lib/NormalModuleFactory.js index 457687f0a..d7ae0809f 100644 --- a/lib/NormalModuleFactory.js +++ b/lib/NormalModuleFactory.js @@ -88,7 +88,7 @@ class NormalModuleFactory extends Tapable { this.ruleSet = new RuleSet(options.defaultRules.concat(options.rules)); this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache); this.context = context || ""; - this.parserCache = {}; + this.parserCache = Object.create(null); this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => { let resolver = this.hooks.resolver.call(null); @@ -332,14 +332,13 @@ class NormalModuleFactory extends Tapable { else ident = JSON.stringify([type, parserOptions]); } - const parser = this.parserCache[ident]; - if(parser) - return parser; + if(ident in this.parserCache) { + return this.parserCache[ident]; + } return this.parserCache[ident] = this.createParser(type, parserOptions); } - createParser(type, parserOptions) { - parserOptions = parserOptions || {}; + createParser(type, parserOptions = {}) { const parser = this.hooks.createParser.for(type).call(parserOptions); if(!parser) { throw new Error(`No parser registered for ${type}`); diff --git a/lib/Parser.js b/lib/Parser.js index d574b49b5..001a888f7 100644 --- a/lib/Parser.js +++ b/lib/Parser.js @@ -21,28 +21,19 @@ const joinRanges = (startRange, endRange) => { return [startRange[0], endRange[1]]; }; -const ECMA_VERSION = 2017; - -const POSSIBLE_AST_OPTIONS = [{ +const defaultParserOptions = { ranges: true, locations: true, - ecmaVersion: ECMA_VERSION, + ecmaVersion: 2017, sourceType: "module", + onComment: null, plugins: { dynamicImport: true } -}, { - ranges: true, - locations: true, - ecmaVersion: ECMA_VERSION, - sourceType: "script", - plugins: { - dynamicImport: true - } -}]; +}; class Parser extends Tapable { - constructor(options) { + constructor(options, sourceType = "auto") { super(); this.hooks = { evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])), @@ -116,6 +107,7 @@ class Parser extends Tapable { } }); this.options = options; + this.sourceType = sourceType; this.scope = undefined; this.state = undefined; this.comments = undefined; @@ -1761,37 +1753,18 @@ class Parser extends Tapable { parse(source, initialState) { let ast; - let comments = []; + let comments; if(typeof source === "object" && source !== null) { ast = source; comments = source.comments; - } - for(let i = 0, len = POSSIBLE_AST_OPTIONS.length; i < len; i++) { - if(!ast) { - try { - comments.length = 0; - POSSIBLE_AST_OPTIONS[i].onComment = comments; - ast = acorn.parse(source, POSSIBLE_AST_OPTIONS[i]); - } catch(e) { - // ignore the error - } - } - } - if(!ast) { - // for the error - ast = acorn.parse(source, { - ranges: true, - locations: true, - ecmaVersion: ECMA_VERSION, - sourceType: "module", - plugins: { - dynamicImport: true - }, + } else { + comments = []; + ast = Parser.parse(source, { + sourceType: this.sourceType, onComment: comments }); } - if(!ast || typeof ast !== "object") - throw new Error("Source couldn't be parsed"); + const oldScope = this.scope; const oldState = this.state; const oldComments = this.comments; @@ -1817,17 +1790,10 @@ class Parser extends Tapable { } evaluate(source) { - const ast = acorn.parse("(" + source + ")", { - ranges: true, - locations: true, - ecmaVersion: ECMA_VERSION, - sourceType: "module", - plugins: { - dynamicImport: true - } + const ast = Parser.parse("(" + source + ")", { + sourceType: this.sourceType, + locations: false, }); - if(!ast || typeof ast !== "object" || ast.type !== "Program") - throw new Error("evaluate: Source couldn't be parsed"); if(ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement") throw new Error("evaluate: Source is not a expression"); return this.evaluateExpression(ast.body[0].expression); @@ -1885,8 +1851,41 @@ class Parser extends Tapable { }; } + static parse(code, options) { + const type = options.sourceType; + const parserOptions = Object.assign(Object.create(null), defaultParserOptions, options); + + if(type === "auto") { + parserOptions.sourceType = "module"; + } + + let ast; + let error; + let threw = false; + try { + ast = acorn.parse(code, parserOptions); + } catch(e) { + error = e; + threw = true; + } + + if(threw && type === "auto") { + parserOptions.sourceType = "script"; + if(Array.isArray(parserOptions.onComment)) { + parserOptions.onComment.length = 0; + } + try { + ast = acorn.parse(code, parserOptions); + threw = false; + } catch(e) {} + } + + if(threw) { + throw error; + } + + return ast; + } } -Parser.ECMA_VERSION = ECMA_VERSION; - module.exports = Parser; diff --git a/lib/optimize/ConcatenatedModule.js b/lib/optimize/ConcatenatedModule.js index 2d10c34f7..5a6e3f9d4 100644 --- a/lib/optimize/ConcatenatedModule.js +++ b/lib/optimize/ConcatenatedModule.js @@ -7,7 +7,6 @@ const Module = require("../Module"); const Template = require("../Template"); const Parser = require("../Parser"); -const acorn = require("acorn"); const eslintScope = require("eslint-scope"); const ReplaceSource = require("webpack-sources").ReplaceSource; const ConcatSource = require("webpack-sources").ConcatSource; @@ -483,11 +482,8 @@ class ConcatenatedModule extends Module { const code = source.source(); let ast; try { - ast = acorn.parse(code, { - ranges: true, - locations: true, - ecmaVersion: Parser.ECMA_VERSION, - sourceType: "module" + ast = Parser.parse(code, { + sourceType: "module", }); } catch(err) { if(err.loc && typeof err.loc === "object" && typeof err.loc.line === "number") {