vite-plugin-uni-inject 0.4.0 → 0.5.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/index.cjs +146 -90
- package/dist/index.d.cts +40 -7
- package/dist/index.d.mts +40 -7
- package/dist/index.mjs +146 -90
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -111,6 +111,10 @@ function uniInject(opts) {
|
|
|
111
111
|
}
|
|
112
112
|
//#endregion
|
|
113
113
|
//#region src/modules/auto-pages/index.ts
|
|
114
|
+
function readPagesJson(pagesJsonPath) {
|
|
115
|
+
if (!fs.default.existsSync(pagesJsonPath)) return null;
|
|
116
|
+
return JSON.parse(fs.default.readFileSync(pagesJsonPath, "utf-8"));
|
|
117
|
+
}
|
|
114
118
|
function toPosixPath(value) {
|
|
115
119
|
return value.replace(/\\/g, "/");
|
|
116
120
|
}
|
|
@@ -130,115 +134,152 @@ function walkFiles(dirPath, filePaths = []) {
|
|
|
130
134
|
}
|
|
131
135
|
return filePaths;
|
|
132
136
|
}
|
|
137
|
+
function analyzeVueContent(content, filePath) {
|
|
138
|
+
if (!content.includes("definePage")) return null;
|
|
139
|
+
const stripRanges = [];
|
|
140
|
+
const { descriptor } = (0, _vue_compiler_sfc.parse)(content);
|
|
141
|
+
const blocks = [descriptor.scriptSetup, descriptor.script].filter((b) => Boolean(b));
|
|
142
|
+
let config = null;
|
|
143
|
+
for (const block of blocks) {
|
|
144
|
+
const src = block.content;
|
|
145
|
+
if (!src.includes("definePage")) continue;
|
|
146
|
+
let ast;
|
|
147
|
+
try {
|
|
148
|
+
ast = (0, _babel_parser.parse)(src, {
|
|
149
|
+
sourceType: "module",
|
|
150
|
+
plugins: ["typescript"]
|
|
151
|
+
});
|
|
152
|
+
} catch {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const base = block.loc.start.offset;
|
|
156
|
+
for (const node of ast.program.body) {
|
|
157
|
+
if (typeof node.start !== "number" || typeof node.end !== "number") continue;
|
|
158
|
+
if (node.type === "ImportDeclaration") {
|
|
159
|
+
if (node.specifiers.some((s) => s.type === "ImportSpecifier" && s.imported.type === "Identifier" && s.imported.name === "definePage")) stripRanges.push({
|
|
160
|
+
start: base + node.start,
|
|
161
|
+
end: base + node.end
|
|
162
|
+
});
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (node.type !== "ExpressionStatement" || node.expression.type !== "CallExpression" || node.expression.callee.type !== "Identifier" || node.expression.callee.name !== "definePage") continue;
|
|
166
|
+
stripRanges.push({
|
|
167
|
+
start: base + node.start,
|
|
168
|
+
end: base + node.end
|
|
169
|
+
});
|
|
170
|
+
const arg = node.expression.arguments[0];
|
|
171
|
+
if (config || !arg || typeof arg.start !== "number" || typeof arg.end !== "number") continue;
|
|
172
|
+
const argSrc = src.slice(arg.start, arg.end);
|
|
173
|
+
try {
|
|
174
|
+
config = new Function(`return (${argSrc})`)();
|
|
175
|
+
} catch (err) {
|
|
176
|
+
console.warn(`解析失败(${filePath}): ${err.message}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!stripRanges.length && !config) return null;
|
|
181
|
+
return {
|
|
182
|
+
config,
|
|
183
|
+
stripRanges
|
|
184
|
+
};
|
|
185
|
+
}
|
|
133
186
|
function collectFileRoutes(srcRoot, routeDirs) {
|
|
134
187
|
const routeSet = /* @__PURE__ */ new Set();
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
188
|
+
const analysisMap = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const dir of routeDirs) for (const filePath of walkFiles(path.default.resolve(srcRoot, dir))) {
|
|
190
|
+
if (!filePath.endsWith(".vue")) continue;
|
|
191
|
+
const route = toPosixPath(path.default.relative(srcRoot, filePath).slice(0, -4));
|
|
192
|
+
routeSet.add(route);
|
|
193
|
+
const result = analyzeVueContent(fs.default.readFileSync(filePath, "utf-8"), filePath);
|
|
194
|
+
if (result) analysisMap.set(route, result);
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
routes: Array.from(routeSet).sort(),
|
|
198
|
+
analysisMap
|
|
199
|
+
};
|
|
143
200
|
}
|
|
144
|
-
function getScanDirs(
|
|
201
|
+
function getScanDirs(mainPackage, subPackages) {
|
|
145
202
|
const dirSet = /* @__PURE__ */ new Set();
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
if (!
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
dirSet.add(normalizedSubRoot);
|
|
156
|
-
});
|
|
203
|
+
const mainDir = normalizeDir(mainPackage);
|
|
204
|
+
if (mainDir) dirSet.add(mainDir);
|
|
205
|
+
for (const subRoot of subPackages) {
|
|
206
|
+
const sub = normalizeDir(subRoot);
|
|
207
|
+
if (!sub) continue;
|
|
208
|
+
dirSet.add(mainDir ? `${sub}/${mainDir}` : sub);
|
|
209
|
+
}
|
|
157
210
|
return Array.from(dirSet);
|
|
158
211
|
}
|
|
159
|
-
function
|
|
160
|
-
if (!fs.default.existsSync(pagesJsonPath)) return null;
|
|
161
|
-
const content = fs.default.readFileSync(pagesJsonPath, "utf-8");
|
|
162
|
-
return JSON.parse(content);
|
|
163
|
-
}
|
|
164
|
-
function mergePages(routes, existingPages) {
|
|
165
|
-
const existingMap = new Map(existingPages.map((p) => [p.path, p]));
|
|
212
|
+
function buildPages(routes, analysisMap, toFullRoute) {
|
|
166
213
|
return routes.map((route) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}) !== JSON.stringify({
|
|
175
|
-
pages: current.pages ?? [],
|
|
176
|
-
subPackages: current.subPackages ?? []
|
|
214
|
+
const cfg = analysisMap.get(toFullRoute(route))?.config;
|
|
215
|
+
if (!cfg) return { path: route };
|
|
216
|
+
return {
|
|
217
|
+
path: route,
|
|
218
|
+
...cfg,
|
|
219
|
+
home: void 0
|
|
220
|
+
};
|
|
177
221
|
});
|
|
178
222
|
}
|
|
179
|
-
function getPagesByFileRoute(routes, pagesJson, subPackageRoots) {
|
|
180
|
-
const originPages = pagesJson.pages ?? [];
|
|
181
|
-
const originSubPackages = pagesJson.subPackages ?? [];
|
|
223
|
+
function getPagesByFileRoute(routes, analysisMap, pagesJson, subPackageRoots) {
|
|
182
224
|
const mainRoutes = [];
|
|
183
225
|
const subRouteMap = /* @__PURE__ */ new Map();
|
|
184
226
|
const normalizedSubRoots = subPackageRoots.map(normalizeDir);
|
|
185
227
|
for (const route of routes) {
|
|
186
|
-
const matchedRoot = normalizedSubRoots.find((root) =>
|
|
187
|
-
return route.startsWith(root + "/");
|
|
188
|
-
});
|
|
228
|
+
const matchedRoot = normalizedSubRoots.find((root) => route.startsWith(root + "/"));
|
|
189
229
|
if (matchedRoot) {
|
|
190
|
-
const subPath = route.slice(matchedRoot.length + 1);
|
|
191
230
|
const list = subRouteMap.get(matchedRoot) ?? [];
|
|
192
|
-
list.push(
|
|
231
|
+
list.push(route.slice(matchedRoot.length + 1));
|
|
193
232
|
subRouteMap.set(matchedRoot, list);
|
|
194
233
|
} else mainRoutes.push(route);
|
|
195
234
|
}
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
const idx = mainRoutes.indexOf(homePage);
|
|
199
|
-
mainRoutes.splice(idx, 1);
|
|
200
|
-
mainRoutes.unshift(homePage);
|
|
201
|
-
}
|
|
202
|
-
const pages = mergePages(mainRoutes, originPages);
|
|
203
|
-
const subPackages = normalizedSubRoots.map((root) => {
|
|
204
|
-
return {
|
|
205
|
-
root,
|
|
206
|
-
pages: mergePages(subRouteMap.get(root) ?? [], originSubPackages.find((s) => {
|
|
207
|
-
return normalizeDir(s.root) === root;
|
|
208
|
-
})?.pages ?? [])
|
|
209
|
-
};
|
|
235
|
+
const homeRoute = mainRoutes.find((r) => {
|
|
236
|
+
return analysisMap.get(r)?.config?.home === true;
|
|
210
237
|
});
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
};
|
|
238
|
+
if (homeRoute) {
|
|
239
|
+
mainRoutes.splice(mainRoutes.indexOf(homeRoute), 1);
|
|
240
|
+
mainRoutes.unshift(homeRoute);
|
|
241
|
+
}
|
|
216
242
|
return {
|
|
217
|
-
|
|
218
|
-
|
|
243
|
+
...pagesJson,
|
|
244
|
+
pages: buildPages(mainRoutes, analysisMap, (r) => r),
|
|
245
|
+
subPackages: normalizedSubRoots.map((root) => ({
|
|
246
|
+
root,
|
|
247
|
+
pages: buildPages(subRouteMap.get(root) ?? [], analysisMap, (r) => `${root}/${r}`)
|
|
248
|
+
}))
|
|
219
249
|
};
|
|
220
250
|
}
|
|
221
|
-
function resolveDtsFilePath(srcRoot, dts) {
|
|
222
|
-
return path.default.isAbsolute(dts) ? dts : path.default.resolve(srcRoot, dts);
|
|
223
|
-
}
|
|
224
251
|
function buildRouteDts(routes) {
|
|
225
|
-
return
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
252
|
+
return `import type { DefinePageConfig } from "vite-plugin-uni-inject";
|
|
253
|
+
|
|
254
|
+
// Auto-generated by vite-plugin-uni-inject. Do not edit.
|
|
255
|
+
|
|
256
|
+
/** 提取路径 */
|
|
257
|
+
export type ExtractPath<T extends string, Prefix extends string> = T extends \`\${Prefix}\${infer P}\` ? P : never;
|
|
258
|
+
|
|
259
|
+
/** 路由路径 */
|
|
260
|
+
export type RoutePath = ${routes.map((r) => `"/${r}"`).join(` |\n`)};
|
|
261
|
+
|
|
262
|
+
declare global {
|
|
263
|
+
/**
|
|
264
|
+
* Vue \`<script setup>\` 宏定义 uniapp 页面级配置。
|
|
265
|
+
*/
|
|
266
|
+
function definePage(config: DefinePageConfig): void;
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
}
|
|
270
|
+
function writeIfTextChanged(targetPath, nextContent) {
|
|
271
|
+
const normalize = (s) => {
|
|
272
|
+
return s.replace(/\s+/g, "").replace(/,(?=[)\]}>])/g, "").replace(/([=(<,|&?:])\|/g, "$1");
|
|
273
|
+
};
|
|
274
|
+
if (fs.default.existsSync(targetPath) && normalize(fs.default.readFileSync(targetPath, "utf-8")) === normalize(nextContent)) return;
|
|
275
|
+
fs.default.mkdirSync(path.default.dirname(targetPath), { recursive: true });
|
|
276
|
+
fs.default.writeFileSync(targetPath, nextContent);
|
|
236
277
|
}
|
|
237
278
|
/**
|
|
238
279
|
* 自动补全 pages.json 插件
|
|
239
280
|
*/
|
|
240
281
|
function uniAutoPages(opts) {
|
|
241
|
-
const {
|
|
282
|
+
const { mainPackage = "pages", subPackages = [], dts = "uni-pages.d.ts" } = opts || {};
|
|
242
283
|
return {
|
|
243
284
|
name: "vite-plugin-uni-auto-pages",
|
|
244
285
|
enforce: "pre",
|
|
@@ -247,16 +288,31 @@ function uniAutoPages(opts) {
|
|
|
247
288
|
const pagesJsonPath = path.default.join(srcRoot, "pages.json");
|
|
248
289
|
const pagesJson = readPagesJson(pagesJsonPath);
|
|
249
290
|
if (!pagesJson) return;
|
|
250
|
-
const routes = collectFileRoutes(srcRoot, getScanDirs(
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
291
|
+
const { routes, analysisMap } = collectFileRoutes(srcRoot, getScanDirs(mainPackage, subPackages));
|
|
292
|
+
const merged = getPagesByFileRoute(routes, analysisMap, pagesJson, subPackages);
|
|
293
|
+
writeIfTextChanged(pagesJsonPath, JSON.stringify(merged, null, 2) + "\n");
|
|
294
|
+
if (dts) {
|
|
295
|
+
const newDts = buildRouteDts(routes);
|
|
296
|
+
writeIfTextChanged(path.default.join(srcRoot, dts), newDts);
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
transform(code, id) {
|
|
300
|
+
const pure = id.split("?")[0];
|
|
301
|
+
if (!pure.endsWith(".vue")) return;
|
|
302
|
+
const analysis = analyzeVueContent(code, pure);
|
|
303
|
+
if (!analysis?.stripRanges.length) return;
|
|
304
|
+
let newCode = code;
|
|
305
|
+
const ranges = [...analysis.stripRanges].sort((a, b) => b.start - a.start);
|
|
306
|
+
for (const { start, end } of ranges) {
|
|
307
|
+
let removeEnd = end;
|
|
308
|
+
if (newCode.startsWith("\r\n", removeEnd)) removeEnd += 2;
|
|
309
|
+
else if (newCode[removeEnd] === "\n") removeEnd += 1;
|
|
310
|
+
newCode = newCode.slice(0, start) + newCode.slice(removeEnd);
|
|
259
311
|
}
|
|
312
|
+
return {
|
|
313
|
+
code: newCode,
|
|
314
|
+
map: null
|
|
315
|
+
};
|
|
260
316
|
}
|
|
261
317
|
};
|
|
262
318
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -7,20 +7,49 @@ interface InjectPluginOptions {
|
|
|
7
7
|
*/
|
|
8
8
|
path?: string;
|
|
9
9
|
}
|
|
10
|
+
/** 页面 style 配置 */
|
|
11
|
+
interface UniPageStyle {
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
/** 窗口背景色 */
|
|
14
|
+
backgroundColor?: string;
|
|
15
|
+
/** 下拉加载样式 */
|
|
16
|
+
backgroundTextStyle?: "dark" | "light";
|
|
17
|
+
/** 是否禁止页面滚动 */
|
|
18
|
+
disableScroll?: boolean;
|
|
19
|
+
/** 是否开启下拉刷新 */
|
|
20
|
+
enablePullDownRefresh?: boolean;
|
|
21
|
+
/** 导航栏背景颜色 */
|
|
22
|
+
navigationBarBackgroundColor?: string;
|
|
23
|
+
/** 导航栏标题颜色 */
|
|
24
|
+
navigationBarTextStyle?: "black" | "white";
|
|
25
|
+
/** 导航栏标题文字 */
|
|
26
|
+
navigationBarTitleText?: string;
|
|
27
|
+
/** 触底距离 */
|
|
28
|
+
onReachBottomDistance?: number;
|
|
29
|
+
/** 自定义组件配置 */
|
|
30
|
+
usingComponents?: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
/** definePage 宏接收的页面配置 */
|
|
33
|
+
interface DefinePageConfig {
|
|
34
|
+
/** 标记为首页,仅主包页面生效。 */
|
|
35
|
+
home?: boolean;
|
|
36
|
+
/** 页面样式 */
|
|
37
|
+
style?: UniPageStyle;
|
|
38
|
+
}
|
|
10
39
|
/** 自动补全 pages 插件配置 */
|
|
11
40
|
interface AutoPagesPluginOptions {
|
|
12
|
-
/**
|
|
13
|
-
* 扫描目录
|
|
14
|
-
* @default 'pages'
|
|
15
|
-
*/
|
|
16
|
-
dir?: string;
|
|
17
41
|
/**
|
|
18
42
|
* 输出类型
|
|
19
43
|
* @default './uni-pages.d.ts'
|
|
20
44
|
*/
|
|
21
45
|
dts?: string;
|
|
22
46
|
/**
|
|
23
|
-
*
|
|
47
|
+
* 主包目录
|
|
48
|
+
* @default 'pages'
|
|
49
|
+
*/
|
|
50
|
+
mainPackage?: string;
|
|
51
|
+
/**
|
|
52
|
+
* 分包列表
|
|
24
53
|
* @default []
|
|
25
54
|
*/
|
|
26
55
|
subPackages?: string[];
|
|
@@ -51,6 +80,10 @@ declare function uniAutoPages(opts?: AutoPagesPluginOptions): {
|
|
|
51
80
|
configResolved(config: {
|
|
52
81
|
root: string;
|
|
53
82
|
}): void;
|
|
83
|
+
transform(code: string, id: string): {
|
|
84
|
+
code: string;
|
|
85
|
+
map: null;
|
|
86
|
+
} | undefined;
|
|
54
87
|
};
|
|
55
88
|
//#endregion
|
|
56
|
-
export { uniAutoPages, uniInject };
|
|
89
|
+
export { type DefinePageConfig, uniAutoPages, uniInject };
|
package/dist/index.d.mts
CHANGED
|
@@ -7,20 +7,49 @@ interface InjectPluginOptions {
|
|
|
7
7
|
*/
|
|
8
8
|
path?: string;
|
|
9
9
|
}
|
|
10
|
+
/** 页面 style 配置 */
|
|
11
|
+
interface UniPageStyle {
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
/** 窗口背景色 */
|
|
14
|
+
backgroundColor?: string;
|
|
15
|
+
/** 下拉加载样式 */
|
|
16
|
+
backgroundTextStyle?: "dark" | "light";
|
|
17
|
+
/** 是否禁止页面滚动 */
|
|
18
|
+
disableScroll?: boolean;
|
|
19
|
+
/** 是否开启下拉刷新 */
|
|
20
|
+
enablePullDownRefresh?: boolean;
|
|
21
|
+
/** 导航栏背景颜色 */
|
|
22
|
+
navigationBarBackgroundColor?: string;
|
|
23
|
+
/** 导航栏标题颜色 */
|
|
24
|
+
navigationBarTextStyle?: "black" | "white";
|
|
25
|
+
/** 导航栏标题文字 */
|
|
26
|
+
navigationBarTitleText?: string;
|
|
27
|
+
/** 触底距离 */
|
|
28
|
+
onReachBottomDistance?: number;
|
|
29
|
+
/** 自定义组件配置 */
|
|
30
|
+
usingComponents?: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
/** definePage 宏接收的页面配置 */
|
|
33
|
+
interface DefinePageConfig {
|
|
34
|
+
/** 标记为首页,仅主包页面生效。 */
|
|
35
|
+
home?: boolean;
|
|
36
|
+
/** 页面样式 */
|
|
37
|
+
style?: UniPageStyle;
|
|
38
|
+
}
|
|
10
39
|
/** 自动补全 pages 插件配置 */
|
|
11
40
|
interface AutoPagesPluginOptions {
|
|
12
|
-
/**
|
|
13
|
-
* 扫描目录
|
|
14
|
-
* @default 'pages'
|
|
15
|
-
*/
|
|
16
|
-
dir?: string;
|
|
17
41
|
/**
|
|
18
42
|
* 输出类型
|
|
19
43
|
* @default './uni-pages.d.ts'
|
|
20
44
|
*/
|
|
21
45
|
dts?: string;
|
|
22
46
|
/**
|
|
23
|
-
*
|
|
47
|
+
* 主包目录
|
|
48
|
+
* @default 'pages'
|
|
49
|
+
*/
|
|
50
|
+
mainPackage?: string;
|
|
51
|
+
/**
|
|
52
|
+
* 分包列表
|
|
24
53
|
* @default []
|
|
25
54
|
*/
|
|
26
55
|
subPackages?: string[];
|
|
@@ -51,6 +80,10 @@ declare function uniAutoPages(opts?: AutoPagesPluginOptions): {
|
|
|
51
80
|
configResolved(config: {
|
|
52
81
|
root: string;
|
|
53
82
|
}): void;
|
|
83
|
+
transform(code: string, id: string): {
|
|
84
|
+
code: string;
|
|
85
|
+
map: null;
|
|
86
|
+
} | undefined;
|
|
54
87
|
};
|
|
55
88
|
//#endregion
|
|
56
|
-
export { uniAutoPages, uniInject };
|
|
89
|
+
export { type DefinePageConfig, uniAutoPages, uniInject };
|
package/dist/index.mjs
CHANGED
|
@@ -86,6 +86,10 @@ function uniInject(opts) {
|
|
|
86
86
|
}
|
|
87
87
|
//#endregion
|
|
88
88
|
//#region src/modules/auto-pages/index.ts
|
|
89
|
+
function readPagesJson(pagesJsonPath) {
|
|
90
|
+
if (!fs.existsSync(pagesJsonPath)) return null;
|
|
91
|
+
return JSON.parse(fs.readFileSync(pagesJsonPath, "utf-8"));
|
|
92
|
+
}
|
|
89
93
|
function toPosixPath(value) {
|
|
90
94
|
return value.replace(/\\/g, "/");
|
|
91
95
|
}
|
|
@@ -105,115 +109,152 @@ function walkFiles(dirPath, filePaths = []) {
|
|
|
105
109
|
}
|
|
106
110
|
return filePaths;
|
|
107
111
|
}
|
|
112
|
+
function analyzeVueContent(content, filePath) {
|
|
113
|
+
if (!content.includes("definePage")) return null;
|
|
114
|
+
const stripRanges = [];
|
|
115
|
+
const { descriptor } = parse$1(content);
|
|
116
|
+
const blocks = [descriptor.scriptSetup, descriptor.script].filter((b) => Boolean(b));
|
|
117
|
+
let config = null;
|
|
118
|
+
for (const block of blocks) {
|
|
119
|
+
const src = block.content;
|
|
120
|
+
if (!src.includes("definePage")) continue;
|
|
121
|
+
let ast;
|
|
122
|
+
try {
|
|
123
|
+
ast = parse(src, {
|
|
124
|
+
sourceType: "module",
|
|
125
|
+
plugins: ["typescript"]
|
|
126
|
+
});
|
|
127
|
+
} catch {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const base = block.loc.start.offset;
|
|
131
|
+
for (const node of ast.program.body) {
|
|
132
|
+
if (typeof node.start !== "number" || typeof node.end !== "number") continue;
|
|
133
|
+
if (node.type === "ImportDeclaration") {
|
|
134
|
+
if (node.specifiers.some((s) => s.type === "ImportSpecifier" && s.imported.type === "Identifier" && s.imported.name === "definePage")) stripRanges.push({
|
|
135
|
+
start: base + node.start,
|
|
136
|
+
end: base + node.end
|
|
137
|
+
});
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (node.type !== "ExpressionStatement" || node.expression.type !== "CallExpression" || node.expression.callee.type !== "Identifier" || node.expression.callee.name !== "definePage") continue;
|
|
141
|
+
stripRanges.push({
|
|
142
|
+
start: base + node.start,
|
|
143
|
+
end: base + node.end
|
|
144
|
+
});
|
|
145
|
+
const arg = node.expression.arguments[0];
|
|
146
|
+
if (config || !arg || typeof arg.start !== "number" || typeof arg.end !== "number") continue;
|
|
147
|
+
const argSrc = src.slice(arg.start, arg.end);
|
|
148
|
+
try {
|
|
149
|
+
config = new Function(`return (${argSrc})`)();
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.warn(`解析失败(${filePath}): ${err.message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!stripRanges.length && !config) return null;
|
|
156
|
+
return {
|
|
157
|
+
config,
|
|
158
|
+
stripRanges
|
|
159
|
+
};
|
|
160
|
+
}
|
|
108
161
|
function collectFileRoutes(srcRoot, routeDirs) {
|
|
109
162
|
const routeSet = /* @__PURE__ */ new Set();
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
163
|
+
const analysisMap = /* @__PURE__ */ new Map();
|
|
164
|
+
for (const dir of routeDirs) for (const filePath of walkFiles(path.resolve(srcRoot, dir))) {
|
|
165
|
+
if (!filePath.endsWith(".vue")) continue;
|
|
166
|
+
const route = toPosixPath(path.relative(srcRoot, filePath).slice(0, -4));
|
|
167
|
+
routeSet.add(route);
|
|
168
|
+
const result = analyzeVueContent(fs.readFileSync(filePath, "utf-8"), filePath);
|
|
169
|
+
if (result) analysisMap.set(route, result);
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
routes: Array.from(routeSet).sort(),
|
|
173
|
+
analysisMap
|
|
174
|
+
};
|
|
118
175
|
}
|
|
119
|
-
function getScanDirs(
|
|
176
|
+
function getScanDirs(mainPackage, subPackages) {
|
|
120
177
|
const dirSet = /* @__PURE__ */ new Set();
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
if (!
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
dirSet.add(normalizedSubRoot);
|
|
131
|
-
});
|
|
178
|
+
const mainDir = normalizeDir(mainPackage);
|
|
179
|
+
if (mainDir) dirSet.add(mainDir);
|
|
180
|
+
for (const subRoot of subPackages) {
|
|
181
|
+
const sub = normalizeDir(subRoot);
|
|
182
|
+
if (!sub) continue;
|
|
183
|
+
dirSet.add(mainDir ? `${sub}/${mainDir}` : sub);
|
|
184
|
+
}
|
|
132
185
|
return Array.from(dirSet);
|
|
133
186
|
}
|
|
134
|
-
function
|
|
135
|
-
if (!fs.existsSync(pagesJsonPath)) return null;
|
|
136
|
-
const content = fs.readFileSync(pagesJsonPath, "utf-8");
|
|
137
|
-
return JSON.parse(content);
|
|
138
|
-
}
|
|
139
|
-
function mergePages(routes, existingPages) {
|
|
140
|
-
const existingMap = new Map(existingPages.map((p) => [p.path, p]));
|
|
187
|
+
function buildPages(routes, analysisMap, toFullRoute) {
|
|
141
188
|
return routes.map((route) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}) !== JSON.stringify({
|
|
150
|
-
pages: current.pages ?? [],
|
|
151
|
-
subPackages: current.subPackages ?? []
|
|
189
|
+
const cfg = analysisMap.get(toFullRoute(route))?.config;
|
|
190
|
+
if (!cfg) return { path: route };
|
|
191
|
+
return {
|
|
192
|
+
path: route,
|
|
193
|
+
...cfg,
|
|
194
|
+
home: void 0
|
|
195
|
+
};
|
|
152
196
|
});
|
|
153
197
|
}
|
|
154
|
-
function getPagesByFileRoute(routes, pagesJson, subPackageRoots) {
|
|
155
|
-
const originPages = pagesJson.pages ?? [];
|
|
156
|
-
const originSubPackages = pagesJson.subPackages ?? [];
|
|
198
|
+
function getPagesByFileRoute(routes, analysisMap, pagesJson, subPackageRoots) {
|
|
157
199
|
const mainRoutes = [];
|
|
158
200
|
const subRouteMap = /* @__PURE__ */ new Map();
|
|
159
201
|
const normalizedSubRoots = subPackageRoots.map(normalizeDir);
|
|
160
202
|
for (const route of routes) {
|
|
161
|
-
const matchedRoot = normalizedSubRoots.find((root) =>
|
|
162
|
-
return route.startsWith(root + "/");
|
|
163
|
-
});
|
|
203
|
+
const matchedRoot = normalizedSubRoots.find((root) => route.startsWith(root + "/"));
|
|
164
204
|
if (matchedRoot) {
|
|
165
|
-
const subPath = route.slice(matchedRoot.length + 1);
|
|
166
205
|
const list = subRouteMap.get(matchedRoot) ?? [];
|
|
167
|
-
list.push(
|
|
206
|
+
list.push(route.slice(matchedRoot.length + 1));
|
|
168
207
|
subRouteMap.set(matchedRoot, list);
|
|
169
208
|
} else mainRoutes.push(route);
|
|
170
209
|
}
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
const idx = mainRoutes.indexOf(homePage);
|
|
174
|
-
mainRoutes.splice(idx, 1);
|
|
175
|
-
mainRoutes.unshift(homePage);
|
|
176
|
-
}
|
|
177
|
-
const pages = mergePages(mainRoutes, originPages);
|
|
178
|
-
const subPackages = normalizedSubRoots.map((root) => {
|
|
179
|
-
return {
|
|
180
|
-
root,
|
|
181
|
-
pages: mergePages(subRouteMap.get(root) ?? [], originSubPackages.find((s) => {
|
|
182
|
-
return normalizeDir(s.root) === root;
|
|
183
|
-
})?.pages ?? [])
|
|
184
|
-
};
|
|
210
|
+
const homeRoute = mainRoutes.find((r) => {
|
|
211
|
+
return analysisMap.get(r)?.config?.home === true;
|
|
185
212
|
});
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
};
|
|
213
|
+
if (homeRoute) {
|
|
214
|
+
mainRoutes.splice(mainRoutes.indexOf(homeRoute), 1);
|
|
215
|
+
mainRoutes.unshift(homeRoute);
|
|
216
|
+
}
|
|
191
217
|
return {
|
|
192
|
-
|
|
193
|
-
|
|
218
|
+
...pagesJson,
|
|
219
|
+
pages: buildPages(mainRoutes, analysisMap, (r) => r),
|
|
220
|
+
subPackages: normalizedSubRoots.map((root) => ({
|
|
221
|
+
root,
|
|
222
|
+
pages: buildPages(subRouteMap.get(root) ?? [], analysisMap, (r) => `${root}/${r}`)
|
|
223
|
+
}))
|
|
194
224
|
};
|
|
195
225
|
}
|
|
196
|
-
function resolveDtsFilePath(srcRoot, dts) {
|
|
197
|
-
return path.isAbsolute(dts) ? dts : path.resolve(srcRoot, dts);
|
|
198
|
-
}
|
|
199
226
|
function buildRouteDts(routes) {
|
|
200
|
-
return
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
227
|
+
return `import type { DefinePageConfig } from "vite-plugin-uni-inject";
|
|
228
|
+
|
|
229
|
+
// Auto-generated by vite-plugin-uni-inject. Do not edit.
|
|
230
|
+
|
|
231
|
+
/** 提取路径 */
|
|
232
|
+
export type ExtractPath<T extends string, Prefix extends string> = T extends \`\${Prefix}\${infer P}\` ? P : never;
|
|
233
|
+
|
|
234
|
+
/** 路由路径 */
|
|
235
|
+
export type RoutePath = ${routes.map((r) => `"/${r}"`).join(` |\n`)};
|
|
236
|
+
|
|
237
|
+
declare global {
|
|
238
|
+
/**
|
|
239
|
+
* Vue \`<script setup>\` 宏定义 uniapp 页面级配置。
|
|
240
|
+
*/
|
|
241
|
+
function definePage(config: DefinePageConfig): void;
|
|
242
|
+
}
|
|
243
|
+
`;
|
|
244
|
+
}
|
|
245
|
+
function writeIfTextChanged(targetPath, nextContent) {
|
|
246
|
+
const normalize = (s) => {
|
|
247
|
+
return s.replace(/\s+/g, "").replace(/,(?=[)\]}>])/g, "").replace(/([=(<,|&?:])\|/g, "$1");
|
|
248
|
+
};
|
|
249
|
+
if (fs.existsSync(targetPath) && normalize(fs.readFileSync(targetPath, "utf-8")) === normalize(nextContent)) return;
|
|
250
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
251
|
+
fs.writeFileSync(targetPath, nextContent);
|
|
211
252
|
}
|
|
212
253
|
/**
|
|
213
254
|
* 自动补全 pages.json 插件
|
|
214
255
|
*/
|
|
215
256
|
function uniAutoPages(opts) {
|
|
216
|
-
const {
|
|
257
|
+
const { mainPackage = "pages", subPackages = [], dts = "uni-pages.d.ts" } = opts || {};
|
|
217
258
|
return {
|
|
218
259
|
name: "vite-plugin-uni-auto-pages",
|
|
219
260
|
enforce: "pre",
|
|
@@ -222,16 +263,31 @@ function uniAutoPages(opts) {
|
|
|
222
263
|
const pagesJsonPath = path.join(srcRoot, "pages.json");
|
|
223
264
|
const pagesJson = readPagesJson(pagesJsonPath);
|
|
224
265
|
if (!pagesJson) return;
|
|
225
|
-
const routes = collectFileRoutes(srcRoot, getScanDirs(
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
266
|
+
const { routes, analysisMap } = collectFileRoutes(srcRoot, getScanDirs(mainPackage, subPackages));
|
|
267
|
+
const merged = getPagesByFileRoute(routes, analysisMap, pagesJson, subPackages);
|
|
268
|
+
writeIfTextChanged(pagesJsonPath, JSON.stringify(merged, null, 2) + "\n");
|
|
269
|
+
if (dts) {
|
|
270
|
+
const newDts = buildRouteDts(routes);
|
|
271
|
+
writeIfTextChanged(path.join(srcRoot, dts), newDts);
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
transform(code, id) {
|
|
275
|
+
const pure = id.split("?")[0];
|
|
276
|
+
if (!pure.endsWith(".vue")) return;
|
|
277
|
+
const analysis = analyzeVueContent(code, pure);
|
|
278
|
+
if (!analysis?.stripRanges.length) return;
|
|
279
|
+
let newCode = code;
|
|
280
|
+
const ranges = [...analysis.stripRanges].sort((a, b) => b.start - a.start);
|
|
281
|
+
for (const { start, end } of ranges) {
|
|
282
|
+
let removeEnd = end;
|
|
283
|
+
if (newCode.startsWith("\r\n", removeEnd)) removeEnd += 2;
|
|
284
|
+
else if (newCode[removeEnd] === "\n") removeEnd += 1;
|
|
285
|
+
newCode = newCode.slice(0, start) + newCode.slice(removeEnd);
|
|
234
286
|
}
|
|
287
|
+
return {
|
|
288
|
+
code: newCode,
|
|
289
|
+
map: null
|
|
290
|
+
};
|
|
235
291
|
}
|
|
236
292
|
};
|
|
237
293
|
}
|