@esmx/import 3.0.0-rc.11 → 3.0.0-rc.111
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 +1 -1
- package/README.md +141 -0
- package/README.zh-CN.md +141 -0
- package/dist/error.d.ts +14 -0
- package/dist/error.mjs +104 -0
- package/dist/error.test.d.ts +1 -0
- package/dist/error.test.mjs +167 -0
- package/dist/import-loader.d.ts +3 -4
- package/dist/import-loader.mjs +11 -14
- package/dist/import-map-resolve.d.ts +2 -0
- package/dist/import-map-resolve.mjs +13 -0
- package/dist/import-map-resolve.test.d.ts +1 -0
- package/dist/import-map-resolve.test.mjs +182 -0
- package/dist/import-vm.d.ts +1 -4
- package/dist/import-vm.mjs +37 -38
- package/dist/index.d.ts +3 -2
- package/dist/index.mjs +8 -1
- package/dist/types.d.ts +1 -0
- package/package.json +9 -11
- package/src/error.test.ts +206 -0
- package/src/error.ts +172 -0
- package/src/import-loader.ts +14 -21
- package/src/import-map-resolve.test.ts +224 -0
- package/src/import-map-resolve.ts +18 -0
- package/src/import-vm.ts +38 -44
- package/src/index.ts +9 -2
- package/src/types.ts +5 -0
package/LICENSE
CHANGED
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://esmx.dev/logo.svg?t=2025" width="120" alt="Esmx Logo" />
|
|
3
|
+
<h1>@esmx/import</h1>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@esmx/import">
|
|
7
|
+
<img src="https://img.shields.io/npm/v/@esmx/import.svg" alt="npm version" />
|
|
8
|
+
</a>
|
|
9
|
+
<a href="https://github.com/esmnext/esmx/actions/workflows/build.yml">
|
|
10
|
+
<img src="https://github.com/esmnext/esmx/actions/workflows/build.yml/badge.svg" alt="Build" />
|
|
11
|
+
</a>
|
|
12
|
+
<a href="https://esmx.dev/coverage/">
|
|
13
|
+
<img src="https://img.shields.io/badge/coverage-live%20report-brightgreen" alt="Coverage Report" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://nodejs.org/">
|
|
16
|
+
<img src="https://img.shields.io/node/v/@esmx/import.svg" alt="node version" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://bundlephobia.com/package/@esmx/import">
|
|
19
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@esmx/import" alt="size" />
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<p>Node.js server-side implementation of Import Maps for the Esmx framework</p>
|
|
24
|
+
|
|
25
|
+
<p>
|
|
26
|
+
English | <a href="https://github.com/esmnext/esmx/blob/master/packages/import/README.zh-CN.md">中文</a>
|
|
27
|
+
</p>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## 🚀 Features
|
|
31
|
+
|
|
32
|
+
- **Dual Implementation** - VM mode for development and Loader mode for production
|
|
33
|
+
- **Hot Reload Support** - VM mode supports multiple creation for development flexibility
|
|
34
|
+
- **High Performance** - Loader mode optimized for production deployment
|
|
35
|
+
- **Node.js Focused** - Specifically designed for Node.js server-side environments
|
|
36
|
+
- **TypeScript Ready** - Full TypeScript support with excellent type safety
|
|
37
|
+
- **ESM Standards** - Fully compliant with Import Maps specification
|
|
38
|
+
|
|
39
|
+
## 📦 Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# npm
|
|
43
|
+
npm install @esmx/import
|
|
44
|
+
|
|
45
|
+
# pnpm
|
|
46
|
+
pnpm add @esmx/import
|
|
47
|
+
|
|
48
|
+
# yarn
|
|
49
|
+
yarn add @esmx/import
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 🚀 Quick Start
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { createVmImport } from '@esmx/import';
|
|
56
|
+
import { pathToFileURL } from 'node:url';
|
|
57
|
+
|
|
58
|
+
const vmImport = createVmImport(baseURL, importMap);
|
|
59
|
+
const module = await vmImport('my-app/src/utils', import.meta.url);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 📖 Mode Comparison
|
|
63
|
+
|
|
64
|
+
`@esmx/import` provides two different Import Maps implementation approaches:
|
|
65
|
+
|
|
66
|
+
| Feature | VM Mode | Loader Mode |
|
|
67
|
+
|---------|---------|-------------|
|
|
68
|
+
| **Function** | `createVmImport()` | `createLoaderImport()` |
|
|
69
|
+
| **Environment** | Development | Production |
|
|
70
|
+
| **Hot Reload** | ✅ Supports multiple creation | ❌ Can only be created once |
|
|
71
|
+
| **Performance** | Relatively slower | High performance |
|
|
72
|
+
| **Isolation** | Fully isolated VM environment | Uses Node.js native Loader |
|
|
73
|
+
| **Debugging** | Easy for development debugging | Suitable for production deployment |
|
|
74
|
+
|
|
75
|
+
## 🔧 Usage Examples
|
|
76
|
+
|
|
77
|
+
### VM Mode (Development)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createVmImport } from '@esmx/import';
|
|
81
|
+
import { pathToFileURL } from 'node:url';
|
|
82
|
+
|
|
83
|
+
const baseURL = pathToFileURL('/project');
|
|
84
|
+
const importMap = {
|
|
85
|
+
imports: {
|
|
86
|
+
'my-app/src/utils': '/project/src/utils.mjs'
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const vmImport = createVmImport(baseURL, importMap);
|
|
91
|
+
const module = await vmImport('my-app/src/utils', import.meta.url);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Loader Mode (Production)
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { createLoaderImport } from '@esmx/import';
|
|
98
|
+
import { pathToFileURL } from 'node:url';
|
|
99
|
+
|
|
100
|
+
const baseURL = pathToFileURL('/app/dist/server');
|
|
101
|
+
const importMap = {
|
|
102
|
+
imports: {
|
|
103
|
+
'my-app/src/utils': '/app/dist/server/my-app/src/utils.mjs'
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const loaderImport = createLoaderImport(baseURL, importMap);
|
|
108
|
+
const module = await loaderImport('my-app/src/utils');
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 📚 API Reference
|
|
112
|
+
|
|
113
|
+
### createVmImport(baseURL, importMap?)
|
|
114
|
+
Creates a VM-based import function with hot reload support.
|
|
115
|
+
```typescript
|
|
116
|
+
const vmImport = createVmImport(baseURL, importMap);
|
|
117
|
+
const module = await vmImport(specifier, parent, sandbox?, options?);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### createLoaderImport(baseURL, importMap?)
|
|
121
|
+
Creates a Loader-based import function with high performance, can only be created once.
|
|
122
|
+
```typescript
|
|
123
|
+
const loaderImport = createLoaderImport(baseURL, importMap);
|
|
124
|
+
const module = await loaderImport(specifier);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### ImportMap Format
|
|
128
|
+
```typescript
|
|
129
|
+
interface ImportMap {
|
|
130
|
+
imports?: Record<string, string>;
|
|
131
|
+
scopes?: Record<string, Record<string, string>>;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Important Notes:**
|
|
136
|
+
- Only supports Node.js environment, not browser
|
|
137
|
+
- Paths must be absolute paths or complete URLs
|
|
138
|
+
|
|
139
|
+
## 📄 License
|
|
140
|
+
|
|
141
|
+
MIT © [Esmx Team](https://github.com/esmnext/esmx)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://esmx.dev/logo.svg?t=2025" width="120" alt="Esmx Logo" />
|
|
3
|
+
<h1>@esmx/import</h1>
|
|
4
|
+
|
|
5
|
+
<div>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@esmx/import">
|
|
7
|
+
<img src="https://img.shields.io/npm/v/@esmx/import.svg" alt="npm version" />
|
|
8
|
+
</a>
|
|
9
|
+
<a href="https://github.com/esmnext/esmx/actions/workflows/build.yml">
|
|
10
|
+
<img src="https://github.com/esmnext/esmx/actions/workflows/build.yml/badge.svg" alt="Build" />
|
|
11
|
+
</a>
|
|
12
|
+
<a href="https://esmx.dev/coverage/">
|
|
13
|
+
<img src="https://img.shields.io/badge/coverage-live%20report-brightgreen" alt="Coverage Report" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://nodejs.org/">
|
|
16
|
+
<img src="https://img.shields.io/node/v/@esmx/import.svg" alt="node version" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://bundlephobia.com/package/@esmx/import">
|
|
19
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@esmx/import" alt="size" />
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<p>为 Esmx 框架提供 Import Maps 的 Node.js 服务端实现</p>
|
|
24
|
+
|
|
25
|
+
<p>
|
|
26
|
+
<a href="https://github.com/esmnext/esmx/blob/master/packages/import/README.md">English</a> | 中文
|
|
27
|
+
</p>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## 🚀 特性
|
|
31
|
+
|
|
32
|
+
- **双重实现** - 开发环境使用 VM 模式,生产环境使用 Loader 模式
|
|
33
|
+
- **热重载支持** - VM 模式支持多次创建,提供开发灵活性
|
|
34
|
+
- **高性能** - Loader 模式为生产部署优化
|
|
35
|
+
- **Node.js 专注** - 专为 Node.js 服务端环境设计
|
|
36
|
+
- **TypeScript 就绪** - 完整的 TypeScript 支持,出色的类型安全
|
|
37
|
+
- **ESM 标准** - 完全符合 Import Maps 规范
|
|
38
|
+
|
|
39
|
+
## 📦 安装
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# npm
|
|
43
|
+
npm install @esmx/import
|
|
44
|
+
|
|
45
|
+
# pnpm
|
|
46
|
+
pnpm add @esmx/import
|
|
47
|
+
|
|
48
|
+
# yarn
|
|
49
|
+
yarn add @esmx/import
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 🚀 快速开始
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { createVmImport } from '@esmx/import';
|
|
56
|
+
import { pathToFileURL } from 'node:url';
|
|
57
|
+
|
|
58
|
+
const vmImport = createVmImport(baseURL, importMap);
|
|
59
|
+
const module = await vmImport('my-app/src/utils', import.meta.url);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 📖 模式对比
|
|
63
|
+
|
|
64
|
+
`@esmx/import` 提供两种不同的 Import Maps 实现方式:
|
|
65
|
+
|
|
66
|
+
| 特性 | VM 模式 | Loader 模式 |
|
|
67
|
+
|------|---------|-------------|
|
|
68
|
+
| **函数** | `createVmImport()` | `createLoaderImport()` |
|
|
69
|
+
| **适用环境** | 开发环境 | 生产环境 |
|
|
70
|
+
| **热重载** | ✅ 支持多次创建 | ❌ 只能创建一次 |
|
|
71
|
+
| **性能** | 相对较慢 | 高性能 |
|
|
72
|
+
| **隔离性** | 完全隔离的 VM 环境 | 使用 Node.js 原生 Loader |
|
|
73
|
+
| **调试** | 便于开发调试 | 适合生产部署 |
|
|
74
|
+
|
|
75
|
+
## 🔧 使用示例
|
|
76
|
+
|
|
77
|
+
### VM 模式 (开发环境)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createVmImport } from '@esmx/import';
|
|
81
|
+
import { pathToFileURL } from 'node:url';
|
|
82
|
+
|
|
83
|
+
const baseURL = pathToFileURL('/project');
|
|
84
|
+
const importMap = {
|
|
85
|
+
imports: {
|
|
86
|
+
'my-app/src/utils': '/project/src/utils.mjs'
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const vmImport = createVmImport(baseURL, importMap);
|
|
91
|
+
const module = await vmImport('my-app/src/utils', import.meta.url);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Loader 模式 (生产环境)
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { createLoaderImport } from '@esmx/import';
|
|
98
|
+
import { pathToFileURL } from 'node:url';
|
|
99
|
+
|
|
100
|
+
const baseURL = pathToFileURL('/app/dist/server');
|
|
101
|
+
const importMap = {
|
|
102
|
+
imports: {
|
|
103
|
+
'my-app/src/utils': '/app/dist/server/my-app/src/utils.mjs'
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const loaderImport = createLoaderImport(baseURL, importMap);
|
|
108
|
+
const module = await loaderImport('my-app/src/utils');
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 📚 API 参考
|
|
112
|
+
|
|
113
|
+
### createVmImport(baseURL, importMap?)
|
|
114
|
+
创建基于 VM 的导入函数,支持热重载。
|
|
115
|
+
```typescript
|
|
116
|
+
const vmImport = createVmImport(baseURL, importMap);
|
|
117
|
+
const module = await vmImport(specifier, parent, sandbox?, options?);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### createLoaderImport(baseURL, importMap?)
|
|
121
|
+
创建基于 Loader 的导入函数,高性能,只能创建一次。
|
|
122
|
+
```typescript
|
|
123
|
+
const loaderImport = createLoaderImport(baseURL, importMap);
|
|
124
|
+
const module = await loaderImport(specifier);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### ImportMap 格式
|
|
128
|
+
```typescript
|
|
129
|
+
interface ImportMap {
|
|
130
|
+
imports?: Record<string, string>;
|
|
131
|
+
scopes?: Record<string, Record<string, string>>;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**注意事项:**
|
|
136
|
+
- 仅支持 Node.js 环境,不支持浏览器
|
|
137
|
+
- 路径必须为绝对路径或完整 URL
|
|
138
|
+
|
|
139
|
+
## 📄 许可证
|
|
140
|
+
|
|
141
|
+
MIT © [Esmx Team](https://github.com/esmnext/esmx)
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const formatCircularDependency: (moduleIds: string[], targetModule: string) => string;
|
|
2
|
+
export declare const formatModuleChain: (moduleIds: string[], targetModule: string, originalError?: Error) => string;
|
|
3
|
+
export declare class ModuleLoadingError extends Error {
|
|
4
|
+
moduleIds: string[];
|
|
5
|
+
targetModule: string;
|
|
6
|
+
originalError?: Error | undefined;
|
|
7
|
+
constructor(message: string, moduleIds: string[], targetModule: string, originalError?: Error | undefined);
|
|
8
|
+
}
|
|
9
|
+
export declare class CircularDependencyError extends ModuleLoadingError {
|
|
10
|
+
constructor(message: string, moduleIds: string[], targetModule: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class FileReadError extends ModuleLoadingError {
|
|
13
|
+
constructor(message: string, moduleIds: string[], targetModule: string, originalError?: Error);
|
|
14
|
+
}
|
package/dist/error.mjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const Colors = {
|
|
3
|
+
RED: "\x1B[31m",
|
|
4
|
+
YELLOW: "\x1B[33m",
|
|
5
|
+
CYAN: "\x1B[36m",
|
|
6
|
+
GRAY: "\x1B[90m",
|
|
7
|
+
RESET: "\x1B[0m",
|
|
8
|
+
BOLD: "\x1B[1m"
|
|
9
|
+
};
|
|
10
|
+
const supportsColor = () => {
|
|
11
|
+
return !!(process.stdout?.isTTY && process.env.TERM !== "dumb") || process.env.FORCE_COLOR === "1" || process.env.FORCE_COLOR === "true";
|
|
12
|
+
};
|
|
13
|
+
const useColors = supportsColor() && process.env.NO_COLOR !== "1";
|
|
14
|
+
const colorize = (text, color) => {
|
|
15
|
+
return useColors ? `${color}${text}${Colors.RESET}` : text;
|
|
16
|
+
};
|
|
17
|
+
const getRelativeFromCwd = (filePath) => {
|
|
18
|
+
return path.relative(process.cwd(), filePath);
|
|
19
|
+
};
|
|
20
|
+
export const formatCircularDependency = (moduleIds, targetModule) => {
|
|
21
|
+
const fullChain = [...moduleIds, targetModule];
|
|
22
|
+
return `${colorize(colorize("Module dependency chain (circular reference found):", Colors.BOLD), Colors.RED)}
|
|
23
|
+
${fullChain.map((module, index) => {
|
|
24
|
+
const isLastModule = index === fullChain.length - 1;
|
|
25
|
+
const prefix = index === 0 ? "\u250C\u2500 " : index === fullChain.length - 1 ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
26
|
+
const displayPath = getRelativeFromCwd(module);
|
|
27
|
+
const isCircularModule = fullChain.filter((m) => m === module).length > 1;
|
|
28
|
+
const coloredFile = isCircularModule ? colorize(colorize(displayPath, Colors.BOLD), Colors.RED) : colorize(displayPath, Colors.CYAN);
|
|
29
|
+
const suffix = isLastModule ? ` ${colorize("\u{1F504} Creates circular reference", Colors.YELLOW)}` : "";
|
|
30
|
+
return `${colorize(prefix, Colors.GRAY)}${coloredFile}${suffix}`;
|
|
31
|
+
}).join("\n")}`;
|
|
32
|
+
};
|
|
33
|
+
export const formatModuleChain = (moduleIds, targetModule, originalError) => {
|
|
34
|
+
let result = "";
|
|
35
|
+
if (moduleIds.length === 0) {
|
|
36
|
+
const displayPath = getRelativeFromCwd(targetModule);
|
|
37
|
+
result = `${colorize("Failed to load:", Colors.CYAN)} ${colorize(displayPath, Colors.RED)}`;
|
|
38
|
+
} else {
|
|
39
|
+
const chain = [...moduleIds, targetModule];
|
|
40
|
+
result = `${colorize(colorize("Module loading path:", Colors.BOLD), Colors.CYAN)}
|
|
41
|
+
${chain.map((module, index) => {
|
|
42
|
+
const indent = " ".repeat(index);
|
|
43
|
+
const connector = index === 0 ? "" : "\u2514\u2500 ";
|
|
44
|
+
const displayPath = getRelativeFromCwd(module);
|
|
45
|
+
const isFailedFile = index === chain.length - 1;
|
|
46
|
+
const coloredFile = isFailedFile ? colorize(colorize(displayPath, Colors.BOLD), Colors.RED) : colorize(displayPath, Colors.CYAN);
|
|
47
|
+
const status = isFailedFile ? ` ${colorize(colorize("\u274C Loading failed", Colors.BOLD), Colors.RED)}` : "";
|
|
48
|
+
return `${colorize(indent + connector, Colors.GRAY)}${coloredFile}${status}`;
|
|
49
|
+
}).join("\n")}`;
|
|
50
|
+
}
|
|
51
|
+
if (originalError) {
|
|
52
|
+
result += `
|
|
53
|
+
|
|
54
|
+
${colorize("Error details:", Colors.YELLOW)} ${originalError.message}`;
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
export class ModuleLoadingError extends Error {
|
|
59
|
+
constructor(message, moduleIds, targetModule, originalError) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.moduleIds = moduleIds;
|
|
62
|
+
this.targetModule = targetModule;
|
|
63
|
+
this.originalError = originalError;
|
|
64
|
+
this.name = "ModuleLoadingError";
|
|
65
|
+
Object.defineProperty(this, "moduleIds", {
|
|
66
|
+
value: moduleIds,
|
|
67
|
+
writable: false,
|
|
68
|
+
enumerable: false,
|
|
69
|
+
configurable: true
|
|
70
|
+
});
|
|
71
|
+
Object.defineProperty(this, "targetModule", {
|
|
72
|
+
value: targetModule,
|
|
73
|
+
writable: false,
|
|
74
|
+
enumerable: false,
|
|
75
|
+
configurable: true
|
|
76
|
+
});
|
|
77
|
+
if (originalError) {
|
|
78
|
+
Object.defineProperty(this, "originalError", {
|
|
79
|
+
value: originalError,
|
|
80
|
+
writable: false,
|
|
81
|
+
enumerable: false,
|
|
82
|
+
configurable: true
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export class CircularDependencyError extends ModuleLoadingError {
|
|
88
|
+
constructor(message, moduleIds, targetModule) {
|
|
89
|
+
super(message, moduleIds, targetModule);
|
|
90
|
+
this.name = "CircularDependencyError";
|
|
91
|
+
this.stack = `${this.name}: ${message}
|
|
92
|
+
|
|
93
|
+
${formatCircularDependency(moduleIds, targetModule)}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export class FileReadError extends ModuleLoadingError {
|
|
97
|
+
constructor(message, moduleIds, targetModule, originalError) {
|
|
98
|
+
super(message, moduleIds, targetModule, originalError);
|
|
99
|
+
this.name = "FileReadError";
|
|
100
|
+
this.stack = `${this.name}: ${message}
|
|
101
|
+
|
|
102
|
+
${formatModuleChain(moduleIds, targetModule, originalError)}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
CircularDependencyError,
|
|
5
|
+
FileReadError,
|
|
6
|
+
formatCircularDependency,
|
|
7
|
+
formatModuleChain,
|
|
8
|
+
ModuleLoadingError
|
|
9
|
+
} from "./error.mjs";
|
|
10
|
+
describe("Module Loading Errors", () => {
|
|
11
|
+
let originalEnv;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
originalEnv = process.env.NO_COLOR;
|
|
14
|
+
process.env.NO_COLOR = "1";
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
if (originalEnv !== void 0) {
|
|
18
|
+
process.env.NO_COLOR = originalEnv;
|
|
19
|
+
} else {
|
|
20
|
+
process.env.NO_COLOR = void 0;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
describe("CircularDependencyError", () => {
|
|
24
|
+
it("should create circular dependency error with correct properties", () => {
|
|
25
|
+
const moduleIds = ["/src/A.js", "/src/B.js"];
|
|
26
|
+
const targetModule = "/src/A.js";
|
|
27
|
+
const error = new CircularDependencyError(
|
|
28
|
+
"Test circular dependency",
|
|
29
|
+
moduleIds,
|
|
30
|
+
targetModule
|
|
31
|
+
);
|
|
32
|
+
expect(error.name).toBe("CircularDependencyError");
|
|
33
|
+
expect(error.message).toBe("Test circular dependency");
|
|
34
|
+
expect(error.stack).toContain("Test circular dependency");
|
|
35
|
+
expect(error.stack).toContain(
|
|
36
|
+
"Module dependency chain (circular reference found):"
|
|
37
|
+
);
|
|
38
|
+
expect(error.stack).toContain("\u250C\u2500");
|
|
39
|
+
expect(error.stack).toContain("\u2514\u2500");
|
|
40
|
+
expect(error.stack).toContain("\u{1F504} Creates circular reference");
|
|
41
|
+
expect(error.moduleIds).toEqual(moduleIds);
|
|
42
|
+
expect(error.targetModule).toBe(targetModule);
|
|
43
|
+
expect(error instanceof ModuleLoadingError).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
it("should format circular dependency in toString", () => {
|
|
46
|
+
const moduleIds = ["/src/A.js", "/src/B.js"];
|
|
47
|
+
const targetModule = "/src/A.js";
|
|
48
|
+
const error = new CircularDependencyError(
|
|
49
|
+
"Circular dependency detected",
|
|
50
|
+
moduleIds,
|
|
51
|
+
targetModule
|
|
52
|
+
);
|
|
53
|
+
const formatted = error.toString();
|
|
54
|
+
expect(formatted).toContain("CircularDependencyError");
|
|
55
|
+
expect(formatted).toContain("Circular dependency detected");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe("FileReadError", () => {
|
|
59
|
+
it("should create file read error with correct properties", () => {
|
|
60
|
+
const moduleIds = ["/src/main.js", "/src/components/App.js"];
|
|
61
|
+
const targetModule = "/src/missing.js";
|
|
62
|
+
const originalError = new Error(
|
|
63
|
+
"ENOENT: no such file or directory"
|
|
64
|
+
);
|
|
65
|
+
const error = new FileReadError(
|
|
66
|
+
"Failed to read module",
|
|
67
|
+
moduleIds,
|
|
68
|
+
targetModule,
|
|
69
|
+
originalError
|
|
70
|
+
);
|
|
71
|
+
expect(error.name).toBe("FileReadError");
|
|
72
|
+
expect(error.message).toBe("Failed to read module");
|
|
73
|
+
expect(error.stack).toContain("Failed to read module");
|
|
74
|
+
expect(error.stack).toContain("Module loading path:");
|
|
75
|
+
expect(error.stack).toContain("main.js");
|
|
76
|
+
expect(error.stack).toContain("App.js");
|
|
77
|
+
expect(error.stack).toContain("missing.js");
|
|
78
|
+
expect(error.stack).toContain("\u274C Loading failed");
|
|
79
|
+
expect(error.stack).toContain("Error details:");
|
|
80
|
+
expect(error.stack).toContain("ENOENT");
|
|
81
|
+
expect(error.moduleIds).toEqual(moduleIds);
|
|
82
|
+
expect(error.targetModule).toBe(targetModule);
|
|
83
|
+
expect(error.originalError).toBe(originalError);
|
|
84
|
+
expect(error instanceof ModuleLoadingError).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
it("should format module chain in toString", () => {
|
|
87
|
+
const moduleIds = ["/src/main.js", "/src/components/App.js"];
|
|
88
|
+
const targetModule = "/src/missing.js";
|
|
89
|
+
const originalError = new Error(
|
|
90
|
+
"ENOENT: no such file or directory"
|
|
91
|
+
);
|
|
92
|
+
const error = new FileReadError(
|
|
93
|
+
"Failed to read module",
|
|
94
|
+
moduleIds,
|
|
95
|
+
targetModule,
|
|
96
|
+
originalError
|
|
97
|
+
);
|
|
98
|
+
const formatted = error.toString();
|
|
99
|
+
expect(formatted).toContain("FileReadError");
|
|
100
|
+
expect(formatted).toContain("Failed to read module");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe("Formatting Functions", () => {
|
|
104
|
+
it("should format circular dependency correctly", () => {
|
|
105
|
+
const moduleIds = ["/src/A.js", "/src/B.js", "/src/C.js"];
|
|
106
|
+
const targetModule = "/src/A.js";
|
|
107
|
+
const formatted = formatCircularDependency(moduleIds, targetModule);
|
|
108
|
+
const relativeA = path.relative(process.cwd(), "/src/A.js");
|
|
109
|
+
const relativeB = path.relative(process.cwd(), "/src/B.js");
|
|
110
|
+
const relativeC = path.relative(process.cwd(), "/src/C.js");
|
|
111
|
+
expect(formatted).toContain(
|
|
112
|
+
"Module dependency chain (circular reference found):"
|
|
113
|
+
);
|
|
114
|
+
expect(formatted).toContain(`\u250C\u2500 ${relativeA}`);
|
|
115
|
+
expect(formatted).toContain(`\u251C\u2500 ${relativeB}`);
|
|
116
|
+
expect(formatted).toContain(`\u251C\u2500 ${relativeC}`);
|
|
117
|
+
expect(formatted).toContain(`\u2514\u2500 ${relativeA}`);
|
|
118
|
+
expect(formatted).toContain("\u{1F504} Creates circular reference");
|
|
119
|
+
});
|
|
120
|
+
it("should format module chain correctly", () => {
|
|
121
|
+
const moduleIds = ["/src/main.js", "/src/app.js"];
|
|
122
|
+
const targetModule = "/src/missing.js";
|
|
123
|
+
const originalError = new Error("File not found");
|
|
124
|
+
const formatted = formatModuleChain(
|
|
125
|
+
moduleIds,
|
|
126
|
+
targetModule,
|
|
127
|
+
originalError
|
|
128
|
+
);
|
|
129
|
+
expect(formatted).toContain("Module loading path:");
|
|
130
|
+
expect(formatted).toContain("main.js");
|
|
131
|
+
expect(formatted).toContain("app.js");
|
|
132
|
+
expect(formatted).toContain("missing.js");
|
|
133
|
+
expect(formatted).toContain("\u274C Loading failed");
|
|
134
|
+
expect(formatted).toContain("Error details:");
|
|
135
|
+
expect(formatted).toContain("File not found");
|
|
136
|
+
});
|
|
137
|
+
it("should handle empty moduleIds in formatModuleChain", () => {
|
|
138
|
+
const moduleIds = [];
|
|
139
|
+
const targetModule = "/src/standalone.js";
|
|
140
|
+
const formatted = formatModuleChain(moduleIds, targetModule);
|
|
141
|
+
expect(formatted).toContain("Failed to load:");
|
|
142
|
+
expect(formatted).toContain("standalone.js");
|
|
143
|
+
});
|
|
144
|
+
it("should handle deep module chains", () => {
|
|
145
|
+
const moduleIds = [
|
|
146
|
+
"/src/a.js",
|
|
147
|
+
"/src/b.js",
|
|
148
|
+
"/src/c.js",
|
|
149
|
+
"/src/d.js"
|
|
150
|
+
];
|
|
151
|
+
const targetModule = "/src/e.js";
|
|
152
|
+
const formatted = formatModuleChain(moduleIds, targetModule);
|
|
153
|
+
const relativeA = path.relative(process.cwd(), "/src/a.js");
|
|
154
|
+
const relativeB = path.relative(process.cwd(), "/src/b.js");
|
|
155
|
+
const relativeC = path.relative(process.cwd(), "/src/c.js");
|
|
156
|
+
const relativeD = path.relative(process.cwd(), "/src/d.js");
|
|
157
|
+
const relativeE = path.relative(process.cwd(), "/src/e.js");
|
|
158
|
+
expect(formatted).toContain(relativeA);
|
|
159
|
+
expect(formatted).toContain(` \u2514\u2500 ${relativeB}`);
|
|
160
|
+
expect(formatted).toContain(` \u2514\u2500 ${relativeC}`);
|
|
161
|
+
expect(formatted).toContain(` \u2514\u2500 ${relativeD}`);
|
|
162
|
+
expect(formatted).toContain(
|
|
163
|
+
` \u2514\u2500 ${relativeE} \u274C Loading failed`
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
package/dist/import-loader.d.ts
CHANGED
|
@@ -3,10 +3,9 @@ interface Data {
|
|
|
3
3
|
baseURL: string;
|
|
4
4
|
importMap: ImportMap;
|
|
5
5
|
}
|
|
6
|
-
/**
|
|
7
|
-
* 创建一个使用 loader 实现的 importmap 的 import 函数,只能创建一次,无热更新,适合生产使用。
|
|
8
|
-
*/
|
|
9
6
|
export declare function createLoaderImport(baseURL: URL, importMap?: ImportMap): (specifier: string) => Promise<Record<string, any>>;
|
|
10
7
|
export declare function initialize(data: Data): void;
|
|
11
|
-
export declare function resolve(specifier: string, context:
|
|
8
|
+
export declare function resolve(specifier: string, context: {
|
|
9
|
+
parentURL: string;
|
|
10
|
+
}, nextResolve: Function): any;
|
|
12
11
|
export {};
|
package/dist/import-loader.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import module from "node:module";
|
|
2
|
-
import {
|
|
3
|
-
import IM from "@import-maps/resolve";
|
|
2
|
+
import { createImportMapResolver } from "./import-map-resolve.mjs";
|
|
4
3
|
let registered = "";
|
|
5
4
|
export function createLoaderImport(baseURL, importMap = {}) {
|
|
6
5
|
if (!registered) {
|
|
7
|
-
module.register(
|
|
6
|
+
module.register(import.meta.url, {
|
|
8
7
|
parentURL: baseURL,
|
|
9
8
|
data: {
|
|
10
9
|
baseURL: baseURL.href,
|
|
@@ -18,20 +17,18 @@ export function createLoaderImport(baseURL, importMap = {}) {
|
|
|
18
17
|
);
|
|
19
18
|
}
|
|
20
19
|
return (specifier) => {
|
|
21
|
-
|
|
20
|
+
try {
|
|
21
|
+
return import(specifier);
|
|
22
|
+
} catch (e) {
|
|
23
|
+
throw new Error(`Failed to import '${specifier}'`);
|
|
24
|
+
}
|
|
22
25
|
};
|
|
23
26
|
}
|
|
24
|
-
let
|
|
25
|
-
let loaderParsedImportMap = {};
|
|
27
|
+
let importMapResolver = null;
|
|
26
28
|
export function initialize(data) {
|
|
27
|
-
|
|
28
|
-
loaderParsedImportMap = IM.parse(data.importMap, loaderBaseURL);
|
|
29
|
+
importMapResolver = createImportMapResolver(data.baseURL, data.importMap);
|
|
29
30
|
}
|
|
30
31
|
export function resolve(specifier, context, nextResolve) {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
if (result.matched && result.resolvedImport) {
|
|
34
|
-
return nextResolve(result.resolvedImport.href);
|
|
35
|
-
}
|
|
36
|
-
return nextResolve(specifier, context);
|
|
32
|
+
const result = importMapResolver?.(specifier, context.parentURL);
|
|
33
|
+
return nextResolve(result ?? specifier, context);
|
|
37
34
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
import { parse, resolve } from "@import-maps/resolve";
|
|
3
|
+
export function createImportMapResolver(base, importMap) {
|
|
4
|
+
const baseURL = pathToFileURL(base);
|
|
5
|
+
const parsedImportMap = parse(importMap, baseURL);
|
|
6
|
+
return (specifier, scriptURL) => {
|
|
7
|
+
const result = resolve(specifier, parsedImportMap, new URL(scriptURL));
|
|
8
|
+
if (result.resolvedImport) {
|
|
9
|
+
return result.resolvedImport.href;
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|