Skip to content

Commit f757352

Browse files
authored
fix: hoist import.meta as module-level variable with complete properties (#20658)
1 parent 8e6aed2 commit f757352

11 files changed

Lines changed: 133 additions & 61 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack": patch
3+
---
4+
5+
`import.meta` as standalone expression now returns a complete object with known properties (`url`, `webpack`, `main`, `env`) instead of an empty object `({})`, and hoists it as a module-level variable to ensure `import.meta === import.meta` identity. In `preserve-unknown` mode (ESM output), the hoisted object merges runtime `import.meta` properties via `Object.assign`.

AGENTS.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,22 @@ Test files live in `test/` with naming conventions:
5353
- `test/hotCases/` — HMR (Hot Module Replacement) test cases
5454
- `test/benchmarkCases/` — Performance benchmark cases (each has `index.js` + `webpack.config.mjs` + optional `options.mjs` with `setup()`)
5555
- `test/*.unittest.js` — Unit tests using Jest directly (e.g., `test/FileSystemInfo.unittest.js` uses `memfs` for filesystem mocking)
56-
- `test/test262-cases/` — ECMAScript test262 conformance cases (git submodule, init with `git submodule update --init test/test262-cases`; test runner: `test/test262.spectest.js`)
5756

5857
### 4. Running Tests
5958

6059
Only run tests when test files are modified or explicitly requested.
6160

6261
**Choose test command based on modified directory:**
6362

64-
| Modified directory/file | Command |
65-
| ----------------------- | ------------------------------------------------------------------------------------- |
66-
| `test/*.unittest.js` | `yarn test:base -- --testPathPatterns="<filename>"` |
67-
| `test/cases/` | `yarn test:basic` |
68-
| `test/configCases/` | `yarn test:basic -- --testPathPatterns="ConfigTestCases"` |
69-
| `test/statsCases/` | `yarn test:basic -- --testPathPatterns="StatsTestCases"` |
70-
| `test/watchCases/` | `yarn test:base -- --testPathPatterns="WatchTestCases"` |
71-
| `test/hotCases/` | `yarn test:base -- --testPathPatterns="HotTestCases"` |
72-
| `test/benchmarkCases/` | `FILTER="<case-name>" yarn benchmark` |
73-
| `test/test262-cases/` | `yarn test:test262` (requires `git submodule update --init test/test262-cases` first) |
63+
| Modified directory/file | Command |
64+
| ----------------------- | --------------------------------------------------------- |
65+
| `test/*.unittest.js` | `yarn test:base -- --testPathPatterns="<filename>"` |
66+
| `test/cases/` | `yarn test:basic` |
67+
| `test/configCases/` | `yarn test:basic -- --testPathPatterns="ConfigTestCases"` |
68+
| `test/statsCases/` | `yarn test:basic -- --testPathPatterns="StatsTestCases"` |
69+
| `test/watchCases/` | `yarn test:base -- --testPathPatterns="WatchTestCases"` |
70+
| `test/hotCases/` | `yarn test:base -- --testPathPatterns="HotTestCases"` |
71+
| `test/benchmarkCases/` | `FILTER="<case-name>" yarn benchmark` |
7472

7573
You can add `--testNamePattern="<case-name>"` to run only specific test cases for faster validation. Multiple patterns can be combined with `|`. For example, if you only modified `test/configCases/css/basic/`:
7674

@@ -91,7 +89,7 @@ Every user-facing change needs a changeset file:
9189
Description of the change.
9290
```
9391

94-
Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes. Do not prefix the description with `fix:`, `feat:`, etc. — the change type is already indicated by `patch`/`minor`/`major`.
92+
Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes.
9593

9694
### 6. Updating Examples (if needed)
9795

lib/dependencies/ImportMetaPlugin.js

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const { pathToFileURL } = require("url");
99
const { SyncBailHook } = require("tapable");
1010
const Compilation = require("../Compilation");
1111
const DefinePlugin = require("../DefinePlugin");
12-
const ModuleDependencyWarning = require("../ModuleDependencyWarning");
1312
const {
1413
JAVASCRIPT_MODULE_TYPE_AUTO,
1514
JAVASCRIPT_MODULE_TYPE_ESM
@@ -23,9 +22,9 @@ const {
2322
evaluateToString,
2423
toConstantDependency
2524
} = require("../javascript/JavascriptParserHelpers");
26-
const memoize = require("../util/memoize");
2725
const { propertyAccess } = require("../util/property");
2826
const ConstDependency = require("./ConstDependency");
27+
const ModuleInitFragmentDependency = require("./ModuleInitFragmentDependency");
2928

3029
/** @typedef {import("estree").MemberExpression} MemberExpression */
3130
/** @typedef {import("estree").Identifier} Identifier */
@@ -39,10 +38,6 @@ const ConstDependency = require("./ConstDependency");
3938
/** @typedef {import("../javascript/JavascriptParser").DestructuringAssignmentProperty} DestructuringAssignmentProperty */
4039
/** @typedef {import("./ConstDependency").RawRuntimeRequirements} RawRuntimeRequirements */
4140

42-
const getCriticalDependencyWarning = memoize(() =>
43-
require("./CriticalDependencyWarning")
44-
);
45-
4641
const PLUGIN_NAME = "ImportMetaPlugin";
4742

4843
/** @type {WeakMap<Compilation, { stringify: string, env: Record<string, string> }>} */
@@ -116,6 +111,11 @@ class ImportMetaPlugin {
116111
(compilation, { normalModuleFactory }) => {
117112
const hooks = ImportMetaPlugin.getCompilationHooks(compilation);
118113

114+
compilation.dependencyTemplates.set(
115+
ModuleInitFragmentDependency,
116+
new ModuleInitFragmentDependency.Template()
117+
);
118+
119119
/**
120120
* @param {NormalModule} module module
121121
* @returns {string} file url
@@ -181,38 +181,48 @@ class ImportMetaPlugin {
181181
parser.hooks.expression
182182
.for("import.meta")
183183
.tap(PLUGIN_NAME, (metaProperty) => {
184+
/** @type {RawRuntimeRequirements} */
185+
const runtimeRequirements = [];
186+
const moduleArgument = parser.state.module.moduleArgument;
187+
184188
const referencedPropertiesInDestructuring =
185189
parser.destructuringAssignmentPropertiesFor(metaProperty);
186190
if (!referencedPropertiesInDestructuring) {
187-
const CriticalDependencyWarning =
188-
getCriticalDependencyWarning();
189-
parser.state.module.addWarning(
190-
new ModuleDependencyWarning(
191-
parser.state.module,
192-
new CriticalDependencyWarning(
193-
"'import.meta' cannot be used as a standalone expression. For static analysis, its properties must be accessed directly (e.g., 'import.meta.url') or through destructuring."
194-
),
195-
/** @type {DependencyLocation} */ (metaProperty.loc)
196-
)
191+
const varName = "__webpack_import_meta__";
192+
const { stringify: envStringify } =
193+
collectImportMetaEnvDefinitions(compilation);
194+
const knownProps =
195+
`{url: ${importMetaUrl()}, ` +
196+
`webpack: ${importMetaWebpackVersion()}, ` +
197+
`main: ${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}] === ${moduleArgument}, ` +
198+
`env: ${envStringify}}`;
199+
const initCode =
200+
importMeta === "preserve-unknown"
201+
? `var ${varName} = Object.assign(import.meta, ${knownProps});\n`
202+
: `var ${varName} = ${knownProps};\n`;
203+
const initDep = new ModuleInitFragmentDependency(
204+
initCode,
205+
[
206+
RuntimeGlobals.moduleCache,
207+
RuntimeGlobals.entryModuleId,
208+
RuntimeGlobals.module
209+
],
210+
varName
197211
);
212+
initDep.loc = /** @type {DependencyLocation} */ (
213+
metaProperty.loc
214+
);
215+
parser.state.module.addPresentationalDependency(initDep);
198216
const dep = new ConstDependency(
199-
`${
200-
parser.isAsiPosition(
201-
/** @type {Range} */ (metaProperty.range)[0]
202-
)
203-
? ";"
204-
: ""
205-
}({})`,
206-
/** @type {Range} */ (metaProperty.range)
217+
varName,
218+
/** @type {Range} */ (metaProperty.range),
219+
runtimeRequirements
207220
);
208221
dep.loc = /** @type {DependencyLocation} */ (metaProperty.loc);
209222
parser.state.module.addPresentationalDependency(dep);
210223
return true;
211224
}
212225

213-
/** @type {RawRuntimeRequirements} */
214-
const runtimeRequirements = [];
215-
216226
let str = "";
217227
for (const prop of referencedPropertiesInDestructuring) {
218228
const value = hooks.propertyInDestructuring.call(prop);
@@ -230,7 +240,7 @@ class ImportMetaPlugin {
230240
str += `webpack: ${importMetaWebpackVersion()},`;
231241
break;
232242
case "main":
233-
str += `main: ${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}] === ${RuntimeGlobals.module},`;
243+
str += `main: ${RuntimeGlobals.moduleCache}[${RuntimeGlobals.entryModuleId}] === ${moduleArgument},`;
234244
runtimeRequirements.push(
235245
RuntimeGlobals.moduleCache,
236246
RuntimeGlobals.entryModuleId,

test/cases/parsing/asi/warnings.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
11
"use strict";
22

3-
module.exports = [
4-
[
5-
/Critical dependency: 'import\.meta' cannot be used as a standalone expression\. For static analysis, its properties must be accessed directly \(e\.g\., 'import\.meta\.url'\) or through destructuring\./
6-
]
7-
];
3+
module.exports = [];

test/cases/parsing/import-meta/index.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@ it("should return undefined for unknown property", () => {
4444
expect(() => import.meta.other.other.other).toThrow();
4545
});
4646

47-
it("should add warning on direct import.meta usage", () => {
48-
expect(Object.keys(import.meta)).toHaveLength(0);
49-
});
50-
5147
it("should support destructuring assignment", async () => {
5248
let version, url2, c;
5349
({ webpack: version } = { url: url2 } = { c } = import.meta);
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
11
"use strict";
22

3-
module.exports = [
4-
[
5-
/'import\.meta' cannot be used as a standalone expression\. For static analysis, its properties must be accessed directly \(e\.g\., 'import\.meta\.url'\) or through destructuring\./
6-
]
7-
];
3+
module.exports = [];

test/configCases/module/import-meta/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ it("should keep import.meta.UNKNOWN_PROPERTY", () => {
2929
}
3030
});
3131

32+
it("should preserve runtime properties when import.meta is used as standalone expression", () => {
33+
const meta = (() => import.meta)();
34+
expect(meta.UNKNOWN_PROPERTY).toBe("HELLO");
35+
expect(meta.url).toBeTypeOf("string");
36+
expect(meta.webpack).toBeTypeOf("number");
37+
});
38+
3239
it("should support destructuring assignment", async () => {
3340
let version, url2, c, unknown;
3441
({ webpack: version } =
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { pathToFileURL } from "url";
2+
import path from "path";
3+
4+
const url = pathToFileURL(
5+
path.resolve("./test/configCases/parsing/import-meta-standalone/index.js")
6+
).toString();
7+
const webpackVersion = parseInt(
8+
// eslint-disable-next-line n/no-missing-require
9+
require("../../../../package.json").version,
10+
10
11+
);
12+
13+
it("should preserve properties when import.meta is assigned to a variable", () => {
14+
const meta = import.meta;
15+
expect(meta.url).toBe(url);
16+
expect(meta.webpack).toBe(webpackVersion);
17+
expect(typeof meta.main).toBe("boolean");
18+
expect(typeof meta.env).toBe("object");
19+
});
20+
21+
it("should preserve properties when import.meta is returned from a function", () => {
22+
function getMeta() {
23+
return import.meta;
24+
}
25+
const meta = getMeta();
26+
expect(meta.url).toBe(url);
27+
expect(meta.webpack).toBe(webpackVersion);
28+
});
29+
30+
it("should preserve properties when import.meta is passed as an argument", () => {
31+
function readUrl(meta) {
32+
return meta.url;
33+
}
34+
expect(readUrl(import.meta)).toBe(url);
35+
});
36+
37+
it("should return the same object for import.meta", () => {
38+
expect(import.meta).toBe(import.meta);
39+
const a = import.meta;
40+
const b = import.meta;
41+
expect(a).toBe(b);
42+
});
43+
44+
it("should preserve runtime properties via Object.assign", () => {
45+
import.meta.CUSTOM_PROP = "custom";
46+
const meta = import.meta;
47+
expect(meta.CUSTOM_PROP).toBe("custom");
48+
expect(meta.url).toBe(url);
49+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"use strict";
2+
3+
/** @type {import("../../../../types").Configuration} */
4+
module.exports = {
5+
target: "node",
6+
experiments: {
7+
outputModule: true
8+
},
9+
output: {
10+
module: true,
11+
chunkFormat: "module"
12+
}
13+
};

0 commit comments

Comments
 (0)