Skip to content

Commit a2938e2

Browse files
feat: support __dirname/__filename/import.meta.dirname/import.meta.filename for universal target (#20058)
1 parent fd9861a commit a2938e2

File tree

13 files changed

+205
-42
lines changed

13 files changed

+205
-42
lines changed

lib/NodeStuffPlugin.js

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -258,26 +258,32 @@ class NodeStuffPlugin {
258258
return true;
259259
}
260260

261-
const dep = environment.importMetaDirnameAndFilename
262-
? new CachedConstDependency(
263-
`${importMetaName}.${property}`,
264-
/** @type {Range} */
265-
(expr.range),
266-
`__webpack_${property}__`
267-
)
268-
: new ExternalModuleDependency(
269-
"url",
270-
[
271-
{
272-
name: "fileURLToPath",
273-
value: URL_MODULE_CONSTANT_FUNCTION_NAME
274-
}
275-
],
276-
undefined,
277-
`${URL_MODULE_CONSTANT_FUNCTION_NAME}(${value()})`,
278-
/** @type {Range} */ (expr.range),
279-
`__webpack_${property}__`
280-
);
261+
// Generate `import.meta.dirname` and `import.meta.filename` when:
262+
// - they are supported by the environment
263+
// - it is a universal target, because we can't use `import mod from "node:url"; ` at the top file
264+
const dep =
265+
environment.importMetaDirnameAndFilename ||
266+
(compiler.platform.web === null &&
267+
compiler.platform.node === null &&
268+
module)
269+
? new ConstDependency(
270+
`${importMetaName}.${property}`,
271+
/** @type {Range} */
272+
(expr.range)
273+
)
274+
: new ExternalModuleDependency(
275+
"url",
276+
[
277+
{
278+
name: "fileURLToPath",
279+
value: URL_MODULE_CONSTANT_FUNCTION_NAME
280+
}
281+
],
282+
undefined,
283+
`${URL_MODULE_CONSTANT_FUNCTION_NAME}(${value()})`,
284+
/** @type {Range} */ (expr.range),
285+
`__webpack_${property}__`
286+
);
281287
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
282288
parser.state.module.addPresentationalDependency(dep);
283289

@@ -372,15 +378,15 @@ class NodeStuffPlugin {
372378
break;
373379
}
374380
case "eval-only":
375-
// Keep `import.meta.filename` in the source code for the ES module output
381+
// Keep `import.meta.filename` in the source code for the ES module output, or create a fallback using `import.meta.url` if possible
376382
if (compilation.outputOptions.module) {
377383
const { importMetaName } = compilation.outputOptions;
378384

379-
setModuleConstant(
385+
setUrlModuleConstant(
380386
parser,
381387
filename,
382-
() => `${importMetaName}.filename`,
383-
"filename"
388+
"filename",
389+
() => `${importMetaName}.url`
384390
);
385391
}
386392
// Replace `import.meta.filename` with `__filename` for the non-ES module output
@@ -455,11 +461,11 @@ class NodeStuffPlugin {
455461
if (compilation.outputOptions.module) {
456462
const { importMetaName } = compilation.outputOptions;
457463

458-
setModuleConstant(
464+
setUrlModuleConstant(
459465
parser,
460466
dirname,
461-
() => `${importMetaName}.dirname`,
462-
"dirname"
467+
"dirname",
468+
() => `${importMetaName}.url.replace(/\\/(?:[^\\/]*)$/, "")`
463469
);
464470
}
465471
// Replace `import.meta.dirname` with `__dirname` for the non-ES module output

lib/config/defaults.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,10 +1646,23 @@ const applyNodeDefaults = (
16461646
});
16471647

16481648
const handlerForNames = () => {
1649-
if (targetProperties && targetProperties.node) {
1650-
return outputModule ? "node-module" : "eval-only";
1649+
// TODO webpack@6 remove `node-module` in favor of `eval-only`
1650+
if (targetProperties) {
1651+
if (targetProperties.node) {
1652+
return "eval-only";
1653+
}
1654+
1655+
// For the "universal" target we only evaluate these values
1656+
if (
1657+
outputModule &&
1658+
targetProperties.node === null &&
1659+
targetProperties.web === null
1660+
) {
1661+
return "eval-only";
1662+
}
16511663
}
1652-
// TODO webpack 6 should always default to false
1664+
1665+
// TODO webpack@6 should we use `warn-even-only`?
16531666
return futureDefaults ? "warn-mock" : "mock";
16541667
};
16551668

test/Defaults.unittest.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,6 +1841,11 @@ describe("snapshots", () => {
18411841
- "target": "web",
18421842
+ "target": undefined,
18431843
@@ ... @@
1844+
- "__dirname": "mock",
1845+
- "__filename": "mock",
1846+
+ "__dirname": "eval-only",
1847+
+ "__filename": "eval-only",
1848+
@@ ... @@
18441849
- "chunkFilename": "[name].js",
18451850
- "chunkFormat": "array-push",
18461851
+ "chunkFilename": "[name].mjs",
@@ -2934,8 +2939,8 @@ describe("snapshots", () => {
29342939
- "__dirname": "mock",
29352940
- "__filename": "mock",
29362941
- "global": true,
2937-
+ "__dirname": "node-module",
2938-
+ "__filename": "node-module",
2942+
+ "__dirname": "eval-only",
2943+
+ "__filename": "eval-only",
29392944
+ "global": false,
29402945
@@ ... @@
29412946
- "chunkFilename": "[name].js",
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const dirname = __dirname;
2+
const filename = __filename;
3+
4+
module.exports = { dirname, filename };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const dirname = import.meta.dirname;
2+
const filename = import.meta.filename;
3+
4+
export { dirname, filename };
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
import path from "path";
2+
import { dirname as dirnameCjs, filename as filenameCjs } from "./cjs.js";
3+
import { dirname as dirnameEsm, filename as filenameEsm } from "./esm.js";
24

35
it("should use custom name", () => {
4-
expect(__dirname).toBe(__STATS__.outputPath);
5-
expect(__filename).toBe(path.join(__STATS__.outputPath, "./bundle0.js"));
6+
const stats = __STATS__.children[__STATS_I__];
7+
8+
if (typeof globalThis.document === "undefined") {
9+
expect(dirnameCjs).toBe(stats.outputPath);
10+
expect(filenameCjs).toBe(path.join(stats.outputPath, `./bundle${__STATS_I__}.${__STATS_I__ === 0 ? "js" : "mjs"}`));
11+
expect(dirnameEsm).toBe(stats.outputPath);
12+
expect(filenameEsm).toBe(path.join(stats.outputPath, `./bundle${__STATS_I__}.${__STATS_I__ === 0 ? "js" : "mjs"}`));
13+
} else {
14+
expect(dirnameCjs).toBe(stats.outputPath);
15+
expect(filenameCjs.endsWith(`bundle${__STATS_I__}.${__STATS_I__ === 3 ? "js" : "mjs"}`)).toBe(true)
16+
expect(dirnameEsm).toBe(stats.outputPath);
17+
expect(filenameEsm.endsWith(`bundle${__STATS_I__}.${__STATS_I__ === 3 ? "js" : "mjs"}`)).toBe(true);
18+
}
619
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"use strict";
2+
3+
const supportsNodePrefix = require("../../../helpers/supportsNodePrefix");
4+
5+
module.exports = () => supportsNodePrefix();
Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,68 @@
11
"use strict";
22

3-
/** @type {import("../../../../").Configuration} */
4-
module.exports = {
5-
target: "node",
6-
node: {
7-
__filename: "eval-only",
8-
__dirname: "eval-only"
3+
/** @type {import("../../../../").Configuration[]} */
4+
module.exports = [
5+
{
6+
target: "node",
7+
node: {
8+
__filename: "eval-only",
9+
__dirname: "eval-only"
10+
}
11+
},
12+
{
13+
target: "node",
14+
node: {
15+
__filename: "eval-only",
16+
__dirname: "eval-only"
17+
},
18+
output: {
19+
module: true
20+
},
21+
experiments: {
22+
outputModule: true
23+
}
24+
},
25+
{
26+
target: "node24",
27+
node: {
28+
__filename: "eval-only",
29+
__dirname: "eval-only"
30+
},
31+
output: {
32+
module: true
33+
},
34+
experiments: {
35+
outputModule: true
36+
}
37+
},
38+
{
39+
target: "web",
40+
node: {
41+
__filename: "eval-only",
42+
__dirname: "eval-only"
43+
},
44+
resolve: {
45+
fallback: {
46+
path: false
47+
}
48+
}
49+
},
50+
{
51+
target: "web",
52+
node: {
53+
__filename: "eval-only",
54+
__dirname: "eval-only"
55+
},
56+
output: {
57+
module: true
58+
},
59+
experiments: {
60+
outputModule: true
61+
},
62+
resolve: {
63+
fallback: {
64+
path: false
65+
}
66+
}
967
}
10-
};
68+
];
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const isBrowser = typeof globalThis.document !== 'undefined';
2+
3+
it("should work", () => {
4+
if (isBrowser) {
5+
expect(true).toBe(true);
6+
} else {
7+
// We can't handle `parser.hooks.typeof` and `parser.hooks.evaluateTypeof` for `import.meta.dirname` and `import.meta.filename`
8+
// because they may not exist when code is running
9+
expect(import.meta.dirname).toBe(__STATS__.children[__STATS_I__].outputPath);
10+
expect(typeof import.meta.dirname).toBe("string");
11+
expect(import.meta.filename.endsWith("bundle1.mjs")).toBe(true);
12+
expect(typeof import.meta.filename).toBe("string");
13+
}
14+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"use strict";
2+
3+
module.exports = {
4+
moduleScope(scope, options) {
5+
if (options.name.includes("node")) {
6+
delete scope.window;
7+
delete scope.document;
8+
delete scope.self;
9+
}
10+
}
11+
};

0 commit comments

Comments
 (0)