@yannick-z/modulo 0.3.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/package.json CHANGED
@@ -2,10 +2,11 @@
2
2
  "name": "@yannick-z/modulo",
3
3
  "description": "",
4
4
  "type": "module",
5
- "version": "0.3.0",
5
+ "version": "0.3.1",
6
6
  "main": "./src/index.ts",
7
7
  "author": "oyangxiao",
8
8
  "scripts": {
9
+ "build": "直接使用ts执行,所以不需要build",
9
10
  "lint": "biome lint .",
10
11
  "format": "biome format --write .",
11
12
  "check": "biome check --write ."
@@ -10,7 +10,7 @@ export const preset_dev_server_config = {
10
10
  port: 8080, // 开发页面时, dev-server服务器端口
11
11
  proxy: {} as Record<
12
12
  string,
13
- string | { target: string; pathRewrite?: Record<string, string> }
13
+ string | { target: string; pathRewrite?: Record<string, string>; changeOrigin?: boolean; secure?: boolean }
14
14
  >, // dev时的代理配置
15
15
  };
16
16
  export type DEV_SERVER_CONFIG = typeof preset_dev_server_config;
package/src/index.ts CHANGED
@@ -90,8 +90,10 @@ cli
90
90
  });
91
91
  });
92
92
 
93
+ import packagejson from "../package.json" with { type: "json" };
94
+
93
95
  cli.help();
94
- cli.version("0.2.0");
96
+ cli.version(packagejson.version);
95
97
 
96
98
  export function exec() {
97
99
  try {
@@ -1,4 +1,6 @@
1
+ import { createRequire } from 'node:module';
1
2
  import type { Compiler, Compilation } from "@rspack/core";
3
+ const require = createRequire(import.meta.url);
2
4
  import type { ModuloArgs_Pack } from "../args/index.ts";
3
5
  import type { GLOBAL_CONFIG } from "../config/type.ts";
4
6
  import { getExternalsAndImportMap } from "./get-externals-and-tags.ts";
@@ -29,15 +31,33 @@ export class AutoExternalPlugin {
29
31
  (compilation: Compilation) => {
30
32
  // 1. 扫描模块依赖
31
33
  compilation.hooks.finishModules.tap("AutoExternalPlugin", (modules) => {
34
+ // ... (省略模块扫描逻辑,这里没有问题) ...
32
35
  for (const module of modules) {
33
- // 检查 module.resource 是否包含 node_modules
34
- // 并且属于我们在 config.externals 中定义的包
35
- const resource = (module as any).resource;
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 的情况
36
59
  if (resource && resource.includes("node_modules")) {
37
60
  for (const libName of this.externalLibNames) {
38
- // 简单的包含匹配,更严谨的可以使用路径解析
39
- // 例如: /node_modules/vue/
40
- // 这里简化处理,匹配 /node_modules/<libName>/
41
61
  if (
42
62
  resource.includes(`/node_modules/${libName}/`) ||
43
63
  resource.includes(`\\node_modules\\${libName}\\`)
@@ -45,66 +65,141 @@ export class AutoExternalPlugin {
45
65
  this.usedExternals.add(libName);
46
66
  }
47
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
+ }
48
80
  }
49
81
  }
50
82
  });
51
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
+
52
97
  // 2. 注入 HTML
53
- const HtmlWebpackPlugin =
54
- compiler.webpack.HtmlWebpackPlugin ||
55
- compiler.options.plugins.find(
56
- (p: any) => p.constructor.name === "HtmlWebpackPlugin",
57
- )?.constructor;
98
+ let HtmlWebpackPlugin: any;
58
99
 
59
- if (!HtmlWebpackPlugin) return;
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
+ );
60
105
 
61
- const hooks = (HtmlWebpackPlugin as any).getHooks(compilation);
62
- hooks.alterAssetTags.tap("AutoExternalPlugin", (data: any) => {
63
- if (!this.config.autoExternal) return data;
64
-
65
- // 重新计算 importmaps
66
- const { importMap } = getExternalsAndImportMap(
67
- this.args,
68
- this.config.externals,
69
- this.config.externalsType,
70
- );
71
-
72
- // 过滤未使用的依赖
73
- const filteredImportMap = Object.fromEntries(
74
- Object.entries(importMap).filter(([key]) =>
75
- this.usedExternals.has(key),
76
- ),
77
- );
78
-
79
- if (Object.keys(filteredImportMap).length === 0) return data;
80
-
81
- let tags: any[] = [];
82
- if (this.config.externalsType === "script") {
83
- tags = Object.values(filteredImportMap).map((url) => ({
84
- tagName: "script",
85
- voidTag: false,
86
- attributes: { src: url },
87
- }));
88
- } else {
89
- tags = [
90
- {
91
- tagName: "script",
92
- voidTag: false,
93
- attributes: { type: "importmap" },
94
- innerHTML: JSON.stringify(
95
- { imports: filteredImportMap },
96
- null,
97
- 2,
98
- ),
99
- },
100
- ];
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
101
123
  }
124
+ }
102
125
 
103
- // 插入到 head 的最前面
104
- data.assetTags.scripts.unshift(...tags);
105
- return data;
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);
106
142
  });
107
143
  },
108
144
  );
109
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
+ }
110
205
  }
@@ -39,17 +39,21 @@ export function getExternalsAndImportMap(
39
39
  return Object.entries(externalLibs).reduce(
40
40
  ({ externals, importMap }, [libName, data]) => {
41
41
  // 归一化为GlobalExternal或者ImportExternal
42
- const externalData = is_env_external(data) ? data[args.pack.env] : data;
43
-
42
+ // 1. data string -> { url: data }
43
+ // 2. data 是 EnvExternalUrl (dev/prd) -> { url: data }
44
+ // 3. data 是 ImportExternal -> data
45
+
44
46
  let externalLib: ImportExternal;
45
-
46
- if (typeof externalData === "string") {
47
- externalLib = { url: externalData };
47
+
48
+ if (is_string(data)) {
49
+ externalLib = { url: data };
50
+ } else if (is_env_external(data)) {
51
+ externalLib = { url: data };
48
52
  } else {
49
- // 此时 externalData 已经是 ImportExternal 类型
50
- externalLib = externalData as ImportExternal;
53
+ externalLib = data as ImportExternal;
51
54
  }
52
-
55
+
56
+ // 解析 url (处理 EnvExternalUrl)
53
57
  const url = getExternalUrl(args, externalLib.url);
54
58
 
55
59
  if (externalsType === "script") {
package/src/packer/lib.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { pluginLess } from "@rsbuild/plugin-less";
2
- import { build, defineConfig } from "@rslib/core";
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";
@@ -23,12 +23,12 @@ export async function lib_pack(args: ModuloArgs_Pack) {
23
23
  return;
24
24
  }
25
25
 
26
- const rslibConfig = defineConfig({
26
+ const rslibConfig = rslib.defineConfig({
27
27
  source: {
28
28
  define: config.define,
29
29
  entry: entries,
30
30
  },
31
- plugins: [framework_plugin(args, config), pluginLess()],
31
+ plugins: [framework_plugin(config), pluginLess()],
32
32
  resolve: {
33
33
  alias: config.alias,
34
34
  },
@@ -73,9 +73,9 @@ export async function lib_pack(args: ModuloArgs_Pack) {
73
73
  performance: {
74
74
  bundleAnalyze: config.analyze
75
75
  ? {
76
- analyzerMode: "disabled",
77
- generateStatsFile: true,
78
- }
76
+ analyzerMode: "disabled",
77
+ generateStatsFile: true,
78
+ }
79
79
  : undefined,
80
80
  chunkSplit: {
81
81
  strategy: "all-in-one",
@@ -83,7 +83,8 @@ export async function lib_pack(args: ModuloArgs_Pack) {
83
83
  },
84
84
  });
85
85
 
86
- await build(rslibConfig, { watch: args.cmd === "build" && args.pack.watch });
86
+ const { build } = await rslib.createRslib({ config: rslibConfig });
87
+ await build({ watch: args.cmd === "build" && args.pack.watch });
87
88
 
88
89
  if (args.cmd === "build") {
89
90
  console.log(picocolors.green("\n**** 构建【module】完成 ****\n"));
@@ -39,14 +39,13 @@ export async function page_pack(args: ModuloArgs_Pack) {
39
39
  tools: {
40
40
  rspack: {
41
41
  experiments: {
42
- outputModule: true,
42
+ outputModule: config.externalsType === "importmap" ? true : undefined,
43
43
  },
44
44
  plugins: [
45
45
  // @ts-ignore Rspack 插件类型兼容问题
46
46
  new AutoExternalPlugin(args, config),
47
47
  ],
48
48
  },
49
- htmlPlugin: true,
50
49
  },
51
50
  output: {
52
51
  assetPrefix: config.url.cdn || config.url.base,
@@ -61,7 +60,7 @@ export async function page_pack(args: ModuloArgs_Pack) {
61
60
  html: {
62
61
  meta: config.html.meta,
63
62
  mountId: config.html.root,
64
- scriptLoading: "module",
63
+ scriptLoading: config.externalsType === "importmap" ? undefined : "module",
65
64
  tags: config.html.tags,
66
65
  template:
67
66
  config.html.template ||