Skip to content

Commit a576d0f

Browse files
feat: added support for import.meta.dirname and import.meta.filename
1 parent 277fc0b commit a576d0f

File tree

17 files changed

+411
-56
lines changed

17 files changed

+411
-56
lines changed

lib/NodeStuffPlugin.js

Lines changed: 179 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
const {
99
JAVASCRIPT_MODULE_TYPE_AUTO,
10-
JAVASCRIPT_MODULE_TYPE_DYNAMIC
10+
JAVASCRIPT_MODULE_TYPE_DYNAMIC,
11+
JAVASCRIPT_MODULE_TYPE_ESM
1112
} = require("./ModuleTypeConstants");
1213
const NodeStuffInWebError = require("./NodeStuffInWebError");
1314
const RuntimeGlobals = require("./RuntimeGlobals");
@@ -16,7 +17,7 @@ const ConstDependency = require("./dependencies/ConstDependency");
1617
const ExternalModuleDependency = require("./dependencies/ExternalModuleDependency");
1718
const {
1819
evaluateToString,
19-
expressionIsUnsupported
20+
toConstantDependency
2021
} = require("./javascript/JavascriptParserHelpers");
2122
const { relative } = require("./util/fs");
2223
const { parseResource } = require("./util/identifier");
@@ -61,7 +62,7 @@ class NodeStuffPlugin {
6162
* @param {JavascriptParserOptions} parserOptions options
6263
* @returns {void}
6364
*/
64-
const handler = (parser, parserOptions) => {
65+
const globalHandler = (parser, parserOptions) => {
6566
if (parserOptions.node === false) return;
6667

6768
let localOptions = options;
@@ -90,12 +91,12 @@ class NodeStuffPlugin {
9091
};
9192

9293
const withWarning = localOptions.global === "warn";
94+
9395
parser.hooks.expression.for("global").tap(PLUGIN_NAME, (expr) => {
9496
const dep = getGlobalDep(expr);
9597
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
9698
parser.state.module.addPresentationalDependency(dep);
9799

98-
// TODO webpack 6 remove
99100
if (withWarning) {
100101
parser.state.module.addWarning(
101102
new NodeStuffInWebError(
@@ -106,29 +107,56 @@ class NodeStuffPlugin {
106107
);
107108
}
108109
});
110+
109111
parser.hooks.rename.for("global").tap(PLUGIN_NAME, (expr) => {
110112
const dep = getGlobalDep(expr);
111113
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
112114
parser.state.module.addPresentationalDependency(dep);
113115
return false;
114116
});
115117
}
118+
};
119+
120+
/**
121+
* @param {JavascriptParser} parser the parser
122+
* @param {JavascriptParserOptions} parserOptions options
123+
* @param {{ dirname: string, filename: string }} identifiers options
124+
* @returns {void}
125+
*/
126+
const dirnameAndFilenameHandler = (
127+
parser,
128+
parserOptions,
129+
{ dirname, filename }
130+
) => {
131+
if (parserOptions.node === false) return;
132+
133+
let localOptions = options;
134+
135+
if (parserOptions.node) {
136+
localOptions = { ...localOptions, ...parserOptions.node };
137+
}
116138

117139
/**
118140
* @param {string} expressionName expression name
119141
* @param {(module: NormalModule) => string} fn function
142+
* @param {string} identifier identifier
120143
* @param {string=} warning warning
121144
* @returns {void}
122145
*/
123-
const setModuleConstant = (expressionName, fn, warning) => {
146+
const setModuleConstant = (
147+
expressionName,
148+
fn,
149+
identifier,
150+
warning
151+
) => {
124152
parser.hooks.expression
125153
.for(expressionName)
126154
.tap(PLUGIN_NAME, (expr) => {
127155
const dep = new CachedConstDependency(
128156
JSON.stringify(fn(parser.state.module)),
129157
/** @type {Range} */
130158
(expr.range),
131-
expressionName
159+
identifier
132160
);
133161
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
134162
parser.state.module.addPresentationalDependency(dep);
@@ -147,19 +175,31 @@ class NodeStuffPlugin {
147175
/**
148176
* @param {string} expressionName expression name
149177
* @param {(value: string) => string} fn function
178+
* @param {"dirname" | "filename"} property property
150179
* @returns {void}
151180
*/
152-
const setUrlModuleConstant = (expressionName, fn) => {
181+
const setUrlModuleConstant = (expressionName, fn, property) => {
153182
parser.hooks.expression
154183
.for(expressionName)
155184
.tap(PLUGIN_NAME, (expr) => {
156-
const dep = compilation.outputOptions.environment
157-
.importMetaDirnameAndFilename
185+
const { importMetaName, environment, module } =
186+
compilation.outputOptions;
187+
188+
if (
189+
module &&
190+
importMetaName === "import.meta" &&
191+
expressionName.startsWith("import.meta") &&
192+
environment.importMetaDirnameAndFilename
193+
) {
194+
return true;
195+
}
196+
197+
const dep = environment.importMetaDirnameAndFilename
158198
? new CachedConstDependency(
159-
`${compilation.outputOptions.importMetaName}.${expressionName.slice(2)}`,
199+
`${compilation.outputOptions.importMetaName}.${property}`,
160200
/** @type {Range} */
161201
(expr.range),
162-
expressionName
202+
`__webpack_${property}__`
163203
)
164204
: new ExternalModuleDependency(
165205
"url",
@@ -172,7 +212,7 @@ class NodeStuffPlugin {
172212
undefined,
173213
fn("__webpack_fileURLToPath__"),
174214
/** @type {Range} */ (expr.range),
175-
expressionName
215+
`__webpack_${property}__`
176216
);
177217
dep.loc = /** @type {DependencyLocation} */ (expr.loc);
178218
parser.state.module.addPresentationalDependency(dep);
@@ -184,41 +224,80 @@ class NodeStuffPlugin {
184224
/**
185225
* @param {string} expressionName expression name
186226
* @param {string} value value
227+
* @param {string} identifier identifier
187228
* @param {string=} warning warning
188229
* @returns {void}
189230
*/
190-
const setConstant = (expressionName, value, warning) =>
191-
setModuleConstant(expressionName, () => value, warning);
231+
const setConstant = (expressionName, value, identifier, warning) =>
232+
setModuleConstant(expressionName, () => value, identifier, warning);
192233

193234
const context = compiler.context;
235+
236+
// Keep `import.meta.filename` in code
237+
if (
238+
localOptions.__filename === false &&
239+
filename === "import.meta.filename"
240+
) {
241+
parser.hooks.expression
242+
.for(filename)
243+
.tap(PLUGIN_NAME, toConstantDependency(parser, filename));
244+
}
245+
194246
if (localOptions.__filename) {
195247
switch (localOptions.__filename) {
196248
case "mock":
197-
setConstant("__filename", "/index.js");
249+
setConstant(filename, "/index.js", "__webpack_filename__");
198250
break;
199251
case "warn-mock":
200252
setConstant(
201-
"__filename",
253+
filename,
202254
"/index.js",
255+
"__webpack_filename__",
203256
"__filename is a Node.js feature and isn't available in browsers."
204257
);
205258
break;
206259
case "node-module": {
207260
const importMetaName = compilation.outputOptions.importMetaName;
208261

209262
setUrlModuleConstant(
210-
"__filename",
211-
(functionName) => `${functionName}(${importMetaName}.url)`
263+
filename,
264+
(functionName) => `${functionName}(${importMetaName}.url)`,
265+
"filename"
212266
);
213267
break;
214268
}
269+
case "eval-only":
270+
// Keep `import.meta.filename` in the source code for the ES module output
271+
if (compilation.outputOptions.module) {
272+
const { importMetaName } = compilation.outputOptions;
273+
274+
parser.hooks.expression
275+
.for(filename)
276+
.tap(
277+
PLUGIN_NAME,
278+
toConstantDependency(parser, `${importMetaName}.filename`)
279+
);
280+
}
281+
// Replace `import.meta.filename` with `__filename` for the non-ES module output
282+
else if (filename === "import.meta.filename") {
283+
parser.hooks.expression
284+
.for(filename)
285+
.tap(
286+
PLUGIN_NAME,
287+
toConstantDependency(parser, "__filename")
288+
);
289+
}
290+
break;
215291
case true:
216-
setModuleConstant("__filename", (module) =>
217-
relative(
218-
/** @type {InputFileSystem} */ (compiler.inputFileSystem),
219-
context,
220-
module.resource
221-
)
292+
setModuleConstant(
293+
filename,
294+
(module) =>
295+
relative(
296+
/** @type {InputFileSystem} */ (compiler.inputFileSystem),
297+
context,
298+
module.resource
299+
),
300+
"__webpack_filename__"
222301
);
223302
break;
224303
}
@@ -231,41 +310,79 @@ class NodeStuffPlugin {
231310
return evaluateToString(resource.path)(expr);
232311
});
233312
}
313+
314+
// Keep `import.meta.dirname` in code
315+
if (
316+
localOptions.__dirname === false &&
317+
dirname === "import.meta.dirname"
318+
) {
319+
parser.hooks.expression
320+
.for(dirname)
321+
.tap(PLUGIN_NAME, toConstantDependency(parser, dirname));
322+
}
323+
234324
if (localOptions.__dirname) {
235325
switch (localOptions.__dirname) {
236326
case "mock":
237-
setConstant("__dirname", "/");
327+
setConstant(dirname, "/", "__webpack_dirname__");
238328
break;
239329
case "warn-mock":
240330
setConstant(
241-
"__dirname",
331+
dirname,
242332
"/",
333+
"__webpack_dirname__",
243334
"__dirname is a Node.js feature and isn't available in browsers."
244335
);
245336
break;
246337
case "node-module": {
247338
const importMetaName = compilation.outputOptions.importMetaName;
248339

249340
setUrlModuleConstant(
250-
"__dirname",
341+
dirname,
251342
(functionName) =>
252-
`${functionName}(${importMetaName}.url + "/..").slice(0, -1)`
343+
`${functionName}(${importMetaName}.url.replace(/\\/(?:[^\\/]*)$/, ""))`,
344+
"dirname"
253345
);
254346
break;
255347
}
348+
case "eval-only":
349+
// Keep `import.meta.dirname` in the source code for the ES module output and replace `__dirname` on `import.meta.dirname`
350+
if (compilation.outputOptions.module) {
351+
const { importMetaName } = compilation.outputOptions;
352+
353+
parser.hooks.expression
354+
.for(dirname)
355+
.tap(
356+
PLUGIN_NAME,
357+
toConstantDependency(parser, `${importMetaName}.dirname`)
358+
);
359+
}
360+
// Replace `import.meta.dirname` with `__dirname` for the non-ES module output
361+
else if (dirname === "import.meta.dirname") {
362+
parser.hooks.expression
363+
.for(dirname)
364+
.tap(
365+
PLUGIN_NAME,
366+
toConstantDependency(parser, "__dirname")
367+
);
368+
}
369+
break;
256370
case true:
257-
setModuleConstant("__dirname", (module) =>
258-
relative(
259-
/** @type {InputFileSystem} */ (compiler.inputFileSystem),
260-
context,
261-
/** @type {string} */ (module.context)
262-
)
371+
setModuleConstant(
372+
dirname,
373+
(module) =>
374+
relative(
375+
/** @type {InputFileSystem} */ (compiler.inputFileSystem),
376+
context,
377+
/** @type {string} */ (module.context)
378+
),
379+
"__webpack_dirname__"
263380
);
264381
break;
265382
}
266383

267384
parser.hooks.evaluateIdentifier
268-
.for("__dirname")
385+
.for(dirname)
269386
.tap(PLUGIN_NAME, (expr) => {
270387
if (!parser.state.module) return;
271388
return evaluateToString(
@@ -274,23 +391,39 @@ class NodeStuffPlugin {
274391
)(expr);
275392
});
276393
}
277-
parser.hooks.expression
278-
.for("require.extensions")
279-
.tap(
280-
PLUGIN_NAME,
281-
expressionIsUnsupported(
282-
parser,
283-
"require.extensions is not supported by webpack. Use a loader instead."
284-
)
285-
);
286394
};
287395

288396
normalModuleFactory.hooks.parser
289397
.for(JAVASCRIPT_MODULE_TYPE_AUTO)
290-
.tap(PLUGIN_NAME, handler);
398+
.tap(PLUGIN_NAME, (parser, parserOptions) => {
399+
globalHandler(parser, parserOptions);
400+
dirnameAndFilenameHandler(parser, parserOptions, {
401+
dirname: "__dirname",
402+
filename: "__filename"
403+
});
404+
dirnameAndFilenameHandler(parser, parserOptions, {
405+
dirname: "import.meta.dirname",
406+
filename: "import.meta.filename"
407+
});
408+
});
291409
normalModuleFactory.hooks.parser
292410
.for(JAVASCRIPT_MODULE_TYPE_DYNAMIC)
293-
.tap(PLUGIN_NAME, handler);
411+
.tap(PLUGIN_NAME, (parser, parserOptions) => {
412+
globalHandler(parser, parserOptions);
413+
dirnameAndFilenameHandler(parser, parserOptions, {
414+
dirname: "__dirname",
415+
filename: "__filename"
416+
});
417+
});
418+
normalModuleFactory.hooks.parser
419+
.for(JAVASCRIPT_MODULE_TYPE_ESM)
420+
.tap(PLUGIN_NAME, (parser, parserOptions) => {
421+
globalHandler(parser, parserOptions);
422+
dirnameAndFilenameHandler(parser, parserOptions, {
423+
dirname: "import.meta.dirname",
424+
filename: "import.meta.filename"
425+
});
426+
});
294427
}
295428
);
296429
}

lib/config/defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1637,7 +1637,7 @@ const applyNodeDefaults = (
16371637

16381638
F(node, "global", () => {
16391639
if (targetProperties && targetProperties.global) return false;
1640-
// TODO webpack 6 should always default to false
1640+
// We use `warm` because overriding `global` with `globalThis` (or a polyfill) is sometimes safe (global.URL), sometimes unsafe (global.process), but we need to warn about it
16411641
return futureDefaults ? "warn" : true;
16421642
});
16431643

0 commit comments

Comments
 (0)