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 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
- routeDirs.forEach((dir) => {
136
- walkFiles(path.default.resolve(srcRoot, dir)).forEach((filePath) => {
137
- if (!filePath.endsWith(".vue")) return;
138
- const noExt = path.default.relative(srcRoot, filePath).slice(0, -4);
139
- routeSet.add(toPosixPath(noExt));
140
- });
141
- });
142
- return Array.from(routeSet).sort();
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(dir, subPackages) {
201
+ function getScanDirs(mainPackage, subPackages) {
145
202
  const dirSet = /* @__PURE__ */ new Set();
146
- const normalizedMainDir = normalizeDir(dir);
147
- if (normalizedMainDir) dirSet.add(normalizedMainDir);
148
- subPackages.forEach((subRoot) => {
149
- const normalizedSubRoot = normalizeDir(subRoot);
150
- if (!normalizedSubRoot) return;
151
- if (normalizedMainDir) {
152
- dirSet.add(`${normalizedSubRoot}/${normalizedMainDir}`);
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 readPagesJson(pagesJsonPath) {
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
- return existingMap.get(route) ?? { path: route };
168
- });
169
- }
170
- function hasPagesJsonChanged(current, next) {
171
- return JSON.stringify({
172
- pages: next.pages,
173
- subPackages: next.subPackages ?? []
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(subPath);
231
+ list.push(route.slice(matchedRoot.length + 1));
193
232
  subRouteMap.set(matchedRoot, list);
194
233
  } else mainRoutes.push(route);
195
234
  }
196
- const homePage = originPages[0]?.path;
197
- if (mainRoutes.includes(homePage)) {
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
- const merged = {
212
- ...pagesJson,
213
- pages,
214
- subPackages
215
- };
238
+ if (homeRoute) {
239
+ mainRoutes.splice(mainRoutes.indexOf(homeRoute), 1);
240
+ mainRoutes.unshift(homeRoute);
241
+ }
216
242
  return {
217
- merged,
218
- changed: hasPagesJsonChanged(pagesJson, merged)
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
- "// Auto-generated by vite-plugin-uni-inject. Do not edit.",
227
- "",
228
- "/** 提取路径 */",
229
- "export type ExtractPath<T extends string, Prefix extends string> = T extends `${Prefix}${infer P}` ? P : never;",
230
- "",
231
- "/** 路由路径 */",
232
- "export type RoutePath =",
233
- routes.map((route) => ` | "/${route}"`).join("\n") + ";",
234
- ""
235
- ].join("\n");
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 { dir = "pages", subPackages = [], dts = "uni-pages.d.ts" } = opts || {};
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(dir, subPackages));
251
- const { merged, changed } = getPagesByFileRoute(routes, pagesJson, subPackages);
252
- if (changed) {
253
- if (dts) {
254
- const dtsFilePath = resolveDtsFilePath(srcRoot, dts);
255
- const dtsContent = buildRouteDts(routes);
256
- fs.default.writeFileSync(dtsFilePath, dtsContent);
257
- }
258
- fs.default.writeFileSync(pagesJsonPath, JSON.stringify(merged, null, 2) + "\n");
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
- routeDirs.forEach((dir) => {
111
- walkFiles(path.resolve(srcRoot, dir)).forEach((filePath) => {
112
- if (!filePath.endsWith(".vue")) return;
113
- const noExt = path.relative(srcRoot, filePath).slice(0, -4);
114
- routeSet.add(toPosixPath(noExt));
115
- });
116
- });
117
- return Array.from(routeSet).sort();
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(dir, subPackages) {
176
+ function getScanDirs(mainPackage, subPackages) {
120
177
  const dirSet = /* @__PURE__ */ new Set();
121
- const normalizedMainDir = normalizeDir(dir);
122
- if (normalizedMainDir) dirSet.add(normalizedMainDir);
123
- subPackages.forEach((subRoot) => {
124
- const normalizedSubRoot = normalizeDir(subRoot);
125
- if (!normalizedSubRoot) return;
126
- if (normalizedMainDir) {
127
- dirSet.add(`${normalizedSubRoot}/${normalizedMainDir}`);
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 readPagesJson(pagesJsonPath) {
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
- return existingMap.get(route) ?? { path: route };
143
- });
144
- }
145
- function hasPagesJsonChanged(current, next) {
146
- return JSON.stringify({
147
- pages: next.pages,
148
- subPackages: next.subPackages ?? []
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(subPath);
206
+ list.push(route.slice(matchedRoot.length + 1));
168
207
  subRouteMap.set(matchedRoot, list);
169
208
  } else mainRoutes.push(route);
170
209
  }
171
- const homePage = originPages[0]?.path;
172
- if (mainRoutes.includes(homePage)) {
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
- const merged = {
187
- ...pagesJson,
188
- pages,
189
- subPackages
190
- };
213
+ if (homeRoute) {
214
+ mainRoutes.splice(mainRoutes.indexOf(homeRoute), 1);
215
+ mainRoutes.unshift(homeRoute);
216
+ }
191
217
  return {
192
- merged,
193
- changed: hasPagesJsonChanged(pagesJson, merged)
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
- "// Auto-generated by vite-plugin-uni-inject. Do not edit.",
202
- "",
203
- "/** 提取路径 */",
204
- "export type ExtractPath<T extends string, Prefix extends string> = T extends `${Prefix}${infer P}` ? P : never;",
205
- "",
206
- "/** 路由路径 */",
207
- "export type RoutePath =",
208
- routes.map((route) => ` | "/${route}"`).join("\n") + ";",
209
- ""
210
- ].join("\n");
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 { dir = "pages", subPackages = [], dts = "uni-pages.d.ts" } = opts || {};
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(dir, subPackages));
226
- const { merged, changed } = getPagesByFileRoute(routes, pagesJson, subPackages);
227
- if (changed) {
228
- if (dts) {
229
- const dtsFilePath = resolveDtsFilePath(srcRoot, dts);
230
- const dtsContent = buildRouteDts(routes);
231
- fs.writeFileSync(dtsFilePath, dtsContent);
232
- }
233
- fs.writeFileSync(pagesJsonPath, JSON.stringify(merged, null, 2) + "\n");
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
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "author": "Kriac",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
- "version": "0.4.0",
7
+ "version": "0.5.0",
8
8
  "homepage": "https://github.com/Kriac/vite-plugin-uni-inject",
9
9
  "repository": {
10
10
  "type": "git",