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.
*/
__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.
*/
__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.
*/

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@ -1944,11 +1944,11 @@
"properties": {
"__dirname": {
"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": {
"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": {
"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.
*/
__dirname?: boolean | "warn-mock" | "mock" | "eval-only";
__dirname?: boolean | "warn-mock" | "mock" | "node-module" | "eval-only";
/**
* 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.