@esmx/core 3.0.0-rc.10
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 +29 -0
- package/dist/app.d.ts +84 -0
- package/dist/app.mjs +34 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.mjs +73 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.mjs +3 -0
- package/dist/gez.d.ts +703 -0
- package/dist/gez.mjs +842 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.mjs +19 -0
- package/dist/manifest-json.d.ts +48 -0
- package/dist/manifest-json.mjs +21 -0
- package/dist/module-config.d.ts +185 -0
- package/dist/module-config.mjs +79 -0
- package/dist/pack-config.d.ts +251 -0
- package/dist/pack-config.mjs +26 -0
- package/dist/render-context.d.ts +1212 -0
- package/dist/render-context.mjs +1010 -0
- package/dist/utils/cache.d.ts +49 -0
- package/dist/utils/cache.mjs +16 -0
- package/dist/utils/import-map.d.ts +8 -0
- package/dist/utils/import-map.mjs +32 -0
- package/dist/utils/middleware.d.ts +57 -0
- package/dist/utils/middleware.mjs +51 -0
- package/dist/utils/path-without-index.d.ts +1 -0
- package/dist/utils/path-without-index.mjs +9 -0
- package/dist/utils/resolve-path.d.ts +2 -0
- package/dist/utils/resolve-path.mjs +4 -0
- package/dist/utils/static-import-lexer.d.ts +26 -0
- package/dist/utils/static-import-lexer.mjs +43 -0
- package/package.json +103 -0
- package/src/app.ts +144 -0
- package/src/cli/cli.ts +99 -0
- package/src/cli/index.ts +5 -0
- package/src/gez.ts +1026 -0
- package/src/index.ts +38 -0
- package/src/manifest-json.ts +80 -0
- package/src/module-config.ts +282 -0
- package/src/pack-config.ts +301 -0
- package/src/render-context.ts +1326 -0
- package/src/utils/cache.ts +68 -0
- package/src/utils/import-map.ts +47 -0
- package/src/utils/middleware.ts +118 -0
- package/src/utils/path-without-index.ts +9 -0
- package/src/utils/resolve-path.ts +33 -0
- package/src/utils/static-import-lexer.ts +85 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 缓存处理函数的类型定义
|
|
3
|
+
*
|
|
4
|
+
* @template T - 缓存数据的类型
|
|
5
|
+
* @param name - 缓存项的唯一标识符
|
|
6
|
+
* @param fetch - 获取数据的异步函数
|
|
7
|
+
* @returns 返回缓存的数据或新获取的数据
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const cache = createCache(true);
|
|
12
|
+
*
|
|
13
|
+
* // 第一次调用会执行 fetch 函数
|
|
14
|
+
* const data1 = await cache('key', async () => {
|
|
15
|
+
* return await fetchSomeData();
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* // 第二次调用会直接返回缓存的结果
|
|
19
|
+
* const data2 = await cache('key', async () => {
|
|
20
|
+
* return await fetchSomeData();
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export type CacheHandle = <T>(
|
|
25
|
+
name: string,
|
|
26
|
+
fetch: () => Promise<T>
|
|
27
|
+
) => Promise<T>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 创建一个缓存处理函数
|
|
31
|
+
*
|
|
32
|
+
* @param enable - 是否启用缓存功能
|
|
33
|
+
* @returns 返回一个缓存处理函数
|
|
34
|
+
*
|
|
35
|
+
* @description
|
|
36
|
+
* 当 enable 为 true 时,会创建一个带有内存缓存的处理函数,相同的 name 只会执行一次 fetch。
|
|
37
|
+
* 当 enable 为 false 时,每次调用都会执行 fetch 函数,不会缓存结果。
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* // 创建一个启用缓存的处理函数
|
|
42
|
+
* const cacheEnabled = createCache(true);
|
|
43
|
+
*
|
|
44
|
+
* // 创建一个禁用缓存的处理函数
|
|
45
|
+
* const cacheDisabled = createCache(false);
|
|
46
|
+
*
|
|
47
|
+
* // 使用缓存处理函数
|
|
48
|
+
* const result = await cacheEnabled('userProfile', async () => {
|
|
49
|
+
* return await fetchUserProfile(userId);
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function createCache(enable: boolean) {
|
|
54
|
+
if (enable) {
|
|
55
|
+
const map = new Map<string, any>();
|
|
56
|
+
return async <T>(name: string, fetch: () => Promise<T>): Promise<T> => {
|
|
57
|
+
if (map.has(name)) {
|
|
58
|
+
return map.get(name);
|
|
59
|
+
}
|
|
60
|
+
const result = await fetch();
|
|
61
|
+
map.set(name, result);
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return <T>(name: string, fetch: () => Promise<T>): Promise<T> => {
|
|
66
|
+
return fetch();
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import { pathWithoutIndex } from './path-without-index';
|
|
4
|
+
|
|
5
|
+
import type { ImportMap, SpecifierMap } from '@esmx/import';
|
|
6
|
+
import type { RuntimeTarget } from '../esmx';
|
|
7
|
+
import type { ManifestJson } from '../manifest-json';
|
|
8
|
+
import type { ParsedModuleConfig } from '../module-config';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 获取导入映射对象
|
|
12
|
+
*/
|
|
13
|
+
export async function getImportMap(
|
|
14
|
+
target: RuntimeTarget,
|
|
15
|
+
manifests: readonly ManifestJson[],
|
|
16
|
+
moduleConfig: ParsedModuleConfig
|
|
17
|
+
): Promise<ImportMap> {
|
|
18
|
+
const imports: SpecifierMap = {};
|
|
19
|
+
if (target === 'client') {
|
|
20
|
+
for (const manifest of manifests) {
|
|
21
|
+
for (const [name, value] of Object.entries(manifest.exports)) {
|
|
22
|
+
imports[`${manifest.name}/${name}`] =
|
|
23
|
+
`/${manifest.name}/${value}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
for (const manifest of manifests) {
|
|
28
|
+
const link = moduleConfig.links.find(
|
|
29
|
+
(item) => item.name === manifest.name
|
|
30
|
+
);
|
|
31
|
+
if (!link) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`'${manifest.name}' service did not find module config`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
for (const [name, value] of Object.entries(manifest.exports)) {
|
|
37
|
+
imports[`${manifest.name}/${name}`] = path.resolve(
|
|
38
|
+
link.root,
|
|
39
|
+
'server',
|
|
40
|
+
value
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
pathWithoutIndex(imports);
|
|
46
|
+
return { imports };
|
|
47
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import send from 'send';
|
|
4
|
+
import type { Esmx } from '../esmx';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 中间件函数类型定义
|
|
8
|
+
*
|
|
9
|
+
* @description
|
|
10
|
+
* 中间件是一个函数,用于处理 HTTP 请求。它接收请求对象、响应对象和下一个中间件函数作为参数。
|
|
11
|
+
* 中间件可以执行以下操作:
|
|
12
|
+
* - 修改请求和响应对象
|
|
13
|
+
* - 结束请求-响应循环
|
|
14
|
+
* - 调用下一个中间件
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* // 创建一个简单的日志中间件
|
|
19
|
+
* const loggerMiddleware: Middleware = (req, res, next) => {
|
|
20
|
+
* console.log(`${req.method} ${req.url}`);
|
|
21
|
+
* next();
|
|
22
|
+
* };
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export type Middleware = (
|
|
26
|
+
req: IncomingMessage,
|
|
27
|
+
res: ServerResponse,
|
|
28
|
+
next: Function
|
|
29
|
+
) => void;
|
|
30
|
+
|
|
31
|
+
const reFinal = /\.final\.[a-zA-Z0-9]+$/;
|
|
32
|
+
/**
|
|
33
|
+
* 判断文件路径是否是一个符合 esmx 规范的不可变文件
|
|
34
|
+
* @param path 文件路径
|
|
35
|
+
*/
|
|
36
|
+
export function isImmutableFile(filename: string) {
|
|
37
|
+
return reFinal.test(filename);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 创建 Esmx 应用的中间件
|
|
42
|
+
*
|
|
43
|
+
* @param esmx - Esmx 实例
|
|
44
|
+
* @returns 返回一个处理静态资源的中间件
|
|
45
|
+
*
|
|
46
|
+
* @description
|
|
47
|
+
* 该函数创建一个中间件,用于处理模块的静态资源请求。它会:
|
|
48
|
+
* - 根据模块配置创建对应的静态资源中间件
|
|
49
|
+
* - 处理资源的缓存控制
|
|
50
|
+
* - 支持不可变文件的长期缓存
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { Esmx, createMiddleware } from '@esmx/core';
|
|
55
|
+
*
|
|
56
|
+
* const esmx = new Esmx();
|
|
57
|
+
* const middleware = createMiddleware(esmx);
|
|
58
|
+
*
|
|
59
|
+
* // 在 HTTP 服务器中使用
|
|
60
|
+
* server.use(middleware);
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export function createMiddleware(esmx: Esmx): Middleware {
|
|
64
|
+
const middlewares = esmx.moduleConfig.links.map((item): Middleware => {
|
|
65
|
+
const base = `/${item.name}/`;
|
|
66
|
+
const baseUrl = new URL(`file:`);
|
|
67
|
+
const root = path.resolve(item.root, 'client');
|
|
68
|
+
// const reFinal = /\.final\.[a-zA-Z0-9]+$/;
|
|
69
|
+
return (req, res, next) => {
|
|
70
|
+
const url = req.url ?? '/';
|
|
71
|
+
const { pathname } = new URL(req.url ?? '/', baseUrl);
|
|
72
|
+
|
|
73
|
+
if (!url.startsWith(base) || req.method !== 'GET') {
|
|
74
|
+
next();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
send(req, pathname.substring(base.length - 1), {
|
|
79
|
+
root
|
|
80
|
+
})
|
|
81
|
+
.on('headers', () => {
|
|
82
|
+
if (isImmutableFile(pathname)) {
|
|
83
|
+
res.setHeader(
|
|
84
|
+
'cache-control',
|
|
85
|
+
'public, max-age=31536000, immutable'
|
|
86
|
+
);
|
|
87
|
+
} else {
|
|
88
|
+
res.setHeader('cache-control', 'no-cache');
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
.pipe(res);
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
return mergeMiddlewares(middlewares);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 将多个中间件,合并成一个中间件执行
|
|
99
|
+
* @param middlewares 中间件列表
|
|
100
|
+
* @returns
|
|
101
|
+
*/
|
|
102
|
+
export function mergeMiddlewares(middlewares: Middleware[]): Middleware {
|
|
103
|
+
return (req, res, next) => {
|
|
104
|
+
let index = 0;
|
|
105
|
+
function dispatch() {
|
|
106
|
+
if (index < middlewares.length) {
|
|
107
|
+
middlewares[index](req, res, () => {
|
|
108
|
+
index++;
|
|
109
|
+
dispatch();
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
} else {
|
|
113
|
+
next();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
dispatch();
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
export type ProjectPath =
|
|
4
|
+
| './'
|
|
5
|
+
| 'dist'
|
|
6
|
+
| 'dist/index.js'
|
|
7
|
+
| 'dist/package.json'
|
|
8
|
+
| 'dist/client'
|
|
9
|
+
| 'dist/client/manifest.json'
|
|
10
|
+
| 'dist/client/css'
|
|
11
|
+
| 'dist/client/images'
|
|
12
|
+
| 'dist/client/media'
|
|
13
|
+
| 'dist/client/fonts'
|
|
14
|
+
| 'dist/client/workers'
|
|
15
|
+
| 'dist/client/importmap'
|
|
16
|
+
| 'dist/client/versions/latest.tgz'
|
|
17
|
+
| 'dist/server'
|
|
18
|
+
| 'dist/server/manifest.json'
|
|
19
|
+
| 'dist/node'
|
|
20
|
+
| 'dist/node/src/entry.node.js'
|
|
21
|
+
| 'src'
|
|
22
|
+
| 'src/entry.node.ts'
|
|
23
|
+
| 'src/entry.client.ts'
|
|
24
|
+
| 'src/entry.server.ts'
|
|
25
|
+
| 'package.json';
|
|
26
|
+
|
|
27
|
+
export function resolvePath(
|
|
28
|
+
root: string,
|
|
29
|
+
projectPath: ProjectPath,
|
|
30
|
+
...args: string[]
|
|
31
|
+
): string {
|
|
32
|
+
return path.resolve(root, projectPath, ...args);
|
|
33
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type fs from 'node:fs';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import * as esmLexer from 'es-module-lexer';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 从 JS 代码中获取静态 import 的模块名列表。也许不能并发多个调用,没实验过。
|
|
9
|
+
* @param code js 代码
|
|
10
|
+
* @returns `Promise<string[]>` 静态 import 的模块名列表
|
|
11
|
+
*/
|
|
12
|
+
export async function getImportsFromJsCode(code: string) {
|
|
13
|
+
await esmLexer.init;
|
|
14
|
+
const [imports] = esmLexer.parse(code);
|
|
15
|
+
// 静态导入 && 拥有模块名
|
|
16
|
+
return imports
|
|
17
|
+
.filter((item) => item.t === 1 && item.n)
|
|
18
|
+
.map((item) => item.n as string);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 从 JS 文件中获取静态 import 的模块名列表。
|
|
23
|
+
* @param filepath js 文件路径
|
|
24
|
+
* @returns `Promise<string[]>` 静态 import 的模块名列表
|
|
25
|
+
*/
|
|
26
|
+
export async function getImportsFromJsFile(
|
|
27
|
+
filepath: fs.PathLike | fs.promises.FileHandle
|
|
28
|
+
) {
|
|
29
|
+
const source = await fsp.readFile(filepath, 'utf-8');
|
|
30
|
+
return getImportsFromJsCode(source);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
import type { ImportMap, SpecifierMap } from '@esmx/import';
|
|
34
|
+
import type { ParsedModuleConfig } from '../module-config';
|
|
35
|
+
|
|
36
|
+
export type ImportPreloadInfo = SpecifierMap;
|
|
37
|
+
/**
|
|
38
|
+
* 获取导入的预加载信息。
|
|
39
|
+
* @param specifier 模块名
|
|
40
|
+
* @param importMap 导入映射对象
|
|
41
|
+
* @param moduleConfig 模块配置
|
|
42
|
+
* @returns
|
|
43
|
+
* - `Promise<{ [specifier: string]: ImportPreloadPathString }>` 模块名和文件路径的映射对象
|
|
44
|
+
* - `null` specifier 不存在
|
|
45
|
+
*/
|
|
46
|
+
export async function getImportPreloadInfo(
|
|
47
|
+
specifier: string,
|
|
48
|
+
importMap: ImportMap,
|
|
49
|
+
moduleConfig: ParsedModuleConfig
|
|
50
|
+
) {
|
|
51
|
+
const importInfo = importMap.imports;
|
|
52
|
+
if (!importInfo || !(specifier in importInfo)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ans: ImportPreloadInfo = {};
|
|
57
|
+
|
|
58
|
+
const needHandles: string[][] = [[specifier]];
|
|
59
|
+
// 词法分析是耗时操作,因此处理的文件越少越快,换句话说就是深度越浅越快,因此这里使用广度优先搜索
|
|
60
|
+
while (needHandles.length) {
|
|
61
|
+
const needHandle: string[] = [];
|
|
62
|
+
for (const specifier of needHandles.shift()!) {
|
|
63
|
+
let filepath = importInfo[specifier];
|
|
64
|
+
const splitRes = filepath.split('/');
|
|
65
|
+
if (splitRes[0] === '') splitRes.shift();
|
|
66
|
+
const name = splitRes.shift() + '';
|
|
67
|
+
const link = moduleConfig.links.find((item) => item.name === name);
|
|
68
|
+
if (!link) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
filepath = path.join(link.root, 'client', ...splitRes);
|
|
72
|
+
const imports = await getImportsFromJsFile(filepath);
|
|
73
|
+
imports.forEach((specifier) => {
|
|
74
|
+
// 如果模块名在 importMap 中存在,且没处理过
|
|
75
|
+
if (specifier in importInfo && !ans[specifier]) {
|
|
76
|
+
ans[specifier] = importInfo[specifier];
|
|
77
|
+
needHandle.push(specifier);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
needHandle.length && needHandles.push(needHandle);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return ans;
|
|
85
|
+
}
|