vite-plugin-uni-inject 0.3.1 → 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 +165 -99
- package/dist/index.d.cts +39 -6
- package/dist/index.d.mts +39 -6
- package/dist/index.mjs +163 -96
- package/package.json +3 -4
package/dist/index.cjs
CHANGED
|
@@ -24,12 +24,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
let _babel_parser = require("@babel/parser");
|
|
25
25
|
let _vue_compiler_sfc = require("@vue/compiler-sfc");
|
|
26
26
|
let fs = require("fs");
|
|
27
|
-
fs = __toESM(fs);
|
|
27
|
+
fs = __toESM(fs, 1);
|
|
28
28
|
let path = require("path");
|
|
29
|
-
path = __toESM(path);
|
|
30
|
-
|
|
31
|
-
magic_string = __toESM(magic_string);
|
|
32
|
-
//#region src/inject.ts
|
|
29
|
+
path = __toESM(path, 1);
|
|
30
|
+
//#region src/modules/inject/index.ts
|
|
33
31
|
/**
|
|
34
32
|
* 注入代码插件
|
|
35
33
|
*/
|
|
@@ -84,25 +82,39 @@ function uniInject(opts) {
|
|
|
84
82
|
const start = newDescriptor.scriptSetup.loc.start.offset;
|
|
85
83
|
const end = newDescriptor.scriptSetup.loc.end.offset;
|
|
86
84
|
const scriptCode = newDescriptor.scriptSetup.content;
|
|
87
|
-
const s = new magic_string.default(scriptCode);
|
|
88
85
|
const ast = (0, _babel_parser.parse)(scriptCode, {
|
|
89
86
|
sourceType: "module",
|
|
90
87
|
plugins: ["typescript"]
|
|
91
88
|
});
|
|
92
89
|
const imports = [];
|
|
93
|
-
for (const node of ast.program.body) if (node.type === "ImportDeclaration") {
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
for (const node of ast.program.body) if (node.type === "ImportDeclaration" && typeof node.start === "number" && typeof node.end === "number") imports.push({
|
|
91
|
+
code: scriptCode.slice(node.start, node.end),
|
|
92
|
+
start: node.start,
|
|
93
|
+
end: node.end
|
|
94
|
+
});
|
|
95
|
+
let nextScriptCode = scriptCode;
|
|
96
|
+
if (imports.length) {
|
|
97
|
+
const ranges = [...imports].sort((a, b) => b.start - a.start);
|
|
98
|
+
for (const { start: rangeStart, end: rangeEnd } of ranges) {
|
|
99
|
+
let removeEnd = rangeEnd;
|
|
100
|
+
if (nextScriptCode.startsWith("\r\n", removeEnd)) removeEnd += 2;
|
|
101
|
+
else if (nextScriptCode[removeEnd] === "\n") removeEnd += 1;
|
|
102
|
+
nextScriptCode = nextScriptCode.slice(0, rangeStart) + nextScriptCode.slice(removeEnd);
|
|
103
|
+
}
|
|
104
|
+
nextScriptCode = `\n${imports.map((item) => item.code).join("\n")}${nextScriptCode}`;
|
|
96
105
|
}
|
|
97
|
-
|
|
98
|
-
newCode = newCode.slice(0, start) + s.toString() + newCode.slice(end);
|
|
106
|
+
newCode = newCode.slice(0, start) + nextScriptCode + newCode.slice(end);
|
|
99
107
|
}
|
|
100
108
|
return { code: newCode };
|
|
101
109
|
}
|
|
102
110
|
};
|
|
103
111
|
}
|
|
104
112
|
//#endregion
|
|
105
|
-
//#region src/auto-pages.ts
|
|
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
|
+
}
|
|
106
118
|
function toPosixPath(value) {
|
|
107
119
|
return value.replace(/\\/g, "/");
|
|
108
120
|
}
|
|
@@ -122,115 +134,152 @@ function walkFiles(dirPath, filePaths = []) {
|
|
|
122
134
|
}
|
|
123
135
|
return filePaths;
|
|
124
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
|
+
}
|
|
125
186
|
function collectFileRoutes(srcRoot, routeDirs) {
|
|
126
187
|
const routeSet = /* @__PURE__ */ new Set();
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
};
|
|
135
200
|
}
|
|
136
|
-
function getScanDirs(
|
|
201
|
+
function getScanDirs(mainPackage, subPackages) {
|
|
137
202
|
const dirSet = /* @__PURE__ */ new Set();
|
|
138
|
-
const
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
if (!
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
dirSet.add(normalizedSubRoot);
|
|
148
|
-
});
|
|
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
|
+
}
|
|
149
210
|
return Array.from(dirSet);
|
|
150
211
|
}
|
|
151
|
-
function
|
|
152
|
-
if (!fs.default.existsSync(pagesJsonPath)) return null;
|
|
153
|
-
const content = fs.default.readFileSync(pagesJsonPath, "utf-8");
|
|
154
|
-
return JSON.parse(content);
|
|
155
|
-
}
|
|
156
|
-
function mergePages(routes, existingPages) {
|
|
157
|
-
const existingMap = new Map(existingPages.map((p) => [p.path, p]));
|
|
212
|
+
function buildPages(routes, analysisMap, toFullRoute) {
|
|
158
213
|
return routes.map((route) => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}) !== JSON.stringify({
|
|
167
|
-
pages: current.pages ?? [],
|
|
168
|
-
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
|
+
};
|
|
169
221
|
});
|
|
170
222
|
}
|
|
171
|
-
function getPagesByFileRoute(routes, pagesJson, subPackageRoots) {
|
|
172
|
-
const originPages = pagesJson.pages ?? [];
|
|
173
|
-
const originSubPackages = pagesJson.subPackages ?? [];
|
|
223
|
+
function getPagesByFileRoute(routes, analysisMap, pagesJson, subPackageRoots) {
|
|
174
224
|
const mainRoutes = [];
|
|
175
225
|
const subRouteMap = /* @__PURE__ */ new Map();
|
|
176
226
|
const normalizedSubRoots = subPackageRoots.map(normalizeDir);
|
|
177
227
|
for (const route of routes) {
|
|
178
|
-
const matchedRoot = normalizedSubRoots.find((root) =>
|
|
179
|
-
return route.startsWith(root + "/");
|
|
180
|
-
});
|
|
228
|
+
const matchedRoot = normalizedSubRoots.find((root) => route.startsWith(root + "/"));
|
|
181
229
|
if (matchedRoot) {
|
|
182
|
-
const subPath = route.slice(matchedRoot.length + 1);
|
|
183
230
|
const list = subRouteMap.get(matchedRoot) ?? [];
|
|
184
|
-
list.push(
|
|
231
|
+
list.push(route.slice(matchedRoot.length + 1));
|
|
185
232
|
subRouteMap.set(matchedRoot, list);
|
|
186
233
|
} else mainRoutes.push(route);
|
|
187
234
|
}
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
const idx = mainRoutes.indexOf(homePage);
|
|
191
|
-
mainRoutes.splice(idx, 1);
|
|
192
|
-
mainRoutes.unshift(homePage);
|
|
193
|
-
}
|
|
194
|
-
const pages = mergePages(mainRoutes, originPages);
|
|
195
|
-
const subPackages = normalizedSubRoots.map((root) => {
|
|
196
|
-
return {
|
|
197
|
-
root,
|
|
198
|
-
pages: mergePages(subRouteMap.get(root) ?? [], originSubPackages.find((s) => {
|
|
199
|
-
return normalizeDir(s.root) === root;
|
|
200
|
-
})?.pages ?? [])
|
|
201
|
-
};
|
|
235
|
+
const homeRoute = mainRoutes.find((r) => {
|
|
236
|
+
return analysisMap.get(r)?.config?.home === true;
|
|
202
237
|
});
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
};
|
|
238
|
+
if (homeRoute) {
|
|
239
|
+
mainRoutes.splice(mainRoutes.indexOf(homeRoute), 1);
|
|
240
|
+
mainRoutes.unshift(homeRoute);
|
|
241
|
+
}
|
|
208
242
|
return {
|
|
209
|
-
|
|
210
|
-
|
|
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
|
+
}))
|
|
211
249
|
};
|
|
212
250
|
}
|
|
213
|
-
function resolveDtsFilePath(srcRoot, dts) {
|
|
214
|
-
return path.default.isAbsolute(dts) ? dts : path.default.resolve(srcRoot, dts);
|
|
215
|
-
}
|
|
216
251
|
function buildRouteDts(routes) {
|
|
217
|
-
return
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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);
|
|
228
277
|
}
|
|
229
278
|
/**
|
|
230
279
|
* 自动补全 pages.json 插件
|
|
231
280
|
*/
|
|
232
281
|
function uniAutoPages(opts) {
|
|
233
|
-
const {
|
|
282
|
+
const { mainPackage = "pages", subPackages = [], dts = "uni-pages.d.ts" } = opts || {};
|
|
234
283
|
return {
|
|
235
284
|
name: "vite-plugin-uni-auto-pages",
|
|
236
285
|
enforce: "pre",
|
|
@@ -239,14 +288,31 @@ function uniAutoPages(opts) {
|
|
|
239
288
|
const pagesJsonPath = path.default.join(srcRoot, "pages.json");
|
|
240
289
|
const pagesJson = readPagesJson(pagesJsonPath);
|
|
241
290
|
if (!pagesJson) return;
|
|
242
|
-
const routes = collectFileRoutes(srcRoot, getScanDirs(
|
|
243
|
-
const
|
|
244
|
-
|
|
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");
|
|
245
294
|
if (dts) {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
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);
|
|
249
311
|
}
|
|
312
|
+
return {
|
|
313
|
+
code: newCode,
|
|
314
|
+
map: null
|
|
315
|
+
};
|
|
250
316
|
}
|
|
251
317
|
};
|
|
252
318
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -7,6 +7,35 @@ 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
41
|
/**
|
|
@@ -15,18 +44,18 @@ interface AutoPagesPluginOptions {
|
|
|
15
44
|
*/
|
|
16
45
|
dts?: string;
|
|
17
46
|
/**
|
|
18
|
-
*
|
|
47
|
+
* 主包目录
|
|
19
48
|
* @default 'pages'
|
|
20
49
|
*/
|
|
21
|
-
|
|
50
|
+
mainPackage?: string;
|
|
22
51
|
/**
|
|
23
|
-
*
|
|
52
|
+
* 分包列表
|
|
24
53
|
* @default []
|
|
25
54
|
*/
|
|
26
55
|
subPackages?: string[];
|
|
27
56
|
}
|
|
28
57
|
//#endregion
|
|
29
|
-
//#region src/inject.d.ts
|
|
58
|
+
//#region src/modules/inject/index.d.ts
|
|
30
59
|
/**
|
|
31
60
|
* 注入代码插件
|
|
32
61
|
*/
|
|
@@ -41,7 +70,7 @@ declare function uniInject(opts?: InjectPluginOptions): {
|
|
|
41
70
|
} | undefined;
|
|
42
71
|
};
|
|
43
72
|
//#endregion
|
|
44
|
-
//#region src/auto-pages.d.ts
|
|
73
|
+
//#region src/modules/auto-pages/index.d.ts
|
|
45
74
|
/**
|
|
46
75
|
* 自动补全 pages.json 插件
|
|
47
76
|
*/
|
|
@@ -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,6 +7,35 @@ 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
41
|
/**
|
|
@@ -15,18 +44,18 @@ interface AutoPagesPluginOptions {
|
|
|
15
44
|
*/
|
|
16
45
|
dts?: string;
|
|
17
46
|
/**
|
|
18
|
-
*
|
|
47
|
+
* 主包目录
|
|
19
48
|
* @default 'pages'
|
|
20
49
|
*/
|
|
21
|
-
|
|
50
|
+
mainPackage?: string;
|
|
22
51
|
/**
|
|
23
|
-
*
|
|
52
|
+
* 分包列表
|
|
24
53
|
* @default []
|
|
25
54
|
*/
|
|
26
55
|
subPackages?: string[];
|
|
27
56
|
}
|
|
28
57
|
//#endregion
|
|
29
|
-
//#region src/inject.d.ts
|
|
58
|
+
//#region src/modules/inject/index.d.ts
|
|
30
59
|
/**
|
|
31
60
|
* 注入代码插件
|
|
32
61
|
*/
|
|
@@ -41,7 +70,7 @@ declare function uniInject(opts?: InjectPluginOptions): {
|
|
|
41
70
|
} | undefined;
|
|
42
71
|
};
|
|
43
72
|
//#endregion
|
|
44
|
-
//#region src/auto-pages.d.ts
|
|
73
|
+
//#region src/modules/auto-pages/index.d.ts
|
|
45
74
|
/**
|
|
46
75
|
* 自动补全 pages.json 插件
|
|
47
76
|
*/
|
|
@@ -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
|
@@ -2,8 +2,7 @@ import { parse } from "@babel/parser";
|
|
|
2
2
|
import { parse as parse$1 } from "@vue/compiler-sfc";
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import path from "path";
|
|
5
|
-
|
|
6
|
-
//#region src/inject.ts
|
|
5
|
+
//#region src/modules/inject/index.ts
|
|
7
6
|
/**
|
|
8
7
|
* 注入代码插件
|
|
9
8
|
*/
|
|
@@ -58,25 +57,39 @@ function uniInject(opts) {
|
|
|
58
57
|
const start = newDescriptor.scriptSetup.loc.start.offset;
|
|
59
58
|
const end = newDescriptor.scriptSetup.loc.end.offset;
|
|
60
59
|
const scriptCode = newDescriptor.scriptSetup.content;
|
|
61
|
-
const s = new MagicString(scriptCode);
|
|
62
60
|
const ast = parse(scriptCode, {
|
|
63
61
|
sourceType: "module",
|
|
64
62
|
plugins: ["typescript"]
|
|
65
63
|
});
|
|
66
64
|
const imports = [];
|
|
67
|
-
for (const node of ast.program.body) if (node.type === "ImportDeclaration") {
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
for (const node of ast.program.body) if (node.type === "ImportDeclaration" && typeof node.start === "number" && typeof node.end === "number") imports.push({
|
|
66
|
+
code: scriptCode.slice(node.start, node.end),
|
|
67
|
+
start: node.start,
|
|
68
|
+
end: node.end
|
|
69
|
+
});
|
|
70
|
+
let nextScriptCode = scriptCode;
|
|
71
|
+
if (imports.length) {
|
|
72
|
+
const ranges = [...imports].sort((a, b) => b.start - a.start);
|
|
73
|
+
for (const { start: rangeStart, end: rangeEnd } of ranges) {
|
|
74
|
+
let removeEnd = rangeEnd;
|
|
75
|
+
if (nextScriptCode.startsWith("\r\n", removeEnd)) removeEnd += 2;
|
|
76
|
+
else if (nextScriptCode[removeEnd] === "\n") removeEnd += 1;
|
|
77
|
+
nextScriptCode = nextScriptCode.slice(0, rangeStart) + nextScriptCode.slice(removeEnd);
|
|
78
|
+
}
|
|
79
|
+
nextScriptCode = `\n${imports.map((item) => item.code).join("\n")}${nextScriptCode}`;
|
|
70
80
|
}
|
|
71
|
-
|
|
72
|
-
newCode = newCode.slice(0, start) + s.toString() + newCode.slice(end);
|
|
81
|
+
newCode = newCode.slice(0, start) + nextScriptCode + newCode.slice(end);
|
|
73
82
|
}
|
|
74
83
|
return { code: newCode };
|
|
75
84
|
}
|
|
76
85
|
};
|
|
77
86
|
}
|
|
78
87
|
//#endregion
|
|
79
|
-
//#region src/auto-pages.ts
|
|
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
|
+
}
|
|
80
93
|
function toPosixPath(value) {
|
|
81
94
|
return value.replace(/\\/g, "/");
|
|
82
95
|
}
|
|
@@ -96,115 +109,152 @@ function walkFiles(dirPath, filePaths = []) {
|
|
|
96
109
|
}
|
|
97
110
|
return filePaths;
|
|
98
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
|
+
}
|
|
99
161
|
function collectFileRoutes(srcRoot, routeDirs) {
|
|
100
162
|
const routeSet = /* @__PURE__ */ new Set();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
};
|
|
109
175
|
}
|
|
110
|
-
function getScanDirs(
|
|
176
|
+
function getScanDirs(mainPackage, subPackages) {
|
|
111
177
|
const dirSet = /* @__PURE__ */ new Set();
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
if (!
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
dirSet.add(normalizedSubRoot);
|
|
122
|
-
});
|
|
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
|
+
}
|
|
123
185
|
return Array.from(dirSet);
|
|
124
186
|
}
|
|
125
|
-
function
|
|
126
|
-
if (!fs.existsSync(pagesJsonPath)) return null;
|
|
127
|
-
const content = fs.readFileSync(pagesJsonPath, "utf-8");
|
|
128
|
-
return JSON.parse(content);
|
|
129
|
-
}
|
|
130
|
-
function mergePages(routes, existingPages) {
|
|
131
|
-
const existingMap = new Map(existingPages.map((p) => [p.path, p]));
|
|
187
|
+
function buildPages(routes, analysisMap, toFullRoute) {
|
|
132
188
|
return routes.map((route) => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}) !== JSON.stringify({
|
|
141
|
-
pages: current.pages ?? [],
|
|
142
|
-
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
|
+
};
|
|
143
196
|
});
|
|
144
197
|
}
|
|
145
|
-
function getPagesByFileRoute(routes, pagesJson, subPackageRoots) {
|
|
146
|
-
const originPages = pagesJson.pages ?? [];
|
|
147
|
-
const originSubPackages = pagesJson.subPackages ?? [];
|
|
198
|
+
function getPagesByFileRoute(routes, analysisMap, pagesJson, subPackageRoots) {
|
|
148
199
|
const mainRoutes = [];
|
|
149
200
|
const subRouteMap = /* @__PURE__ */ new Map();
|
|
150
201
|
const normalizedSubRoots = subPackageRoots.map(normalizeDir);
|
|
151
202
|
for (const route of routes) {
|
|
152
|
-
const matchedRoot = normalizedSubRoots.find((root) =>
|
|
153
|
-
return route.startsWith(root + "/");
|
|
154
|
-
});
|
|
203
|
+
const matchedRoot = normalizedSubRoots.find((root) => route.startsWith(root + "/"));
|
|
155
204
|
if (matchedRoot) {
|
|
156
|
-
const subPath = route.slice(matchedRoot.length + 1);
|
|
157
205
|
const list = subRouteMap.get(matchedRoot) ?? [];
|
|
158
|
-
list.push(
|
|
206
|
+
list.push(route.slice(matchedRoot.length + 1));
|
|
159
207
|
subRouteMap.set(matchedRoot, list);
|
|
160
208
|
} else mainRoutes.push(route);
|
|
161
209
|
}
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
const idx = mainRoutes.indexOf(homePage);
|
|
165
|
-
mainRoutes.splice(idx, 1);
|
|
166
|
-
mainRoutes.unshift(homePage);
|
|
167
|
-
}
|
|
168
|
-
const pages = mergePages(mainRoutes, originPages);
|
|
169
|
-
const subPackages = normalizedSubRoots.map((root) => {
|
|
170
|
-
return {
|
|
171
|
-
root,
|
|
172
|
-
pages: mergePages(subRouteMap.get(root) ?? [], originSubPackages.find((s) => {
|
|
173
|
-
return normalizeDir(s.root) === root;
|
|
174
|
-
})?.pages ?? [])
|
|
175
|
-
};
|
|
210
|
+
const homeRoute = mainRoutes.find((r) => {
|
|
211
|
+
return analysisMap.get(r)?.config?.home === true;
|
|
176
212
|
});
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
};
|
|
213
|
+
if (homeRoute) {
|
|
214
|
+
mainRoutes.splice(mainRoutes.indexOf(homeRoute), 1);
|
|
215
|
+
mainRoutes.unshift(homeRoute);
|
|
216
|
+
}
|
|
182
217
|
return {
|
|
183
|
-
|
|
184
|
-
|
|
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
|
+
}))
|
|
185
224
|
};
|
|
186
225
|
}
|
|
187
|
-
function resolveDtsFilePath(srcRoot, dts) {
|
|
188
|
-
return path.isAbsolute(dts) ? dts : path.resolve(srcRoot, dts);
|
|
189
|
-
}
|
|
190
226
|
function buildRouteDts(routes) {
|
|
191
|
-
return
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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);
|
|
202
252
|
}
|
|
203
253
|
/**
|
|
204
254
|
* 自动补全 pages.json 插件
|
|
205
255
|
*/
|
|
206
256
|
function uniAutoPages(opts) {
|
|
207
|
-
const {
|
|
257
|
+
const { mainPackage = "pages", subPackages = [], dts = "uni-pages.d.ts" } = opts || {};
|
|
208
258
|
return {
|
|
209
259
|
name: "vite-plugin-uni-auto-pages",
|
|
210
260
|
enforce: "pre",
|
|
@@ -213,14 +263,31 @@ function uniAutoPages(opts) {
|
|
|
213
263
|
const pagesJsonPath = path.join(srcRoot, "pages.json");
|
|
214
264
|
const pagesJson = readPagesJson(pagesJsonPath);
|
|
215
265
|
if (!pagesJson) return;
|
|
216
|
-
const routes = collectFileRoutes(srcRoot, getScanDirs(
|
|
217
|
-
const
|
|
218
|
-
|
|
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");
|
|
219
269
|
if (dts) {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
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);
|
|
223
286
|
}
|
|
287
|
+
return {
|
|
288
|
+
code: newCode,
|
|
289
|
+
map: null
|
|
290
|
+
};
|
|
224
291
|
}
|
|
225
292
|
};
|
|
226
293
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"author": "Kriac",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.5.0",
|
|
8
8
|
"homepage": "https://github.com/Kriac/vite-plugin-uni-inject",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -20,12 +20,11 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@vue/compiler-sfc": "3.5.27",
|
|
23
|
-
"@babel/parser": "7.29.0"
|
|
24
|
-
"magic-string": "0.30.21"
|
|
23
|
+
"@babel/parser": "7.29.0"
|
|
25
24
|
},
|
|
26
25
|
"devDependencies": {
|
|
27
26
|
"@types/node": "25.0.10",
|
|
28
|
-
"tsdown": "0.
|
|
27
|
+
"tsdown": "0.22.0",
|
|
29
28
|
"vue-tsc": "3.2.1"
|
|
30
29
|
},
|
|
31
30
|
"files": [
|