vite-plugin-uni-inject 0.4.0 → 0.6.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/README.md +10 -32
- package/dist/index.cjs +169 -89
- package/dist/index.d.cts +41 -7
- package/dist/index.d.mts +41 -7
- package/dist/index.mjs +169 -89
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Vite-plugin-uni-inject
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个轻量级的 uniapp 注入增强插件,旨在为 uniapp 项目提供更舒适的开发体验。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 文档
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- 支持注入 `<page-meta/>` 这种只能放在页面第一个位置的标签节点。
|
|
9
|
-
- 支持基于文件路由的 `page.json` 自动生成并注入,无需手动维护页面表。
|
|
7
|
+
请阅读 [文档](https://kriac.github.io/vite-plugin-uni-inject) 了解更多使用细节。
|
|
10
8
|
|
|
11
|
-
##
|
|
9
|
+
## 使用
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
如果你在开发过程中遇到了什么问题,或者有更好的建议,欢迎提交 issue 与我们讨论。
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
pnpm i -D vite-plugin-uni-inject
|
|
17
|
-
```
|
|
13
|
+
## 贡献
|
|
18
14
|
|
|
19
|
-
|
|
15
|
+
首先感谢你考虑为本项目做出贡献!我们欢迎社区成员的贡献,以帮助改进和扩展本项目。
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
import { defineConfig } from "vite";
|
|
23
|
-
import { uniAutoPages, uniInject } from "vite-plugin-uni-inject";
|
|
24
|
-
import uni from "@dcloudio/vite-plugin-uni";
|
|
17
|
+
## 执照
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
export default defineConfig(() => {
|
|
28
|
-
return {
|
|
29
|
-
plugins: [uniAutoPages(), uniInject(), uni()],
|
|
30
|
-
};
|
|
31
|
-
});
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### 独立插件说明
|
|
35
|
-
|
|
36
|
-
- `uniAutoPages(options)`:负责扫描文件路由并补全 `src/pages.json` 。
|
|
37
|
-
- `uniInject(options)`:负责注入 `App.inject.vue`(或自定义文件)到页面文件。
|
|
38
|
-
|
|
39
|
-
## 报告错误
|
|
40
|
-
|
|
41
|
-
欢迎提交 issue 与我们讨论。
|
|
19
|
+
本项目采用 MIT 许可证,详细内容请见 [LICENSE](LICENSE) 文件。
|
package/dist/index.cjs
CHANGED
|
@@ -94,14 +94,18 @@ function uniInject(opts) {
|
|
|
94
94
|
});
|
|
95
95
|
let nextScriptCode = scriptCode;
|
|
96
96
|
if (imports.length) {
|
|
97
|
-
const ranges = [...imports].sort((a, b) =>
|
|
97
|
+
const ranges = [...imports].sort((a, b) => {
|
|
98
|
+
return b.start - a.start;
|
|
99
|
+
});
|
|
98
100
|
for (const { start: rangeStart, end: rangeEnd } of ranges) {
|
|
99
101
|
let removeEnd = rangeEnd;
|
|
100
102
|
if (nextScriptCode.startsWith("\r\n", removeEnd)) removeEnd += 2;
|
|
101
103
|
else if (nextScriptCode[removeEnd] === "\n") removeEnd += 1;
|
|
102
104
|
nextScriptCode = nextScriptCode.slice(0, rangeStart) + nextScriptCode.slice(removeEnd);
|
|
103
105
|
}
|
|
104
|
-
nextScriptCode = `\n${imports.map((item) =>
|
|
106
|
+
nextScriptCode = `\n${imports.map((item) => {
|
|
107
|
+
return item.code;
|
|
108
|
+
}).join("\n")}${nextScriptCode}`;
|
|
105
109
|
}
|
|
106
110
|
newCode = newCode.slice(0, start) + nextScriptCode + newCode.slice(end);
|
|
107
111
|
}
|
|
@@ -111,6 +115,10 @@ function uniInject(opts) {
|
|
|
111
115
|
}
|
|
112
116
|
//#endregion
|
|
113
117
|
//#region src/modules/auto-pages/index.ts
|
|
118
|
+
function readPagesJson(pagesJsonPath) {
|
|
119
|
+
if (!fs.default.existsSync(pagesJsonPath)) return null;
|
|
120
|
+
return JSON.parse(fs.default.readFileSync(pagesJsonPath, "utf-8"));
|
|
121
|
+
}
|
|
114
122
|
function toPosixPath(value) {
|
|
115
123
|
return value.replace(/\\/g, "/");
|
|
116
124
|
}
|
|
@@ -130,55 +138,97 @@ function walkFiles(dirPath, filePaths = []) {
|
|
|
130
138
|
}
|
|
131
139
|
return filePaths;
|
|
132
140
|
}
|
|
141
|
+
function analyzeVueContent(content, filePath) {
|
|
142
|
+
if (!content.includes("definePage")) return null;
|
|
143
|
+
const stripRanges = [];
|
|
144
|
+
const { descriptor } = (0, _vue_compiler_sfc.parse)(content);
|
|
145
|
+
const blocks = [descriptor.scriptSetup, descriptor.script].filter((b) => {
|
|
146
|
+
return Boolean(b);
|
|
147
|
+
});
|
|
148
|
+
let config = null;
|
|
149
|
+
for (const block of blocks) {
|
|
150
|
+
const src = block.content;
|
|
151
|
+
if (!src.includes("definePage")) continue;
|
|
152
|
+
let ast;
|
|
153
|
+
try {
|
|
154
|
+
ast = (0, _babel_parser.parse)(src, {
|
|
155
|
+
sourceType: "module",
|
|
156
|
+
plugins: ["typescript"]
|
|
157
|
+
});
|
|
158
|
+
} catch {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const base = block.loc.start.offset;
|
|
162
|
+
for (const node of ast.program.body) {
|
|
163
|
+
if (typeof node.start !== "number" || typeof node.end !== "number") continue;
|
|
164
|
+
if (node.type === "ImportDeclaration") {
|
|
165
|
+
if (node.specifiers.some((s) => {
|
|
166
|
+
return s.type === "ImportSpecifier" && s.imported.type === "Identifier" && s.imported.name === "definePage";
|
|
167
|
+
})) stripRanges.push({
|
|
168
|
+
start: base + node.start,
|
|
169
|
+
end: base + node.end
|
|
170
|
+
});
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (node.type !== "ExpressionStatement" || node.expression.type !== "CallExpression" || node.expression.callee.type !== "Identifier" || node.expression.callee.name !== "definePage") continue;
|
|
174
|
+
stripRanges.push({
|
|
175
|
+
start: base + node.start,
|
|
176
|
+
end: base + node.end
|
|
177
|
+
});
|
|
178
|
+
const arg = node.expression.arguments[0];
|
|
179
|
+
if (config || !arg || typeof arg.start !== "number" || typeof arg.end !== "number") continue;
|
|
180
|
+
const argSrc = src.slice(arg.start, arg.end);
|
|
181
|
+
try {
|
|
182
|
+
config = new Function(`return (${argSrc})`)();
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.warn(`解析失败(${filePath}): ${err.message}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (!stripRanges.length && !config) return null;
|
|
189
|
+
return {
|
|
190
|
+
config,
|
|
191
|
+
stripRanges
|
|
192
|
+
};
|
|
193
|
+
}
|
|
133
194
|
function collectFileRoutes(srcRoot, routeDirs) {
|
|
134
195
|
const routeSet = /* @__PURE__ */ new Set();
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
196
|
+
const analysisMap = /* @__PURE__ */ new Map();
|
|
197
|
+
for (const dir of routeDirs) for (const filePath of walkFiles(path.default.resolve(srcRoot, dir))) {
|
|
198
|
+
if (!filePath.endsWith(".vue")) continue;
|
|
199
|
+
const route = toPosixPath(path.default.relative(srcRoot, filePath).slice(0, -4));
|
|
200
|
+
routeSet.add(route);
|
|
201
|
+
const result = analyzeVueContent(fs.default.readFileSync(filePath, "utf-8"), filePath);
|
|
202
|
+
if (result) analysisMap.set(route, result);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
routes: Array.from(routeSet).sort(),
|
|
206
|
+
analysisMap
|
|
207
|
+
};
|
|
143
208
|
}
|
|
144
|
-
function getScanDirs(
|
|
209
|
+
function getScanDirs(mainPackage, subPackages) {
|
|
145
210
|
const dirSet = /* @__PURE__ */ new Set();
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
if (!
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
dirSet.add(normalizedSubRoot);
|
|
156
|
-
});
|
|
211
|
+
const mainDir = normalizeDir(mainPackage);
|
|
212
|
+
if (mainDir) dirSet.add(mainDir);
|
|
213
|
+
for (const subRoot of subPackages) {
|
|
214
|
+
const sub = normalizeDir(subRoot);
|
|
215
|
+
if (!sub) continue;
|
|
216
|
+
dirSet.add(mainDir ? `${sub}/${mainDir}` : sub);
|
|
217
|
+
}
|
|
157
218
|
return Array.from(dirSet);
|
|
158
219
|
}
|
|
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]));
|
|
220
|
+
function buildPages(routes, analysisMap, toFullRoute) {
|
|
166
221
|
return routes.map((route) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}) !== JSON.stringify({
|
|
175
|
-
pages: current.pages ?? [],
|
|
176
|
-
subPackages: current.subPackages ?? []
|
|
222
|
+
const cfg = analysisMap.get(toFullRoute(route))?.config;
|
|
223
|
+
if (!cfg) return { path: route };
|
|
224
|
+
return {
|
|
225
|
+
path: route,
|
|
226
|
+
...cfg,
|
|
227
|
+
home: void 0
|
|
228
|
+
};
|
|
177
229
|
});
|
|
178
230
|
}
|
|
179
|
-
function getPagesByFileRoute(routes, pagesJson, subPackageRoots) {
|
|
180
|
-
const originPages = pagesJson.pages ?? [];
|
|
181
|
-
const originSubPackages = pagesJson.subPackages ?? [];
|
|
231
|
+
function getPagesByFileRoute(routes, analysisMap, pagesJson, subPackageRoots) {
|
|
182
232
|
const mainRoutes = [];
|
|
183
233
|
const subRouteMap = /* @__PURE__ */ new Map();
|
|
184
234
|
const normalizedSubRoots = subPackageRoots.map(normalizeDir);
|
|
@@ -187,58 +237,71 @@ function getPagesByFileRoute(routes, pagesJson, subPackageRoots) {
|
|
|
187
237
|
return route.startsWith(root + "/");
|
|
188
238
|
});
|
|
189
239
|
if (matchedRoot) {
|
|
190
|
-
const subPath = route.slice(matchedRoot.length + 1);
|
|
191
240
|
const list = subRouteMap.get(matchedRoot) ?? [];
|
|
192
|
-
list.push(
|
|
241
|
+
list.push(route.slice(matchedRoot.length + 1));
|
|
193
242
|
subRouteMap.set(matchedRoot, list);
|
|
194
243
|
} else mainRoutes.push(route);
|
|
195
244
|
}
|
|
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
|
-
};
|
|
245
|
+
const homeRoute = mainRoutes.find((r) => {
|
|
246
|
+
return analysisMap.get(r)?.config?.home === true;
|
|
210
247
|
});
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
};
|
|
248
|
+
if (homeRoute) {
|
|
249
|
+
mainRoutes.splice(mainRoutes.indexOf(homeRoute), 1);
|
|
250
|
+
mainRoutes.unshift(homeRoute);
|
|
251
|
+
}
|
|
216
252
|
return {
|
|
217
|
-
|
|
218
|
-
|
|
253
|
+
...pagesJson,
|
|
254
|
+
pages: buildPages(mainRoutes, analysisMap, (r) => {
|
|
255
|
+
return r;
|
|
256
|
+
}),
|
|
257
|
+
subPackages: normalizedSubRoots.map((root) => {
|
|
258
|
+
return {
|
|
259
|
+
root,
|
|
260
|
+
pages: buildPages(subRouteMap.get(root) ?? [], analysisMap, (r) => {
|
|
261
|
+
return `${root}/${r}`;
|
|
262
|
+
})
|
|
263
|
+
};
|
|
264
|
+
})
|
|
219
265
|
};
|
|
220
266
|
}
|
|
221
|
-
function resolveDtsFilePath(srcRoot, dts) {
|
|
222
|
-
return path.default.isAbsolute(dts) ? dts : path.default.resolve(srcRoot, dts);
|
|
223
|
-
}
|
|
224
267
|
function buildRouteDts(routes) {
|
|
225
|
-
return
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
""
|
|
235
|
-
|
|
268
|
+
return `import type { DefinePageConfig } from "vite-plugin-uni-inject";
|
|
269
|
+
|
|
270
|
+
// Auto-generated by vite-plugin-uni-inject. Do not edit.
|
|
271
|
+
|
|
272
|
+
/** 提取路径 */
|
|
273
|
+
export type ExtractPath<T extends string, Prefix extends string> = T extends \`\${Prefix}\${infer P}\` ? P : never;
|
|
274
|
+
|
|
275
|
+
/** 路由路径 */
|
|
276
|
+
export type RoutePath = ${routes.map((r) => {
|
|
277
|
+
return `"/${r}"`;
|
|
278
|
+
}).join(` |\n`)};
|
|
279
|
+
|
|
280
|
+
declare global {
|
|
281
|
+
/**
|
|
282
|
+
* Vue \`<script setup>\` 宏定义 uniapp 页面级配置。
|
|
283
|
+
*/
|
|
284
|
+
function definePage(config: DefinePageConfig): void;
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
function getCachePath(cacheDir, targetPath) {
|
|
289
|
+
const cacheName = Buffer.from(path.default.resolve(targetPath)).toString("base64url");
|
|
290
|
+
return path.default.join(cacheDir, "vite-plugin-uni-inject", "auto-pages", cacheName);
|
|
291
|
+
}
|
|
292
|
+
function writeFileWithCache(targetPath, nextContent, cacheDir, forceWrite = false) {
|
|
293
|
+
const cachePath = getCachePath(cacheDir, targetPath);
|
|
294
|
+
if (!forceWrite && fs.default.existsSync(targetPath) && fs.default.existsSync(cachePath) && fs.default.readFileSync(cachePath, "utf-8") === nextContent) return;
|
|
295
|
+
fs.default.mkdirSync(path.default.dirname(targetPath), { recursive: true });
|
|
296
|
+
fs.default.writeFileSync(targetPath, nextContent);
|
|
297
|
+
fs.default.mkdirSync(path.default.dirname(cachePath), { recursive: true });
|
|
298
|
+
fs.default.writeFileSync(cachePath, nextContent);
|
|
236
299
|
}
|
|
237
300
|
/**
|
|
238
301
|
* 自动补全 pages.json 插件
|
|
239
302
|
*/
|
|
240
303
|
function uniAutoPages(opts) {
|
|
241
|
-
const {
|
|
304
|
+
const { mainPackage = "pages", subPackages = [], dts = "uni-pages.d.ts" } = opts || {};
|
|
242
305
|
return {
|
|
243
306
|
name: "vite-plugin-uni-auto-pages",
|
|
244
307
|
enforce: "pre",
|
|
@@ -247,16 +310,33 @@ function uniAutoPages(opts) {
|
|
|
247
310
|
const pagesJsonPath = path.default.join(srcRoot, "pages.json");
|
|
248
311
|
const pagesJson = readPagesJson(pagesJsonPath);
|
|
249
312
|
if (!pagesJson) return;
|
|
250
|
-
const routes = collectFileRoutes(srcRoot, getScanDirs(
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
313
|
+
const { routes, analysisMap } = collectFileRoutes(srcRoot, getScanDirs(mainPackage, subPackages));
|
|
314
|
+
const merged = getPagesByFileRoute(routes, analysisMap, pagesJson, subPackages);
|
|
315
|
+
const newPagesJson = JSON.stringify(merged, null, 2) + "\n";
|
|
316
|
+
const dtsPath = dts ? path.default.join(srcRoot, dts) : null;
|
|
317
|
+
const shouldRefreshPagesJson = dtsPath ? !fs.default.existsSync(dtsPath) : false;
|
|
318
|
+
writeFileWithCache(pagesJsonPath, newPagesJson, config.cacheDir, shouldRefreshPagesJson);
|
|
319
|
+
if (dtsPath) writeFileWithCache(dtsPath, buildRouteDts(routes), config.cacheDir);
|
|
320
|
+
},
|
|
321
|
+
transform(code, id) {
|
|
322
|
+
const pure = id.split("?")[0];
|
|
323
|
+
if (!pure.endsWith(".vue")) return;
|
|
324
|
+
const analysis = analyzeVueContent(code, pure);
|
|
325
|
+
if (!analysis?.stripRanges.length) return;
|
|
326
|
+
let newCode = code;
|
|
327
|
+
const ranges = [...analysis.stripRanges].sort((a, b) => {
|
|
328
|
+
return b.start - a.start;
|
|
329
|
+
});
|
|
330
|
+
for (const { start, end } of ranges) {
|
|
331
|
+
let removeEnd = end;
|
|
332
|
+
if (newCode.startsWith("\r\n", removeEnd)) removeEnd += 2;
|
|
333
|
+
else if (newCode[removeEnd] === "\n") removeEnd += 1;
|
|
334
|
+
newCode = newCode.slice(0, start) + newCode.slice(removeEnd);
|
|
259
335
|
}
|
|
336
|
+
return {
|
|
337
|
+
code: newCode,
|
|
338
|
+
map: null
|
|
339
|
+
};
|
|
260
340
|
}
|
|
261
341
|
};
|
|
262
342
|
}
|
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[];
|
|
@@ -49,8 +78,13 @@ declare function uniAutoPages(opts?: AutoPagesPluginOptions): {
|
|
|
49
78
|
name: string;
|
|
50
79
|
enforce: "pre";
|
|
51
80
|
configResolved(config: {
|
|
81
|
+
cacheDir: string;
|
|
52
82
|
root: string;
|
|
53
83
|
}): void;
|
|
84
|
+
transform(code: string, id: string): {
|
|
85
|
+
code: string;
|
|
86
|
+
map: null;
|
|
87
|
+
} | undefined;
|
|
54
88
|
};
|
|
55
89
|
//#endregion
|
|
56
|
-
export { uniAutoPages, uniInject };
|
|
90
|
+
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[];
|
|
@@ -49,8 +78,13 @@ declare function uniAutoPages(opts?: AutoPagesPluginOptions): {
|
|
|
49
78
|
name: string;
|
|
50
79
|
enforce: "pre";
|
|
51
80
|
configResolved(config: {
|
|
81
|
+
cacheDir: string;
|
|
52
82
|
root: string;
|
|
53
83
|
}): void;
|
|
84
|
+
transform(code: string, id: string): {
|
|
85
|
+
code: string;
|
|
86
|
+
map: null;
|
|
87
|
+
} | undefined;
|
|
54
88
|
};
|
|
55
89
|
//#endregion
|
|
56
|
-
export { uniAutoPages, uniInject };
|
|
90
|
+
export { type DefinePageConfig, uniAutoPages, uniInject };
|
package/dist/index.mjs
CHANGED
|
@@ -69,14 +69,18 @@ function uniInject(opts) {
|
|
|
69
69
|
});
|
|
70
70
|
let nextScriptCode = scriptCode;
|
|
71
71
|
if (imports.length) {
|
|
72
|
-
const ranges = [...imports].sort((a, b) =>
|
|
72
|
+
const ranges = [...imports].sort((a, b) => {
|
|
73
|
+
return b.start - a.start;
|
|
74
|
+
});
|
|
73
75
|
for (const { start: rangeStart, end: rangeEnd } of ranges) {
|
|
74
76
|
let removeEnd = rangeEnd;
|
|
75
77
|
if (nextScriptCode.startsWith("\r\n", removeEnd)) removeEnd += 2;
|
|
76
78
|
else if (nextScriptCode[removeEnd] === "\n") removeEnd += 1;
|
|
77
79
|
nextScriptCode = nextScriptCode.slice(0, rangeStart) + nextScriptCode.slice(removeEnd);
|
|
78
80
|
}
|
|
79
|
-
nextScriptCode = `\n${imports.map((item) =>
|
|
81
|
+
nextScriptCode = `\n${imports.map((item) => {
|
|
82
|
+
return item.code;
|
|
83
|
+
}).join("\n")}${nextScriptCode}`;
|
|
80
84
|
}
|
|
81
85
|
newCode = newCode.slice(0, start) + nextScriptCode + newCode.slice(end);
|
|
82
86
|
}
|
|
@@ -86,6 +90,10 @@ function uniInject(opts) {
|
|
|
86
90
|
}
|
|
87
91
|
//#endregion
|
|
88
92
|
//#region src/modules/auto-pages/index.ts
|
|
93
|
+
function readPagesJson(pagesJsonPath) {
|
|
94
|
+
if (!fs.existsSync(pagesJsonPath)) return null;
|
|
95
|
+
return JSON.parse(fs.readFileSync(pagesJsonPath, "utf-8"));
|
|
96
|
+
}
|
|
89
97
|
function toPosixPath(value) {
|
|
90
98
|
return value.replace(/\\/g, "/");
|
|
91
99
|
}
|
|
@@ -105,55 +113,97 @@ function walkFiles(dirPath, filePaths = []) {
|
|
|
105
113
|
}
|
|
106
114
|
return filePaths;
|
|
107
115
|
}
|
|
116
|
+
function analyzeVueContent(content, filePath) {
|
|
117
|
+
if (!content.includes("definePage")) return null;
|
|
118
|
+
const stripRanges = [];
|
|
119
|
+
const { descriptor } = parse$1(content);
|
|
120
|
+
const blocks = [descriptor.scriptSetup, descriptor.script].filter((b) => {
|
|
121
|
+
return Boolean(b);
|
|
122
|
+
});
|
|
123
|
+
let config = null;
|
|
124
|
+
for (const block of blocks) {
|
|
125
|
+
const src = block.content;
|
|
126
|
+
if (!src.includes("definePage")) continue;
|
|
127
|
+
let ast;
|
|
128
|
+
try {
|
|
129
|
+
ast = parse(src, {
|
|
130
|
+
sourceType: "module",
|
|
131
|
+
plugins: ["typescript"]
|
|
132
|
+
});
|
|
133
|
+
} catch {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const base = block.loc.start.offset;
|
|
137
|
+
for (const node of ast.program.body) {
|
|
138
|
+
if (typeof node.start !== "number" || typeof node.end !== "number") continue;
|
|
139
|
+
if (node.type === "ImportDeclaration") {
|
|
140
|
+
if (node.specifiers.some((s) => {
|
|
141
|
+
return s.type === "ImportSpecifier" && s.imported.type === "Identifier" && s.imported.name === "definePage";
|
|
142
|
+
})) stripRanges.push({
|
|
143
|
+
start: base + node.start,
|
|
144
|
+
end: base + node.end
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (node.type !== "ExpressionStatement" || node.expression.type !== "CallExpression" || node.expression.callee.type !== "Identifier" || node.expression.callee.name !== "definePage") continue;
|
|
149
|
+
stripRanges.push({
|
|
150
|
+
start: base + node.start,
|
|
151
|
+
end: base + node.end
|
|
152
|
+
});
|
|
153
|
+
const arg = node.expression.arguments[0];
|
|
154
|
+
if (config || !arg || typeof arg.start !== "number" || typeof arg.end !== "number") continue;
|
|
155
|
+
const argSrc = src.slice(arg.start, arg.end);
|
|
156
|
+
try {
|
|
157
|
+
config = new Function(`return (${argSrc})`)();
|
|
158
|
+
} catch (err) {
|
|
159
|
+
console.warn(`解析失败(${filePath}): ${err.message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!stripRanges.length && !config) return null;
|
|
164
|
+
return {
|
|
165
|
+
config,
|
|
166
|
+
stripRanges
|
|
167
|
+
};
|
|
168
|
+
}
|
|
108
169
|
function collectFileRoutes(srcRoot, routeDirs) {
|
|
109
170
|
const routeSet = /* @__PURE__ */ new Set();
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
171
|
+
const analysisMap = /* @__PURE__ */ new Map();
|
|
172
|
+
for (const dir of routeDirs) for (const filePath of walkFiles(path.resolve(srcRoot, dir))) {
|
|
173
|
+
if (!filePath.endsWith(".vue")) continue;
|
|
174
|
+
const route = toPosixPath(path.relative(srcRoot, filePath).slice(0, -4));
|
|
175
|
+
routeSet.add(route);
|
|
176
|
+
const result = analyzeVueContent(fs.readFileSync(filePath, "utf-8"), filePath);
|
|
177
|
+
if (result) analysisMap.set(route, result);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
routes: Array.from(routeSet).sort(),
|
|
181
|
+
analysisMap
|
|
182
|
+
};
|
|
118
183
|
}
|
|
119
|
-
function getScanDirs(
|
|
184
|
+
function getScanDirs(mainPackage, subPackages) {
|
|
120
185
|
const dirSet = /* @__PURE__ */ new Set();
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
if (!
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
dirSet.add(normalizedSubRoot);
|
|
131
|
-
});
|
|
186
|
+
const mainDir = normalizeDir(mainPackage);
|
|
187
|
+
if (mainDir) dirSet.add(mainDir);
|
|
188
|
+
for (const subRoot of subPackages) {
|
|
189
|
+
const sub = normalizeDir(subRoot);
|
|
190
|
+
if (!sub) continue;
|
|
191
|
+
dirSet.add(mainDir ? `${sub}/${mainDir}` : sub);
|
|
192
|
+
}
|
|
132
193
|
return Array.from(dirSet);
|
|
133
194
|
}
|
|
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]));
|
|
195
|
+
function buildPages(routes, analysisMap, toFullRoute) {
|
|
141
196
|
return routes.map((route) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}) !== JSON.stringify({
|
|
150
|
-
pages: current.pages ?? [],
|
|
151
|
-
subPackages: current.subPackages ?? []
|
|
197
|
+
const cfg = analysisMap.get(toFullRoute(route))?.config;
|
|
198
|
+
if (!cfg) return { path: route };
|
|
199
|
+
return {
|
|
200
|
+
path: route,
|
|
201
|
+
...cfg,
|
|
202
|
+
home: void 0
|
|
203
|
+
};
|
|
152
204
|
});
|
|
153
205
|
}
|
|
154
|
-
function getPagesByFileRoute(routes, pagesJson, subPackageRoots) {
|
|
155
|
-
const originPages = pagesJson.pages ?? [];
|
|
156
|
-
const originSubPackages = pagesJson.subPackages ?? [];
|
|
206
|
+
function getPagesByFileRoute(routes, analysisMap, pagesJson, subPackageRoots) {
|
|
157
207
|
const mainRoutes = [];
|
|
158
208
|
const subRouteMap = /* @__PURE__ */ new Map();
|
|
159
209
|
const normalizedSubRoots = subPackageRoots.map(normalizeDir);
|
|
@@ -162,58 +212,71 @@ function getPagesByFileRoute(routes, pagesJson, subPackageRoots) {
|
|
|
162
212
|
return route.startsWith(root + "/");
|
|
163
213
|
});
|
|
164
214
|
if (matchedRoot) {
|
|
165
|
-
const subPath = route.slice(matchedRoot.length + 1);
|
|
166
215
|
const list = subRouteMap.get(matchedRoot) ?? [];
|
|
167
|
-
list.push(
|
|
216
|
+
list.push(route.slice(matchedRoot.length + 1));
|
|
168
217
|
subRouteMap.set(matchedRoot, list);
|
|
169
218
|
} else mainRoutes.push(route);
|
|
170
219
|
}
|
|
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
|
-
};
|
|
220
|
+
const homeRoute = mainRoutes.find((r) => {
|
|
221
|
+
return analysisMap.get(r)?.config?.home === true;
|
|
185
222
|
});
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
};
|
|
223
|
+
if (homeRoute) {
|
|
224
|
+
mainRoutes.splice(mainRoutes.indexOf(homeRoute), 1);
|
|
225
|
+
mainRoutes.unshift(homeRoute);
|
|
226
|
+
}
|
|
191
227
|
return {
|
|
192
|
-
|
|
193
|
-
|
|
228
|
+
...pagesJson,
|
|
229
|
+
pages: buildPages(mainRoutes, analysisMap, (r) => {
|
|
230
|
+
return r;
|
|
231
|
+
}),
|
|
232
|
+
subPackages: normalizedSubRoots.map((root) => {
|
|
233
|
+
return {
|
|
234
|
+
root,
|
|
235
|
+
pages: buildPages(subRouteMap.get(root) ?? [], analysisMap, (r) => {
|
|
236
|
+
return `${root}/${r}`;
|
|
237
|
+
})
|
|
238
|
+
};
|
|
239
|
+
})
|
|
194
240
|
};
|
|
195
241
|
}
|
|
196
|
-
function resolveDtsFilePath(srcRoot, dts) {
|
|
197
|
-
return path.isAbsolute(dts) ? dts : path.resolve(srcRoot, dts);
|
|
198
|
-
}
|
|
199
242
|
function buildRouteDts(routes) {
|
|
200
|
-
return
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
""
|
|
210
|
-
|
|
243
|
+
return `import type { DefinePageConfig } from "vite-plugin-uni-inject";
|
|
244
|
+
|
|
245
|
+
// Auto-generated by vite-plugin-uni-inject. Do not edit.
|
|
246
|
+
|
|
247
|
+
/** 提取路径 */
|
|
248
|
+
export type ExtractPath<T extends string, Prefix extends string> = T extends \`\${Prefix}\${infer P}\` ? P : never;
|
|
249
|
+
|
|
250
|
+
/** 路由路径 */
|
|
251
|
+
export type RoutePath = ${routes.map((r) => {
|
|
252
|
+
return `"/${r}"`;
|
|
253
|
+
}).join(` |\n`)};
|
|
254
|
+
|
|
255
|
+
declare global {
|
|
256
|
+
/**
|
|
257
|
+
* Vue \`<script setup>\` 宏定义 uniapp 页面级配置。
|
|
258
|
+
*/
|
|
259
|
+
function definePage(config: DefinePageConfig): void;
|
|
260
|
+
}
|
|
261
|
+
`;
|
|
262
|
+
}
|
|
263
|
+
function getCachePath(cacheDir, targetPath) {
|
|
264
|
+
const cacheName = Buffer.from(path.resolve(targetPath)).toString("base64url");
|
|
265
|
+
return path.join(cacheDir, "vite-plugin-uni-inject", "auto-pages", cacheName);
|
|
266
|
+
}
|
|
267
|
+
function writeFileWithCache(targetPath, nextContent, cacheDir, forceWrite = false) {
|
|
268
|
+
const cachePath = getCachePath(cacheDir, targetPath);
|
|
269
|
+
if (!forceWrite && fs.existsSync(targetPath) && fs.existsSync(cachePath) && fs.readFileSync(cachePath, "utf-8") === nextContent) return;
|
|
270
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
271
|
+
fs.writeFileSync(targetPath, nextContent);
|
|
272
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
273
|
+
fs.writeFileSync(cachePath, nextContent);
|
|
211
274
|
}
|
|
212
275
|
/**
|
|
213
276
|
* 自动补全 pages.json 插件
|
|
214
277
|
*/
|
|
215
278
|
function uniAutoPages(opts) {
|
|
216
|
-
const {
|
|
279
|
+
const { mainPackage = "pages", subPackages = [], dts = "uni-pages.d.ts" } = opts || {};
|
|
217
280
|
return {
|
|
218
281
|
name: "vite-plugin-uni-auto-pages",
|
|
219
282
|
enforce: "pre",
|
|
@@ -222,16 +285,33 @@ function uniAutoPages(opts) {
|
|
|
222
285
|
const pagesJsonPath = path.join(srcRoot, "pages.json");
|
|
223
286
|
const pagesJson = readPagesJson(pagesJsonPath);
|
|
224
287
|
if (!pagesJson) return;
|
|
225
|
-
const routes = collectFileRoutes(srcRoot, getScanDirs(
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
288
|
+
const { routes, analysisMap } = collectFileRoutes(srcRoot, getScanDirs(mainPackage, subPackages));
|
|
289
|
+
const merged = getPagesByFileRoute(routes, analysisMap, pagesJson, subPackages);
|
|
290
|
+
const newPagesJson = JSON.stringify(merged, null, 2) + "\n";
|
|
291
|
+
const dtsPath = dts ? path.join(srcRoot, dts) : null;
|
|
292
|
+
const shouldRefreshPagesJson = dtsPath ? !fs.existsSync(dtsPath) : false;
|
|
293
|
+
writeFileWithCache(pagesJsonPath, newPagesJson, config.cacheDir, shouldRefreshPagesJson);
|
|
294
|
+
if (dtsPath) writeFileWithCache(dtsPath, buildRouteDts(routes), config.cacheDir);
|
|
295
|
+
},
|
|
296
|
+
transform(code, id) {
|
|
297
|
+
const pure = id.split("?")[0];
|
|
298
|
+
if (!pure.endsWith(".vue")) return;
|
|
299
|
+
const analysis = analyzeVueContent(code, pure);
|
|
300
|
+
if (!analysis?.stripRanges.length) return;
|
|
301
|
+
let newCode = code;
|
|
302
|
+
const ranges = [...analysis.stripRanges].sort((a, b) => {
|
|
303
|
+
return b.start - a.start;
|
|
304
|
+
});
|
|
305
|
+
for (const { start, end } of ranges) {
|
|
306
|
+
let removeEnd = end;
|
|
307
|
+
if (newCode.startsWith("\r\n", removeEnd)) removeEnd += 2;
|
|
308
|
+
else if (newCode[removeEnd] === "\n") removeEnd += 1;
|
|
309
|
+
newCode = newCode.slice(0, start) + newCode.slice(removeEnd);
|
|
234
310
|
}
|
|
311
|
+
return {
|
|
312
|
+
code: newCode,
|
|
313
|
+
map: null
|
|
314
|
+
};
|
|
235
315
|
}
|
|
236
316
|
};
|
|
237
317
|
}
|