@esmx/import 3.0.0-rc.116 → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Esmx Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -55,6 +55,13 @@ yarn add @esmx/import
55
55
  import { createVmImport } from '@esmx/import';
56
56
  import { pathToFileURL } from 'node:url';
57
57
 
58
+ const baseURL = pathToFileURL('/project');
59
+ const importMap = {
60
+ imports: {
61
+ 'my-app/src/utils': '/project/src/utils.mjs'
62
+ }
63
+ };
64
+
58
65
  const vmImport = createVmImport(baseURL, importMap);
59
66
  const module = await vmImport('my-app/src/utils', import.meta.url);
60
67
  ```
@@ -124,7 +131,7 @@ const loaderImport = createLoaderImport(baseURL, importMap);
124
131
  const module = await loaderImport(specifier);
125
132
  ```
126
133
 
127
- ### ImportMap Format
134
+ ### Import Map Format
128
135
  ```typescript
129
136
  interface ImportMap {
130
137
  imports?: Record<string, string>;
package/README.zh-CN.md CHANGED
@@ -20,7 +20,7 @@
20
20
  </a>
21
21
  </div>
22
22
 
23
- <p>为 Esmx 框架提供 Import Maps 的 Node.js 服务端实现</p>
23
+ <p>为 Esmx 框架提供 Import Map 的 Node.js 服务端实现</p>
24
24
 
25
25
  <p>
26
26
  <a href="https://github.com/esmnext/esmx/blob/master/packages/import/README.md">English</a> | 中文
@@ -31,10 +31,10 @@
31
31
 
32
32
  - **双重实现** - 开发环境使用 VM 模式,生产环境使用 Loader 模式
33
33
  - **热重载支持** - VM 模式支持多次创建,提供开发灵活性
34
- - **高性能** - Loader 模式为生产部署优化
34
+ - **高性能** - Loader 模式针对生产环境优化
35
35
  - **Node.js 专注** - 专为 Node.js 服务端环境设计
36
- - **TypeScript 就绪** - 完整的 TypeScript 支持,出色的类型安全
37
- - **ESM 标准** - 完全符合 Import Maps 规范
36
+ - **TypeScript 支持** - 完整的 TypeScript 类型安全
37
+ - **ESM 标准** - 完全符合 Import Map 规范
38
38
 
39
39
  ## 📦 安装
40
40
 
@@ -55,13 +55,20 @@ yarn add @esmx/import
55
55
  import { createVmImport } from '@esmx/import';
56
56
  import { pathToFileURL } from 'node:url';
57
57
 
58
+ const baseURL = pathToFileURL('/project');
59
+ const importMap = {
60
+ imports: {
61
+ 'my-app/src/utils': '/project/src/utils.mjs'
62
+ }
63
+ };
64
+
58
65
  const vmImport = createVmImport(baseURL, importMap);
59
66
  const module = await vmImport('my-app/src/utils', import.meta.url);
60
67
  ```
61
68
 
62
69
  ## 📖 模式对比
63
70
 
64
- `@esmx/import` 提供两种不同的 Import Maps 实现方式:
71
+ `@esmx/import` 提供两种不同的 Import Map 实现方式:
65
72
 
66
73
  | 特性 | VM 模式 | Loader 模式 |
67
74
  |------|---------|-------------|
@@ -124,7 +131,7 @@ const loaderImport = createLoaderImport(baseURL, importMap);
124
131
  const module = await loaderImport(specifier);
125
132
  ```
126
133
 
127
- ### ImportMap 格式
134
+ ### Import Map 格式
128
135
  ```typescript
129
136
  interface ImportMap {
130
137
  imports?: Record<string, string>;
@@ -163,5 +163,43 @@ describe("Module Loading Errors", () => {
163
163
  ` \u2514\u2500 ${relativeE} \u274C Loading failed`
164
164
  );
165
165
  });
166
+ it("should handle formatModuleChain with original error", () => {
167
+ const moduleIds = [];
168
+ const targetModule = "/src/error.js";
169
+ const originalError = new Error("Original error message");
170
+ const formatted = formatModuleChain(
171
+ moduleIds,
172
+ targetModule,
173
+ originalError
174
+ );
175
+ expect(formatted).toContain("Error details:");
176
+ expect(formatted).toContain("Original error message");
177
+ });
178
+ });
179
+ describe("ModuleLoadingError base class", () => {
180
+ it("should create base error with all properties", () => {
181
+ const moduleIds = ["/src/a.js"];
182
+ const targetModule = "/src/b.js";
183
+ const originalError = new Error("cause");
184
+ const error = new ModuleLoadingError(
185
+ "Base error",
186
+ moduleIds,
187
+ targetModule,
188
+ originalError
189
+ );
190
+ expect(error.name).toBe("ModuleLoadingError");
191
+ expect(error.message).toBe("Base error");
192
+ expect(error.moduleIds).toEqual(moduleIds);
193
+ expect(error.targetModule).toBe(targetModule);
194
+ expect(error.originalError).toBe(originalError);
195
+ });
196
+ it("should create base error without original error", () => {
197
+ const error = new ModuleLoadingError(
198
+ "Simple error",
199
+ [],
200
+ "/src/x.js"
201
+ );
202
+ expect(error.originalError).toBeUndefined();
203
+ });
166
204
  });
167
205
  });
@@ -13,14 +13,17 @@ export function createLoaderImport(baseURL, importMap = {}) {
13
13
  registered = JSON.stringify(importMap);
14
14
  } else if (registered !== JSON.stringify(importMap)) {
15
15
  throw new Error(
16
- `'createLoaderImport()' can only be created once and cannot be created repeatedly`
16
+ `[@esmx/import] createLoaderImport() is a per-process singleton and was called twice with different importMap data. Ensure your app initializes the loader exactly once at startup.`
17
17
  );
18
18
  }
19
19
  return (specifier) => {
20
20
  try {
21
21
  return import(specifier);
22
22
  } catch (e) {
23
- throw new Error(`Failed to import '${specifier}'`);
23
+ const cause = e instanceof Error ? e.message : String(e);
24
+ throw new Error(
25
+ `[@esmx/import] Failed to import '${specifier}'. Verify the specifier is declared in the importMap (modules.imports in entry.node.ts) or matches a federated module name. Original error: ${cause}`
26
+ );
24
27
  }
25
28
  };
26
29
  }
@@ -1,3 +1,6 @@
1
1
  import vm from 'node:vm';
2
2
  import type { ImportMap } from './types';
3
- export declare function createVmImport(baseURL: URL, importMap?: ImportMap): (specifier: string, parent: string, sandbox?: vm.Context, options?: vm.CreateContextOptions) => Promise<any>;
3
+ export interface VmImportOptions {
4
+ readFileSync?: (path: string) => string;
5
+ }
6
+ export declare function createVmImport(baseURL: URL, importMap?: ImportMap, options?: VmImportOptions): (specifier: string, parent: string, sandbox?: vm.Context, options?: vm.CreateContextOptions) => Promise<Record<string, any>>;
@@ -1,10 +1,23 @@
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 { CircularDependencyError, FileReadError } from "./error.mjs";
6
+ import { FileReadError } from "./error.mjs";
7
7
  import { createImportMapResolver } from "./import-map-resolve.mjs";
8
+ const requireSync = createRequire(import.meta.url);
9
+ const STYLE_ASSET_RE = /\.(?:css|scss|sass|less|stylus|styl|pcss|postcss)(?:\?.*)?$/i;
10
+ function isStyleAsset(url) {
11
+ return STYLE_ASSET_RE.test(url);
12
+ }
13
+ let lazyGlobalsMaterialized = false;
14
+ function materializeLazyGlobals() {
15
+ if (lazyGlobalsMaterialized) {
16
+ return;
17
+ }
18
+ lazyGlobalsMaterialized = true;
19
+ void globalThis.DOMException;
20
+ }
8
21
  async function importBuiltinModule(specifier, context) {
9
22
  const nodeModule = await import(specifier);
10
23
  const keys = Object.keys(nodeModule);
@@ -26,7 +39,8 @@ async function importBuiltinModule(specifier, context) {
26
39
  await module.evaluate();
27
40
  return module;
28
41
  }
29
- export function createVmImport(baseURL, importMap = {}) {
42
+ export function createVmImport(baseURL, importMap = {}, options) {
43
+ const readFileSync = options?.readFileSync ?? ((path2) => fs.readFileSync(path2, "utf-8"));
30
44
  const importMapResolver = createImportMapResolver(baseURL.href, importMap);
31
45
  const buildMeta = (specifier, parent) => {
32
46
  const result = importMapResolver(specifier, parent);
@@ -42,77 +56,141 @@ export function createVmImport(baseURL, importMap = {}) {
42
56
  }
43
57
  };
44
58
  };
45
- async function moduleLinker(specifier, parent, context, cache, moduleIds) {
59
+ function syncLinker(specifier, parent, context, cache, linkStatus, moduleIds) {
46
60
  if (isBuiltin(specifier)) {
47
- return importBuiltinModule(specifier, context);
48
- }
49
- const meta = buildMeta(specifier, parent);
50
- if (moduleIds.includes(meta.url)) {
51
- throw new CircularDependencyError(
52
- "Circular dependency detected",
53
- moduleIds,
54
- meta.filename
61
+ const nodeModule = requireSync(specifier);
62
+ const keys = Object.keys(nodeModule);
63
+ const hasDefault = keys.includes("default");
64
+ if (!hasDefault) {
65
+ keys.push("default");
66
+ }
67
+ const module2 = new vm.SyntheticModule(
68
+ keys,
69
+ function evaluateCallback() {
70
+ keys.forEach((key) => {
71
+ this.setExport(
72
+ key,
73
+ key === "default" && !hasDefault ? nodeModule : nodeModule[key]
74
+ );
75
+ });
76
+ },
77
+ {
78
+ identifier: specifier,
79
+ context
80
+ }
55
81
  );
82
+ const linkResult = module2.link(() => {
83
+ throw new TypeError(`Native modules should not be linked`);
84
+ });
85
+ const linkPromise2 = linkResult && typeof linkResult.then === "function" ? linkResult.then(() => module2.evaluate()) : module2.evaluate();
86
+ linkStatus.set(specifier, linkPromise2);
87
+ return module2;
56
88
  }
57
- const module = cache.get(meta.url);
58
- if (module) {
59
- return module;
89
+ const meta = buildMeta(specifier, parent);
90
+ const cachedModule = cache.get(meta.url);
91
+ if (cachedModule) {
92
+ return cachedModule;
60
93
  }
61
- const modulePromise = new Promise((resolve) => {
62
- process.nextTick(() => {
63
- moduleBuild().then(resolve);
64
- });
65
- });
66
- cache.set(meta.url, modulePromise);
67
- return modulePromise;
68
- async function moduleBuild() {
69
- let text;
70
- try {
71
- text = fs.readFileSync(meta.filename, "utf-8");
72
- } catch (error) {
73
- throw new FileReadError(
74
- `Failed to read module: ${meta.filename}`,
75
- moduleIds,
76
- meta.filename,
77
- error
78
- );
79
- }
80
- const module2 = new vm.SourceTextModule(text, {
81
- initializeImportMeta: (importMeta) => {
82
- Object.assign(importMeta, meta);
94
+ if (isStyleAsset(meta.url)) {
95
+ const exportNames = ["default", "href"];
96
+ const styleModule = new vm.SyntheticModule(
97
+ exportNames,
98
+ function evaluateCallback() {
99
+ this.setExport("default", meta.url);
100
+ this.setExport("href", meta.url);
83
101
  },
84
- identifier: specifier,
85
- context,
86
- importModuleDynamically: (specifier2, referrer) => {
87
- return moduleLinker(
88
- specifier2,
89
- meta.url,
90
- referrer.context,
91
- cache,
92
- [...moduleIds, meta.filename]
93
- );
94
- }
102
+ { identifier: specifier, context }
103
+ );
104
+ cache.set(meta.url, styleModule);
105
+ const linkResult = styleModule.link(() => {
106
+ throw new TypeError(
107
+ `Style modules should not be linked: ${specifier}`
108
+ );
95
109
  });
96
- await module2.link((specifier2, referrer) => {
110
+ const linkPromise2 = linkResult && typeof linkResult.then === "function" ? linkResult.then(() => styleModule.evaluate()) : styleModule.evaluate();
111
+ linkStatus.set(meta.url, linkPromise2);
112
+ return styleModule;
113
+ }
114
+ let text;
115
+ try {
116
+ text = readFileSync(meta.filename);
117
+ } catch (error) {
118
+ throw new FileReadError(
119
+ `Failed to read module: ${meta.filename}`,
120
+ moduleIds,
121
+ meta.filename,
122
+ error
123
+ );
124
+ }
125
+ const module = new vm.SourceTextModule(text, {
126
+ initializeImportMeta: (importMeta) => {
127
+ Object.assign(importMeta, meta);
128
+ },
129
+ identifier: specifier,
130
+ context,
131
+ importModuleDynamically: (specifier2, referrer) => {
97
132
  return moduleLinker(
98
133
  specifier2,
99
134
  meta.url,
100
135
  referrer.context,
101
136
  cache,
137
+ linkStatus,
102
138
  [...moduleIds, meta.filename]
103
139
  );
104
- });
105
- await module2.evaluate();
106
- return module2;
140
+ }
141
+ });
142
+ cache.set(meta.url, module);
143
+ const linkPromise = module.link((specifier2, referrer) => {
144
+ return syncLinker(
145
+ specifier2,
146
+ meta.url,
147
+ referrer.context,
148
+ cache,
149
+ linkStatus,
150
+ [...moduleIds, meta.filename]
151
+ );
152
+ }).then(() => module.evaluate());
153
+ linkStatus.set(meta.url, linkPromise);
154
+ return module;
155
+ }
156
+ async function moduleLinker(specifier, parent, context, cache, linkStatus, moduleIds) {
157
+ if (isBuiltin(specifier)) {
158
+ return importBuiltinModule(specifier, context);
159
+ }
160
+ const meta = buildMeta(specifier, parent);
161
+ const cachedModule = cache.get(meta.url);
162
+ if (cachedModule) {
163
+ const status2 = linkStatus.get(meta.url);
164
+ if (status2) {
165
+ await status2;
166
+ }
167
+ return cachedModule;
168
+ }
169
+ const module = syncLinker(
170
+ specifier,
171
+ parent,
172
+ context,
173
+ cache,
174
+ linkStatus,
175
+ moduleIds
176
+ );
177
+ const status = linkStatus.get(meta.url);
178
+ if (status) {
179
+ await status;
107
180
  }
181
+ return module;
108
182
  }
109
- return async (specifier, parent, sandbox, options) => {
110
- const context = vm.createContext(sandbox, options);
183
+ return async (specifier, parent, sandbox, options2) => {
184
+ materializeLazyGlobals();
185
+ const context = vm.createContext(sandbox, options2);
186
+ const cache = /* @__PURE__ */ new Map();
187
+ const linkStatus = /* @__PURE__ */ new Map();
111
188
  const module = await moduleLinker(
112
189
  specifier,
113
190
  parent,
114
191
  context,
115
- /* @__PURE__ */ new Map(),
192
+ cache,
193
+ linkStatus,
116
194
  []
117
195
  );
118
196
  return module.namespace;
@@ -0,0 +1 @@
1
+ export {};