@proteus-vue/plugin-vite 0.2.0-beta.0
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/dist/appSkeleton.d.ts +1 -0
- package/dist/cache.d.ts +61 -0
- package/dist/config.d.ts +1 -0
- package/dist/gen-routes.d.ts +28 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1082 -0
- package/dist/plugin.d.ts +61 -0
- package/package.json +40 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const APP_LAUNCH_SKELETON = "App({\n onLaunch() {\n // \u5168\u94FE\u8DEF\u8C03\u8BD5\u5F00\u5173\uFF08PROTEUS_DEBUG=1 \u6784\u5EFA\u65F6\u7531\u63D2\u4EF6\u66FF\u6362\u4E3A true\uFF09\n const debug = typeof __PROTEUS_DEBUG__ !== 'undefined' && __PROTEUS_DEBUG__\n if (debug) console.log('[proteus][app] \u542F\u52A8', Date.now())\n // \u5168\u5C40\u9519\u8BEF\u6355\u83B7\uFF08debug \u6784\u5EFA\u8F93\u51FA\uFF0C\u6B63\u5F0F\u6784\u5EFA\u5E38\u91CF\u6298\u53E0\u96F6\u6B8B\u7559\uFF09\n if (typeof wx !== 'undefined' && wx.onError) {\n wx.onError(function (err) {\n if (debug) console.error('[proteus][error]', err, Date.now())\n })\n }\n // \u5185\u7F6E\u9884\u8BBE\u6CE8\u518C\uFF08\u540C\u6587\u4EF6\u9759\u6001\u53EF\u5206\u6790\uFF1A\u51FD\u6570\u5B9A\u4E49\u5728\u524D\u3001\u6CE8\u518C\u5728\u540E\uFF0C\u63D2\u4EF6\u5DF2\u4FDD\u8BC1\u987A\u5E8F\uFF09\n if (typeof wx !== 'undefined' && wx.router) {\n__PRESET_REGISTRATION__\n }\n },\n // \u2605lifecycle-plan B4\uFF1AApp \u7EA7 onShow/onHide \u94A9\u5B50\uFF08\u8C03\u8BD5\u65E5\u5FD7\uFF1BWeb \u7AEF\u5BF9\u5E94 visibilitychange\uFF09\n onShow() {\n const debug = typeof __PROTEUS_DEBUG__ !== 'undefined' && __PROTEUS_DEBUG__\n if (debug) console.log('[proteus][app] onShow', Date.now())\n },\n onHide() {\n const debug = typeof __PROTEUS_DEBUG__ !== 'undefined' && __PROTEUS_DEBUG__\n if (debug) console.log('[proteus][app] onHide', Date.now())\n },\n})\n";
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface CompileCacheEntry {
|
|
2
|
+
wxml: string;
|
|
3
|
+
js: string;
|
|
4
|
+
wxss: string;
|
|
5
|
+
warnings: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface CompileCacheStats {
|
|
8
|
+
hits: number;
|
|
9
|
+
misses: number;
|
|
10
|
+
}
|
|
11
|
+
/** 编译器版本指纹(缓存键组成部分——编译器代码变更即全局失效)
|
|
12
|
+
* ★修复①:仅 package.json version 不足(代码改了版本没变 → 缓存命中旧产物);改用 dist 代码内容哈希
|
|
13
|
+
* ★修复②(2026-08 真机复现):vite 5.4 加载 config 时把插件 bundle 到 os.tmpdir() → import.meta.url 基准失效 →
|
|
14
|
+
* require.resolve 找不到项目 node_modules(MODULE_NOT_FOUND)→ 恒 'unknown' → 指纹永不变化 → 改编译器代码缓存不失效
|
|
15
|
+
* (8aac7c3 的 dist 指纹设计在 vite bundle 场景从未生效)。改以 projectRoot 为基准(createRequire(projectRoot))显式解析 */
|
|
16
|
+
export declare function getCompilerVersion(projectRoot: string): string;
|
|
17
|
+
/** esbuild 版本(bundle 缓存键组成部分;同样以 projectRoot 为基准解析,避免 vite config bundle 基准失效) */
|
|
18
|
+
export declare function getEsbuildVersion(projectRoot: string): string;
|
|
19
|
+
/** 计算缓存键:sha1(源码 + 全编译入参 JSON + 编译器版本) */
|
|
20
|
+
export declare function compileCacheKey(source: string, options: {
|
|
21
|
+
rel: string;
|
|
22
|
+
isComponent: boolean;
|
|
23
|
+
px2rpx: boolean;
|
|
24
|
+
rpxRatio: number;
|
|
25
|
+
rules?: Record<string, unknown>;
|
|
26
|
+
moduleImports?: Array<{
|
|
27
|
+
source: string;
|
|
28
|
+
requirePath: string;
|
|
29
|
+
}>;
|
|
30
|
+
annotateLines: boolean;
|
|
31
|
+
debug: boolean;
|
|
32
|
+
autoScrollContainer?: boolean;
|
|
33
|
+
}, projectRoot: string): string;
|
|
34
|
+
/** 编译缓存:磁盘 + 内存双层(root 为项目根,缓存目录 node_modules/.cache/proteus/compile/) */
|
|
35
|
+
export declare function createCompileCache(cacheDir: string): {
|
|
36
|
+
get(key: string): CompileCacheEntry | null;
|
|
37
|
+
set(key: string, entry: CompileCacheEntry): void;
|
|
38
|
+
stats(): CompileCacheStats;
|
|
39
|
+
};
|
|
40
|
+
/** 输入文件快照(mtime+size 指纹,对齐 webpack/babel-loader 持久化缓存实践) */
|
|
41
|
+
export interface BundleInputSnapshot {
|
|
42
|
+
file: string;
|
|
43
|
+
mtimeMs: number;
|
|
44
|
+
size: number;
|
|
45
|
+
}
|
|
46
|
+
export interface BundleCacheEntry {
|
|
47
|
+
output: string;
|
|
48
|
+
inputs: BundleInputSnapshot[];
|
|
49
|
+
}
|
|
50
|
+
/** bundle 缓存键:sha1(入口文件 + esbuild 版本 + 构建选项) */
|
|
51
|
+
export declare function bundleCacheKey(entryFile: string, projectRoot: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* esbuild bundle 缓存:磁盘 + 内存双层
|
|
54
|
+
* 键 = sha1(入口文件 + esbuild 版本 + 构建选项);命中需输入快照全部有效(mtime+size 一致)
|
|
55
|
+
* 注意:首次构建需 metafile 记录输入集(第一次必然未命中,后续命中)
|
|
56
|
+
*/
|
|
57
|
+
export declare function createBundleCache(cacheDir: string): {
|
|
58
|
+
get(key: string): BundleCacheEntry | null;
|
|
59
|
+
set(key: string, entry: BundleCacheEntry): void;
|
|
60
|
+
stats(): CompileCacheStats;
|
|
61
|
+
};
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { ProteusConfig } from '@proteus-vue/types/config';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ProteusConfig } from './config';
|
|
2
|
+
/** 入口选项:config 为项目编译配置,root 为项目根目录(默认 process.cwd()) */
|
|
3
|
+
export interface GenRoutesOptions {
|
|
4
|
+
config: ProteusConfig;
|
|
5
|
+
root?: string;
|
|
6
|
+
/** --trace-router:输出每条路由的生成决策(来源登记 + 父路由推导依据) */
|
|
7
|
+
trace?: (msg: string) => void;
|
|
8
|
+
/**
|
|
9
|
+
* ★框架内置组件目录(组件库未拆包,决策 #115):显式传入绝对路径(如 monorepo 根 src/components);
|
|
10
|
+
* 缺省相对 root 的 src/components(create-proteus 模板工程用)
|
|
11
|
+
* ★v2.0 退役:@proteus-vue/components 拆为独立 npm 包后本选项删除(改 resolvePkgPath 包内路径,见 docs/packages.md)
|
|
12
|
+
*/
|
|
13
|
+
frameworkComponentsDir?: string;
|
|
14
|
+
/**
|
|
15
|
+
* ★module-plan B5:模块契约(@proteus-vue/module 扫描产物,调用方 async 扫描后传入):
|
|
16
|
+
* 分包依赖(dependencies)与 preloadRule 生成——模块 chunk/name 与 config.subPackages 的 name/root 基名匹配
|
|
17
|
+
*/
|
|
18
|
+
moduleConfigs?: Array<{
|
|
19
|
+
name: string;
|
|
20
|
+
chunk?: string;
|
|
21
|
+
dependencies?: Record<string, string>;
|
|
22
|
+
preload?: string[];
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 运行路由表生成(纯函数,可单测):清理 dist 产物 → 扫描页面 → 生成 auto-routes/app.json/page.json/component.json
|
|
27
|
+
*/
|
|
28
|
+
export declare function runGenRoutes(options: GenRoutesOptions): void;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as mpTransform, defaultScopedPlugin } from './plugin';
|
|
2
|
+
export { runGenRoutes } from './gen-routes';
|
|
3
|
+
export type { GenRoutesOptions } from './gen-routes';
|
|
4
|
+
export type { ProteusConfig } from './config';
|
|
5
|
+
export type { PluginOptions } from './plugin';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
// src/plugin.ts
|
|
2
|
+
import fs2 from "node:fs";
|
|
3
|
+
import path2 from "node:path";
|
|
4
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
5
|
+
import { transform as esbuildTransform, build as esbuildBuild } from "esbuild";
|
|
6
|
+
import * as sass from "sass";
|
|
7
|
+
import { compileVueSfc } from "@proteus-vue/compiler";
|
|
8
|
+
|
|
9
|
+
// src/appSkeleton.ts
|
|
10
|
+
var APP_LAUNCH_SKELETON = `App({
|
|
11
|
+
onLaunch() {
|
|
12
|
+
// \u5168\u94FE\u8DEF\u8C03\u8BD5\u5F00\u5173\uFF08PROTEUS_DEBUG=1 \u6784\u5EFA\u65F6\u7531\u63D2\u4EF6\u66FF\u6362\u4E3A true\uFF09
|
|
13
|
+
const debug = typeof __PROTEUS_DEBUG__ !== 'undefined' && __PROTEUS_DEBUG__
|
|
14
|
+
if (debug) console.log('[proteus][app] \u542F\u52A8', Date.now())
|
|
15
|
+
// \u5168\u5C40\u9519\u8BEF\u6355\u83B7\uFF08debug \u6784\u5EFA\u8F93\u51FA\uFF0C\u6B63\u5F0F\u6784\u5EFA\u5E38\u91CF\u6298\u53E0\u96F6\u6B8B\u7559\uFF09
|
|
16
|
+
if (typeof wx !== 'undefined' && wx.onError) {
|
|
17
|
+
wx.onError(function (err) {
|
|
18
|
+
if (debug) console.error('[proteus][error]', err, Date.now())
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
// \u5185\u7F6E\u9884\u8BBE\u6CE8\u518C\uFF08\u540C\u6587\u4EF6\u9759\u6001\u53EF\u5206\u6790\uFF1A\u51FD\u6570\u5B9A\u4E49\u5728\u524D\u3001\u6CE8\u518C\u5728\u540E\uFF0C\u63D2\u4EF6\u5DF2\u4FDD\u8BC1\u987A\u5E8F\uFF09
|
|
22
|
+
if (typeof wx !== 'undefined' && wx.router) {
|
|
23
|
+
__PRESET_REGISTRATION__
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
// \u2605lifecycle-plan B4\uFF1AApp \u7EA7 onShow/onHide \u94A9\u5B50\uFF08\u8C03\u8BD5\u65E5\u5FD7\uFF1BWeb \u7AEF\u5BF9\u5E94 visibilitychange\uFF09
|
|
27
|
+
onShow() {
|
|
28
|
+
const debug = typeof __PROTEUS_DEBUG__ !== 'undefined' && __PROTEUS_DEBUG__
|
|
29
|
+
if (debug) console.log('[proteus][app] onShow', Date.now())
|
|
30
|
+
},
|
|
31
|
+
onHide() {
|
|
32
|
+
const debug = typeof __PROTEUS_DEBUG__ !== 'undefined' && __PROTEUS_DEBUG__
|
|
33
|
+
if (debug) console.log('[proteus][app] onHide', Date.now())
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
// src/cache.ts
|
|
39
|
+
import fs from "node:fs";
|
|
40
|
+
import path from "node:path";
|
|
41
|
+
import crypto from "node:crypto";
|
|
42
|
+
import { createRequire } from "node:module";
|
|
43
|
+
function getCompilerVersion(projectRoot) {
|
|
44
|
+
try {
|
|
45
|
+
const require3 = createRequire(path.join(projectRoot, "package.json"));
|
|
46
|
+
const pkgJsonPath = require3.resolve("@proteus-vue/compiler/package.json");
|
|
47
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
48
|
+
const dist = path.join(path.dirname(pkgJsonPath), pkg.main ?? "dist/index.js");
|
|
49
|
+
const h = crypto.createHash("sha1");
|
|
50
|
+
h.update(fs.readFileSync(dist, "utf-8"));
|
|
51
|
+
return `${pkg.version}-${h.digest("hex").slice(0, 8)}`;
|
|
52
|
+
} catch {
|
|
53
|
+
return "unknown";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function getEsbuildVersion(projectRoot) {
|
|
57
|
+
try {
|
|
58
|
+
const require3 = createRequire(path.join(projectRoot, "package.json"));
|
|
59
|
+
const pkg = JSON.parse(fs.readFileSync(require3.resolve("esbuild/package.json"), "utf-8"));
|
|
60
|
+
return pkg.version;
|
|
61
|
+
} catch {
|
|
62
|
+
return "unknown";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function compileCacheKey(source, options, projectRoot) {
|
|
66
|
+
const h = crypto.createHash("sha1");
|
|
67
|
+
h.update(source);
|
|
68
|
+
h.update("|");
|
|
69
|
+
h.update(JSON.stringify({ ...options, compilerVersion: getCompilerVersion(projectRoot) }));
|
|
70
|
+
return h.digest("hex");
|
|
71
|
+
}
|
|
72
|
+
function createCompileCache(cacheDir) {
|
|
73
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
74
|
+
const memory = /* @__PURE__ */ new Map();
|
|
75
|
+
let hits = 0;
|
|
76
|
+
let misses = 0;
|
|
77
|
+
return {
|
|
78
|
+
get(key) {
|
|
79
|
+
const mem = memory.get(key);
|
|
80
|
+
if (mem) {
|
|
81
|
+
hits++;
|
|
82
|
+
return mem;
|
|
83
|
+
}
|
|
84
|
+
const file = path.join(cacheDir, `${key}.json`);
|
|
85
|
+
if (fs.existsSync(file)) {
|
|
86
|
+
try {
|
|
87
|
+
const entry = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
88
|
+
memory.set(key, entry);
|
|
89
|
+
hits++;
|
|
90
|
+
return entry;
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
misses++;
|
|
95
|
+
return null;
|
|
96
|
+
},
|
|
97
|
+
set(key, entry) {
|
|
98
|
+
memory.set(key, entry);
|
|
99
|
+
try {
|
|
100
|
+
fs.writeFileSync(path.join(cacheDir, `${key}.json`), JSON.stringify(entry));
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
stats() {
|
|
105
|
+
return { hits, misses };
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function bundleCacheKey(entryFile, projectRoot) {
|
|
110
|
+
const h = crypto.createHash("sha1");
|
|
111
|
+
h.update(entryFile);
|
|
112
|
+
h.update("|");
|
|
113
|
+
h.update(
|
|
114
|
+
JSON.stringify({
|
|
115
|
+
esbuild: getEsbuildVersion(projectRoot),
|
|
116
|
+
target: "es2018",
|
|
117
|
+
format: "cjs",
|
|
118
|
+
charset: "utf8",
|
|
119
|
+
minify: true,
|
|
120
|
+
external: ["@proteus-vue/*"]
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
return h.digest("hex");
|
|
124
|
+
}
|
|
125
|
+
function inputsValid(inputs) {
|
|
126
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
127
|
+
try {
|
|
128
|
+
const st = fs.statSync(inputs[i].file);
|
|
129
|
+
if (st.mtimeMs !== inputs[i].mtimeMs || st.size !== inputs[i].size) return false;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
function createBundleCache(cacheDir) {
|
|
137
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
138
|
+
const memory = /* @__PURE__ */ new Map();
|
|
139
|
+
let hits = 0;
|
|
140
|
+
let misses = 0;
|
|
141
|
+
return {
|
|
142
|
+
get(key) {
|
|
143
|
+
const mem = memory.get(key);
|
|
144
|
+
if (mem && inputsValid(mem.inputs)) {
|
|
145
|
+
hits++;
|
|
146
|
+
return mem;
|
|
147
|
+
}
|
|
148
|
+
const file = path.join(cacheDir, `${key}.json`);
|
|
149
|
+
if (fs.existsSync(file)) {
|
|
150
|
+
try {
|
|
151
|
+
const entry = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
152
|
+
if (inputsValid(entry.inputs)) {
|
|
153
|
+
memory.set(key, entry);
|
|
154
|
+
hits++;
|
|
155
|
+
return entry;
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
misses++;
|
|
161
|
+
return null;
|
|
162
|
+
},
|
|
163
|
+
set(key, entry) {
|
|
164
|
+
memory.set(key, entry);
|
|
165
|
+
try {
|
|
166
|
+
fs.writeFileSync(path.join(cacheDir, `${key}.json`), JSON.stringify(entry));
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
stats() {
|
|
171
|
+
return { hits, misses };
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/plugin.ts
|
|
177
|
+
var MP_TAG_MAP = {
|
|
178
|
+
view: "proteus-view",
|
|
179
|
+
text: "proteus-text",
|
|
180
|
+
button: "proteus-button",
|
|
181
|
+
input: "proteus-input",
|
|
182
|
+
image: "proteus-image",
|
|
183
|
+
"scroll-view": "proteus-scroll-view",
|
|
184
|
+
textarea: "proteus-textarea",
|
|
185
|
+
switch: "proteus-switch",
|
|
186
|
+
slider: "proteus-slider",
|
|
187
|
+
icon: "proteus-icon",
|
|
188
|
+
progress: "proteus-progress",
|
|
189
|
+
navigator: "proteus-navigator",
|
|
190
|
+
picker: "proteus-picker"
|
|
191
|
+
};
|
|
192
|
+
function defaultScopedPlugin() {
|
|
193
|
+
return {
|
|
194
|
+
name: "proteus-default-scoped",
|
|
195
|
+
enforce: "pre",
|
|
196
|
+
transform(code, id) {
|
|
197
|
+
if (!id.endsWith(".vue")) return null;
|
|
198
|
+
let out = code.replace(/<style\b([^>]*)>/g, (m, attrs) => {
|
|
199
|
+
if (/\bscoped\b/.test(attrs)) return m;
|
|
200
|
+
if (/\bglobal\b/.test(attrs)) return m.replace(/\bglobal\b\s*/, "");
|
|
201
|
+
return m.replace(/^<style/, "<style scoped");
|
|
202
|
+
});
|
|
203
|
+
out = out.replace(/<(\/)?(view|text|button|input|image|scroll-view|textarea|switch|slider|icon|progress|navigator|picker)(\s|\/?>)/g, (m, close, tag, rest) => {
|
|
204
|
+
return `<${close ?? ""}${MP_TAG_MAP[tag]}${rest}`;
|
|
205
|
+
});
|
|
206
|
+
if ((/function\s+onPageScroll\s*\(/.test(out) || /function\s+onReachBottom\s*\(/.test(out)) && out.includes("<script")) {
|
|
207
|
+
const importLine = "\nimport { onMounted as __proteusOnMounted, onUnmounted as __proteusOnUnmounted } from 'vue'\n";
|
|
208
|
+
const hookCode = "\nlet __proteusScrollHandler = () => {\n const __y = window.scrollY || 0\n // typeof \u5B89\u5168\u5305\u88F9\uFF1A\u9875\u9762\u53EF\u80FD\u53EA\u58F0\u660E onPageScroll \u6216 onReachBottom \u4E4B\u4E00\uFF08\u672A\u58F0\u660E\u6807\u8BC6\u7B26\u76F4\u63A5\u5F15\u7528\u629B ReferenceError\uFF09\n const __f = typeof onPageScroll === 'function' ? onPageScroll : null; if (__f) __f({ scrollTop: __y, scrollLeft: window.scrollX || 0 })\n if (typeof onReachBottom === 'function' && __y + window.innerHeight >= document.documentElement.scrollHeight - 50) onReachBottom()\n}\n__proteusOnMounted(() => { window.addEventListener('scroll', __proteusScrollHandler) })\n__proteusOnUnmounted(() => { window.removeEventListener('scroll', __proteusScrollHandler) })\n";
|
|
209
|
+
out = out.replace(/<script([^>]*)>/, (m) => `${m}${importLine}`);
|
|
210
|
+
out = out.replace("</script>", `${hookCode}</script>`);
|
|
211
|
+
}
|
|
212
|
+
return out === code ? null : { code: out, map: null };
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
var require2 = createRequire2(import.meta.url);
|
|
217
|
+
function resolvePkgPath(projectRoot, modPath) {
|
|
218
|
+
const m = modPath.match(/^node_modules\/((?:@[^/]+\/)?[^/]+)\/([\s\S]+)$/);
|
|
219
|
+
if (m) {
|
|
220
|
+
try {
|
|
221
|
+
const pkgRequire = createRequire2(path2.join(projectRoot, "package.json"));
|
|
222
|
+
const pkgRoot = path2.dirname(pkgRequire.resolve(`${m[1]}/package.json`));
|
|
223
|
+
return path2.join(pkgRoot, m[2]);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return path2.join(projectRoot, modPath);
|
|
228
|
+
}
|
|
229
|
+
function preprocessStyle(lang, content) {
|
|
230
|
+
if (lang === "scss" || lang === "sass") {
|
|
231
|
+
try {
|
|
232
|
+
return sass.compileString(content).css;
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.warn(`[mp-transform] scss \u7F16\u8BD1\u5931\u8D25\uFF08\u539F\u6837\u8F93\u51FA\uFF09\uFF1A${err.message}`);
|
|
235
|
+
return content;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (lang === "less") {
|
|
239
|
+
console.warn("[mp-transform] less \u9884\u5904\u7406\u5668\u6682\u672A\u5185\u7F6E\uFF08MVP \u4EC5 scss\uFF09\uFF0C\u5DF2\u539F\u6837\u8F93\u51FA");
|
|
240
|
+
return content;
|
|
241
|
+
}
|
|
242
|
+
return content;
|
|
243
|
+
}
|
|
244
|
+
function resolveSharedModule(appDir, absFrom, source, frameworkDir) {
|
|
245
|
+
if (source.startsWith("@proteus-vue/")) {
|
|
246
|
+
try {
|
|
247
|
+
const pkgRoot = path2.dirname(require2.resolve(`${source}/package.json`));
|
|
248
|
+
const entry = path2.join(pkgRoot, "dist", "index.js");
|
|
249
|
+
if (!fs2.existsSync(entry)) return null;
|
|
250
|
+
return { file: entry, relNoExt: `_proteus/${source.replace("@proteus-vue/", "")}` };
|
|
251
|
+
} catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (!source.startsWith(".")) return null;
|
|
256
|
+
const base = path2.resolve(path2.dirname(absFrom), source);
|
|
257
|
+
for (const cand of [base, `${base}.ts`, `${base}.js`, path2.join(base, "index.ts"), path2.join(base, "index.js")]) {
|
|
258
|
+
if (cand.endsWith(".vue")) continue;
|
|
259
|
+
let isFile = false;
|
|
260
|
+
try {
|
|
261
|
+
isFile = fs2.statSync(cand).isFile();
|
|
262
|
+
} catch {
|
|
263
|
+
isFile = false;
|
|
264
|
+
}
|
|
265
|
+
if (isFile) {
|
|
266
|
+
let relNoExt = path2.relative(appDir, cand).replace(/\\/g, "/").replace(/\.(ts|js)$/, "");
|
|
267
|
+
if (relNoExt.startsWith("../") && frameworkDir && !path2.relative(frameworkDir, cand).startsWith("..")) {
|
|
268
|
+
relNoExt = `proteus/${path2.relative(frameworkDir, cand).replace(/\\/g, "/").replace(/\.(ts|js)$/, "")}`;
|
|
269
|
+
}
|
|
270
|
+
return { file: cand, relNoExt };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
function extractBuilderFnName(code) {
|
|
276
|
+
const m = code.match(/function\s+([A-Za-z_$][\w$]*)\s*\(/);
|
|
277
|
+
return m ? m[1] : null;
|
|
278
|
+
}
|
|
279
|
+
function assembleAppJs(mainCode, presets) {
|
|
280
|
+
const presetCode = presets.map((p) => p.source.trim()).join("\n\n");
|
|
281
|
+
const custom = mainCode.trim();
|
|
282
|
+
const registerLines = presets.map((p) => ` wx.router.addRouteBuilder('${p.name}', ${p.fnName})`);
|
|
283
|
+
if (custom.includes("App(")) {
|
|
284
|
+
const register = presets.length ? `
|
|
285
|
+
if (typeof wx !== 'undefined' && wx.router) {
|
|
286
|
+
${registerLines.join("\n")}
|
|
287
|
+
}
|
|
288
|
+
` : "";
|
|
289
|
+
return `${custom}
|
|
290
|
+
|
|
291
|
+
${presetCode}${register}`;
|
|
292
|
+
}
|
|
293
|
+
const skeletonReg = presets.map((p) => ` wx.router.addRouteBuilder('${p.name}', ${p.fnName})`);
|
|
294
|
+
const skeleton = APP_LAUNCH_SKELETON.replace("__PRESET_REGISTRATION__", skeletonReg.join("\n") || " // \u65E0\u5185\u7F6E\u9884\u8BBE");
|
|
295
|
+
return `${custom ? `${custom}
|
|
296
|
+
|
|
297
|
+
` : ""}${presetCode ? `${presetCode}
|
|
298
|
+
|
|
299
|
+
` : ""}${skeleton}`;
|
|
300
|
+
}
|
|
301
|
+
function filterOverriddenPresets(mainCode, presets) {
|
|
302
|
+
return presets.filter((p) => !new RegExp(`addRouteBuilder\\s*\\(\\s*['"]${p.name}['"]`).test(mainCode));
|
|
303
|
+
}
|
|
304
|
+
async function loadPresetBuilders(projectRoot, cfg) {
|
|
305
|
+
const presets = [];
|
|
306
|
+
for (const [name, modPath] of Object.entries(cfg.customRoute.builders)) {
|
|
307
|
+
const abs = resolvePkgPath(projectRoot, modPath);
|
|
308
|
+
if (!fs2.existsSync(abs)) {
|
|
309
|
+
console.warn(`[mp-transform] \u9884\u8BBE builder ${name} \u4E0D\u5B58\u5728\uFF1A${modPath}`);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const { code } = await esbuildTransform(fs2.readFileSync(abs, "utf-8"), { loader: "ts", charset: "utf8" });
|
|
313
|
+
const fnName = extractBuilderFnName(code);
|
|
314
|
+
if (!fnName) {
|
|
315
|
+
console.warn(`[mp-transform] \u9884\u8BBE builder ${name} \u672A\u627E\u5230\u51FD\u6570\u58F0\u660E\uFF0C\u5DF2\u8DF3\u8FC7`);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
presets.push({ name, fnName, source: code });
|
|
319
|
+
}
|
|
320
|
+
return presets;
|
|
321
|
+
}
|
|
322
|
+
function walkVueFiles(dir, acc = []) {
|
|
323
|
+
if (!fs2.existsSync(dir)) return acc;
|
|
324
|
+
for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
|
|
325
|
+
if (entry.name.startsWith(".")) continue;
|
|
326
|
+
const full = path2.join(dir, entry.name);
|
|
327
|
+
if (entry.isDirectory()) walkVueFiles(full, acc);
|
|
328
|
+
else if (entry.name.endsWith(".vue")) acc.push(full);
|
|
329
|
+
}
|
|
330
|
+
return acc;
|
|
331
|
+
}
|
|
332
|
+
function mpTransform(opts) {
|
|
333
|
+
const cfg = opts.config;
|
|
334
|
+
const px2rpx = opts.px2rpx ?? cfg.style.px2rpx;
|
|
335
|
+
const rpxRatio = opts.rpxRatio ?? cfg.style.rpxRatio;
|
|
336
|
+
const rules = opts.rules ?? cfg.rules;
|
|
337
|
+
const autoScrollContainer = cfg.page?.autoScrollContainer ?? true;
|
|
338
|
+
const isDebug = process.env.PROTEUS_DEBUG === "1";
|
|
339
|
+
let projectRoot = process.cwd();
|
|
340
|
+
const warningReport = [];
|
|
341
|
+
return {
|
|
342
|
+
name: "vite-plugin-mp-transform",
|
|
343
|
+
enforce: "pre",
|
|
344
|
+
configResolved(resolved) {
|
|
345
|
+
projectRoot = resolved.root;
|
|
346
|
+
},
|
|
347
|
+
async buildStart() {
|
|
348
|
+
const appDir = path2.join(projectRoot, path2.dirname(cfg.pagesDir));
|
|
349
|
+
const compileCache = createCompileCache(path2.join(projectRoot, "node_modules", ".cache", "proteus", "compile"));
|
|
350
|
+
const bundleCache = createBundleCache(path2.join(projectRoot, "node_modules", ".cache", "proteus", "bundle"));
|
|
351
|
+
const files = [];
|
|
352
|
+
const pushRel = (dir) => {
|
|
353
|
+
for (const f of walkVueFiles(dir)) {
|
|
354
|
+
files.push({ file: f, rel: path2.relative(appDir, f).replace(/\\/g, "/").replace(/\.vue$/, "") });
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
pushRel(path2.join(projectRoot, cfg.pagesDir));
|
|
358
|
+
for (const sp of cfg.subPackages ?? []) {
|
|
359
|
+
pushRel(path2.join(projectRoot, sp.root));
|
|
360
|
+
}
|
|
361
|
+
const appComponents = path2.join(appDir, "components");
|
|
362
|
+
if (fs2.existsSync(appComponents)) pushRel(appComponents);
|
|
363
|
+
const frameworkComponents = opts.frameworkComponentsDir ?? path2.join(projectRoot, "src", "components");
|
|
364
|
+
if (fs2.existsSync(frameworkComponents)) {
|
|
365
|
+
for (const f of walkVueFiles(frameworkComponents)) {
|
|
366
|
+
const relIn = path2.relative(frameworkComponents, f).replace(/\\/g, "/").replace(/\.vue$/, "");
|
|
367
|
+
files.push({ file: f, rel: `proteus/${relIn}` });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const mpEntry = path2.join(appDir, "main.mp.ts");
|
|
371
|
+
if (fs2.existsSync(mpEntry)) {
|
|
372
|
+
const src = fs2.readFileSync(mpEntry, "utf-8");
|
|
373
|
+
const { code } = await esbuildTransform(src, { loader: "ts", charset: "utf8" });
|
|
374
|
+
const presets = filterOverriddenPresets(code, await loadPresetBuilders(projectRoot, cfg));
|
|
375
|
+
const appJs = assembleAppJs(code, presets).replace(/__PROTEUS_DEBUG__/g, isDebug ? "true" : "false").replace(/"worklet"/g, "'worklet'");
|
|
376
|
+
this.emitFile({ type: "asset", fileName: "app.js", source: appJs });
|
|
377
|
+
console.log(`[mp-transform] app.js \u5DF2\u76F4\u51FA\uFF08${isDebug ? "debug" : "\u6B63\u5F0F"}\uFF09\uFF0C\u5185\u7F6E\u9884\u8BBE\uFF1A${presets.map((p) => p.name).join("/") || "\u65E0"}`);
|
|
378
|
+
}
|
|
379
|
+
const moduleImportsByFile = /* @__PURE__ */ new Map();
|
|
380
|
+
const sharedModules = /* @__PURE__ */ new Set();
|
|
381
|
+
const sharedRelNoExt = /* @__PURE__ */ new Map();
|
|
382
|
+
const resolveShared = (absFrom, source) => resolveSharedModule(appDir, absFrom, source, frameworkComponents);
|
|
383
|
+
const scanImports = (absFile) => {
|
|
384
|
+
const src = fs2.readFileSync(absFile, "utf-8");
|
|
385
|
+
const script = src.includes("<script") ? src.match(/<script[^>]*>([\s\S]*?)<\/script>/i)?.[1] ?? "" : src;
|
|
386
|
+
const out = [];
|
|
387
|
+
for (const m of script.matchAll(/import\s+(?:type\s+)?.*?from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]/gm)) {
|
|
388
|
+
const s = m[1] || m[2];
|
|
389
|
+
if (s) out.push({ source: s, typeOnly: m[0].includes("import type") });
|
|
390
|
+
}
|
|
391
|
+
return out;
|
|
392
|
+
};
|
|
393
|
+
for (const { file } of files) {
|
|
394
|
+
const list = [];
|
|
395
|
+
for (const imp of scanImports(file)) {
|
|
396
|
+
if (imp.typeOnly) continue;
|
|
397
|
+
const resolved = resolveShared(file, imp.source);
|
|
398
|
+
if (!resolved) continue;
|
|
399
|
+
sharedModules.add(resolved.file);
|
|
400
|
+
sharedRelNoExt.set(resolved.file, resolved.relNoExt);
|
|
401
|
+
list.push({ source: imp.source, requirePath: "" });
|
|
402
|
+
}
|
|
403
|
+
if (list.length) moduleImportsByFile.set(file, list);
|
|
404
|
+
}
|
|
405
|
+
const pending = [...sharedModules];
|
|
406
|
+
while (pending.length) {
|
|
407
|
+
const cur = pending.pop();
|
|
408
|
+
for (const imp of scanImports(cur)) {
|
|
409
|
+
if (imp.typeOnly) continue;
|
|
410
|
+
const resolved = resolveShared(cur, imp.source);
|
|
411
|
+
if (!resolved || sharedModules.has(resolved.file)) continue;
|
|
412
|
+
sharedModules.add(resolved.file);
|
|
413
|
+
sharedRelNoExt.set(resolved.file, resolved.relNoExt);
|
|
414
|
+
pending.push(resolved.file);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const THIRD_PARTY_ALLOW = /* @__PURE__ */ new Set(["pinia", "vue", "vue-demi", "@vue/reactivity", "@vue/shared", "@vue/runtime-core"]);
|
|
418
|
+
const hasThirdParty = /* @__PURE__ */ new Set();
|
|
419
|
+
for (const sharedFile of sharedModules) {
|
|
420
|
+
for (const imp of scanImports(sharedFile)) {
|
|
421
|
+
if (imp.typeOnly) continue;
|
|
422
|
+
if (!imp.source.startsWith(".") && !imp.source.startsWith("@proteus-vue/") && !THIRD_PARTY_ALLOW.has(imp.source)) hasThirdParty.add(sharedFile);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const skipShared = /* @__PURE__ */ new Set();
|
|
426
|
+
const markSkip = (f) => {
|
|
427
|
+
if (skipShared.has(f)) return;
|
|
428
|
+
skipShared.add(f);
|
|
429
|
+
for (const other of sharedModules) {
|
|
430
|
+
if (other === f) continue;
|
|
431
|
+
const deps = scanImports(other).map((i) => resolveShared(other, i.source)?.file).filter(Boolean);
|
|
432
|
+
if (deps.includes(f)) markSkip(other);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
for (const f of hasThirdParty) markSkip(f);
|
|
436
|
+
if (skipShared.size) {
|
|
437
|
+
console.warn(`[mp-transform] \u26A0 ${skipShared.size} \u4E2A\u5171\u4EAB\u6A21\u5757\u542B\u7B2C\u4E09\u65B9\u4F9D\u8D56\uFF08pinia/vue \u7B49\uFF09\u5DF2\u8DF3\u8FC7\u7F16\u8BD1\uFF08B0 MVP\uFF1A\u4EC5\u652F\u6301\u7EAF\u903B\u8F91 + @proteus-vue/* \u6846\u67B6\u5305\u5171\u4EAB\u6A21\u5757\uFF09\u2014\u2014\u8BF7\u7528 store \u6865 / \u5185\u8054\uFF0CPinia \u63A5\u5165\u4E3A\u540E\u7EED\u6279\u6B21`);
|
|
438
|
+
for (const [file, list] of moduleImportsByFile) {
|
|
439
|
+
moduleImportsByFile.set(file, list.filter((item) => !skipShared.has(resolveShared(file, item.source)?.file ?? "")));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const bundleCacheEnabled = !process.env.PROTEUS_NO_CACHE && !isDebug;
|
|
443
|
+
for (const sharedFile of sharedModules) {
|
|
444
|
+
if (skipShared.has(sharedFile)) continue;
|
|
445
|
+
const relNoExt = sharedRelNoExt.get(sharedFile) ?? "";
|
|
446
|
+
let code = "";
|
|
447
|
+
let bundleHit = false;
|
|
448
|
+
if (bundleCacheEnabled) {
|
|
449
|
+
const bKey = bundleCacheKey(sharedFile, projectRoot);
|
|
450
|
+
const cachedBundle = bundleCache.get(bKey);
|
|
451
|
+
if (cachedBundle) {
|
|
452
|
+
code = cachedBundle.output;
|
|
453
|
+
bundleHit = true;
|
|
454
|
+
console.log(`[mp-transform] bundle \u7F13\u5B58\u547D\u4E2D\uFF1A${relNoExt}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (!code) {
|
|
458
|
+
const build = await esbuildBuild({
|
|
459
|
+
entryPoints: [sharedFile],
|
|
460
|
+
bundle: true,
|
|
461
|
+
format: "cjs",
|
|
462
|
+
write: false,
|
|
463
|
+
target: "es2018",
|
|
464
|
+
charset: "utf8",
|
|
465
|
+
logLevel: "silent",
|
|
466
|
+
minify: true,
|
|
467
|
+
metafile: true,
|
|
468
|
+
// ★@proteus-vue/* external:运行时 require 产物 _proteus/<name>.js(微信 require 缓存同路径同实例)
|
|
469
|
+
external: ["@proteus-vue/*"],
|
|
470
|
+
plugins: [
|
|
471
|
+
{
|
|
472
|
+
name: "proteus-pkg-require-path",
|
|
473
|
+
setup(b) {
|
|
474
|
+
b.onResolve({ filter: /^@proteus-vue\// }, (args) => {
|
|
475
|
+
const pkgRel = `_proteus/${args.path.replace("@proteus-vue/", "")}.js`;
|
|
476
|
+
const dir = path2.posix.dirname(relNoExt);
|
|
477
|
+
let rel = path2.posix.relative(dir, pkgRel);
|
|
478
|
+
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
479
|
+
return { path: rel, external: true };
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
]
|
|
484
|
+
});
|
|
485
|
+
code = build.outputFiles[0]?.text ?? "";
|
|
486
|
+
if (!code) {
|
|
487
|
+
console.warn(`[mp-transform] \u5171\u4EAB\u6A21\u5757\u7F16\u8BD1\u5931\u8D25\uFF1A${relNoExt}`);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
if (bundleCacheEnabled && build.metafile) {
|
|
491
|
+
const inputFiles = Object.keys(build.metafile.inputs);
|
|
492
|
+
const inputs = inputFiles.map((f) => {
|
|
493
|
+
try {
|
|
494
|
+
const st = fs2.statSync(f);
|
|
495
|
+
return { file: f, mtimeMs: st.mtimeMs, size: st.size };
|
|
496
|
+
} catch {
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
}).filter((x) => x !== null);
|
|
500
|
+
bundleCache.set(bundleCacheKey(sharedFile, projectRoot), { output: code, inputs });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
this.emitFile({ type: "asset", fileName: `${relNoExt}.js`, source: code });
|
|
504
|
+
console.log(`[mp-transform] \u5171\u4EAB\u6A21\u5757 \u2192 ${relNoExt}.js\uFF08${(code.length / 1024).toFixed(1)}KB\uFF0Cbundle \u5185\u8054\uFF09`);
|
|
505
|
+
}
|
|
506
|
+
for (const [file, list] of moduleImportsByFile) {
|
|
507
|
+
const entry = files.find((f) => f.file === file);
|
|
508
|
+
if (!entry) continue;
|
|
509
|
+
const pageDir = path2.posix.dirname(entry.rel);
|
|
510
|
+
for (const item of list) {
|
|
511
|
+
const shared = resolveShared(file, item.source);
|
|
512
|
+
if (!shared) continue;
|
|
513
|
+
const sharedRel = `${shared.relNoExt}.js`;
|
|
514
|
+
let rel = path2.posix.relative(pageDir, sharedRel);
|
|
515
|
+
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
516
|
+
item.requirePath = rel;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
for (const { file, rel } of files) {
|
|
520
|
+
const source = fs2.readFileSync(file, "utf-8");
|
|
521
|
+
const isComponent = file.includes(`${path2.sep}components${path2.sep}`);
|
|
522
|
+
const cacheEnabled = !process.env.PROTEUS_NO_CACHE && !isDebug;
|
|
523
|
+
let wxml;
|
|
524
|
+
let js;
|
|
525
|
+
let wxss;
|
|
526
|
+
let warnings = [];
|
|
527
|
+
let trace;
|
|
528
|
+
let sourcemap;
|
|
529
|
+
let cached = false;
|
|
530
|
+
if (cacheEnabled) {
|
|
531
|
+
const key = compileCacheKey(
|
|
532
|
+
source,
|
|
533
|
+
{
|
|
534
|
+
rel,
|
|
535
|
+
isComponent,
|
|
536
|
+
px2rpx,
|
|
537
|
+
rpxRatio,
|
|
538
|
+
rules,
|
|
539
|
+
moduleImports: moduleImportsByFile.get(file),
|
|
540
|
+
annotateLines: isDebug,
|
|
541
|
+
debug: isDebug,
|
|
542
|
+
autoScrollContainer
|
|
543
|
+
},
|
|
544
|
+
projectRoot
|
|
545
|
+
);
|
|
546
|
+
const entry = compileCache.get(key);
|
|
547
|
+
if (entry) {
|
|
548
|
+
wxml = entry.wxml;
|
|
549
|
+
js = entry.js;
|
|
550
|
+
wxss = entry.wxss;
|
|
551
|
+
warnings = entry.warnings;
|
|
552
|
+
cached = true;
|
|
553
|
+
} else {
|
|
554
|
+
const result = compileVueSfc(source, {
|
|
555
|
+
filename: rel,
|
|
556
|
+
isComponent,
|
|
557
|
+
px2rpx,
|
|
558
|
+
rpxRatio,
|
|
559
|
+
rules,
|
|
560
|
+
moduleImports: moduleImportsByFile.get(file),
|
|
561
|
+
annotateLines: isDebug,
|
|
562
|
+
debug: isDebug,
|
|
563
|
+
preprocessStyle,
|
|
564
|
+
autoScrollContainer
|
|
565
|
+
});
|
|
566
|
+
wxml = result.wxml;
|
|
567
|
+
js = result.js;
|
|
568
|
+
wxss = result.wxss;
|
|
569
|
+
warnings = result.warnings;
|
|
570
|
+
trace = result.trace;
|
|
571
|
+
sourcemap = result.sourcemap;
|
|
572
|
+
compileCache.set(key, { wxml, js, wxss, warnings });
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
const result = compileVueSfc(source, {
|
|
576
|
+
filename: rel,
|
|
577
|
+
isComponent,
|
|
578
|
+
px2rpx,
|
|
579
|
+
rpxRatio,
|
|
580
|
+
rules,
|
|
581
|
+
moduleImports: moduleImportsByFile.get(file),
|
|
582
|
+
annotateLines: isDebug,
|
|
583
|
+
debug: isDebug,
|
|
584
|
+
preprocessStyle,
|
|
585
|
+
autoScrollContainer
|
|
586
|
+
});
|
|
587
|
+
wxml = result.wxml;
|
|
588
|
+
js = result.js;
|
|
589
|
+
wxss = result.wxss;
|
|
590
|
+
warnings = result.warnings;
|
|
591
|
+
trace = result.trace;
|
|
592
|
+
sourcemap = result.sourcemap;
|
|
593
|
+
}
|
|
594
|
+
if (cached) {
|
|
595
|
+
console.log(`[mp-transform] \u7F16\u8BD1\u7F13\u5B58\u547D\u4E2D\uFF1A${rel}`);
|
|
596
|
+
}
|
|
597
|
+
const jsWithMap = sourcemap && isDebug ? `${js}//# sourceMappingURL=${rel}.js.map
|
|
598
|
+
` : js;
|
|
599
|
+
this.emitFile({ type: "asset", fileName: `${rel}.wxml`, source: wxml });
|
|
600
|
+
this.emitFile({ type: "asset", fileName: `${rel}.js`, source: jsWithMap });
|
|
601
|
+
this.emitFile({ type: "asset", fileName: `${rel}.wxss`, source: wxss });
|
|
602
|
+
if (sourcemap && isDebug) {
|
|
603
|
+
this.emitFile({ type: "asset", fileName: `${rel}.js.map`, source: sourcemap });
|
|
604
|
+
}
|
|
605
|
+
if (isDebug) {
|
|
606
|
+
this.emitFile({
|
|
607
|
+
type: "asset",
|
|
608
|
+
fileName: `.transform-debug/${rel}.json`,
|
|
609
|
+
source: JSON.stringify({ file: rel, wxml, js, wxss, warnings, trace }, null, 2)
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
if (warnings.length) warningReport.push({ file: rel, warnings });
|
|
613
|
+
console.log(`[mp-transform] ${rel} \u2192 wxml/js/wxss \u5DF2\u8F93\u51FA`);
|
|
614
|
+
}
|
|
615
|
+
if (!process.env.PROTEUS_NO_CACHE && !isDebug) {
|
|
616
|
+
const st = compileCache.stats();
|
|
617
|
+
const bs = bundleCache.stats();
|
|
618
|
+
console.log(`[mp-transform] \u7F16\u8BD1\u7F13\u5B58\uFF1A${st.hits} \u547D\u4E2D / ${st.misses} \u672A\u547D\u4E2D\uFF08${files.length} \u4E2A\u6587\u4EF6\uFF09\uFF1Bbundle \u7F13\u5B58\uFF1A${bs.hits} \u547D\u4E2D / ${bs.misses} \u672A\u547D\u4E2D\uFF08${sharedModules.size} \u4E2A\u5171\u4EAB\u6A21\u5757\uFF09`);
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
buildEnd() {
|
|
622
|
+
const total = warningReport.reduce((n, w) => n + w.warnings.length, 0);
|
|
623
|
+
if (total) {
|
|
624
|
+
console.warn(`[mp-transform] \u26A0 \u7F16\u8BD1\u6458\u8981\uFF1A${warningReport.length} \u4E2A\u6587\u4EF6\u5171 ${total} \u6761\u8B66\u544A`);
|
|
625
|
+
for (const w of warningReport) {
|
|
626
|
+
console.warn(` ${w.file}: ${w.warnings.join("\uFF1B")}`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
generateBundle(_options, bundle) {
|
|
631
|
+
delete bundle["mp-entry.js"];
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/gen-routes.ts
|
|
637
|
+
import fs3 from "node:fs";
|
|
638
|
+
import path3 from "node:path";
|
|
639
|
+
import { mergeMeta } from "@proteus-vue/router/merge";
|
|
640
|
+
import { scanRoutes } from "@proteus-vue/router/scan";
|
|
641
|
+
import { buildRouteTree } from "@proteus-vue/router/tree";
|
|
642
|
+
function runGenRoutes(options) {
|
|
643
|
+
const config = options.config;
|
|
644
|
+
const ROOT = options.root ?? process.cwd();
|
|
645
|
+
const trace = options.trace ?? (() => {
|
|
646
|
+
});
|
|
647
|
+
const APP_DIR = path3.resolve(ROOT, path3.dirname(config.pagesDir));
|
|
648
|
+
const OUT_DIR = path3.join(ROOT, "dist", "mp-weixin");
|
|
649
|
+
const FW_COMPONENTS = options.frameworkComponentsDir ?? path3.join(ROOT, "src", "components");
|
|
650
|
+
const moduleChunks = /* @__PURE__ */ new Map();
|
|
651
|
+
for (const mc of options.moduleConfigs ?? []) moduleChunks.set(mc.name, mc.chunk ?? mc.name);
|
|
652
|
+
const subPackageModules = /* @__PURE__ */ new Map();
|
|
653
|
+
for (const mc of options.moduleConfigs ?? []) {
|
|
654
|
+
const chunk = mc.chunk ?? mc.name;
|
|
655
|
+
const matched = (config.subPackages ?? []).some((sp) => (sp.name ?? path3.basename(sp.root)) === chunk);
|
|
656
|
+
if (matched) subPackageModules.set(chunk, { deps: Object.keys(mc.dependencies ?? {}), preload: mc.preload ?? [] });
|
|
657
|
+
for (const dep of Object.keys(mc.dependencies ?? {})) {
|
|
658
|
+
if (!moduleChunks.has(dep)) console.warn(`[gen-routes] \u6A21\u5757 ${mc.name} \u4F9D\u8D56 "${dep}" \u672A\u627E\u5230\u5BF9\u5E94\u6A21\u5757\u5951\u7EA6\uFF08proteus-module.config.ts\uFF09\u2014\u2014\u4F9D\u8D56\u5C06\u4E0D\u751F\u6548`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
const subPackageNameOf = (moduleName) => {
|
|
662
|
+
const chunk = moduleChunks.get(moduleName);
|
|
663
|
+
return (config.subPackages ?? []).some((sp) => (sp.name ?? path3.basename(sp.root)) === chunk) ? chunk : void 0;
|
|
664
|
+
};
|
|
665
|
+
function walkVueFiles2(dir, acc = []) {
|
|
666
|
+
if (!fs3.existsSync(dir)) return acc;
|
|
667
|
+
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
668
|
+
if (entry.name.startsWith(".")) continue;
|
|
669
|
+
const full = path3.join(dir, entry.name);
|
|
670
|
+
if (entry.isDirectory()) walkVueFiles2(full, acc);
|
|
671
|
+
else if (entry.name.endsWith(".vue")) acc.push(full);
|
|
672
|
+
}
|
|
673
|
+
return acc;
|
|
674
|
+
}
|
|
675
|
+
function resolveConfigMeta(configMeta, pageRel) {
|
|
676
|
+
if (!configMeta) return void 0;
|
|
677
|
+
let dirMeta;
|
|
678
|
+
const segs = pageRel.split("/");
|
|
679
|
+
for (let i = segs.length - 1; i >= 1; i--) {
|
|
680
|
+
const prefix = segs.slice(0, i).join("/");
|
|
681
|
+
if (configMeta[prefix]) {
|
|
682
|
+
dirMeta = configMeta[prefix];
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const exact = configMeta[pageRel];
|
|
687
|
+
if (dirMeta && exact) return mergeMeta(dirMeta, exact);
|
|
688
|
+
return dirMeta ?? exact;
|
|
689
|
+
}
|
|
690
|
+
function scanPages() {
|
|
691
|
+
const pages2 = [];
|
|
692
|
+
const configMeta = config.router?.meta;
|
|
693
|
+
const mainBlocks = scanRoutes(path3.join(ROOT, config.pagesDir), { derivePath: true, verbose: true, includeNoRoute: true });
|
|
694
|
+
for (const b of mainBlocks) {
|
|
695
|
+
const relSrc = path3.relative(APP_DIR, b.componentPath).replace(/\\/g, "/").replace(/\.vue$/, "");
|
|
696
|
+
const pageRel = relSrc.replace(/^pages\//, "");
|
|
697
|
+
trace(`[route] ${relSrc} \u6765\u6E90\u767B\u8BB0\uFF08${b.loc.file}:${b.loc.line}\uFF0Croute/scan\uFF09`);
|
|
698
|
+
pages2.push({
|
|
699
|
+
file: b.componentPath,
|
|
700
|
+
relSrc,
|
|
701
|
+
mpPath: relSrc,
|
|
702
|
+
// ★集中 meta:config(精确/目录前缀)→ 页面 <route> 覆盖(mergeMeta 页面胜)
|
|
703
|
+
meta: mergeMeta(resolveConfigMeta(configMeta, pageRel), b.meta),
|
|
704
|
+
params: b.params,
|
|
705
|
+
pageJson: b.pageJson,
|
|
706
|
+
customRouteKeyName: b.customRouteKeyName
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
for (const sp of config.subPackages ?? []) {
|
|
710
|
+
const spRootAbs = path3.join(ROOT, sp.root);
|
|
711
|
+
const spName = sp.name ?? path3.basename(sp.root);
|
|
712
|
+
const spBlocks = scanRoutes(spRootAbs, { derivePath: true, verbose: true, includeNoRoute: true });
|
|
713
|
+
for (const b of spBlocks) {
|
|
714
|
+
const relSrc = path3.relative(APP_DIR, b.componentPath).replace(/\\/g, "/").replace(/\.vue$/, "");
|
|
715
|
+
const relInSub = path3.relative(spRootAbs, b.componentPath).replace(/\\/g, "/").replace(/\.vue$/, "");
|
|
716
|
+
const pageRel = relInSub.replace(/^pages\//, "");
|
|
717
|
+
pages2.push({
|
|
718
|
+
file: b.componentPath,
|
|
719
|
+
relSrc,
|
|
720
|
+
mpPath: relSrc,
|
|
721
|
+
subPackage: spName,
|
|
722
|
+
relInSub,
|
|
723
|
+
meta: mergeMeta(resolveConfigMeta(configMeta, pageRel), b.meta),
|
|
724
|
+
params: b.params,
|
|
725
|
+
pageJson: b.pageJson,
|
|
726
|
+
customRouteKeyName: b.customRouteKeyName,
|
|
727
|
+
chunk: b.chunk
|
|
728
|
+
});
|
|
729
|
+
if (b.chunk && b.chunk !== spName) {
|
|
730
|
+
console.warn(`[gen-routes] \u5206\u5305\u9875\u9762 ${relInSub} \u58F0\u660E chunk="${b.chunk}" \u4E0E\u5206\u5305\u540D "${spName}" \u4E0D\u4E00\u81F4\uFF08Router M7.1\uFF1A\u9875\u9762 chunk \u5E94\u5BF9\u9F50\u6A21\u5757\u5206\u5305\u540D\uFF0C\u89C1 module-plan 05\uFF09`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return pages2;
|
|
735
|
+
}
|
|
736
|
+
function buildRoutes(pages2) {
|
|
737
|
+
const routes2 = pages2.map((p) => {
|
|
738
|
+
const r = {
|
|
739
|
+
// ★统一后 name 由 scan 推导(derivePath 模式:index 归并目录名,与旧 toRouteName 一致)
|
|
740
|
+
name: deriveName(p),
|
|
741
|
+
path: p.mpPath,
|
|
742
|
+
// 相对 RouterView 所在目录({appDir}/router)的路径,Web 端 import.meta.glob 按此匹配
|
|
743
|
+
component: path3.relative(path3.join(APP_DIR, "router"), p.file).replace(/\\/g, "/")
|
|
744
|
+
};
|
|
745
|
+
if (p.subPackage) r.subPackage = p.subPackage;
|
|
746
|
+
if (p.meta && Object.keys(p.meta).length > 0) r.meta = p.meta;
|
|
747
|
+
if (p.customRouteKeyName) r.customRouteKeyName = p.customRouteKeyName;
|
|
748
|
+
if (p.params && Object.keys(p.params).length > 0) r.params = p.params;
|
|
749
|
+
return r;
|
|
750
|
+
});
|
|
751
|
+
const nameByRel = new Map(pages2.map((p) => [p.relSrc, deriveName(p)]));
|
|
752
|
+
const blocks = pages2.map((p) => ({
|
|
753
|
+
loc: { file: p.file, line: 1, column: 1 },
|
|
754
|
+
path: p.relSrc.endsWith("/index") ? p.relSrc.slice(0, -"/index".length) : p.relSrc,
|
|
755
|
+
name: nameByRel.get(p.relSrc),
|
|
756
|
+
meta: p.meta ?? {},
|
|
757
|
+
componentPath: p.file
|
|
758
|
+
}));
|
|
759
|
+
const tree = buildRouteTree(blocks, {}, trace);
|
|
760
|
+
const parentByName = /* @__PURE__ */ new Map();
|
|
761
|
+
const walk = (nodes, parent) => {
|
|
762
|
+
for (const n of nodes) {
|
|
763
|
+
if (n.name && parent) parentByName.set(n.name, parent);
|
|
764
|
+
walk(n.children, n.name);
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
walk(tree);
|
|
768
|
+
for (const r of routes2) {
|
|
769
|
+
const parent = parentByName.get(r.name);
|
|
770
|
+
if (parent && parent !== r.name) r.parent = parent;
|
|
771
|
+
}
|
|
772
|
+
return routes2;
|
|
773
|
+
}
|
|
774
|
+
function deriveName(p) {
|
|
775
|
+
const base = p.relSrc.split("/").pop() ?? "";
|
|
776
|
+
if (base === "index") {
|
|
777
|
+
const dir = p.relSrc.slice(0, p.relSrc.lastIndexOf("/"));
|
|
778
|
+
const stripped = dir.replace(/^(pages|subpackages)(\/|$)/, "").replace(/\/$/, "");
|
|
779
|
+
return stripped ? stripped.replace(/\//g, "-") : "index";
|
|
780
|
+
}
|
|
781
|
+
return p.relSrc.replace(/^(pages|subpackages)\//, "").replace(/\//g, "-");
|
|
782
|
+
}
|
|
783
|
+
function validate(pages2, routes2) {
|
|
784
|
+
const mainCount = pages2.filter((p) => !p.subPackage).length;
|
|
785
|
+
if (mainCount > 32) {
|
|
786
|
+
throw new Error(
|
|
787
|
+
`[gen-routes] \u4E3B\u5305\u9875\u9762\u6570 ${mainCount} \u8D85\u8FC7\u5E73\u53F0\u786C\u8FB9\u754C 32\uFF0C\u8BF7\u5C06\u90E8\u5206\u9875\u9762\u79FB\u5165\u5206\u5305\uFF08platform limitation, cannot exceed\uFF09`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
const dupNames = routes2.filter((r, i) => routes2.findIndex((x) => x.name === r.name) !== i);
|
|
791
|
+
if (dupNames.length) {
|
|
792
|
+
throw new Error(`[gen-routes] \u547D\u540D\u8DEF\u7531\u91CD\u590D\uFF1A${dupNames.map((r) => r.name).join(", ")}`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
function formatRoute(r) {
|
|
796
|
+
const parts = [
|
|
797
|
+
`name: ${JSON.stringify(r.name)}`,
|
|
798
|
+
`path: ${JSON.stringify(r.path)}`,
|
|
799
|
+
`component: ${JSON.stringify(r.component)}`
|
|
800
|
+
];
|
|
801
|
+
if (r.parent) parts.push(`parent: ${JSON.stringify(r.parent)}`);
|
|
802
|
+
if (r.subPackage) parts.push(`subPackage: ${JSON.stringify(r.subPackage)}`);
|
|
803
|
+
if (r.meta && Object.keys(r.meta).length) parts.push(`meta: ${JSON.stringify(r.meta)}`);
|
|
804
|
+
if (r.customRouteKeyName) parts.push(`customRouteKeyName: ${JSON.stringify(r.customRouteKeyName)}`);
|
|
805
|
+
return ` { ${parts.join(", ")} },`;
|
|
806
|
+
}
|
|
807
|
+
function writeAutoRoutes(routes2) {
|
|
808
|
+
const lines = [
|
|
809
|
+
`// ${config.routesOutput} \u2014\u2014 \u5E94\u7528\u4FA7\u8DEF\u7531\u8868\uFF08AUTO-GENERATED by scripts/gen-routes.ts\uFF0C\u52FF\u624B\u52A8\u7F16\u8F91\uFF09`,
|
|
810
|
+
"// \u2605\u62C6\u5305\u6B65\u9AA4 4\uFF1Aauto-routes \u968F\u5E94\u7528\u5B58\u653E\uFF08\u5DE5\u5382\u5316\u540E\u8DEF\u7531\u8868\u7531\u5E94\u7528\u6CE8\u5165 createRouter\uFF09\uFF0C\u4E0D\u518D\u5C5E\u4E8E @proteus-vue/router \u5305",
|
|
811
|
+
"import type { RouteRecord } from '@proteus-vue/router/types'",
|
|
812
|
+
"",
|
|
813
|
+
"export const routes: RouteRecord[] = [",
|
|
814
|
+
...routes2.map(formatRoute),
|
|
815
|
+
"]",
|
|
816
|
+
"",
|
|
817
|
+
"export const tabRoutes: RouteRecord[] = routes.filter(r => r.meta?.isTab)",
|
|
818
|
+
"export const routeMap: Record<string, RouteRecord> = routes.reduce((m, r) => { m[r.name] = r; return m }, {} as Record<string, RouteRecord>)",
|
|
819
|
+
""
|
|
820
|
+
];
|
|
821
|
+
lines.push("// \u2605 \u7C7B\u578B\u63D0\u793A\uFF1A\u6309\u8DEF\u7531\u540D\u7D22\u5F15\u7684\u53C2\u6570\u7C7B\u578B\u8868\uFF08\u6765\u6E90\uFF1A<route> \u5757 params \u58F0\u660E\uFF09");
|
|
822
|
+
lines.push("declare module '@proteus-vue/router/types' {");
|
|
823
|
+
lines.push(" interface RouteParamsByName {");
|
|
824
|
+
for (const r of routes2) {
|
|
825
|
+
const params = r.params ?? {};
|
|
826
|
+
const fields = Object.entries(params);
|
|
827
|
+
const body = fields.length ? fields.map(([k, t]) => `${k}?: ${tsType(t)}`).join("; ") : "";
|
|
828
|
+
lines.push(` '${r.name}': { ${body} },`);
|
|
829
|
+
}
|
|
830
|
+
lines.push(" }", "}");
|
|
831
|
+
const outFile = path3.join(ROOT, config.routesOutput);
|
|
832
|
+
fs3.mkdirSync(path3.dirname(outFile), { recursive: true });
|
|
833
|
+
fs3.writeFileSync(outFile, lines.join("\n"));
|
|
834
|
+
console.log(`[gen-routes] \u5DF2\u751F\u6210 ${path3.relative(ROOT, outFile)}\uFF08${routes2.length} \u6761\u8DEF\u7531 + RouteParamsByName\uFF09`);
|
|
835
|
+
}
|
|
836
|
+
function tsType(t) {
|
|
837
|
+
if (t === "number") return "number";
|
|
838
|
+
if (t === "boolean") return "boolean";
|
|
839
|
+
if (t !== "string") {
|
|
840
|
+
console.warn(`[gen-routes] \u672A\u77E5\u53C2\u6570\u7C7B\u578B ${t}\uFF08\u652F\u6301 string/number/boolean\uFF09\uFF0C\u5DF2\u6309 string \u5904\u7406`);
|
|
841
|
+
}
|
|
842
|
+
return "string";
|
|
843
|
+
}
|
|
844
|
+
function writeAppJson(pages2, routes2) {
|
|
845
|
+
const mainPages = pages2.filter((p) => !p.subPackage).map((p) => p.mpPath);
|
|
846
|
+
const entryPath = path3.relative(APP_DIR, path3.join(ROOT, config.pagesDir, "index")).replace(/\\/g, "/");
|
|
847
|
+
const entryIdx = mainPages.indexOf(entryPath);
|
|
848
|
+
if (entryIdx > 0) {
|
|
849
|
+
mainPages.splice(entryIdx, 1);
|
|
850
|
+
mainPages.unshift(entryPath);
|
|
851
|
+
}
|
|
852
|
+
const subPackages = (config.subPackages ?? []).filter((sp) => pages2.some((p) => p.subPackage === (sp.name ?? path3.basename(sp.root)))).map((sp) => {
|
|
853
|
+
const spName = sp.name ?? path3.basename(sp.root);
|
|
854
|
+
const out = {
|
|
855
|
+
root: path3.relative(APP_DIR, path3.join(ROOT, sp.root)).replace(/\\/g, "/"),
|
|
856
|
+
...sp.name ? { name: sp.name } : {},
|
|
857
|
+
pages: pages2.filter((p) => p.subPackage === spName && p.relInSub).map((p) => p.relInSub)
|
|
858
|
+
};
|
|
859
|
+
const mod = subPackageModules.get(spName);
|
|
860
|
+
if (mod) {
|
|
861
|
+
const depNames = mod.deps.map(subPackageNameOf).filter((n) => Boolean(n));
|
|
862
|
+
if (depNames.length) out.dependencies = depNames;
|
|
863
|
+
}
|
|
864
|
+
return out;
|
|
865
|
+
});
|
|
866
|
+
const preloadRule = {};
|
|
867
|
+
for (const [spName, mod] of subPackageModules) {
|
|
868
|
+
const targetPackages = mod.preload.map(subPackageNameOf).filter((n) => Boolean(n));
|
|
869
|
+
if (!targetPackages.length) continue;
|
|
870
|
+
const entryPage = pages2.find((p) => p.subPackage === spName && p.relInSub);
|
|
871
|
+
if (!entryPage) continue;
|
|
872
|
+
preloadRule[entryPage.mpPath] = { network: "all", packages: targetPackages };
|
|
873
|
+
}
|
|
874
|
+
const windowConfig = { navigationStyle: "custom" };
|
|
875
|
+
const tabRoutes = routes2.filter((r) => r.meta?.isTab);
|
|
876
|
+
const appJson = { pages: mainPages };
|
|
877
|
+
if (subPackages.length) appJson.subPackages = subPackages;
|
|
878
|
+
if (Object.keys(preloadRule).length) appJson.preloadRule = preloadRule;
|
|
879
|
+
appJson.window = windowConfig;
|
|
880
|
+
if (config.skyline) appJson.lazyCodeLoading = "requiredComponents";
|
|
881
|
+
if (config.skyline) {
|
|
882
|
+
appJson.rendererOptions = { skyline: { defaultDisplayBlock: config.skylineLayout?.defaultDisplayBlock ?? true } };
|
|
883
|
+
}
|
|
884
|
+
if (tabRoutes.length >= 2) {
|
|
885
|
+
appJson.tabBar = {
|
|
886
|
+
list: tabRoutes.map((r) => ({ pagePath: r.path, text: r.meta?.title ?? r.name }))
|
|
887
|
+
};
|
|
888
|
+
} else if (tabRoutes.length === 1) {
|
|
889
|
+
console.warn(`[gen-routes] tabBar \u4EC5\u58F0\u660E 1 \u9879\uFF08${tabRoutes[0].name}\uFF09\uFF0C\u5FAE\u4FE1\u8981\u6C42\u81F3\u5C11 2 \u9879\uFF0C\u5DF2\u5FFD\u7565 tabBar \u914D\u7F6E\uFF1B\u53EF\u5C06\u66F4\u591A\u9875\u9762\u6807\u8BB0 isTab \u6216\u79FB\u9664\u73B0\u6709 isTab`);
|
|
890
|
+
}
|
|
891
|
+
fs3.mkdirSync(OUT_DIR, { recursive: true });
|
|
892
|
+
fs3.writeFileSync(path3.join(OUT_DIR, "app.json"), JSON.stringify(appJson, null, 2) + "\n");
|
|
893
|
+
console.log(`[gen-routes] \u5DF2\u751F\u6210 dist/mp-weixin/app.json\uFF08\u4E3B\u5305 ${mainPages.length} \u9875\uFF0C\u5206\u5305 ${subPackages.length} \u4E2A\uFF09`);
|
|
894
|
+
}
|
|
895
|
+
const NATIVE_MP_TAGS = /* @__PURE__ */ new Set([
|
|
896
|
+
"view",
|
|
897
|
+
"text",
|
|
898
|
+
"image",
|
|
899
|
+
"button",
|
|
900
|
+
"input",
|
|
901
|
+
"textarea",
|
|
902
|
+
"video",
|
|
903
|
+
"canvas",
|
|
904
|
+
"scroll-view",
|
|
905
|
+
"slot",
|
|
906
|
+
"rich-text",
|
|
907
|
+
"swiper",
|
|
908
|
+
"swiper-item",
|
|
909
|
+
"navigator",
|
|
910
|
+
"icon",
|
|
911
|
+
"progress",
|
|
912
|
+
"checkbox",
|
|
913
|
+
"radio",
|
|
914
|
+
"form",
|
|
915
|
+
"label",
|
|
916
|
+
"picker",
|
|
917
|
+
"slider",
|
|
918
|
+
"switch",
|
|
919
|
+
"map",
|
|
920
|
+
"web-view",
|
|
921
|
+
"cover-view",
|
|
922
|
+
"cover-image",
|
|
923
|
+
"movable-area",
|
|
924
|
+
"movable-view",
|
|
925
|
+
"block",
|
|
926
|
+
"template",
|
|
927
|
+
"wxs",
|
|
928
|
+
"audio",
|
|
929
|
+
"camera",
|
|
930
|
+
"live-player",
|
|
931
|
+
"ad",
|
|
932
|
+
"official-account",
|
|
933
|
+
"open-data",
|
|
934
|
+
"page-container",
|
|
935
|
+
"root-portal",
|
|
936
|
+
"match-media",
|
|
937
|
+
// ★vue-compat-advance Batch 2/5:<transition> 由编译器消费(装饰式,产物不输出该标签)——扫描跳过,非自定义组件
|
|
938
|
+
"transition"
|
|
939
|
+
]);
|
|
940
|
+
const HTML_TAGS = /* @__PURE__ */ new Set([
|
|
941
|
+
"div",
|
|
942
|
+
"span",
|
|
943
|
+
"p",
|
|
944
|
+
"h1",
|
|
945
|
+
"h2",
|
|
946
|
+
"h3",
|
|
947
|
+
"h4",
|
|
948
|
+
"h5",
|
|
949
|
+
"h6",
|
|
950
|
+
"a",
|
|
951
|
+
"img",
|
|
952
|
+
"br",
|
|
953
|
+
"ul",
|
|
954
|
+
"ol",
|
|
955
|
+
"li",
|
|
956
|
+
"section",
|
|
957
|
+
"header",
|
|
958
|
+
"footer",
|
|
959
|
+
"main",
|
|
960
|
+
"aside",
|
|
961
|
+
"nav",
|
|
962
|
+
"article",
|
|
963
|
+
"strong",
|
|
964
|
+
"em",
|
|
965
|
+
"b",
|
|
966
|
+
"i",
|
|
967
|
+
"small",
|
|
968
|
+
"code",
|
|
969
|
+
"pre",
|
|
970
|
+
"select",
|
|
971
|
+
"option",
|
|
972
|
+
"table",
|
|
973
|
+
"tr",
|
|
974
|
+
"td",
|
|
975
|
+
"th",
|
|
976
|
+
"form",
|
|
977
|
+
"label",
|
|
978
|
+
"tbody",
|
|
979
|
+
"thead",
|
|
980
|
+
"caption",
|
|
981
|
+
"figure",
|
|
982
|
+
"figcaption",
|
|
983
|
+
"details",
|
|
984
|
+
"summary"
|
|
985
|
+
]);
|
|
986
|
+
function collectComponents(file) {
|
|
987
|
+
const src = fs3.readFileSync(file, "utf-8");
|
|
988
|
+
const tpl = src.match(/<template[^>]*>([\s\S]*?)<\/template>/i)?.[1] ?? "";
|
|
989
|
+
const customTags = new Set(Object.keys(config.rules?.customTags ?? {}));
|
|
990
|
+
const used = /* @__PURE__ */ new Set();
|
|
991
|
+
const tagRe = /<([a-z][\w-]*)/g;
|
|
992
|
+
let m;
|
|
993
|
+
while (m = tagRe.exec(tpl)) {
|
|
994
|
+
const tag = m[1];
|
|
995
|
+
if (tag.startsWith("!")) continue;
|
|
996
|
+
if (NATIVE_MP_TAGS.has(tag) || HTML_TAGS.has(tag) || customTags.has(tag)) continue;
|
|
997
|
+
used.add(tag);
|
|
998
|
+
}
|
|
999
|
+
const out = {};
|
|
1000
|
+
for (const tag of used) {
|
|
1001
|
+
const appCandidates = [path3.join(APP_DIR, "components", tag, "index.vue"), path3.join(APP_DIR, "components", `${tag}.vue`)];
|
|
1002
|
+
const appFound = appCandidates.find((c) => fs3.existsSync(c));
|
|
1003
|
+
if (appFound) {
|
|
1004
|
+
out[tag] = `/components/${tag}/index`;
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
const fwCandidates = [path3.join(FW_COMPONENTS, tag, "index.vue"), path3.join(FW_COMPONENTS, `${tag}.vue`)];
|
|
1008
|
+
const fwFound = fwCandidates.find((c) => fs3.existsSync(c));
|
|
1009
|
+
if (fwFound) {
|
|
1010
|
+
out[tag] = `/proteus/${tag}/index`;
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
console.warn(`[gen-routes] ${file} \u4F7F\u7528\u4E86\u7EC4\u4EF6 <${tag}>\uFF0C\u4F46\u672A\u627E\u5230 ${appCandidates.join(" \u6216 ")} \u6216\u6846\u67B6\u7EC4\u4EF6 ${fwCandidates.join(" \u6216 ")}`);
|
|
1014
|
+
}
|
|
1015
|
+
return out;
|
|
1016
|
+
}
|
|
1017
|
+
function writePageJsons(pages2) {
|
|
1018
|
+
for (const p of pages2) {
|
|
1019
|
+
const pageJson = {};
|
|
1020
|
+
if (config.skyline) {
|
|
1021
|
+
pageJson.renderer = "skyline";
|
|
1022
|
+
pageJson.componentFramework = "glass-easel";
|
|
1023
|
+
}
|
|
1024
|
+
if (p.pageJson) Object.assign(pageJson, p.pageJson);
|
|
1025
|
+
const components = collectComponents(p.file);
|
|
1026
|
+
if (Object.keys(components).length) pageJson.usingComponents = components;
|
|
1027
|
+
const outFile = path3.join(OUT_DIR, p.mpPath + ".json");
|
|
1028
|
+
fs3.mkdirSync(path3.dirname(outFile), { recursive: true });
|
|
1029
|
+
fs3.writeFileSync(outFile, JSON.stringify(pageJson, null, 2) + "\n");
|
|
1030
|
+
}
|
|
1031
|
+
console.log(`[gen-routes] \u5DF2\u751F\u6210 ${pages2.length} \u4E2A\u9875\u9762 page.json`);
|
|
1032
|
+
}
|
|
1033
|
+
function writeComponentJsons() {
|
|
1034
|
+
const roots = [
|
|
1035
|
+
{ dir: path3.join(APP_DIR, "components"), prefix: "components" },
|
|
1036
|
+
{ dir: FW_COMPONENTS, prefix: "proteus" }
|
|
1037
|
+
];
|
|
1038
|
+
let count = 0;
|
|
1039
|
+
for (const { dir, prefix } of roots) {
|
|
1040
|
+
if (!fs3.existsSync(dir)) continue;
|
|
1041
|
+
for (const f of walkVueFiles2(dir)) {
|
|
1042
|
+
const rel = path3.relative(dir, f).replace(/\\/g, "/").replace(/\.vue$/, "");
|
|
1043
|
+
const comps = collectComponents(f);
|
|
1044
|
+
const outFile = path3.join(OUT_DIR, prefix, `${rel}.json`);
|
|
1045
|
+
fs3.mkdirSync(path3.dirname(outFile), { recursive: true });
|
|
1046
|
+
const json = { component: true };
|
|
1047
|
+
json.styleIsolation = "apply-shared";
|
|
1048
|
+
if (Object.keys(comps).length) json.usingComponents = comps;
|
|
1049
|
+
fs3.writeFileSync(outFile, JSON.stringify(json, null, 2) + "\n");
|
|
1050
|
+
count++;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (count) console.log(`[gen-routes] \u5DF2\u751F\u6210 ${count} \u4E2A\u7EC4\u4EF6 component.json\uFF08component \u58F0\u660E + usingComponents \u5D4C\u5957\uFF09`);
|
|
1054
|
+
}
|
|
1055
|
+
function writeProjectConfig() {
|
|
1056
|
+
const projectName = path3.basename(ROOT).replace(/[^\w.-]/g, "-");
|
|
1057
|
+
const projectConfig = {
|
|
1058
|
+
compileType: "miniprogram",
|
|
1059
|
+
appid: config.appid,
|
|
1060
|
+
projectname: projectName,
|
|
1061
|
+
setting: { minifyWXML: true, urlCheck: false }
|
|
1062
|
+
};
|
|
1063
|
+
fs3.mkdirSync(OUT_DIR, { recursive: true });
|
|
1064
|
+
fs3.writeFileSync(path3.join(OUT_DIR, "project.config.json"), JSON.stringify(projectConfig, null, 2) + "\n");
|
|
1065
|
+
console.log(`[gen-routes] \u5DF2\u751F\u6210 dist/mp-weixin/project.config.json\uFF08appid=${config.appid}\uFF0Cprojectname=${projectName}\uFF09`);
|
|
1066
|
+
}
|
|
1067
|
+
fs3.rmSync(OUT_DIR, { recursive: true, force: true });
|
|
1068
|
+
const pages = scanPages();
|
|
1069
|
+
const routes = buildRoutes(pages);
|
|
1070
|
+
validate(pages, routes);
|
|
1071
|
+
writeAutoRoutes(routes);
|
|
1072
|
+
writeAppJson(pages, routes);
|
|
1073
|
+
writePageJsons(pages);
|
|
1074
|
+
writeComponentJsons();
|
|
1075
|
+
writeProjectConfig();
|
|
1076
|
+
console.log(`[gen-routes] \u5B8C\u6210\uFF1A\u5171 ${pages.length} \u4E2A\u9875\u9762`);
|
|
1077
|
+
}
|
|
1078
|
+
export {
|
|
1079
|
+
defaultScopedPlugin,
|
|
1080
|
+
mpTransform,
|
|
1081
|
+
runGenRoutes
|
|
1082
|
+
};
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import type { TransformRuleOverrides } from '@proteus-vue/compiler';
|
|
3
|
+
import type { ProteusConfig } from './config';
|
|
4
|
+
export declare function defaultScopedPlugin(): Plugin;
|
|
5
|
+
export declare function resolvePkgPath(projectRoot: string, modPath: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* ★module-plan B0 + platform-plan B5 尾:共享模块解析(纯函数可测)
|
|
8
|
+
* - 相对路径(本地 .ts/.js)→ 产物相对 appDir 路径
|
|
9
|
+
* - @proteus-vue/*(框架包 dist)→ 产物 _proteus/<name>(白名单放行;微信 require 缓存同路径同实例)
|
|
10
|
+
* - 其余裸模块(vue/pinia 等第三方)→ null(不参与)
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveSharedModule(appDir: string, absFrom: string, source: string, frameworkDir?: string): {
|
|
13
|
+
file: string;
|
|
14
|
+
relNoExt: string;
|
|
15
|
+
} | null;
|
|
16
|
+
/**
|
|
17
|
+
* 提取 builder 函数名:function xxxBuilder(...)
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractBuilderFnName(code: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* 组装 app.js:入口源码 + 内置预设 builder 函数定义 + 注册(纯函数,可单测)
|
|
22
|
+
* 两种模式:
|
|
23
|
+
* ① 全量自定义(向后兼容):入口含 App() → 原样保留入口,追加预设定义 + 注册块
|
|
24
|
+
* ② 极简模式(★默认推荐):入口不含 App() → 自动拼装 app 骨架(App/调试/错误捕获/预设注册),
|
|
25
|
+
* 开发者只需写自定义 builder(覆盖预设 / 新增预设),零样板
|
|
26
|
+
* 注册块在模块顶层执行(官方文档形态),builder 与 addRouteBuilder 同文件静态可分析
|
|
27
|
+
*/
|
|
28
|
+
export declare function assembleAppJs(mainCode: string, presets: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
fnName: string;
|
|
31
|
+
source: string;
|
|
32
|
+
}>): string;
|
|
33
|
+
/**
|
|
34
|
+
* 过滤被开发者覆盖的预设:main 中已 addRouteBuilder('<name>' 的预设跳过自动注册(开发者优先)
|
|
35
|
+
*/
|
|
36
|
+
export declare function filterOverriddenPresets(mainCode: string, presets: Array<{
|
|
37
|
+
name: string;
|
|
38
|
+
fnName: string;
|
|
39
|
+
source: string;
|
|
40
|
+
}>): Array<{
|
|
41
|
+
name: string;
|
|
42
|
+
fnName: string;
|
|
43
|
+
source: string;
|
|
44
|
+
}>;
|
|
45
|
+
export interface PluginOptions {
|
|
46
|
+
/** ★拆包步骤 5:完整 ProteusConfig(由 vite.config 从项目 proteus.config.ts 注入) */
|
|
47
|
+
config: ProteusConfig;
|
|
48
|
+
/** 样式换算(缺省取 config.style.px2rpx) */
|
|
49
|
+
px2rpx?: boolean;
|
|
50
|
+
rpxRatio?: number;
|
|
51
|
+
/** ★底线循环 ①③:规则覆盖(缺省取 config.rules) */
|
|
52
|
+
rules?: TransformRuleOverrides;
|
|
53
|
+
/**
|
|
54
|
+
* ★框架内置组件目录(@proteus-vue/components 组件库拆包前的定位方式,决策 #115):
|
|
55
|
+
* 组件库未拆包,仓库在工程根之外(如 monorepo 根 src/components)时,工程显式传入绝对路径;
|
|
56
|
+
* 缺省相对工程根 src/components(create-proteus 模板工程用)
|
|
57
|
+
* ★v2.0 退役:@proteus-vue/components 拆为独立 npm 包后本选项删除(改 resolvePkgPath 包内路径,见 docs/packages.md)
|
|
58
|
+
*/
|
|
59
|
+
frameworkComponentsDir?: string;
|
|
60
|
+
}
|
|
61
|
+
export default function mpTransform(opts: PluginOptions): Plugin;
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@proteus-vue/plugin-vite",
|
|
3
|
+
"version": "0.2.0-beta.0",
|
|
4
|
+
"description": "Proteus Vite 插件(mp-weixin 编译管线适配层)+ gen-routes 路由表生成器",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"prepare": "npm run build",
|
|
23
|
+
"build": "tsc -p tsconfig.build.json --emitDeclarationOnly && esbuild src/index.ts --bundle --format=esm --platform=node --outfile=dist/index.js --external:@proteus-vue/compiler --external:@proteus-vue/router --external:esbuild --external:sass --external:vite"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@proteus-vue/compiler": "0.3.0-beta.0",
|
|
27
|
+
"@proteus-vue/module": "0.1.0",
|
|
28
|
+
"@proteus-vue/router": "0.2.0-beta.0",
|
|
29
|
+
"@proteus-vue/types": "0.1.0",
|
|
30
|
+
"esbuild": "^0.28.2",
|
|
31
|
+
"sass": "^1.103.1"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"vite": "^5.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^20.0.0",
|
|
38
|
+
"vite": "^5.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|