add node-module option for node.__file/dirname

evaluate __filename and __dirname for common js modules when output.module to
fileURLToPath(import.meta.url) and fileURLToPath(import.meta.url + "/..") respectively
This commit is contained in:
Ivan Kopeykin 2021-09-14 16:50:03 +03:00
parent 562f17a8c0
commit 6c3a04d5ce
7 changed files with 109 additions and 31 deletions

View File

@ -1633,11 +1633,17 @@ export interface NodeOptions {
/** /**
* Include a polyfill for the '__dirname' variable. * Include a polyfill for the '__dirname' variable.
*/ */
__dirname?: false | true | "warn-mock" | "mock" | "eval-only"; __dirname?: false | true | "warn-mock" | "mock" | "node-module" | "eval-only";
/** /**
* Include a polyfill for the '__filename' variable. * Include a polyfill for the '__filename' variable.
*/ */
__filename?: false | true | "warn-mock" | "mock" | "eval-only"; __filename?:
| false
| true
| "warn-mock"
| "mock"
| "node-module"
| "eval-only";
/** /**
* Include a polyfill for the 'global' variable. * Include a polyfill for the 'global' variable.
*/ */

View File

@ -15,14 +15,19 @@ const {
} = require("./javascript/JavascriptParserHelpers"); } = require("./javascript/JavascriptParserHelpers");
const { relative } = require("./util/fs"); const { relative } = require("./util/fs");
const { parseResource } = require("./util/identifier"); const { parseResource } = require("./util/identifier");
const InitFragment = require("./InitFragment");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("./Compiler")} Compiler */ /** @typedef {import("./Compiler")} Compiler */
/** @typedef {import("./Dependency")} Dependency */ /** @typedef {import("./Dependency")} Dependency */
/** @typedef {import("./DependencyTemplates")} DependencyTemplates */ /** @typedef {import("./DependencyTemplates")} DependencyTemplates */
/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */ /** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
/** @typedef {import("../declarations/WebpackOptions").NodeOptions} NodeOptions */
class NodeStuffPlugin { class NodeStuffPlugin {
/**
* @param {NodeOptions} options options
*/
constructor(options) { constructor(options) {
this.options = options; this.options = options;
} }
@ -71,7 +76,7 @@ class NodeStuffPlugin {
}); });
} }
const setModuleConstant = (expressionName, fn, warning) => { const setCjsModuleConstant = (expressionName, fn, warning) => {
parser.hooks.expression parser.hooks.expression
.for(expressionName) .for(expressionName)
.tap("NodeStuffPlugin", expr => { .tap("NodeStuffPlugin", expr => {
@ -94,24 +99,54 @@ class NodeStuffPlugin {
}); });
}; };
const setConstant = (expressionName, value, warning) => const setEsmModuleConstant = (expressionName, fn) => {
setModuleConstant(expressionName, () => value, warning); parser.hooks.expression
.for(expressionName)
.tap("NodeStuffPlugin", expr => {
const dep = new CachedConstDependency(
JSON.stringify(fn(parser.state.module)),
expr.range,
expressionName,
[
new InitFragment(
'import url from "url"',
InitFragment.STAGE_CONSTANTS,
0,
"import url"
)
]
);
dep.loc = expr.loc;
parser.state.module.addPresentationalDependency(dep);
return true;
});
};
const setCjsConstant = (expressionName, value, warning) =>
setCjsModuleConstant(expressionName, () => value, warning);
const setEsmConstant = (expressionName, value) =>
setEsmModuleConstant(expressionName, () => value);
const context = compiler.context; const context = compiler.context;
if (localOptions.__filename) { if (localOptions.__filename) {
switch (localOptions.__filename) { switch (localOptions.__filename) {
case "mock": case "mock":
setConstant("__filename", "/index.js"); setCjsConstant("__filename", "/index.js");
break; break;
case "warn-mock": case "warn-mock":
setConstant( setCjsConstant(
"__filename", "__filename",
"/index.js", "/index.js",
"The __filename is Node.js feature and doesn't present in browser." "The __filename is Node.js feature and doesn't present in browser."
); );
break; break;
case "node-module":
setEsmConstant("__filename", "fileURLToPath(import.meta.url)");
break;
case true: case true:
setModuleConstant("__filename", module => setCjsModuleConstant("__filename", module =>
relative(compiler.inputFileSystem, context, module.resource) relative(compiler.inputFileSystem, context, module.resource)
); );
break; break;
@ -128,17 +163,23 @@ class NodeStuffPlugin {
if (localOptions.__dirname) { if (localOptions.__dirname) {
switch (localOptions.__dirname) { switch (localOptions.__dirname) {
case "mock": case "mock":
setConstant("__dirname", "/"); setCjsConstant("__dirname", "/");
break; break;
case "warn-mock": case "warn-mock":
setConstant( setCjsConstant(
"__dirname", "__dirname",
"/", "/",
"The __dirname is Node.js feature and doesn't present in browser." "The __dirname is Node.js feature and doesn't present in browser."
); );
break; break;
case "node-module":
setEsmConstant(
"__filename",
'fileURLToPath(import.meta.url + "/..")'
);
break;
case true: case true:
setModuleConstant("__dirname", module => setCjsModuleConstant("__dirname", module =>
relative(compiler.inputFileSystem, context, module.context) relative(compiler.inputFileSystem, context, module.context)
); );
break; break;

View File

@ -213,6 +213,7 @@ const applyWebpackOptionsDefaults = options => {
applyNodeDefaults(options.node, { applyNodeDefaults(options.node, {
futureDefaults: options.experiments.futureDefaults, futureDefaults: options.experiments.futureDefaults,
outputModule: options.output.module,
targetProperties targetProperties
}); });
@ -948,9 +949,13 @@ const applyLoaderDefaults = (loader, { targetProperties }) => {
* @param {Object} options options * @param {Object} options options
* @param {TargetProperties | false} options.targetProperties target properties * @param {TargetProperties | false} options.targetProperties target properties
* @param {boolean} options.futureDefaults is future defaults enabled * @param {boolean} options.futureDefaults is future defaults enabled
* @param {boolean} options.outputModule is output type is module
* @returns {void} * @returns {void}
*/ */
const applyNodeDefaults = (node, { futureDefaults, targetProperties }) => { const applyNodeDefaults = (
node,
{ futureDefaults, outputModule, targetProperties }
) => {
if (node === false) return; if (node === false) return;
F(node, "global", () => { F(node, "global", () => {
@ -958,16 +963,16 @@ const applyNodeDefaults = (node, { futureDefaults, targetProperties }) => {
// TODO webpack 6 should always default to false // TODO webpack 6 should always default to false
return futureDefaults ? "warn" : true; return futureDefaults ? "warn" : true;
}); });
F(node, "__filename", () => {
if (targetProperties && targetProperties.node) return "eval-only"; const handlerForNames = () => {
if (targetProperties && targetProperties.node)
return outputModule ? "node-module" : "eval-only";
// TODO webpack 6 should always default to false // TODO webpack 6 should always default to false
return futureDefaults ? "warn-mock" : "mock"; return futureDefaults ? "warn-mock" : "mock";
}); };
F(node, "__dirname", () => {
if (targetProperties && targetProperties.node) return "eval-only"; F(node, "__filename", handlerForNames);
// TODO webpack 6 should always default to false F(node, "__dirname", handlerForNames);
return futureDefaults ? "warn-mock" : "mock";
});
}; };
/** /**

View File

@ -13,6 +13,7 @@ const NullDependency = require("./NullDependency");
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */ /** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../ChunkGraph")} ChunkGraph */ /** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Dependency")} Dependency */ /** @typedef {import("../Dependency")} Dependency */
/** @typedef {import("../Generator").GenerateContext} GenerateContext */
/** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */ /** @typedef {import("../Dependency").UpdateHashContext} UpdateHashContext */
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */ /** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
/** @typedef {import("../DependencyTemplates")} DependencyTemplates */ /** @typedef {import("../DependencyTemplates")} DependencyTemplates */
@ -21,12 +22,19 @@ const NullDependency = require("./NullDependency");
/** @typedef {import("../util/Hash")} Hash */ /** @typedef {import("../util/Hash")} Hash */
class CachedConstDependency extends NullDependency { class CachedConstDependency extends NullDependency {
constructor(expression, range, identifier) { /**
* @param {string} expression expression
* @param {number|[number, number]} range range
* @param {string} identifier identifier
* @param {InitFragment<GenerateContext>[]=} initFragments init fragments
*/
constructor(expression, range, identifier, initFragments) {
super(); super();
this.expression = expression; this.expression = expression;
this.range = range; this.range = range;
this.identifier = identifier; this.identifier = identifier;
this.initFragments = initFragments;
} }
/** /**
@ -36,9 +44,14 @@ class CachedConstDependency extends NullDependency {
* @returns {void} * @returns {void}
*/ */
updateHash(hash, context) { updateHash(hash, context) {
hash.update(this.identifier + ""); hash.update(`${this.identifier}`);
hash.update(this.range + ""); hash.update(`${this.range}`);
hash.update(this.expression + ""); hash.update(`${this.expression}`);
if (this.initFragments) {
for (const fragment of this.initFragments)
hash.update(`${fragment.key}_${fragment.position}`);
}
} }
serialize(context) { serialize(context) {
@ -47,6 +60,7 @@ class CachedConstDependency extends NullDependency {
write(this.expression); write(this.expression);
write(this.range); write(this.range);
write(this.identifier); write(this.identifier);
write(this.initFragments);
super.serialize(context); super.serialize(context);
} }
@ -57,6 +71,7 @@ class CachedConstDependency extends NullDependency {
this.expression = read(); this.expression = read();
this.range = read(); this.range = read();
this.identifier = read(); this.identifier = read();
this.initFragments = read();
super.deserialize(context); super.deserialize(context);
} }
@ -83,11 +98,22 @@ CachedConstDependency.Template = class CachedConstDependencyTemplate extends (
) { ) {
const dep = /** @type {CachedConstDependency} */ (dependency); const dep = /** @type {CachedConstDependency} */ (dependency);
let position = 0;
if (dep.initFragments && dep.initFragments.length > 0) {
for (const fragment of dep.initFragments) {
initFragments.push(fragment);
}
position = dep.initFragments[dep.initFragments.length - 1].position + 1;
}
initFragments.push( initFragments.push(
new InitFragment( new InitFragment(
`var ${dep.identifier} = ${dep.expression};\n`, `${runtimeTemplate.supportsConst() ? "const" : "var"} ${
dep.identifier
} = ${dep.expression};\n`,
InitFragment.STAGE_CONSTANTS, InitFragment.STAGE_CONSTANTS,
0, position,
`const ${dep.identifier}` `const ${dep.identifier}`
) )
); );

File diff suppressed because one or more lines are too long

View File

@ -1944,11 +1944,11 @@
"properties": { "properties": {
"__dirname": { "__dirname": {
"description": "Include a polyfill for the '__dirname' variable.", "description": "Include a polyfill for the '__dirname' variable.",
"enum": [false, true, "warn-mock", "mock", "eval-only"] "enum": [false, true, "warn-mock", "mock", "node-module", "eval-only"]
}, },
"__filename": { "__filename": {
"description": "Include a polyfill for the '__filename' variable.", "description": "Include a polyfill for the '__filename' variable.",
"enum": [false, true, "warn-mock", "mock", "eval-only"] "enum": [false, true, "warn-mock", "mock", "node-module", "eval-only"]
}, },
"global": { "global": {
"description": "Include a polyfill for the 'global' variable.", "description": "Include a polyfill for the 'global' variable.",

4
types.d.ts vendored
View File

@ -7161,12 +7161,12 @@ declare interface NodeOptions {
/** /**
* Include a polyfill for the '__dirname' variable. * Include a polyfill for the '__dirname' variable.
*/ */
__dirname?: boolean | "warn-mock" | "mock" | "eval-only"; __dirname?: boolean | "warn-mock" | "mock" | "node-module" | "eval-only";
/** /**
* Include a polyfill for the '__filename' variable. * Include a polyfill for the '__filename' variable.
*/ */
__filename?: boolean | "warn-mock" | "mock" | "eval-only"; __filename?: boolean | "warn-mock" | "mock" | "node-module" | "eval-only";
/** /**
* Include a polyfill for the 'global' variable. * Include a polyfill for the 'global' variable.