@esmx/import 3.0.0-rc.117 → 3.0.0-rc.118
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/README.zh-CN.md +13 -6
- package/dist/error.test.mjs +38 -0
- package/dist/import-loader.mjs +5 -2
- package/dist/import-vm.d.ts +4 -1
- package/dist/import-vm.mjs +133 -55
- package/dist/import-vm.test.d.ts +1 -0
- package/dist/import-vm.test.mjs +653 -0
- package/dist/index.d.ts +2 -2
- package/dist/types.d.ts +7 -0
- package/package.json +23 -6
- package/src/error.test.ts +46 -0
- package/src/import-loader.ts +5 -2
- package/src/import-vm.test.ts +755 -0
- package/src/import-vm.ts +222 -56
- package/src/index.ts +7 -2
- package/src/types.ts +8 -0
package/src/import-vm.ts
CHANGED
|
@@ -1,12 +1,73 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { isBuiltin } from 'node:module';
|
|
2
|
+
import { createRequire, isBuiltin } from 'node:module';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import vm from 'node:vm';
|
|
6
|
-
import {
|
|
6
|
+
import { FileReadError } from './error';
|
|
7
7
|
import { createImportMapResolver } from './import-map-resolve';
|
|
8
8
|
import type { ImportMap } from './types';
|
|
9
9
|
|
|
10
|
+
const requireSync = createRequire(import.meta.url);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cache value type. Style-asset imports (`*.css`, `*.scss`, etc.) materialize
|
|
14
|
+
* as a `SyntheticModule` exposing the asset URL; everything else parses through
|
|
15
|
+
* `SourceTextModule`. Both inherit from the (internal) base `vm.Module` and
|
|
16
|
+
* expose the `namespace` getter the public API ultimately returns.
|
|
17
|
+
*/
|
|
18
|
+
type CachedVmModule = vm.SourceTextModule | vm.SyntheticModule;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Extensions that the esmx federation contract treats as STYLE ASSETS rather
|
|
22
|
+
* than executable JS modules. On the server they materialize as no-op synthetic
|
|
23
|
+
* modules exposing the asset URL — they have no observable side effect there,
|
|
24
|
+
* because there is no document. Browsers receive the stylesheet via a
|
|
25
|
+
* `<link rel="stylesheet">` that the host injects from the federation manifest
|
|
26
|
+
* (`chunks[*].css[]`), not by evaluating these modules. See
|
|
27
|
+
* `docs/design/principles.md` P1 and the G section of the redesign plan.
|
|
28
|
+
*
|
|
29
|
+
* Query strings (`?inline`, `?url`) are tolerated — bundlers sometimes attach
|
|
30
|
+
* them when rewriting CSS imports.
|
|
31
|
+
*/
|
|
32
|
+
const STYLE_ASSET_RE =
|
|
33
|
+
/\.(?:css|scss|sass|less|stylus|styl|pcss|postcss)(?:\?.*)?$/i;
|
|
34
|
+
|
|
35
|
+
function isStyleAsset(url: string): boolean {
|
|
36
|
+
return STYLE_ASSET_RE.test(url);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let lazyGlobalsMaterialized = false;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Materializes Node's lazily-defined `DOMException` global in the main realm
|
|
43
|
+
* before a vm SSR context is created.
|
|
44
|
+
*
|
|
45
|
+
* Node installs `DOMException` as a lazy getter on the global object. When that
|
|
46
|
+
* getter is first invoked from inside a vm context — which happens when an SSR
|
|
47
|
+
* library copies/enumerates `globalThis` during rendering (e.g. `@lit-labs/ssr`
|
|
48
|
+
* runs `Object.assign(globalThis, window)`) — Node hits an `isolate_data`
|
|
49
|
+
* native assertion and aborts the entire process, because vm contexts do not
|
|
50
|
+
* carry Node's per-context data.
|
|
51
|
+
*
|
|
52
|
+
* This is a robustness property of esmx's own vm execution sandbox, not a shim
|
|
53
|
+
* for any specific framework: reading the global here, in the main realm,
|
|
54
|
+
* replaces the lazy getter with a plain data property on the shared global so
|
|
55
|
+
* code running inside the vm only ever observes a concrete value. It runs once
|
|
56
|
+
* and has no observable effect beyond eagerly instantiating a global that would
|
|
57
|
+
* otherwise be created on first access anyway.
|
|
58
|
+
*/
|
|
59
|
+
function materializeLazyGlobals(): void {
|
|
60
|
+
if (lazyGlobalsMaterialized) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
lazyGlobalsMaterialized = true;
|
|
64
|
+
void globalThis.DOMException;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface VmImportOptions {
|
|
68
|
+
readFileSync?: (path: string) => string;
|
|
69
|
+
}
|
|
70
|
+
|
|
10
71
|
async function importBuiltinModule(specifier: string, context: vm.Context) {
|
|
11
72
|
const nodeModule = await import(specifier);
|
|
12
73
|
const keys = Object.keys(nodeModule);
|
|
@@ -29,7 +90,13 @@ async function importBuiltinModule(specifier: string, context: vm.Context) {
|
|
|
29
90
|
return module;
|
|
30
91
|
}
|
|
31
92
|
|
|
32
|
-
export function createVmImport(
|
|
93
|
+
export function createVmImport(
|
|
94
|
+
baseURL: URL,
|
|
95
|
+
importMap: ImportMap = {},
|
|
96
|
+
options?: VmImportOptions
|
|
97
|
+
) {
|
|
98
|
+
const readFileSync =
|
|
99
|
+
options?.readFileSync ?? ((path) => fs.readFileSync(path, 'utf-8'));
|
|
33
100
|
const importMapResolver = createImportMapResolver(baseURL.href, importMap);
|
|
34
101
|
const buildMeta = (specifier: string, parent: string): ImportMeta => {
|
|
35
102
|
const result = importMapResolver(specifier, parent);
|
|
@@ -46,95 +113,194 @@ export function createVmImport(baseURL: URL, importMap: ImportMap = {}) {
|
|
|
46
113
|
}
|
|
47
114
|
};
|
|
48
115
|
};
|
|
49
|
-
|
|
116
|
+
|
|
117
|
+
function syncLinker(
|
|
50
118
|
specifier: string,
|
|
51
119
|
parent: string,
|
|
52
120
|
context: vm.Context,
|
|
53
|
-
cache: Map<string,
|
|
121
|
+
cache: Map<string, CachedVmModule>,
|
|
122
|
+
linkStatus: Map<string, Promise<void>>,
|
|
54
123
|
moduleIds: string[]
|
|
55
|
-
) {
|
|
124
|
+
): CachedVmModule {
|
|
56
125
|
if (isBuiltin(specifier)) {
|
|
57
|
-
|
|
126
|
+
const nodeModule = requireSync(specifier);
|
|
127
|
+
const keys = Object.keys(nodeModule);
|
|
128
|
+
const hasDefault = keys.includes('default');
|
|
129
|
+
if (!hasDefault) {
|
|
130
|
+
keys.push('default');
|
|
131
|
+
}
|
|
132
|
+
const module = new vm.SyntheticModule(
|
|
133
|
+
keys,
|
|
134
|
+
function evaluateCallback() {
|
|
135
|
+
keys.forEach((key) => {
|
|
136
|
+
this.setExport(
|
|
137
|
+
key,
|
|
138
|
+
key === 'default' && !hasDefault
|
|
139
|
+
? nodeModule
|
|
140
|
+
: nodeModule[key]
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
identifier: specifier,
|
|
146
|
+
context: context
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
const linkResult = module.link(() => {
|
|
150
|
+
throw new TypeError(`Native modules should not be linked`);
|
|
151
|
+
});
|
|
152
|
+
const linkPromise =
|
|
153
|
+
linkResult && typeof linkResult.then === 'function'
|
|
154
|
+
? linkResult.then(() => module.evaluate())
|
|
155
|
+
: module.evaluate();
|
|
156
|
+
linkStatus.set(specifier, linkPromise);
|
|
157
|
+
return module;
|
|
58
158
|
}
|
|
59
159
|
const meta = buildMeta(specifier, parent);
|
|
60
160
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
161
|
+
const cachedModule = cache.get(meta.url);
|
|
162
|
+
if (cachedModule) {
|
|
163
|
+
return cachedModule;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Style assets (.css and friends) are part of esmx's federation
|
|
167
|
+
// contract but carry no observable server-side behaviour — the host
|
|
168
|
+
// emits a `<link rel="stylesheet">` from the manifest, not by
|
|
169
|
+
// evaluating the file. Return a SyntheticModule exposing the asset
|
|
170
|
+
// URL so user code that writes `import sheet from './x.css'`
|
|
171
|
+
// typechecks and runs without the VM trying to parse CSS as JS.
|
|
172
|
+
if (isStyleAsset(meta.url)) {
|
|
173
|
+
const exportNames = ['default', 'href'];
|
|
174
|
+
const styleModule = new vm.SyntheticModule(
|
|
175
|
+
exportNames,
|
|
176
|
+
function evaluateCallback() {
|
|
177
|
+
this.setExport('default', meta.url);
|
|
178
|
+
this.setExport('href', meta.url);
|
|
179
|
+
},
|
|
180
|
+
{ identifier: specifier, context }
|
|
66
181
|
);
|
|
182
|
+
cache.set(meta.url, styleModule);
|
|
183
|
+
const linkResult = styleModule.link(() => {
|
|
184
|
+
throw new TypeError(
|
|
185
|
+
`Style modules should not be linked: ${specifier}`
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
const linkPromise =
|
|
189
|
+
linkResult && typeof linkResult.then === 'function'
|
|
190
|
+
? linkResult.then(() => styleModule.evaluate())
|
|
191
|
+
: styleModule.evaluate();
|
|
192
|
+
linkStatus.set(meta.url, linkPromise);
|
|
193
|
+
return styleModule;
|
|
67
194
|
}
|
|
68
195
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
196
|
+
let text: string;
|
|
197
|
+
try {
|
|
198
|
+
text = readFileSync(meta.filename);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
throw new FileReadError(
|
|
201
|
+
`Failed to read module: ${meta.filename}`,
|
|
202
|
+
moduleIds,
|
|
203
|
+
meta.filename,
|
|
204
|
+
error as Error
|
|
205
|
+
);
|
|
72
206
|
}
|
|
73
|
-
const modulePromise = new Promise<vm.SourceTextModule>((resolve) => {
|
|
74
|
-
process.nextTick(() => {
|
|
75
|
-
moduleBuild().then(resolve);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
207
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
208
|
+
const module = new vm.SourceTextModule(text, {
|
|
209
|
+
initializeImportMeta: (importMeta) => {
|
|
210
|
+
Object.assign(importMeta, meta);
|
|
211
|
+
},
|
|
212
|
+
identifier: specifier,
|
|
213
|
+
context: context,
|
|
214
|
+
importModuleDynamically: (specifier, referrer) => {
|
|
215
|
+
return moduleLinker(
|
|
216
|
+
specifier,
|
|
217
|
+
meta.url,
|
|
218
|
+
referrer.context,
|
|
219
|
+
cache,
|
|
220
|
+
linkStatus,
|
|
221
|
+
[...moduleIds, meta.filename]
|
|
92
222
|
);
|
|
93
223
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return moduleLinker(
|
|
102
|
-
specifier,
|
|
103
|
-
meta.url,
|
|
104
|
-
referrer.context,
|
|
105
|
-
cache,
|
|
106
|
-
[...moduleIds, meta.filename]
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
await module.link((specifier: string, referrer) => {
|
|
111
|
-
return moduleLinker(
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
cache.set(meta.url, module);
|
|
227
|
+
|
|
228
|
+
const linkPromise = module
|
|
229
|
+
.link((specifier: string, referrer) => {
|
|
230
|
+
return syncLinker(
|
|
112
231
|
specifier,
|
|
113
232
|
meta.url,
|
|
114
233
|
referrer.context,
|
|
115
234
|
cache,
|
|
235
|
+
linkStatus,
|
|
116
236
|
[...moduleIds, meta.filename]
|
|
117
237
|
);
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
|
|
238
|
+
})
|
|
239
|
+
.then(() => module.evaluate());
|
|
240
|
+
|
|
241
|
+
linkStatus.set(meta.url, linkPromise);
|
|
242
|
+
|
|
243
|
+
return module;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function moduleLinker(
|
|
247
|
+
specifier: string,
|
|
248
|
+
parent: string,
|
|
249
|
+
context: vm.Context,
|
|
250
|
+
cache: Map<string, CachedVmModule>,
|
|
251
|
+
linkStatus: Map<string, Promise<void>>,
|
|
252
|
+
moduleIds: string[]
|
|
253
|
+
) {
|
|
254
|
+
if (isBuiltin(specifier)) {
|
|
255
|
+
return importBuiltinModule(specifier, context);
|
|
256
|
+
}
|
|
257
|
+
const meta = buildMeta(specifier, parent);
|
|
258
|
+
|
|
259
|
+
const cachedModule = cache.get(meta.url);
|
|
260
|
+
if (cachedModule) {
|
|
261
|
+
const status = linkStatus.get(meta.url);
|
|
262
|
+
if (status) {
|
|
263
|
+
await status;
|
|
264
|
+
}
|
|
265
|
+
return cachedModule;
|
|
121
266
|
}
|
|
267
|
+
|
|
268
|
+
const module = syncLinker(
|
|
269
|
+
specifier,
|
|
270
|
+
parent,
|
|
271
|
+
context,
|
|
272
|
+
cache,
|
|
273
|
+
linkStatus,
|
|
274
|
+
moduleIds
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const status = linkStatus.get(meta.url);
|
|
278
|
+
if (status) {
|
|
279
|
+
await status;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return module;
|
|
122
283
|
}
|
|
284
|
+
|
|
123
285
|
return async (
|
|
124
286
|
specifier: string,
|
|
125
287
|
parent: string,
|
|
126
288
|
sandbox?: vm.Context,
|
|
127
289
|
options?: vm.CreateContextOptions
|
|
128
290
|
) => {
|
|
291
|
+
materializeLazyGlobals();
|
|
129
292
|
const context = vm.createContext(sandbox, options);
|
|
293
|
+
const cache = new Map<string, CachedVmModule>();
|
|
294
|
+
const linkStatus = new Map<string, Promise<void>>();
|
|
130
295
|
const module = await moduleLinker(
|
|
131
296
|
specifier,
|
|
132
297
|
parent,
|
|
133
298
|
context,
|
|
134
|
-
|
|
299
|
+
cache,
|
|
300
|
+
linkStatus,
|
|
135
301
|
[]
|
|
136
302
|
);
|
|
137
303
|
|
|
138
|
-
return module.namespace
|
|
304
|
+
return module.namespace as Record<string, any>;
|
|
139
305
|
};
|
|
140
306
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,5 +6,10 @@ export {
|
|
|
6
6
|
ModuleLoadingError
|
|
7
7
|
} from './error';
|
|
8
8
|
export { createLoaderImport } from './import-loader';
|
|
9
|
-
export { createVmImport } from './import-vm';
|
|
10
|
-
export type {
|
|
9
|
+
export { createVmImport, type VmImportOptions } from './import-vm';
|
|
10
|
+
export type {
|
|
11
|
+
ImportMap,
|
|
12
|
+
IntegrityMap,
|
|
13
|
+
ScopesMap,
|
|
14
|
+
SpecifierMap
|
|
15
|
+
} from './types';
|
package/src/types.ts
CHANGED
|
@@ -2,9 +2,17 @@ export type SpecifierMap = Record<string, string>;
|
|
|
2
2
|
|
|
3
3
|
export type ScopesMap = Record<string, SpecifierMap>;
|
|
4
4
|
|
|
5
|
+
export type IntegrityMap = Record<string, string>;
|
|
6
|
+
|
|
5
7
|
export interface ImportMap {
|
|
6
8
|
imports?: SpecifierMap;
|
|
7
9
|
scopes?: ScopesMap;
|
|
10
|
+
/**
|
|
11
|
+
* Subresource Integrity metadata, mapping module URLs to integrity hashes.
|
|
12
|
+
* Part of the Import Maps specification; ignored by browsers that do not
|
|
13
|
+
* support it.
|
|
14
|
+
*/
|
|
15
|
+
integrity?: IntegrityMap;
|
|
8
16
|
}
|
|
9
17
|
|
|
10
18
|
export type ImportMapResolver = (
|