@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.
Files changed (47) hide show
  1. package/README.md +29 -0
  2. package/dist/app.d.ts +84 -0
  3. package/dist/app.mjs +34 -0
  4. package/dist/cli/cli.d.ts +2 -0
  5. package/dist/cli/cli.mjs +73 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.mjs +3 -0
  8. package/dist/gez.d.ts +703 -0
  9. package/dist/gez.mjs +842 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.mjs +19 -0
  12. package/dist/manifest-json.d.ts +48 -0
  13. package/dist/manifest-json.mjs +21 -0
  14. package/dist/module-config.d.ts +185 -0
  15. package/dist/module-config.mjs +79 -0
  16. package/dist/pack-config.d.ts +251 -0
  17. package/dist/pack-config.mjs +26 -0
  18. package/dist/render-context.d.ts +1212 -0
  19. package/dist/render-context.mjs +1010 -0
  20. package/dist/utils/cache.d.ts +49 -0
  21. package/dist/utils/cache.mjs +16 -0
  22. package/dist/utils/import-map.d.ts +8 -0
  23. package/dist/utils/import-map.mjs +32 -0
  24. package/dist/utils/middleware.d.ts +57 -0
  25. package/dist/utils/middleware.mjs +51 -0
  26. package/dist/utils/path-without-index.d.ts +1 -0
  27. package/dist/utils/path-without-index.mjs +9 -0
  28. package/dist/utils/resolve-path.d.ts +2 -0
  29. package/dist/utils/resolve-path.mjs +4 -0
  30. package/dist/utils/static-import-lexer.d.ts +26 -0
  31. package/dist/utils/static-import-lexer.mjs +43 -0
  32. package/package.json +103 -0
  33. package/src/app.ts +144 -0
  34. package/src/cli/cli.ts +99 -0
  35. package/src/cli/index.ts +5 -0
  36. package/src/gez.ts +1026 -0
  37. package/src/index.ts +38 -0
  38. package/src/manifest-json.ts +80 -0
  39. package/src/module-config.ts +282 -0
  40. package/src/pack-config.ts +301 -0
  41. package/src/render-context.ts +1326 -0
  42. package/src/utils/cache.ts +68 -0
  43. package/src/utils/import-map.ts +47 -0
  44. package/src/utils/middleware.ts +118 -0
  45. package/src/utils/path-without-index.ts +9 -0
  46. package/src/utils/resolve-path.ts +33 -0
  47. package/src/utils/static-import-lexer.ts +85 -0
@@ -0,0 +1,49 @@
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>(name: string, fetch: () => Promise<T>) => Promise<T>;
25
+ /**
26
+ * 创建一个缓存处理函数
27
+ *
28
+ * @param enable - 是否启用缓存功能
29
+ * @returns 返回一个缓存处理函数
30
+ *
31
+ * @description
32
+ * 当 enable 为 true 时,会创建一个带有内存缓存的处理函数,相同的 name 只会执行一次 fetch。
33
+ * 当 enable 为 false 时,每次调用都会执行 fetch 函数,不会缓存结果。
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * // 创建一个启用缓存的处理函数
38
+ * const cacheEnabled = createCache(true);
39
+ *
40
+ * // 创建一个禁用缓存的处理函数
41
+ * const cacheDisabled = createCache(false);
42
+ *
43
+ * // 使用缓存处理函数
44
+ * const result = await cacheEnabled('userProfile', async () => {
45
+ * return await fetchUserProfile(userId);
46
+ * });
47
+ * ```
48
+ */
49
+ export declare function createCache(enable: boolean): <T>(name: string, fetch: () => Promise<T>) => Promise<T>;
@@ -0,0 +1,16 @@
1
+ export function createCache(enable) {
2
+ if (enable) {
3
+ const map = /* @__PURE__ */ new Map();
4
+ return async (name, fetch) => {
5
+ if (map.has(name)) {
6
+ return map.get(name);
7
+ }
8
+ const result = await fetch();
9
+ map.set(name, result);
10
+ return result;
11
+ };
12
+ }
13
+ return (name, fetch) => {
14
+ return fetch();
15
+ };
16
+ }
@@ -0,0 +1,8 @@
1
+ import type { ImportMap } from '@esmx/import';
2
+ import type { RuntimeTarget } from '../esmx';
3
+ import type { ManifestJson } from '../manifest-json';
4
+ import type { ParsedModuleConfig } from '../module-config';
5
+ /**
6
+ * 获取导入映射对象
7
+ */
8
+ export declare function getImportMap(target: RuntimeTarget, manifests: readonly ManifestJson[], moduleConfig: ParsedModuleConfig): Promise<ImportMap>;
@@ -0,0 +1,32 @@
1
+ import path from "node:path";
2
+ import { pathWithoutIndex } from "./path-without-index.mjs";
3
+ export async function getImportMap(target, manifests, moduleConfig) {
4
+ const imports = {};
5
+ if (target === "client") {
6
+ for (const manifest of manifests) {
7
+ for (const [name, value] of Object.entries(manifest.exports)) {
8
+ imports[`${manifest.name}/${name}`] = `/${manifest.name}/${value}`;
9
+ }
10
+ }
11
+ } else {
12
+ for (const manifest of manifests) {
13
+ const link = moduleConfig.links.find(
14
+ (item) => item.name === manifest.name
15
+ );
16
+ if (!link) {
17
+ throw new Error(
18
+ `'${manifest.name}' service did not find module config`
19
+ );
20
+ }
21
+ for (const [name, value] of Object.entries(manifest.exports)) {
22
+ imports[`${manifest.name}/${name}`] = path.resolve(
23
+ link.root,
24
+ "server",
25
+ value
26
+ );
27
+ }
28
+ }
29
+ }
30
+ pathWithoutIndex(imports);
31
+ return { imports };
32
+ }
@@ -0,0 +1,57 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { Esmx } from '../esmx';
3
+ /**
4
+ * 中间件函数类型定义
5
+ *
6
+ * @description
7
+ * 中间件是一个函数,用于处理 HTTP 请求。它接收请求对象、响应对象和下一个中间件函数作为参数。
8
+ * 中间件可以执行以下操作:
9
+ * - 修改请求和响应对象
10
+ * - 结束请求-响应循环
11
+ * - 调用下一个中间件
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // 创建一个简单的日志中间件
16
+ * const loggerMiddleware: Middleware = (req, res, next) => {
17
+ * console.log(`${req.method} ${req.url}`);
18
+ * next();
19
+ * };
20
+ * ```
21
+ */
22
+ export type Middleware = (req: IncomingMessage, res: ServerResponse, next: Function) => void;
23
+ /**
24
+ * 判断文件路径是否是一个符合 esmx 规范的不可变文件
25
+ * @param path 文件路径
26
+ */
27
+ export declare function isImmutableFile(filename: string): boolean;
28
+ /**
29
+ * 创建 Esmx 应用的中间件
30
+ *
31
+ * @param esmx - Esmx 实例
32
+ * @returns 返回一个处理静态资源的中间件
33
+ *
34
+ * @description
35
+ * 该函数创建一个中间件,用于处理模块的静态资源请求。它会:
36
+ * - 根据模块配置创建对应的静态资源中间件
37
+ * - 处理资源的缓存控制
38
+ * - 支持不可变文件的长期缓存
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { Esmx, createMiddleware } from '@esmx/core';
43
+ *
44
+ * const esmx = new Esmx();
45
+ * const middleware = createMiddleware(esmx);
46
+ *
47
+ * // 在 HTTP 服务器中使用
48
+ * server.use(middleware);
49
+ * ```
50
+ */
51
+ export declare function createMiddleware(esmx: Esmx): Middleware;
52
+ /**
53
+ * 将多个中间件,合并成一个中间件执行
54
+ * @param middlewares 中间件列表
55
+ * @returns
56
+ */
57
+ export declare function mergeMiddlewares(middlewares: Middleware[]): Middleware;
@@ -0,0 +1,51 @@
1
+ import path from "node:path";
2
+ import send from "send";
3
+ const reFinal = /\.final\.[a-zA-Z0-9]+$/;
4
+ export function isImmutableFile(filename) {
5
+ return reFinal.test(filename);
6
+ }
7
+ export function createMiddleware(esmx) {
8
+ const middlewares = esmx.moduleConfig.links.map((item) => {
9
+ const base = `/${item.name}/`;
10
+ const baseUrl = new URL(`file:`);
11
+ const root = path.resolve(item.root, "client");
12
+ return (req, res, next) => {
13
+ const url = req.url ?? "/";
14
+ const { pathname } = new URL(req.url ?? "/", baseUrl);
15
+ if (!url.startsWith(base) || req.method !== "GET") {
16
+ next();
17
+ return;
18
+ }
19
+ send(req, pathname.substring(base.length - 1), {
20
+ root
21
+ }).on("headers", () => {
22
+ if (isImmutableFile(pathname)) {
23
+ res.setHeader(
24
+ "cache-control",
25
+ "public, max-age=31536000, immutable"
26
+ );
27
+ } else {
28
+ res.setHeader("cache-control", "no-cache");
29
+ }
30
+ }).pipe(res);
31
+ };
32
+ });
33
+ return mergeMiddlewares(middlewares);
34
+ }
35
+ export function mergeMiddlewares(middlewares) {
36
+ return (req, res, next) => {
37
+ let index = 0;
38
+ function dispatch() {
39
+ if (index < middlewares.length) {
40
+ middlewares[index](req, res, () => {
41
+ index++;
42
+ dispatch();
43
+ });
44
+ return;
45
+ } else {
46
+ next();
47
+ }
48
+ }
49
+ dispatch();
50
+ };
51
+ }
@@ -0,0 +1 @@
1
+ export declare function pathWithoutIndex(i: Record<string, string>): void;
@@ -0,0 +1,9 @@
1
+ export function pathWithoutIndex(i) {
2
+ const s = "/index";
3
+ Object.entries(i).forEach(([k, v]) => {
4
+ if (k.endsWith(s)) {
5
+ k = k.substring(0, k.length - s.length);
6
+ i[k] = v;
7
+ }
8
+ });
9
+ }
@@ -0,0 +1,2 @@
1
+ export type ProjectPath = './' | 'dist' | 'dist/index.js' | 'dist/package.json' | 'dist/client' | 'dist/client/manifest.json' | 'dist/client/css' | 'dist/client/images' | 'dist/client/media' | 'dist/client/fonts' | 'dist/client/workers' | 'dist/client/importmap' | 'dist/client/versions/latest.tgz' | 'dist/server' | 'dist/server/manifest.json' | 'dist/node' | 'dist/node/src/entry.node.js' | 'src' | 'src/entry.node.ts' | 'src/entry.client.ts' | 'src/entry.server.ts' | 'package.json';
2
+ export declare function resolvePath(root: string, projectPath: ProjectPath, ...args: string[]): string;
@@ -0,0 +1,4 @@
1
+ import path from "node:path";
2
+ export function resolvePath(root, projectPath, ...args) {
3
+ return path.resolve(root, projectPath, ...args);
4
+ }
@@ -0,0 +1,26 @@
1
+ import type fs from 'node:fs';
2
+ /**
3
+ * 从 JS 代码中获取静态 import 的模块名列表。也许不能并发多个调用,没实验过。
4
+ * @param code js 代码
5
+ * @returns `Promise<string[]>` 静态 import 的模块名列表
6
+ */
7
+ export declare function getImportsFromJsCode(code: string): Promise<string[]>;
8
+ /**
9
+ * 从 JS 文件中获取静态 import 的模块名列表。
10
+ * @param filepath js 文件路径
11
+ * @returns `Promise<string[]>` 静态 import 的模块名列表
12
+ */
13
+ export declare function getImportsFromJsFile(filepath: fs.PathLike | fs.promises.FileHandle): Promise<string[]>;
14
+ import type { ImportMap, SpecifierMap } from '@esmx/import';
15
+ import type { ParsedModuleConfig } from '../module-config';
16
+ export type ImportPreloadInfo = SpecifierMap;
17
+ /**
18
+ * 获取导入的预加载信息。
19
+ * @param specifier 模块名
20
+ * @param importMap 导入映射对象
21
+ * @param moduleConfig 模块配置
22
+ * @returns
23
+ * - `Promise<{ [specifier: string]: ImportPreloadPathString }>` 模块名和文件路径的映射对象
24
+ * - `null` specifier 不存在
25
+ */
26
+ export declare function getImportPreloadInfo(specifier: string, importMap: ImportMap, moduleConfig: ParsedModuleConfig): Promise<SpecifierMap | null>;
@@ -0,0 +1,43 @@
1
+ import fsp from "node:fs/promises";
2
+ import path from "node:path";
3
+ import * as esmLexer from "es-module-lexer";
4
+ export async function getImportsFromJsCode(code) {
5
+ await esmLexer.init;
6
+ const [imports] = esmLexer.parse(code);
7
+ return imports.filter((item) => item.t === 1 && item.n).map((item) => item.n);
8
+ }
9
+ export async function getImportsFromJsFile(filepath) {
10
+ const source = await fsp.readFile(filepath, "utf-8");
11
+ return getImportsFromJsCode(source);
12
+ }
13
+ export async function getImportPreloadInfo(specifier, importMap, moduleConfig) {
14
+ const importInfo = importMap.imports;
15
+ if (!importInfo || !(specifier in importInfo)) {
16
+ return null;
17
+ }
18
+ const ans = {};
19
+ const needHandles = [[specifier]];
20
+ while (needHandles.length) {
21
+ const needHandle = [];
22
+ for (const specifier2 of needHandles.shift()) {
23
+ let filepath = importInfo[specifier2];
24
+ const splitRes = filepath.split("/");
25
+ if (splitRes[0] === "") splitRes.shift();
26
+ const name = splitRes.shift() + "";
27
+ const link = moduleConfig.links.find((item) => item.name === name);
28
+ if (!link) {
29
+ continue;
30
+ }
31
+ filepath = path.join(link.root, "client", ...splitRes);
32
+ const imports = await getImportsFromJsFile(filepath);
33
+ imports.forEach((specifier3) => {
34
+ if (specifier3 in importInfo && !ans[specifier3]) {
35
+ ans[specifier3] = importInfo[specifier3];
36
+ needHandle.push(specifier3);
37
+ }
38
+ });
39
+ }
40
+ needHandle.length && needHandles.push(needHandle);
41
+ }
42
+ return ans;
43
+ }
package/package.json ADDED
@@ -0,0 +1,103 @@
1
+ {
2
+ "name": "@esmx/core",
3
+ "description": "A high-performance microfrontend framework supporting Vue, React, Preact, Solid, and Svelte with SSR and Module Federation capabilities.",
4
+ "author": {
5
+ "name": "lzxb",
6
+ "url": "https://github.com/lzxb"
7
+ },
8
+ "contributors": [
9
+ {
10
+ "name": "RockShi1994",
11
+ "url": "https://github.com/RockShi1994"
12
+ },
13
+ {
14
+ "name": "jerrychan7",
15
+ "url": "https://github.com/jerrychan7"
16
+ }
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/js-esm/esmx.git",
21
+ "directory": "packages/core"
22
+ },
23
+ "homepage": "https://github.com/js-esm/esmx",
24
+ "bugs": {
25
+ "url": "https://github.com/js-esm/esmx/issues"
26
+ },
27
+ "template": "library-node",
28
+ "keywords": [
29
+ "Vue",
30
+ "React",
31
+ "Preact",
32
+ "Solid",
33
+ "Svelte",
34
+ "ESM",
35
+ "Microfrontend",
36
+ "SSR",
37
+ "Rspack",
38
+ "Module Federation",
39
+ "High Performance",
40
+ "TypeScript"
41
+ ],
42
+ "license": "MIT",
43
+ "bin": {
44
+ "esmx": "./dist/cli/index.mjs"
45
+ },
46
+ "engines": {
47
+ "node": ">=22.6"
48
+ },
49
+ "scripts": {
50
+ "lint:js": "biome check --write --no-errors-on-unmatched",
51
+ "lint:css": "stylelint '**/*.{css,vue}' --fix --aei",
52
+ "lint:type": "tsc --noEmit",
53
+ "test": "vitest run --pass-with-no-tests",
54
+ "coverage": "vitest run --coverage --pass-with-no-tests",
55
+ "build": "unbuild"
56
+ },
57
+ "dependencies": {
58
+ "@esmx/import": "3.0.0-rc.10",
59
+ "@types/serialize-javascript": "^5.0.4",
60
+ "es-module-lexer": "^1.6.0",
61
+ "find": "^0.3.0",
62
+ "send": "^1.1.0",
63
+ "serialize-javascript": "^6.0.2",
64
+ "write": "^2.0.0"
65
+ },
66
+ "devDependencies": {
67
+ "@biomejs/biome": "1.9.4",
68
+ "@esmx/lint": "3.0.0-rc.10",
69
+ "@types/find": "^0.2.4",
70
+ "@types/node": "22.13.10",
71
+ "@types/send": "^0.17.4",
72
+ "@types/write": "^2.0.4",
73
+ "@vitest/coverage-v8": "3.0.8",
74
+ "stylelint": "16.15.0",
75
+ "typescript": "5.8.2",
76
+ "unbuild": "2.0.0",
77
+ "vitest": "3.0.8"
78
+ },
79
+ "version": "3.0.0-rc.10",
80
+ "type": "module",
81
+ "private": false,
82
+ "exports": {
83
+ ".": {
84
+ "import": "./dist/index.mjs",
85
+ "types": "./dist/index.d.ts"
86
+ },
87
+ "./cli": {
88
+ "import": "./dist/cli/cli.mjs",
89
+ "types": "./dist/cli/cli.d.ts"
90
+ }
91
+ },
92
+ "module": "dist/index.mjs",
93
+ "types": "./dist/index.d.ts",
94
+ "files": [
95
+ "lib",
96
+ "src",
97
+ "dist",
98
+ "*.mjs",
99
+ "template",
100
+ "public"
101
+ ],
102
+ "gitHead": "4a528ffecfdc6f2c6e7d97bc952427745f467691"
103
+ }
package/src/app.ts ADDED
@@ -0,0 +1,144 @@
1
+ import { pathToFileURL } from 'node:url';
2
+ import { createLoaderImport } from '@esmx/import';
3
+ import type { COMMAND, Esmx } from './esmx';
4
+ import {
5
+ RenderContext,
6
+ type RenderContextOptions,
7
+ type ServerRenderHandle
8
+ } from './render-context';
9
+ import { type Middleware, createMiddleware } from './utils/middleware';
10
+
11
+ /**
12
+ * 应用程序实例接口。
13
+ *
14
+ * App 是 Esmx 框架的应用抽象,提供了统一的接口来管理应用的生命周期、
15
+ * 静态资源和服务端渲染。
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // entry.node.ts
20
+ * export default {
21
+ * // 开发环境配置
22
+ * async devApp(esmx) {
23
+ * return import('@esmx/rspack').then((m) =>
24
+ * m.createRspackHtmlApp(esmx, {
25
+ * config(rc) {
26
+ * // 自定义 Rspack 配置
27
+ * }
28
+ * })
29
+ * );
30
+ * }
31
+ * }
32
+ * ```
33
+ */
34
+ export interface App {
35
+ /**
36
+ * 静态资源处理中间件。
37
+ *
38
+ * 开发环境:
39
+ * - 处理源码的静态资源请求
40
+ * - 支持实时编译和热更新
41
+ * - 使用 no-cache 缓存策略
42
+ *
43
+ * 生产环境:
44
+ * - 处理构建后的静态资源
45
+ * - 支持不可变文件的长期缓存(.final.xxx)
46
+ * - 优化的资源加载策略
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * server.use(esmx.middleware);
51
+ * ```
52
+ */
53
+ middleware: Middleware;
54
+
55
+ /**
56
+ * 服务端渲染函数。
57
+ *
58
+ * 根据运行环境提供不同实现:
59
+ * - 生产环境(start):加载构建后的服务端入口文件(entry.server)执行渲染
60
+ * - 开发环境(dev):加载源码中的服务端入口文件执行渲染
61
+ *
62
+ * @param options - 渲染选项
63
+ * @returns 返回渲染上下文,包含渲染结果
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const rc = await esmx.render({
68
+ * params: { url: '/page' }
69
+ * });
70
+ * res.end(rc.html);
71
+ * ```
72
+ */
73
+ render: (options?: RenderContextOptions) => Promise<RenderContext>;
74
+
75
+ /**
76
+ * 生产环境构建函数。
77
+ * 用于资源打包和优化。
78
+ *
79
+ * @returns 构建成功返回 true,失败返回 false
80
+ */
81
+ build?: () => Promise<boolean>;
82
+
83
+ /**
84
+ * 资源清理函数。
85
+ * 用于关闭服务器、断开连接等。
86
+ *
87
+ * @returns 清理成功返回 true,失败返回 false
88
+ */
89
+ destroy?: () => Promise<boolean>;
90
+ }
91
+
92
+ /**
93
+ * 创建生产环境的应用程序实例,开发环境不可用。
94
+ */
95
+ export async function createApp(esmx: Esmx, command: COMMAND): Promise<App> {
96
+ const render =
97
+ command === esmx.COMMAND.start
98
+ ? await createStartRender(esmx) // 提供实际的渲染函数
99
+ : createErrorRender(esmx); // 提供错误提示渲染函数
100
+ return {
101
+ middleware: createMiddleware(esmx),
102
+ render
103
+ };
104
+ }
105
+
106
+ /**
107
+ * 创建生产环境渲染函数。
108
+ * 加载构建后的服务端入口文件(entry.server)执行渲染。
109
+ *
110
+ * @param esmx - Esmx 实例
111
+ * @returns 返回渲染函数
112
+ * @internal
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * // 服务端入口文件 (entry.server)
117
+ * export default async function render(rc: RenderContext) {
118
+ * rc.html = '<html>...</html>';
119
+ * }
120
+ * ```
121
+ */
122
+ async function createStartRender(esmx: Esmx) {
123
+ const baseURL = pathToFileURL(esmx.root) as URL;
124
+ const importMap = await esmx.getImportMap('server');
125
+ const loaderImport = createLoaderImport(baseURL, importMap);
126
+
127
+ return async (options?: RenderContextOptions): Promise<RenderContext> => {
128
+ const rc = new RenderContext(esmx, options);
129
+ const result = await loaderImport(`${esmx.name}/src/entry.server`);
130
+ const serverRender: ServerRenderHandle = result[rc.entryName];
131
+ if (typeof serverRender === 'function') {
132
+ await serverRender(rc);
133
+ }
134
+ return rc;
135
+ };
136
+ }
137
+
138
+ function createErrorRender(esmx: Esmx) {
139
+ return (options?: RenderContextOptions) => {
140
+ throw new Error(
141
+ `App instance is only available in production and can only execute built artifacts.`
142
+ );
143
+ };
144
+ }
package/src/cli/cli.ts ADDED
@@ -0,0 +1,99 @@
1
+ import module from 'node:module';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { COMMAND, Esmx, type EsmxOptions } from '../esmx';
5
+
6
+ async function getSrcOptions(): Promise<EsmxOptions> {
7
+ return import(path.resolve(process.cwd(), './src/entry.node.ts')).then(
8
+ (m) => m.default
9
+ );
10
+ }
11
+
12
+ export async function cli(command: string) {
13
+ if (command !== COMMAND.dev) {
14
+ process.env.NODE_ENV = 'production';
15
+ }
16
+ let esmx: Esmx | null;
17
+ let opts: EsmxOptions | null = null;
18
+ switch (command) {
19
+ case COMMAND.dev:
20
+ opts = await getSrcOptions();
21
+ esmx = new Esmx(opts);
22
+ exit(await esmx.init(COMMAND.dev));
23
+
24
+ // 释放内存
25
+ esmx = null;
26
+ opts = null;
27
+ break;
28
+ case COMMAND.start:
29
+ throw new Error(
30
+ `Please use 'NODE_ENV=production node dist/index.js' to run the built program`
31
+ );
32
+ case COMMAND.build:
33
+ // 编译代码。
34
+ opts = await getSrcOptions();
35
+ esmx = new Esmx(opts);
36
+ exit(await esmx.init(COMMAND.build));
37
+ exit(await esmx.destroy());
38
+
39
+ if (typeof opts.postBuild === 'function') {
40
+ // 生产模式运行
41
+ esmx = new Esmx({
42
+ ...opts,
43
+ server: undefined
44
+ });
45
+ exit(await esmx.init(COMMAND.start));
46
+ exit(await esmx.postBuild());
47
+ }
48
+
49
+ // 释放内存
50
+ esmx = null;
51
+ opts = null;
52
+ break;
53
+ case COMMAND.preview:
54
+ opts = await getSrcOptions();
55
+ // 编译代码
56
+ esmx = new Esmx(opts);
57
+ exit(await esmx.init(COMMAND.build));
58
+ exit(await esmx.destroy());
59
+
60
+ // 生产模式运行
61
+ esmx = new Esmx(opts);
62
+ exit(await esmx.init(COMMAND.start));
63
+ exit(await esmx.postBuild());
64
+
65
+ // 释放内存
66
+ esmx = null;
67
+ opts = null;
68
+ break;
69
+ default:
70
+ await import(path.resolve(process.cwd(), command));
71
+ break;
72
+ }
73
+ }
74
+
75
+ function exit(ok: boolean) {
76
+ if (!ok) {
77
+ process.exit(17);
78
+ }
79
+ }
80
+
81
+ // 支持 TS 文件不需要编写 .ts 后缀。
82
+ module.register(fileURLToPath(import.meta.url), {
83
+ parentURL: import.meta.url
84
+ });
85
+
86
+ export function resolve(
87
+ specifier: string,
88
+ context: Record<string, any>,
89
+ nextResolve: Function
90
+ ) {
91
+ if (
92
+ context?.parentURL.endsWith('.ts') &&
93
+ specifier.startsWith('.') &&
94
+ !specifier.endsWith('.ts')
95
+ ) {
96
+ return nextResolve(specifier + '.ts', context);
97
+ }
98
+ return nextResolve(specifier, context);
99
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node --no-warnings --experimental-vm-modules --experimental-import-meta-resolve --experimental-strip-types
2
+
3
+ import { cli } from './cli';
4
+
5
+ cli(process.argv.slice(2)[0] || '');