@yannick-z/modulo 0.2.0 → 0.3.1
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/.vscode/extensions.json +3 -0
- package/README.md +57 -60
- package/bin/modulo.js +29 -19
- package/biome.json +34 -0
- package/package.json +36 -35
- package/rslib.config.ts +19 -19
- package/src/args/get-framework-name.ts +7 -7
- package/src/args/index.ts +16 -74
- package/src/args/preset.ts +16 -16
- package/src/cli/init.ts +10 -6
- package/src/cli/pack-code.ts +20 -9
- package/src/config/example/example-config.ts +50 -39
- package/src/config/example/example-externals.ts +21 -37
- package/src/config/index.ts +186 -16
- package/src/config/presets.ts +100 -0
- package/src/config/type.ts +34 -12
- package/src/index.ts +104 -10
- package/src/initiator/create-config-file.ts +52 -36
- package/src/initiator/create-project.ts +249 -0
- package/src/initiator/modify-scripts.ts +42 -42
- package/src/packer/auto-external-plugin.ts +205 -0
- package/src/packer/collect-modules.ts +58 -69
- package/src/packer/get-externals-and-tags.ts +82 -55
- package/src/packer/lib.ts +78 -70
- package/src/packer/page.ts +105 -82
- package/src/packer/prepare.ts +63 -57
- package/src/tools/cli.ts +21 -0
- package/src/tools/file.ts +84 -14
- package/src/tools/find-path-root.ts +52 -25
- package/src/tools/get-framework-name.ts +7 -7
- package/src/tools/get-ui-plugin.ts +27 -13
- package/src/tools/json.ts +63 -9
- package/src/tools/log.ts +58 -0
- package/src/tools/merge-user-config.ts +17 -17
- package/src/tools/omit-root-path.ts +17 -7
- package/src/tools/panic.ts +12 -8
- package/src/tools/string.ts +13 -2
- package/src/type/guard.ts +22 -3
- package/tsconfig.json +9 -9
- package/dist/index.js +0 -773
- package/src/args/cmd.ts +0 -16
- package/src/args/mode.ts +0 -38
- package/src/args/node_env.ts +0 -15
- package/src/args/target.ts +0 -44
- package/src/config/externals.ts +0 -70
- package/src/config/generate_config.ts +0 -105
- package/src/config/preset/alias.ts +0 -3
- package/src/config/preset/dev-server.ts +0 -6
- package/src/config/preset/dirs.ts +0 -12
- package/src/config/preset/html.ts +0 -19
- package/src/config/preset/index.ts +0 -23
- package/src/config/preset/libs.ts +0 -5
- package/src/config/preset/minify.ts +0 -24
- package/src/config/preset/url.ts +0 -4
- package/src/tools/debug-log.ts +0 -37
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import type { Compiler, Compilation } from "@rspack/core";
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
import type { ModuloArgs_Pack } from "../args/index.ts";
|
|
5
|
+
import type { GLOBAL_CONFIG } from "../config/type.ts";
|
|
6
|
+
import { getExternalsAndImportMap } from "./get-externals-and-tags.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 自动 External 插件
|
|
10
|
+
*
|
|
11
|
+
* 1. 扫描编译过程中使用到的 node_modules 依赖
|
|
12
|
+
* 2. 如果这些依赖在 config.externals 中定义了,则标记为已使用
|
|
13
|
+
* 3. 在 HTML 生成阶段,仅注入已使用的依赖的 importmap 或 script 标签
|
|
14
|
+
*/
|
|
15
|
+
export class AutoExternalPlugin {
|
|
16
|
+
private externalLibNames: string[];
|
|
17
|
+
private usedExternals: Set<string>;
|
|
18
|
+
private args: ModuloArgs_Pack;
|
|
19
|
+
private config: GLOBAL_CONFIG;
|
|
20
|
+
|
|
21
|
+
constructor(args: ModuloArgs_Pack, config: GLOBAL_CONFIG) {
|
|
22
|
+
this.args = args;
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.externalLibNames = Object.keys(config.externals);
|
|
25
|
+
this.usedExternals = new Set<string>();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
apply(compiler: Compiler) {
|
|
29
|
+
compiler.hooks.compilation.tap(
|
|
30
|
+
"AutoExternalPlugin",
|
|
31
|
+
(compilation: Compilation) => {
|
|
32
|
+
// 1. 扫描模块依赖
|
|
33
|
+
compilation.hooks.finishModules.tap("AutoExternalPlugin", (modules) => {
|
|
34
|
+
// ... (省略模块扫描逻辑,这里没有问题) ...
|
|
35
|
+
for (const module of modules) {
|
|
36
|
+
// @ts-ignore
|
|
37
|
+
const constructorName = module.constructor.name;
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
const request = module.request;
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
const userRequest = module.userRequest;
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
const resource = module.resource;
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
const externalType = module.externalType;
|
|
46
|
+
|
|
47
|
+
// 策略 A: 检查 ExternalModule
|
|
48
|
+
// Rspack 中 ExternalModule 的 userRequest 通常是 import 的路径 (例如 'vue')
|
|
49
|
+
if (constructorName === 'ExternalModule' || externalType) {
|
|
50
|
+
const libName = request || userRequest; // 通常 request 是 'vue'
|
|
51
|
+
if (libName && this.externalLibNames.includes(libName)) {
|
|
52
|
+
this.usedExternals.add(libName);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 策略 B: 检查 module.resource 是否包含 node_modules (针对未被 external 但我们想知道是否被引用的情况 - 但这通常意味着它被打包了)
|
|
58
|
+
// 之前的逻辑保留,作为兜底,或者针对未正确 external 的情况
|
|
59
|
+
if (resource && resource.includes("node_modules")) {
|
|
60
|
+
for (const libName of this.externalLibNames) {
|
|
61
|
+
if (
|
|
62
|
+
resource.includes(`/node_modules/${libName}/`) ||
|
|
63
|
+
resource.includes(`\\node_modules\\${libName}\\`)
|
|
64
|
+
) {
|
|
65
|
+
this.usedExternals.add(libName);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} else if (resource && resource.includes("/node_modules/.pnpm/")) {
|
|
69
|
+
// 兼容 pnpm
|
|
70
|
+
for (const libName of this.externalLibNames) {
|
|
71
|
+
if (
|
|
72
|
+
resource.includes(`/node_modules/.pnpm/${libName}@`) ||
|
|
73
|
+
resource.includes(`/node_modules/.pnpm/${libName}+`) ||
|
|
74
|
+
// 某些情况下 pnpm 可能会直接链接
|
|
75
|
+
resource.includes(`/node_modules/${libName}/`)
|
|
76
|
+
) {
|
|
77
|
+
this.usedExternals.add(libName);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 尝试通过 compilation.hooks 访问 HtmlWebpackPlugin hooks
|
|
85
|
+
// Rsbuild 可能通过 mixin 或者其他方式将 hooks 注入到了 compilation 上
|
|
86
|
+
// 或者我们可以尝试直接使用 tapAsync 到 HtmlWebpackPlugin 的实例上,如果我们能找到它
|
|
87
|
+
|
|
88
|
+
// 终极 Hook 调试: 使用 compiler.hooks.emit 来检查 assets,看是否包含 index.html,以及内容
|
|
89
|
+
compiler.hooks.emit.tapAsync("AutoExternalPlugin", (compilation, cb) => {
|
|
90
|
+
if (process.env.DEBUG) console.log('[AutoExternalPlugin] emit hook triggered');
|
|
91
|
+
// 检查 compilation.assets
|
|
92
|
+
const assetNames = Object.keys(compilation.assets);
|
|
93
|
+
if (process.env.DEBUG) console.log('[AutoExternalPlugin] Assets:', assetNames);
|
|
94
|
+
cb();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// 2. 注入 HTML
|
|
98
|
+
let HtmlWebpackPlugin: any;
|
|
99
|
+
|
|
100
|
+
// 优先从 compiler.options.plugins 中查找实例并获取其构造函数
|
|
101
|
+
// 这是最稳妥的方式,确保我们获取的是同一个类的构造函数
|
|
102
|
+
const htmlPluginInstance = compiler.options.plugins.find(
|
|
103
|
+
(p: any) => p && (p.constructor.name === "HtmlWebpackPlugin" || p.constructor.name === "HtmlRspackPlugin")
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (htmlPluginInstance) {
|
|
107
|
+
HtmlWebpackPlugin = htmlPluginInstance.constructor;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Fallback: 如果没找到实例(可能被封装了),尝试从 compiler.webpack 中获取
|
|
111
|
+
if (!HtmlWebpackPlugin) {
|
|
112
|
+
HtmlWebpackPlugin =
|
|
113
|
+
compiler.webpack.HtmlRspackPlugin ||
|
|
114
|
+
(compiler.webpack as any).HtmlWebpackPlugin;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!HtmlWebpackPlugin) {
|
|
118
|
+
// Try to import from @rspack/plugin-html
|
|
119
|
+
try {
|
|
120
|
+
HtmlWebpackPlugin = require('@rspack/plugin-html').HtmlRspackPlugin;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
// ignore
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!HtmlWebpackPlugin) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 确保我们拿到的确实是 HtmlWebpackPlugin 构造函数,并且它有 getHooks 方法
|
|
131
|
+
if (typeof (HtmlWebpackPlugin as any).getHooks !== 'function') {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const hooks = (HtmlWebpackPlugin as any).getHooks(compilation);
|
|
136
|
+
|
|
137
|
+
hooks.alterAssetTags.tapAsync("AutoExternalPlugin", (data: any, cb: any) => {
|
|
138
|
+
if (data.assetTags && data.assetTags.scripts) {
|
|
139
|
+
this.processTags(data, 'assetTags.scripts');
|
|
140
|
+
}
|
|
141
|
+
cb(null, data);
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private processTags(data: any, targetProp: string) {
|
|
148
|
+
if (!this.config.autoExternal) {
|
|
149
|
+
return data;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 重新计算 importmaps
|
|
153
|
+
const { importMap } = getExternalsAndImportMap(
|
|
154
|
+
this.args,
|
|
155
|
+
this.config.externals,
|
|
156
|
+
this.config.externalsType,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// 过滤未使用的依赖
|
|
160
|
+
const filteredImportMap = Object.fromEntries(
|
|
161
|
+
Object.entries(importMap).filter(([key]) =>
|
|
162
|
+
this.usedExternals.has(key),
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (Object.keys(filteredImportMap).length === 0) {
|
|
167
|
+
return data;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let tags: any[] = [];
|
|
171
|
+
if (this.config.externalsType === "script") {
|
|
172
|
+
tags = Object.values(filteredImportMap).map((url) => ({
|
|
173
|
+
tagName: "script",
|
|
174
|
+
voidTag: false,
|
|
175
|
+
attributes: {
|
|
176
|
+
src: url,
|
|
177
|
+
},
|
|
178
|
+
}));
|
|
179
|
+
} else {
|
|
180
|
+
tags = [
|
|
181
|
+
{
|
|
182
|
+
tagName: "script",
|
|
183
|
+
voidTag: false,
|
|
184
|
+
attributes: { type: "importmap" },
|
|
185
|
+
innerHTML: JSON.stringify(
|
|
186
|
+
{ imports: filteredImportMap },
|
|
187
|
+
null,
|
|
188
|
+
2,
|
|
189
|
+
),
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 插入到 head 的最前面
|
|
195
|
+
if (targetProp === 'headTags') {
|
|
196
|
+
data.headTags.unshift(...tags);
|
|
197
|
+
} else if (targetProp === 'assetTags.scripts') {
|
|
198
|
+
data.assetTags.scripts.unshift(...tags);
|
|
199
|
+
} else if (targetProp === 'head') {
|
|
200
|
+
data.head.unshift(...tags);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return data;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -1,77 +1,66 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
2
1
|
import { resolve } from "node:path";
|
|
3
2
|
import picocolors from "picocolors";
|
|
4
|
-
import {
|
|
5
|
-
import { debug_log } from "../tools/debug-log.ts";
|
|
3
|
+
import { debug_log } from "../tools/log.ts";
|
|
6
4
|
import { get_framework_name } from "../tools/get-framework-name.ts";
|
|
5
|
+
import { get_directories, find_entry_file, exists } from "../tools/file.ts";
|
|
7
6
|
import type { ModuloArgs_Pack } from "../args/index.ts";
|
|
7
|
+
import type { GLOBAL_CONFIG } from "../config/type.ts";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* 收集模块入口文件
|
|
11
|
+
* 扫描指定目录下的子目录,查找符合规则的入口文件(index/main/同名文件)
|
|
12
|
+
*
|
|
13
|
+
* @param args CLI 参数
|
|
14
|
+
* @param kind 模块类型(page 或 module)
|
|
15
|
+
* @returns 模块名到入口文件路径的映射对象,如果未找到任何模块则返回 undefined
|
|
16
|
+
*/
|
|
9
17
|
export function collect_modules(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
debug_log(
|
|
18
|
-
picocolors.blue("check module_path"),
|
|
19
|
-
module_path,
|
|
20
|
-
exist ? "exists" : "NOT exists"
|
|
21
|
-
);
|
|
22
|
-
// 解析目录下的所有模块
|
|
23
|
-
const module_entries = !exist
|
|
24
|
-
? []
|
|
25
|
-
: readdirSync(module_path, { withFileTypes: true })
|
|
26
|
-
.filter((item) => {
|
|
27
|
-
debug_log(
|
|
28
|
-
"checking module is directory",
|
|
29
|
-
item.name,
|
|
30
|
-
item.isDirectory()
|
|
31
|
-
);
|
|
32
|
-
return item.isDirectory();
|
|
33
|
-
})
|
|
34
|
-
// 每个模块都要求以index或者main或者文件夹同名命名
|
|
35
|
-
.map((dirent) => {
|
|
36
|
-
const dir_path = resolve(module_path, dirent.name);
|
|
37
|
-
const entry_file_path = [
|
|
38
|
-
resolve(dir_path, "index.ts"),
|
|
39
|
-
resolve(dir_path, "index.tsx"),
|
|
40
|
-
resolve(dir_path, "index.js"),
|
|
41
|
-
resolve(dir_path, "index.jsx"),
|
|
42
|
-
resolve(dir_path, "main.ts"),
|
|
43
|
-
resolve(dir_path, "main.tsx"),
|
|
44
|
-
resolve(dir_path, "main.js"),
|
|
45
|
-
resolve(dir_path, "main.jsx"),
|
|
46
|
-
// vue组件常常用vue文件作为入口,因此优先级高于同名ts和tsx文件
|
|
47
|
-
...(framework_name === "vue"
|
|
48
|
-
? [
|
|
49
|
-
resolve(dir_path, "index.vue"),
|
|
50
|
-
resolve(dir_path, "main.vue"),
|
|
51
|
-
resolve(dir_path, `${dirent.name}.vue`),
|
|
52
|
-
]
|
|
53
|
-
: []),
|
|
54
|
-
resolve(dir_path, `${dirent.name}.ts`),
|
|
55
|
-
resolve(dir_path, `${dirent.name}.tsx`),
|
|
56
|
-
resolve(dir_path, `${dirent.name}.js`),
|
|
57
|
-
resolve(dir_path, `${dirent.name}.jsx`),
|
|
58
|
-
].find((path) => {
|
|
59
|
-
const exists = existsSync(path);
|
|
60
|
-
debug_log(
|
|
61
|
-
"checking entry candidate",
|
|
62
|
-
`${path} ${picocolors[exists ? "green" : "red"](
|
|
63
|
-
`exists - ${exists}`
|
|
64
|
-
)}`
|
|
65
|
-
);
|
|
66
|
-
return exists;
|
|
67
|
-
});
|
|
68
|
-
debug_log("found entry", entry_file_path);
|
|
69
|
-
return [dirent.name, entry_file_path];
|
|
70
|
-
})
|
|
71
|
-
// 没有找到入口的就不管
|
|
72
|
-
.filter((entry): entry is [string, string] => !!entry[1]);
|
|
18
|
+
args: ModuloArgs_Pack,
|
|
19
|
+
kind: "page" | "module",
|
|
20
|
+
global_config: GLOBAL_CONFIG,
|
|
21
|
+
): Record<string, string> | undefined {
|
|
22
|
+
const framework_name = get_framework_name();
|
|
23
|
+
const module_path = global_config.input[`${kind}s`];
|
|
24
|
+
const isExist = exists(module_path);
|
|
73
25
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
26
|
+
debug_log(
|
|
27
|
+
picocolors.blue("check module_path"),
|
|
28
|
+
module_path,
|
|
29
|
+
isExist ? "exists" : "NOT exists",
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (!isExist) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 基础候选文件名
|
|
37
|
+
const baseCandidates = ["index", "main"];
|
|
38
|
+
// 扩展名列表
|
|
39
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
40
|
+
// Vue 特有扩展名
|
|
41
|
+
if (framework_name === "vue") {
|
|
42
|
+
extensions.unshift(".vue");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 解析目录下的所有模块
|
|
46
|
+
const module_entries = get_directories(module_path)
|
|
47
|
+
.map((dirName) => {
|
|
48
|
+
const dir_path = resolve(module_path, dirName);
|
|
49
|
+
|
|
50
|
+
// 构建候选文件名列表,包含目录名
|
|
51
|
+
const candidates = [...baseCandidates, dirName];
|
|
52
|
+
|
|
53
|
+
// 查找入口文件
|
|
54
|
+
const entry_file_path = find_entry_file(dir_path, candidates, extensions);
|
|
55
|
+
|
|
56
|
+
debug_log("found entry", dirName, entry_file_path || "NOT FOUND");
|
|
57
|
+
|
|
58
|
+
return [dirName, entry_file_path];
|
|
59
|
+
})
|
|
60
|
+
// 没有找到入口的就不管
|
|
61
|
+
.filter((entry): entry is [string, string] => !!entry[1]);
|
|
62
|
+
|
|
63
|
+
return module_entries.length > 0
|
|
64
|
+
? Object.fromEntries(module_entries)
|
|
65
|
+
: undefined;
|
|
77
66
|
}
|
|
@@ -1,65 +1,92 @@
|
|
|
1
1
|
import type { ModuloArgs_Pack } from "../args/index.ts";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
type ExternalLibs,
|
|
4
|
+
type ConfigExternalUrl,
|
|
5
|
+
type ImportExternal,
|
|
6
|
+
} from "../config/type.ts";
|
|
7
|
+
import {
|
|
8
|
+
is_env_external,
|
|
9
|
+
is_string,
|
|
10
|
+
is_import_external,
|
|
11
|
+
} from "../type/guard.ts";
|
|
12
12
|
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
function getExternalUrl(args: ModuloArgs_Pack, url: ConfigExternalUrl) {
|
|
14
|
+
let resolvedUrl = url;
|
|
15
|
+
while (!is_string(resolvedUrl)) {
|
|
16
|
+
if (is_env_external(resolvedUrl)) {
|
|
17
|
+
resolvedUrl = resolvedUrl[args.pack.env];
|
|
18
|
+
} else {
|
|
19
|
+
// 理论上不会走到这里,因为现在只有 string 和 EnvExternalUrl
|
|
20
|
+
// 但为了类型安全,还是保留一个 else 分支或者抛出错误
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return resolvedUrl;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* 解析 External 配置,生成 externals 对象和 importMap
|
|
29
|
+
* @param args CLI 参数
|
|
30
|
+
* @param externalLibs 外部依赖配置
|
|
31
|
+
* @param externalsType External 类型(importmap 或 script)
|
|
32
|
+
* @returns externals 和 importMap
|
|
33
|
+
*/
|
|
34
|
+
export function getExternalsAndImportMap(
|
|
35
|
+
args: ModuloArgs_Pack,
|
|
36
|
+
externalLibs: ExternalLibs,
|
|
37
|
+
externalsType: "importmap" | "script" = "importmap",
|
|
29
38
|
) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
return Object.entries(externalLibs).reduce(
|
|
40
|
+
({ externals, importMap }, [libName, data]) => {
|
|
41
|
+
// 归一化为GlobalExternal或者ImportExternal
|
|
42
|
+
// 1. data 是 string -> { url: data }
|
|
43
|
+
// 2. data 是 EnvExternalUrl (dev/prd) -> { url: data }
|
|
44
|
+
// 3. data 是 ImportExternal -> data
|
|
45
|
+
|
|
46
|
+
let externalLib: ImportExternal;
|
|
47
|
+
|
|
48
|
+
if (is_string(data)) {
|
|
49
|
+
externalLib = { url: data };
|
|
50
|
+
} else if (is_env_external(data)) {
|
|
51
|
+
externalLib = { url: data };
|
|
52
|
+
} else {
|
|
53
|
+
externalLib = data as ImportExternal;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 解析 url (处理 EnvExternalUrl)
|
|
57
|
+
const url = getExternalUrl(args, externalLib.url);
|
|
58
|
+
|
|
59
|
+
if (externalsType === "script") {
|
|
60
|
+
// Script 模式:external 映射为全局变量
|
|
61
|
+
const globalVar = externalLib.global || libName;
|
|
62
|
+
const importName = externalLib.importName || libName;
|
|
63
|
+
(Array.isArray(importName) ? importName : [importName]).forEach(
|
|
64
|
+
(name) => (externals[name] = globalVar),
|
|
65
|
+
);
|
|
41
66
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
67
|
+
if (url) {
|
|
68
|
+
importMap[libName] = url; // 复用 importMap 存储 URL,但在 prepare 中会生成 script 标签
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// Importmap 模式:external 映射为包名(由 importmap 解析)
|
|
72
|
+
const importName = externalLib.importName || libName;
|
|
73
|
+
(Array.isArray(importName) ? importName : [importName]).forEach(
|
|
74
|
+
(name) => (externals[name] = libName),
|
|
75
|
+
);
|
|
47
76
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
importmaps[lib_name] = url;
|
|
53
|
-
}
|
|
77
|
+
if (url) {
|
|
78
|
+
importMap[libName] = url;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
54
81
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
return {
|
|
83
|
+
externals,
|
|
84
|
+
importMap,
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
externals: {} as Record<string, string>,
|
|
89
|
+
importMap: {} as Record<string, string>,
|
|
90
|
+
},
|
|
91
|
+
);
|
|
65
92
|
}
|
package/src/packer/lib.ts
CHANGED
|
@@ -1,84 +1,92 @@
|
|
|
1
1
|
import { pluginLess } from "@rsbuild/plugin-less";
|
|
2
|
-
import
|
|
2
|
+
import * as rslib from "@rslib/core";
|
|
3
3
|
import picocolors from "picocolors";
|
|
4
4
|
import type { ModuloArgs_Pack } from "../args/index.ts";
|
|
5
5
|
import { get_global_config, get_packagejson } from "../config/index.ts";
|
|
6
6
|
import { framework_plugin } from "../tools/get-ui-plugin.ts";
|
|
7
7
|
import { prepare_config } from "./prepare.ts";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* 执行库(module)打包
|
|
11
|
+
*
|
|
12
|
+
* 使用 Rslib 将模块目录下的代码打包为 ESM 和 UMD 格式。
|
|
13
|
+
*
|
|
14
|
+
* @param args CLI 参数
|
|
15
|
+
*/
|
|
9
16
|
export async function lib_pack(args: ModuloArgs_Pack) {
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
const config = await get_global_config(args);
|
|
18
|
+
const packagejson = get_packagejson();
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
const { entries, externals } = prepare_config(args, "module", config);
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
if (!entries) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
26
|
+
const rslibConfig = rslib.defineConfig({
|
|
27
|
+
source: {
|
|
28
|
+
define: config.define,
|
|
29
|
+
entry: entries,
|
|
30
|
+
},
|
|
31
|
+
plugins: [framework_plugin(config), pluginLess()],
|
|
32
|
+
resolve: {
|
|
33
|
+
alias: config.alias,
|
|
34
|
+
},
|
|
35
|
+
lib: [
|
|
36
|
+
{
|
|
37
|
+
format: "esm",
|
|
38
|
+
syntax: "esnext",
|
|
39
|
+
dts: false,
|
|
40
|
+
output: {
|
|
41
|
+
assetPrefix: `${config.url.base}/modules`,
|
|
42
|
+
externals,
|
|
43
|
+
distPath: {
|
|
44
|
+
root: config.output.modules,
|
|
45
|
+
js: "esm",
|
|
46
|
+
jsAsync: "esm",
|
|
47
|
+
css: "esm",
|
|
48
|
+
},
|
|
49
|
+
minify: config.minify,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
format: "umd",
|
|
54
|
+
output: {
|
|
55
|
+
assetPrefix: `${config.url.base}/modules`,
|
|
56
|
+
externals,
|
|
57
|
+
distPath: {
|
|
58
|
+
root: config.output.modules,
|
|
59
|
+
js: "umd",
|
|
60
|
+
jsAsync: "umd",
|
|
61
|
+
css: "umd",
|
|
62
|
+
},
|
|
63
|
+
minify: config.minify,
|
|
64
|
+
},
|
|
65
|
+
syntax: "es6",
|
|
66
|
+
umdName: `${packagejson.name}-modules-[name]`,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
output: {
|
|
70
|
+
legalComments: "none",
|
|
71
|
+
target: "web",
|
|
72
|
+
},
|
|
73
|
+
performance: {
|
|
74
|
+
bundleAnalyze: config.analyze
|
|
75
|
+
? {
|
|
76
|
+
analyzerMode: "disabled",
|
|
77
|
+
generateStatsFile: true,
|
|
78
|
+
}
|
|
79
|
+
: undefined,
|
|
80
|
+
chunkSplit: {
|
|
81
|
+
strategy: "all-in-one",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
78
85
|
|
|
79
|
-
|
|
86
|
+
const { build } = await rslib.createRslib({ config: rslibConfig });
|
|
87
|
+
await build({ watch: args.cmd === "build" && args.pack.watch });
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
if (args.cmd === "build") {
|
|
90
|
+
console.log(picocolors.green("\n**** 构建【module】完成 ****\n"));
|
|
91
|
+
}
|
|
84
92
|
}
|