diff --git a/lib/dependencies/CommonJsImportsParserPlugin.js b/lib/dependencies/CommonJsImportsParserPlugin.js index 3f8658adc67..bf1c9e2af1e 100644 --- a/lib/dependencies/CommonJsImportsParserPlugin.js +++ b/lib/dependencies/CommonJsImportsParserPlugin.js @@ -5,6 +5,7 @@ "use strict"; +const { fileURLToPath } = require("url"); const CommentCompilationWarning = require("../CommentCompilationWarning"); const RuntimeGlobals = require("../RuntimeGlobals"); const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning"); @@ -45,6 +46,13 @@ class CommonJsImportsParserPlugin { apply(parser) { const options = this.options; + const getContext = () => { + if (parser.currentTagData) { + const { context } = parser.currentTagData; + return context; + } + }; + //#region metadata const tapRequireExpression = (expression, getMembers) => { parser.hooks.typeof @@ -172,7 +180,8 @@ class CommonJsImportsParserPlugin { }, expr.range, undefined, - parser.scope.inShorthand + parser.scope.inShorthand, + getContext() ); dep.critical = options.unknownContextCritical && @@ -190,7 +199,11 @@ class CommonJsImportsParserPlugin { //#region Require const processRequireItem = (expr, param) => { if (param.isString()) { - const dep = new CommonJsRequireDependency(param.string, param.range); + const dep = new CommonJsRequireDependency( + param.string, + param.range, + getContext() + ); dep.loc = expr.loc; dep.optional = !!parser.scope.inTry; parser.state.current.addDependency(dep); @@ -207,7 +220,9 @@ class CommonJsImportsParserPlugin { { category: "commonjs" }, - parser + parser, + undefined, + getContext() ); if (!dep) return; dep.loc = expr.loc; @@ -380,7 +395,11 @@ class CommonJsImportsParserPlugin { }; const processResolveItem = (expr, param, weak) => { if (param.isString()) { - const dep = new RequireResolveDependency(param.string, param.range); + const dep = new RequireResolveDependency( + param.string, + param.range, + getContext() + ); dep.loc = expr.loc; dep.optional = !!parser.scope.inTry; dep.weak = weak; @@ -399,7 +418,8 @@ class CommonJsImportsParserPlugin { category: "commonjs", mode: weak ? "weak" : "sync" }, - parser + parser, + getContext() ); if (!dep) return; dep.loc = expr.loc; @@ -489,7 +509,7 @@ class CommonJsImportsParserPlugin { .tap("CommonJsImportsParserPlugin", createRequireHandler(false)); /** * @param {CallExpressionNode} expr call expression - * @returns {string|boolean} context + * @returns {string} context */ const parseCreateRequireArguments = expr => { const args = expr.arguments; @@ -509,31 +529,16 @@ class CommonJsImportsParserPlugin { parser.state.module.addWarning(err); }; const arg = args[0]; - if ( - arg.type === "MemberExpression" && - arg.object.type === "MetaProperty" - ) { - if ( - arg.object.meta.type === "Identifier" && - arg.object.meta.name === "import" && - arg.object.property.type === "Identifier" && - arg.object.property.name === "meta" && - arg.property.type === "Identifier" && - arg.property.name === "url" - ) { - // same module context - return false; - } else { - createParsingError(arg); - } - } else { - const evaluated = parser.evaluateExpression(arg); - if (!evaluated.isString()) { - createParsingError(arg); - return; - } - return evaluated.string; + const evaluated = parser.evaluateExpression(arg); + if (!evaluated.isString()) { + createParsingError(arg); + return; } + const ctx = evaluated.string.startsWith("file://") + ? fileURLToPath(evaluated.string) + : evaluated.string; + // argument always should be a filename + return ctx.slice(0, ctx.lastIndexOf(ctx.startsWith("/") ? "/" : "\\")); }; parser.hooks.import.tap( diff --git a/lib/dependencies/CommonJsRequireContextDependency.js b/lib/dependencies/CommonJsRequireContextDependency.js index 627d234e5a6..e8637835d73 100644 --- a/lib/dependencies/CommonJsRequireContextDependency.js +++ b/lib/dependencies/CommonJsRequireContextDependency.js @@ -10,8 +10,8 @@ const ContextDependency = require("./ContextDependency"); const ContextDependencyTemplateAsRequireCall = require("./ContextDependencyTemplateAsRequireCall"); class CommonJsRequireContextDependency extends ContextDependency { - constructor(options, range, valueRange, inShorthand) { - super(options); + constructor(options, range, valueRange, inShorthand, context) { + super(options, context); this.range = range; this.valueRange = valueRange; diff --git a/lib/dependencies/CommonJsRequireDependency.js b/lib/dependencies/CommonJsRequireDependency.js index 3c711ff31b7..03d0a251a13 100644 --- a/lib/dependencies/CommonJsRequireDependency.js +++ b/lib/dependencies/CommonJsRequireDependency.js @@ -10,9 +10,10 @@ const ModuleDependency = require("./ModuleDependency"); const ModuleDependencyTemplateAsId = require("./ModuleDependencyTemplateAsId"); class CommonJsRequireDependency extends ModuleDependency { - constructor(request, range) { + constructor(request, range, context) { super(request); this.range = range; + this._context = context; } get type() { diff --git a/lib/dependencies/ContextDependency.js b/lib/dependencies/ContextDependency.js index 4b23b527e5a..8c41b8c1440 100644 --- a/lib/dependencies/ContextDependency.js +++ b/lib/dependencies/ContextDependency.js @@ -26,8 +26,9 @@ const regExpToString = r => (r ? r + "" : ""); class ContextDependency extends Dependency { /** * @param {ContextDependencyOptions} options options for the context module + * @param {string=} context request context */ - constructor(options) { + constructor(options, context) { super(); this.options = options; @@ -50,6 +51,14 @@ class ContextDependency extends Dependency { this.inShorthand = undefined; // TODO refactor this this.replaces = undefined; + this._requestContext = context; + } + + /** + * @returns {string | undefined} a request context + */ + getContext() { + return this._requestContext; } get category() { @@ -68,7 +77,9 @@ class ContextDependency extends Dependency { */ getResourceIdentifier() { return ( - `context${this.options.request} ${this.options.recursive} ` + + `context${this._requestContext || ""}|ctx request${ + this.options.request + } ${this.options.recursive} ` + `${regExpToString(this.options.regExp)} ${regExpToString( this.options.include )} ${regExpToString(this.options.exclude)} ` + @@ -112,6 +123,7 @@ class ContextDependency extends Dependency { write(this.critical); write(this.hadGlobalOrStickyRegExp); write(this.request); + write(this._requestContext); write(this.range); write(this.valueRange); write(this.prepend); @@ -128,6 +140,7 @@ class ContextDependency extends Dependency { this.critical = read(); this.hadGlobalOrStickyRegExp = read(); this.request = read(); + this._requestContext = read(); this.range = read(); this.valueRange = read(); this.prepend = read(); diff --git a/lib/dependencies/ContextDependencyHelpers.js b/lib/dependencies/ContextDependencyHelpers.js index 45d865242ac..97d059bcb4f 100644 --- a/lib/dependencies/ContextDependencyHelpers.js +++ b/lib/dependencies/ContextDependencyHelpers.js @@ -39,7 +39,7 @@ const splitContextFromPrefix = prefix => { /** @typedef {Partial>} PartialContextDependencyOptions */ -/** @typedef {{ new(options: ContextDependencyOptions, range: [number, number], valueRange: [number, number]): ContextDependency }} ContextDependencyConstructor */ +/** @typedef {{ new(options: ContextDependencyOptions, range: [number, number], valueRange: [number, number], ...args: any[]): ContextDependency }} ContextDependencyConstructor */ /** * @param {ContextDependencyConstructor} Dep the Dependency class @@ -49,9 +49,19 @@ const splitContextFromPrefix = prefix => { * @param {Pick} options options for context creation * @param {PartialContextDependencyOptions} contextOptions options for the ContextModule * @param {JavascriptParser} parser the parser + * @param {...any} depArgs depArgs * @returns {ContextDependency} the created Dependency */ -exports.create = (Dep, range, param, expr, options, contextOptions, parser) => { +exports.create = ( + Dep, + range, + param, + expr, + options, + contextOptions, + parser, + ...depArgs +) => { if (param.isTemplateString()) { let prefixRaw = param.quasis[0].string; let postfixRaw = @@ -97,7 +107,8 @@ exports.create = (Dep, range, param, expr, options, contextOptions, parser) => { ...contextOptions }, range, - valueRange + valueRange, + ...depArgs ); dep.loc = expr.loc; const replaces = []; @@ -180,7 +191,8 @@ exports.create = (Dep, range, param, expr, options, contextOptions, parser) => { ...contextOptions }, range, - valueRange + valueRange, + ...depArgs ); dep.loc = expr.loc; const replaces = []; @@ -218,7 +230,8 @@ exports.create = (Dep, range, param, expr, options, contextOptions, parser) => { ...contextOptions }, range, - param.range + param.range, + ...depArgs ); dep.loc = expr.loc; dep.critical = diff --git a/lib/dependencies/ContextElementDependency.js b/lib/dependencies/ContextElementDependency.js index ec0390e85ce..21681f57711 100644 --- a/lib/dependencies/ContextElementDependency.js +++ b/lib/dependencies/ContextElementDependency.js @@ -49,20 +49,6 @@ class ContextElementDependency extends ModuleDependency { return "context element"; } - /** - * @returns {string | undefined} a request context - */ - getContext() { - return this._context; - } - - /** - * @returns {string | null} an identifier to merge equal requests - */ - getResourceIdentifier() { - return `context${this._context || ""}|${super.getResourceIdentifier()}`; - } - get category() { return this._category; } @@ -86,7 +72,6 @@ class ContextElementDependency extends ModuleDependency { const { write } = context; write(this._typePrefix); write(this._category); - write(this._context); write(this.referencedExports); super.serialize(context); } @@ -95,7 +80,6 @@ class ContextElementDependency extends ModuleDependency { const { read } = context; this._typePrefix = read(); this._category = read(); - this._context = read(); this.referencedExports = read(); super.deserialize(context); } diff --git a/lib/dependencies/ModuleDependency.js b/lib/dependencies/ModuleDependency.js index d94e7a6d1f0..0efbdaeb8cf 100644 --- a/lib/dependencies/ModuleDependency.js +++ b/lib/dependencies/ModuleDependency.js @@ -26,13 +26,21 @@ class ModuleDependency extends Dependency { // assertions must be serialized by subclasses that use it /** @type {Record | undefined} */ this.assertions = undefined; + this._context = undefined; + } + + /** + * @returns {string | undefined} a request context + */ + getContext() { + return this._context; } /** * @returns {string | null} an identifier to merge equal requests */ getResourceIdentifier() { - let str = `module${this.request}`; + let str = `context${this._context || ""}|module${this.request}`; if (this.assertions !== undefined) { str += JSON.stringify(this.assertions); } @@ -63,6 +71,7 @@ class ModuleDependency extends Dependency { const { write } = context; write(this.request); write(this.userRequest); + write(this._context); write(this.range); super.serialize(context); } @@ -71,6 +80,7 @@ class ModuleDependency extends Dependency { const { read } = context; this.request = read(); this.userRequest = read(); + this._context = read(); this.range = read(); super.deserialize(context); } diff --git a/lib/dependencies/RequireResolveContextDependency.js b/lib/dependencies/RequireResolveContextDependency.js index 4e998ec0f12..1bfe600d3e4 100644 --- a/lib/dependencies/RequireResolveContextDependency.js +++ b/lib/dependencies/RequireResolveContextDependency.js @@ -10,8 +10,8 @@ const ContextDependency = require("./ContextDependency"); const ContextDependencyTemplateAsId = require("./ContextDependencyTemplateAsId"); class RequireResolveContextDependency extends ContextDependency { - constructor(options, range, valueRange) { - super(options); + constructor(options, range, valueRange, context) { + super(options, context); this.range = range; this.valueRange = valueRange; diff --git a/lib/dependencies/RequireResolveDependency.js b/lib/dependencies/RequireResolveDependency.js index 8d6ade3d110..e3f0917ecb4 100644 --- a/lib/dependencies/RequireResolveDependency.js +++ b/lib/dependencies/RequireResolveDependency.js @@ -15,10 +15,11 @@ const ModuleDependencyAsId = require("./ModuleDependencyTemplateAsId"); /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ class RequireResolveDependency extends ModuleDependency { - constructor(request, range) { + constructor(request, range, context) { super(request); this.range = range; + this._context = context; } get type() { diff --git a/lib/dependencies/URLPlugin.js b/lib/dependencies/URLPlugin.js index fc0b15c313b..92473a44c9e 100644 --- a/lib/dependencies/URLPlugin.js +++ b/lib/dependencies/URLPlugin.js @@ -5,12 +5,15 @@ "use strict"; +const { pathToFileURL } = require("url"); +const BasicEvaluatedExpression = require("../javascript/BasicEvaluatedExpression"); const { approve } = require("../javascript/JavascriptParserHelpers"); const InnerGraph = require("../optimize/InnerGraph"); const URLDependency = require("./URLDependency"); /** @typedef {import("estree").NewExpression} NewExpressionNode */ /** @typedef {import("../Compiler")} Compiler */ +/** @typedef {import("../NormalModule")} NormalModule */ /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */ class URLPlugin { @@ -27,6 +30,13 @@ class URLPlugin { new URLDependency.Template() ); + /** + * @param {NormalModule} module module + * @returns {URL} file url + */ + const getUrl = module => { + return pathToFileURL(module.resource); + }; /** * @param {JavascriptParser} parser parser * @param {object} parserOptions options @@ -67,6 +77,17 @@ class URLPlugin { }; parser.hooks.canRename.for("URL").tap("URLPlugin", approve); + parser.hooks.evaluateNewExpression + .for("URL") + .tap("URLPlugin", expr => { + const request = getUrlRequest(expr); + if (!request) return; + const url = new URL(request, getUrl(parser.state.module)); + + return new BasicEvaluatedExpression() + .setString(url.toString()) + .setRange(expr.range); + }); parser.hooks.new.for("URL").tap("URLPlugin", _expr => { const expr = /** @type {NewExpressionNode} */ (_expr); diff --git a/lib/javascript/JavascriptParser.js b/lib/javascript/JavascriptParser.js index ecaae863d9f..0909f48ede1 100644 --- a/lib/javascript/JavascriptParser.js +++ b/lib/javascript/JavascriptParser.js @@ -165,6 +165,10 @@ class JavascriptParser extends Parser { evaluateDefinedIdentifier: new HookMap( () => new SyncBailHook(["expression"]) ), + /** @type {HookMap>} */ + evaluateNewExpression: new HookMap( + () => new SyncBailHook(["expression"]) + ), /** @type {HookMap>} */ evaluateCallExpression: new HookMap( () => new SyncBailHook(["expression"]) @@ -365,9 +369,14 @@ class JavascriptParser extends Parser { this.hooks.evaluate.for("NewExpression").tap("JavascriptParser", _expr => { const expr = /** @type {NewExpressionNode} */ (_expr); const callee = expr.callee; - if ( - callee.type !== "Identifier" || - callee.name !== "RegExp" || + if (callee.type !== "Identifier") return; + if (callee.name !== "RegExp") { + return this.callHooksForName( + this.hooks.evaluateNewExpression, + callee.name, + expr + ); + } else if ( expr.arguments.length > 2 || this.getVariableInfo("RegExp") !== "RegExp" ) diff --git a/test/configCases/require/module-require/foo/a.js b/test/configCases/require/module-require/foo/a.js new file mode 100644 index 00000000000..a9bbdd80578 --- /dev/null +++ b/test/configCases/require/module-require/foo/a.js @@ -0,0 +1 @@ +module.exports = 4; diff --git a/test/configCases/require/module-require/foo/c.js b/test/configCases/require/module-require/foo/c.js new file mode 100644 index 00000000000..f4e8d9d29a5 --- /dev/null +++ b/test/configCases/require/module-require/foo/c.js @@ -0,0 +1 @@ +module.exports = 5; diff --git a/test/configCases/require/module-require/index.js b/test/configCases/require/module-require/index.js index 8891d4b09a3..0df127418cf 100644 --- a/test/configCases/require/module-require/index.js +++ b/test/configCases/require/module-require/index.js @@ -35,6 +35,13 @@ it("should provide require.cache", () => { expect(require.cache).toBe(_createRequire(import.meta.url).cache); }); +it("should provide dependency context", () => { + const _require = _createRequire(new URL("./foo/c.js", import.meta.url)); + expect(_require("./a")).toBe(4); + const _require1 = _createRequire(new URL("./foo/", import.meta.url)); + expect(_require1("./c")).toBe(5); +}); + it("should import Node.js module", () => { expect(Array.isArray(builtinModules)).toBe(true); }); diff --git a/types.d.ts b/types.d.ts index b4235b14026..7f1abdda2ee 100644 --- a/types.d.ts +++ b/types.d.ts @@ -4885,6 +4885,9 @@ declare class JavascriptParser extends Parser { undefined | null | BasicEvaluatedExpression > >; + evaluateNewExpression: HookMap< + SyncBailHook<[NewExpression], undefined | null | BasicEvaluatedExpression> + >; evaluateCallExpression: HookMap< SyncBailHook< [CallExpression],