Skip to content

Commit 78ddad1

Browse files
feat: support destructuring for import.meta.filename and import.meta.dirname (#20066)
1 parent 9c1ba09 commit 78ddad1

File tree

11 files changed

+567
-213
lines changed

11 files changed

+567
-213
lines changed

lib/NodeStuffPlugin.js

Lines changed: 339 additions & 200 deletions
Large diffs are not rendered by default.

lib/WebpackOptionsApply.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const FlagDependencyExportsPlugin = require("./FlagDependencyExportsPlugin");
1818

1919
const JavascriptMetaInfoPlugin = require("./JavascriptMetaInfoPlugin");
2020

21+
const NodeStuffPlugin = require("./NodeStuffPlugin");
2122
const OptionsApply = require("./OptionsApply");
2223

2324
const RecordIdsPlugin = require("./RecordIdsPlugin");
@@ -452,11 +453,11 @@ class WebpackOptionsApply extends OptionsApply {
452453
}
453454
new CommonJsPlugin().apply(compiler);
454455
new LoaderPlugin().apply(compiler);
455-
if (options.node !== false) {
456-
const NodeStuffPlugin = require("./NodeStuffPlugin");
457-
458-
new NodeStuffPlugin(options.node).apply(compiler);
459-
}
456+
new NodeStuffPlugin({
457+
global: options.node ? options.node.global : false,
458+
__dirname: options.node ? options.node.__dirname : false,
459+
__filename: options.node ? options.node.__filename : false
460+
}).apply(compiler);
460461
new APIPlugin().apply(compiler);
461462
new ExportsInfoApiPlugin().apply(compiler);
462463
new WebpackIsIncludedPlugin().apply(compiler);
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Alexander Akait @alexander-akait
4+
*/
5+
6+
"use strict";
7+
8+
const DependencyTemplate = require("../DependencyTemplate");
9+
const makeSerializable = require("../util/makeSerializable");
10+
const ExternalModuleInitFragment = require("./ExternalModuleInitFragment");
11+
const NullDependency = require("./NullDependency");
12+
13+
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
14+
/** @typedef {import("../Dependency")} Dependency */
15+
/** @typedef {import("../DependencyTemplate").DependencyTemplateContext} DependencyTemplateContext */
16+
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
17+
/** @typedef {import("../serialization/ObjectMiddleware").ObjectDeserializerContext} ObjectDeserializerContext */
18+
/** @typedef {import("../serialization/ObjectMiddleware").ObjectSerializerContext} ObjectSerializerContext */
19+
20+
class ExternalModuleInitFragmentDependency extends NullDependency {
21+
/**
22+
* @param {string} module module
23+
* @param {{ name: string, value: string }[]} importSpecifiers import specifiers
24+
* @param {string | undefined} defaultImport default import
25+
*/
26+
constructor(module, importSpecifiers, defaultImport) {
27+
super();
28+
this.importedModule = module;
29+
this.specifiers = importSpecifiers;
30+
this.default = defaultImport;
31+
}
32+
33+
/**
34+
* @returns {string} hash update
35+
*/
36+
_createHashUpdate() {
37+
return `${this.importedModule}${JSON.stringify(this.specifiers)}${
38+
this.default || "null"
39+
}`;
40+
}
41+
42+
/**
43+
* @param {ObjectSerializerContext} context context
44+
*/
45+
serialize(context) {
46+
const { write } = context;
47+
write(this.importedModule);
48+
write(this.specifiers);
49+
write(this.default);
50+
}
51+
52+
/**
53+
* @param {ObjectDeserializerContext} context context
54+
*/
55+
deserialize(context) {
56+
const { read } = context;
57+
this.importedModule = read();
58+
this.specifiers = read();
59+
this.default = read();
60+
}
61+
}
62+
63+
makeSerializable(
64+
ExternalModuleInitFragmentDependency,
65+
"webpack/lib/dependencies/ExternalModuleConstDependency"
66+
);
67+
68+
ExternalModuleInitFragmentDependency.Template = class ExternalModuleConstDependencyTemplate extends (
69+
DependencyTemplate
70+
) {
71+
/**
72+
* @param {Dependency} dependency the dependency for which the template should be applied
73+
* @param {ReplaceSource} source the current replace source which can be modified
74+
* @param {DependencyTemplateContext} templateContext the context object
75+
* @returns {void}
76+
*/
77+
apply(dependency, source, templateContext) {
78+
const dep =
79+
/** @type {ExternalModuleInitFragmentDependency} */
80+
(dependency);
81+
const { chunkInitFragments, runtimeTemplate } = templateContext;
82+
83+
chunkInitFragments.push(
84+
new ExternalModuleInitFragment(
85+
`${runtimeTemplate.supportNodePrefixForCoreModules() ? "node:" : ""}${
86+
dep.importedModule
87+
}`,
88+
dep.specifiers,
89+
dep.default
90+
)
91+
);
92+
}
93+
};
94+
95+
module.exports = ExternalModuleInitFragmentDependency;

lib/dependencies/ImportMetaPlugin.js

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"use strict";
77

88
const { pathToFileURL } = require("url");
9+
const { SyncBailHook } = require("tapable");
10+
const Compilation = require("../Compilation");
911
const ModuleDependencyWarning = require("../ModuleDependencyWarning");
1012
const {
1113
JAVASCRIPT_MODULE_TYPE_AUTO,
@@ -31,21 +33,52 @@ const ConstDependency = require("./ConstDependency");
3133
/** @typedef {import("../javascript/JavascriptParser")} Parser */
3234
/** @typedef {import("../javascript/JavascriptParser").Range} Range */
3335
/** @typedef {import("../javascript/JavascriptParser").Members} Members */
36+
/** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */
3437

3538
const getCriticalDependencyWarning = memoize(() =>
3639
require("./CriticalDependencyWarning")
3740
);
3841

3942
const PLUGIN_NAME = "ImportMetaPlugin";
4043

44+
/**
45+
* @typedef {object} ImportMetaPluginHooks
46+
* @property {SyncBailHook<[DestructuringAssignmentProperty], string | void>} propertyInDestructuring
47+
*/
48+
49+
/** @type {WeakMap<Compilation, ImportMetaPluginHooks>} */
50+
const compilationHooksMap = new WeakMap();
51+
4152
class ImportMetaPlugin {
53+
/**
54+
* @param {Compilation} compilation the compilation
55+
* @returns {ImportMetaPluginHooks} the attached hooks
56+
*/
57+
static getCompilationHooks(compilation) {
58+
if (!(compilation instanceof Compilation)) {
59+
throw new TypeError(
60+
"The 'compilation' argument must be an instance of Compilation"
61+
);
62+
}
63+
let hooks = compilationHooksMap.get(compilation);
64+
if (hooks === undefined) {
65+
hooks = {
66+
propertyInDestructuring: new SyncBailHook(["property"])
67+
};
68+
compilationHooksMap.set(compilation, hooks);
69+
}
70+
return hooks;
71+
}
72+
4273
/**
4374
* @param {Compiler} compiler compiler
4475
*/
4576
apply(compiler) {
4677
compiler.hooks.compilation.tap(
4778
PLUGIN_NAME,
4879
(compilation, { normalModuleFactory }) => {
80+
const hooks = ImportMetaPlugin.getCompilationHooks(compilation);
81+
4982
/**
5083
* @param {NormalModule} module module
5184
* @returns {string} file url
@@ -136,8 +169,15 @@ class ImportMetaPlugin {
136169
}
137170

138171
let str = "";
139-
for (const { id: prop } of referencedPropertiesInDestructuring) {
140-
switch (prop) {
172+
for (const prop of referencedPropertiesInDestructuring) {
173+
const value = hooks.propertyInDestructuring.call(prop);
174+
175+
if (value) {
176+
str += value;
177+
continue;
178+
}
179+
180+
switch (prop.id) {
141181
case "url":
142182
str += `url: ${importMetaUrl()},`;
143183
break;
@@ -146,8 +186,8 @@ class ImportMetaPlugin {
146186
break;
147187
default:
148188
str += `[${JSON.stringify(
149-
prop
150-
)}]: ${importMetaUnknownProperty([prop])},`;
189+
prop.id
190+
)}]: ${importMetaUnknownProperty([prop.id])},`;
151191
break;
152192
}
153193
}
@@ -222,7 +262,12 @@ class ImportMetaPlugin {
222262
.tap(PLUGIN_NAME, (expr, members) => {
223263
// keep import.meta.env unknown property
224264
// don't evaluate import.meta.env.UNKNOWN_PROPERTY -> undefined.UNKNOWN_PROPERTY
225-
if (members[0] === "env") {
265+
// `dirname` and `filename` logic in NodeStuffPlugin
266+
if (
267+
members[0] === "env" ||
268+
members[0] === "dirname" ||
269+
members[0] === "filename"
270+
) {
226271
return true;
227272
}
228273
const dep = new ConstDependency(

lib/javascript/JavascriptParser.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ const SCOPE_INFO_TERMINATED_THROW = 2;
375375
* @typedef {object} DestructuringAssignmentProperty
376376
* @property {string} id
377377
* @property {Range} range
378+
* @property {SourceLocation} loc
378379
* @property {Set<DestructuringAssignmentProperty> | undefined=} pattern
379380
* @property {boolean | string} shorthand
380381
*/
@@ -3038,6 +3039,7 @@ class JavascriptParser extends Parser {
30383039
props.add({
30393040
id: key.name,
30403041
range: /** @type {Range} */ (key.range),
3042+
loc: /** @type {SourceLocation} */ (key.loc),
30413043
pattern,
30423044
shorthand: this.scope.inShorthand
30433045
});
@@ -3054,6 +3056,7 @@ class JavascriptParser extends Parser {
30543056
props.add({
30553057
id: str,
30563058
range: /** @type {Range} */ (key.range),
3059+
loc: /** @type {SourceLocation} */ (key.loc),
30573060
pattern,
30583061
shorthand: this.scope.inShorthand
30593062
});
@@ -3089,6 +3092,7 @@ class JavascriptParser extends Parser {
30893092
props.add({
30903093
id: `${i}`,
30913094
range: /** @type {Range} */ (element.range),
3095+
loc: /** @type {SourceLocation} */ (element.loc),
30923096
pattern,
30933097
shorthand: false
30943098
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const { dirname, filename } = import.meta;
2+
3+
export { dirname, filename };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import path from "path";
22
import { dirname, filename } from "./esm.js"
3+
import { dirname as otherDirname, filename as otherFilename } from "./destructuring.js"
34

45
it("should bundle", async () => {
56
const __dirname = __STATS__.children[__STATS_I__].outputPath;
67
const __filename = path.join(__STATS__.children[__STATS_I__].outputPath, `./bundle${__STATS_I__}.mjs`);
78

89
expect(dirname).toBe(__dirname);
910
expect(filename).toBe(__filename);
11+
expect(otherDirname).toBe(__dirname);
12+
expect(otherFilename).toBe(__filename);
1013
});

test/configCases/node/filename-and-dirname/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
11
import path from "path";
22
import { dirname as dirnameCommonJS, filename as filenameCommonJS } from "./commonjs.js"
33
import { dirname as dirnameESM, filename as filenameESM } from "./esm.js"
4+
import { dirname as otherDirnameESM, filename as otherFilenameESM } from "./destructuring.js"
45

56
const __dirname = "dirname";
67
const __filename = "filename";
78

8-
it("should bundle", async () => {
9+
it("should bundle", () => {
910
switch (NODE_VALUE) {
1011
case true:
1112
expect(dirnameCommonJS).toBe("");
1213
expect(filenameCommonJS).toBe("commonjs.js");
1314
expect(dirnameESM).toBe("");
1415
expect(filenameESM).toBe("esm.js");
16+
expect(otherDirnameESM).toBe("");
17+
expect(otherFilenameESM).toBe("destructuring.js");
1518
break;
1619
case "mock":
1720
case "warn-mock":
1821
expect(dirnameCommonJS).toBe("/");
1922
expect(filenameCommonJS).toBe("/index.js");
2023
expect(dirnameESM).toBe("/");
2124
expect(filenameESM).toBe("/index.js");
25+
expect(otherDirnameESM).toBe("/");
26+
expect(otherFilenameESM).toBe("/index.js");
2227
break;
2328
case "node-module": {
2429
const dirname = __STATS__.children[__STATS_I__].outputPath;
@@ -28,6 +33,8 @@ it("should bundle", async () => {
2833
expect(filenameCommonJS).toBe(filename);
2934
expect(dirnameESM).toBe(dirname);
3035
expect(filenameESM).toBe(filename);
36+
expect(otherDirnameESM).toBe(dirname);
37+
expect(otherFilenameESM).toBe(filename);
3138
break;
3239
}
3340
case false:
@@ -42,6 +49,8 @@ it("should bundle", async () => {
4249
expect(filenameCommonJS).toBe(filename);
4350
expect(dirnameESM).toBe(dirname);
4451
expect(filenameESM).toBe(filename);
52+
expect(otherDirnameESM).toBe(dirname);
53+
expect(otherFilenameESM).toBe(filename);
4554
break;
4655
}
4756
}

test/configCases/node/filename-and-dirname/warnings.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@ module.exports = [
55
/"__filename" has been used/,
66
/"import.meta.dirname" has been used/,
77
/"import.meta.filename" has been used/,
8+
/"import.meta.dirname" has been used/,
9+
/"import.meta.filename" has been used/,
810
/"__dirname" has been used/,
911
/"__filename" has been used/,
1012
/"import.meta.dirname" has been used/,
1113
/"import.meta.filename" has been used/,
14+
/"import.meta.dirname" has been used/,
15+
/"import.meta.filename" has been used/,
1216
/"__dirname" has been used/,
1317
/"__filename" has been used/,
1418
/"import.meta.dirname" has been used/,
19+
/"import.meta.filename" has been used/,
20+
/"import.meta.dirname" has been used/,
1521
/"import.meta.filename" has been used/
1622
];

0 commit comments

Comments
 (0)