vite-plugin-uni-inject 0.1.0 → 0.3.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 +9 -6
- package/dist/index.cjs +248 -6
- package/dist/index.d.cts +54 -17
- package/dist/index.d.mts +56 -0
- package/dist/index.mjs +221 -0
- package/package.json +10 -9
- package/dist/index.d.ts +0 -19
- package/dist/index.js +0 -6
package/README.md
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
# vite-plugin-uni-inject
|
|
2
2
|
|
|
3
|
-
利用 Vite
|
|
3
|
+
利用 Vite 插件机制,实现自动注入代码,解放你的双手。
|
|
4
4
|
|
|
5
5
|
## 功能特点
|
|
6
6
|
|
|
7
7
|
- 干净的注入任何代码,没有多余的结构。
|
|
8
8
|
- 支持注入 `<page-meta/>` 这种只能放在页面第一个位置的标签节点。
|
|
9
|
-
-
|
|
9
|
+
- 支持基于文件路由的 `page.json` 自动生成并注入,无需手动维护页面表。
|
|
10
10
|
|
|
11
11
|
## 如何使用
|
|
12
12
|
|
|
13
|
-
原理就是通过 vite 插件机制,在构建过程中自动注入 inject.vue 中的代码。
|
|
14
|
-
|
|
15
13
|
### 安装依赖
|
|
16
14
|
|
|
17
15
|
```bash
|
|
@@ -22,17 +20,22 @@ pnpm i -D vite-plugin-uni-inject
|
|
|
22
20
|
|
|
23
21
|
```ts
|
|
24
22
|
import { defineConfig } from "vite";
|
|
23
|
+
import { uniAutoPages, uniInject } from "vite-plugin-uni-inject";
|
|
25
24
|
import uni from "@dcloudio/vite-plugin-uni";
|
|
26
|
-
import uniInject from "vite-plugin-uni-inject";
|
|
27
25
|
|
|
28
26
|
// 如果有重写 page.json 文件的插件,请确保写在 uniInject 之前
|
|
29
27
|
export default defineConfig(() => {
|
|
30
28
|
return {
|
|
31
|
-
plugins: [uniInject(), uni()],
|
|
29
|
+
plugins: [uniAutoPages(), uniInject(), uni()],
|
|
32
30
|
};
|
|
33
31
|
});
|
|
34
32
|
```
|
|
35
33
|
|
|
34
|
+
### 独立插件说明
|
|
35
|
+
|
|
36
|
+
- `uniAutoPages(options)`:负责扫描文件路由并补全 `src/pages.json` 。
|
|
37
|
+
- `uniInject(options)`:负责注入 `App.inject.vue`(或自定义文件)到页面文件。
|
|
38
|
+
|
|
36
39
|
## 报告错误
|
|
37
40
|
|
|
38
41
|
欢迎提交 issue 与我们讨论。
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,248 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let _babel_parser = require("@babel/parser");
|
|
25
|
+
let _vue_compiler_sfc = require("@vue/compiler-sfc");
|
|
26
|
+
let fs = require("fs");
|
|
27
|
+
fs = __toESM(fs);
|
|
28
|
+
let path = require("path");
|
|
29
|
+
path = __toESM(path);
|
|
30
|
+
let magic_string = require("magic-string");
|
|
31
|
+
magic_string = __toESM(magic_string);
|
|
32
|
+
//#region src/inject.ts
|
|
33
|
+
/**
|
|
34
|
+
* 注入代码插件
|
|
35
|
+
*/
|
|
36
|
+
function uniInject(opts) {
|
|
37
|
+
const { path: injectPath = "App.inject.vue" } = opts || {};
|
|
38
|
+
const pageSet = /* @__PURE__ */ new Set();
|
|
39
|
+
let injectTemplate = "";
|
|
40
|
+
let injectScriptSetup = "";
|
|
41
|
+
return {
|
|
42
|
+
name: "vite-plugin-uni-inject",
|
|
43
|
+
enforce: "pre",
|
|
44
|
+
configResolved(config) {
|
|
45
|
+
const srcRoot = path.default.resolve(config.root, "src");
|
|
46
|
+
const injPath = path.default.join(srcRoot, injectPath);
|
|
47
|
+
if (!fs.default.existsSync(injPath)) return;
|
|
48
|
+
const pagesJsonPath = path.default.join(srcRoot, "pages.json");
|
|
49
|
+
if (!fs.default.existsSync(pagesJsonPath)) return;
|
|
50
|
+
const { descriptor } = (0, _vue_compiler_sfc.parse)(fs.default.readFileSync(injPath, "utf-8"));
|
|
51
|
+
if (descriptor.template) injectTemplate = descriptor.template.content.trim();
|
|
52
|
+
if (descriptor.scriptSetup) injectScriptSetup = descriptor.scriptSetup.content.trim();
|
|
53
|
+
const pagesJsonContent = fs.default.readFileSync(pagesJsonPath, "utf-8");
|
|
54
|
+
const pagesJson = JSON.parse(pagesJsonContent);
|
|
55
|
+
const subPackages = pagesJson.subPackages ?? [];
|
|
56
|
+
const collectPages = (pages, rootDir = "") => {
|
|
57
|
+
pages.forEach((page) => {
|
|
58
|
+
const pageVuePath = path.default.join(srcRoot, rootDir, `${page.path}.vue`);
|
|
59
|
+
pageSet.add(path.default.normalize(pageVuePath));
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
collectPages(pagesJson.pages);
|
|
63
|
+
subPackages.forEach((sub) => {
|
|
64
|
+
collectPages(sub.pages, sub.root);
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
transform(code, id) {
|
|
68
|
+
if (!id.endsWith(".vue")) return;
|
|
69
|
+
const normalizedId = path.default.normalize(id);
|
|
70
|
+
if (!pageSet.has(normalizedId)) return;
|
|
71
|
+
let newCode = code;
|
|
72
|
+
const { descriptor } = (0, _vue_compiler_sfc.parse)(code);
|
|
73
|
+
if (injectTemplate && descriptor.template) {
|
|
74
|
+
const tplContent = descriptor.template.content.trim();
|
|
75
|
+
const injectedTemplate = `${injectTemplate}\n${tplContent}`.trim();
|
|
76
|
+
newCode = newCode.slice(0, descriptor.template.loc.start.offset) + `\n${injectedTemplate}\n` + newCode.slice(descriptor.template.loc.end.offset);
|
|
77
|
+
}
|
|
78
|
+
if (injectScriptSetup && descriptor.scriptSetup) {
|
|
79
|
+
const start = descriptor.scriptSetup.loc.start.offset;
|
|
80
|
+
newCode = newCode.slice(0, start) + `\n${injectScriptSetup}` + newCode.slice(start);
|
|
81
|
+
}
|
|
82
|
+
const { descriptor: newDescriptor } = (0, _vue_compiler_sfc.parse)(newCode);
|
|
83
|
+
if (newDescriptor.scriptSetup) {
|
|
84
|
+
const start = newDescriptor.scriptSetup.loc.start.offset;
|
|
85
|
+
const end = newDescriptor.scriptSetup.loc.end.offset;
|
|
86
|
+
const scriptCode = newDescriptor.scriptSetup.content;
|
|
87
|
+
const s = new magic_string.default(scriptCode);
|
|
88
|
+
const ast = (0, _babel_parser.parse)(scriptCode, {
|
|
89
|
+
sourceType: "module",
|
|
90
|
+
plugins: ["typescript"]
|
|
91
|
+
});
|
|
92
|
+
const imports = [];
|
|
93
|
+
for (const node of ast.program.body) if (node.type === "ImportDeclaration") {
|
|
94
|
+
imports.push(scriptCode.slice(node.start, node.end));
|
|
95
|
+
s.remove(node.start, node.end + 1);
|
|
96
|
+
}
|
|
97
|
+
if (imports.length) s.prepend("\n" + imports.join("\n"));
|
|
98
|
+
newCode = newCode.slice(0, start) + s.toString() + newCode.slice(end);
|
|
99
|
+
}
|
|
100
|
+
return { code: newCode };
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/auto-pages.ts
|
|
106
|
+
function toPosixPath(value) {
|
|
107
|
+
return value.replace(/\\/g, "/");
|
|
108
|
+
}
|
|
109
|
+
function normalizeDir(value) {
|
|
110
|
+
return toPosixPath(value).replace(/^\/+|\/+$/g, "");
|
|
111
|
+
}
|
|
112
|
+
function walkFiles(dirPath, filePaths = []) {
|
|
113
|
+
if (!fs.default.existsSync(dirPath)) return filePaths;
|
|
114
|
+
const entries = fs.default.readdirSync(dirPath, { withFileTypes: true });
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
const fullPath = path.default.join(dirPath, entry.name);
|
|
117
|
+
if (entry.isDirectory()) {
|
|
118
|
+
walkFiles(fullPath, filePaths);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
filePaths.push(fullPath);
|
|
122
|
+
}
|
|
123
|
+
return filePaths;
|
|
124
|
+
}
|
|
125
|
+
function collectFileRoutes(srcRoot, routeDirs) {
|
|
126
|
+
const routeSet = /* @__PURE__ */ new Set();
|
|
127
|
+
routeDirs.forEach((dir) => {
|
|
128
|
+
walkFiles(path.default.resolve(srcRoot, dir)).forEach((filePath) => {
|
|
129
|
+
if (!filePath.endsWith(".vue")) return;
|
|
130
|
+
const noExt = path.default.relative(srcRoot, filePath).slice(0, -4);
|
|
131
|
+
routeSet.add(toPosixPath(noExt));
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
return Array.from(routeSet).sort();
|
|
135
|
+
}
|
|
136
|
+
function getScanDirs(dir, subPackages) {
|
|
137
|
+
const dirSet = /* @__PURE__ */ new Set();
|
|
138
|
+
const normalizedMainDir = normalizeDir(dir);
|
|
139
|
+
if (normalizedMainDir) dirSet.add(normalizedMainDir);
|
|
140
|
+
subPackages.forEach((subRoot) => {
|
|
141
|
+
const normalizedSubRoot = normalizeDir(subRoot);
|
|
142
|
+
if (!normalizedSubRoot) return;
|
|
143
|
+
if (normalizedMainDir) {
|
|
144
|
+
dirSet.add(`${normalizedSubRoot}/${normalizedMainDir}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
dirSet.add(normalizedSubRoot);
|
|
148
|
+
});
|
|
149
|
+
return Array.from(dirSet);
|
|
150
|
+
}
|
|
151
|
+
function readPagesJson(pagesJsonPath) {
|
|
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]));
|
|
158
|
+
return routes.map((route) => {
|
|
159
|
+
return existingMap.get(route) ?? { path: route };
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function hasPagesJsonChanged(current, next) {
|
|
163
|
+
return JSON.stringify({
|
|
164
|
+
pages: next.pages,
|
|
165
|
+
subPackages: next.subPackages ?? []
|
|
166
|
+
}) !== JSON.stringify({
|
|
167
|
+
pages: current.pages ?? [],
|
|
168
|
+
subPackages: current.subPackages ?? []
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function getPagesByFileRoute(routes, pagesJson, subPackageRoots) {
|
|
172
|
+
const mainRoutes = [];
|
|
173
|
+
const subRouteMap = /* @__PURE__ */ new Map();
|
|
174
|
+
const normalizedSubRoots = subPackageRoots.map(normalizeDir);
|
|
175
|
+
for (const route of routes) {
|
|
176
|
+
const matchedRoot = normalizedSubRoots.find((root) => {
|
|
177
|
+
return route.startsWith(root + "/");
|
|
178
|
+
});
|
|
179
|
+
if (matchedRoot) {
|
|
180
|
+
const subPath = route.slice(matchedRoot.length + 1);
|
|
181
|
+
const list = subRouteMap.get(matchedRoot) ?? [];
|
|
182
|
+
list.push(subPath);
|
|
183
|
+
subRouteMap.set(matchedRoot, list);
|
|
184
|
+
} else mainRoutes.push(route);
|
|
185
|
+
}
|
|
186
|
+
const pages = mergePages(mainRoutes, pagesJson.pages ?? []);
|
|
187
|
+
const existSubs = pagesJson.subPackages ?? [];
|
|
188
|
+
const subPackages = normalizedSubRoots.map((root) => {
|
|
189
|
+
return {
|
|
190
|
+
root,
|
|
191
|
+
pages: mergePages(subRouteMap.get(root) ?? [], existSubs.find((s) => {
|
|
192
|
+
return normalizeDir(s.root) === root;
|
|
193
|
+
})?.pages ?? [])
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
const merged = {
|
|
197
|
+
...pagesJson,
|
|
198
|
+
pages,
|
|
199
|
+
subPackages
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
merged,
|
|
203
|
+
changed: hasPagesJsonChanged(pagesJson, merged)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function resolveDtsFilePath(srcRoot, dts) {
|
|
207
|
+
return path.default.isAbsolute(dts) ? dts : path.default.resolve(srcRoot, dts);
|
|
208
|
+
}
|
|
209
|
+
function buildRouteDts(routes) {
|
|
210
|
+
return [
|
|
211
|
+
"// Auto-generated by vite-plugin-uni-inject. Do not edit.",
|
|
212
|
+
"",
|
|
213
|
+
"/** 提取路径 */",
|
|
214
|
+
"export type ExtractPath<T extends string, Prefix extends string> = T extends `${Prefix}${infer P}` ? P : never;",
|
|
215
|
+
"",
|
|
216
|
+
"/** 路由路径 */",
|
|
217
|
+
"export type RoutePath =",
|
|
218
|
+
routes.map((route) => ` | "/${route}"`).join("\n") + ";",
|
|
219
|
+
""
|
|
220
|
+
].join("\n");
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 自动补全 pages.json 插件
|
|
224
|
+
*/
|
|
225
|
+
function uniAutoPages(opts) {
|
|
226
|
+
const { dir = "pages", subPackages = [], dts = "uni-pages.d.ts" } = opts || {};
|
|
227
|
+
return {
|
|
228
|
+
name: "vite-plugin-uni-auto-pages",
|
|
229
|
+
enforce: "pre",
|
|
230
|
+
configResolved(config) {
|
|
231
|
+
const srcRoot = path.default.resolve(config.root, "src");
|
|
232
|
+
const pagesJsonPath = path.default.join(srcRoot, "pages.json");
|
|
233
|
+
const pagesJson = readPagesJson(pagesJsonPath);
|
|
234
|
+
if (!pagesJson) return;
|
|
235
|
+
const routes = collectFileRoutes(srcRoot, getScanDirs(dir, subPackages));
|
|
236
|
+
const { merged, changed } = getPagesByFileRoute(routes, pagesJson, subPackages);
|
|
237
|
+
if (changed) fs.default.writeFileSync(pagesJsonPath, JSON.stringify(merged, null, 2) + "\n");
|
|
238
|
+
if (dts) {
|
|
239
|
+
const dtsFilePath = resolveDtsFilePath(srcRoot, dts);
|
|
240
|
+
const dtsContent = buildRouteDts(routes);
|
|
241
|
+
fs.default.writeFileSync(dtsFilePath, dtsContent);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
//#endregion
|
|
247
|
+
exports.uniAutoPages = uniAutoPages;
|
|
248
|
+
exports.uniInject = uniInject;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,19 +1,56 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/** 注入插件配置 */
|
|
3
|
+
interface InjectPluginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* 注入的文件路径
|
|
6
|
+
* @default 'src/App.inject.vue'
|
|
7
|
+
*/
|
|
8
|
+
path?: string;
|
|
5
9
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
/** 自动补全 pages 插件配置 */
|
|
11
|
+
interface AutoPagesPluginOptions {
|
|
12
|
+
/**
|
|
13
|
+
* 扫描目录
|
|
14
|
+
* @default 'pages'
|
|
15
|
+
*/
|
|
16
|
+
dir?: string;
|
|
17
|
+
/**
|
|
18
|
+
* 分包目录
|
|
19
|
+
* @default []
|
|
20
|
+
*/
|
|
21
|
+
subPackages?: string[];
|
|
22
|
+
/**
|
|
23
|
+
* 生成类型声明
|
|
24
|
+
* @default 'src/uni-pages.d.ts'
|
|
25
|
+
*/
|
|
26
|
+
dts?: string;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/inject.d.ts
|
|
30
|
+
/**
|
|
31
|
+
* 注入代码插件
|
|
32
|
+
*/
|
|
33
|
+
declare function uniInject(opts?: InjectPluginOptions): {
|
|
34
|
+
name: string;
|
|
35
|
+
enforce: "pre";
|
|
36
|
+
configResolved(config: {
|
|
37
|
+
root: string;
|
|
38
|
+
}): void;
|
|
39
|
+
transform(code: string, id: string): {
|
|
40
|
+
code: string;
|
|
41
|
+
} | undefined;
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/auto-pages.d.ts
|
|
45
|
+
/**
|
|
46
|
+
* 自动补全 pages.json 插件
|
|
47
|
+
*/
|
|
48
|
+
declare function uniAutoPages(opts?: AutoPagesPluginOptions): {
|
|
49
|
+
name: string;
|
|
50
|
+
enforce: "pre";
|
|
51
|
+
configResolved(config: {
|
|
52
|
+
root: string;
|
|
53
|
+
}): void;
|
|
17
54
|
};
|
|
18
|
-
|
|
19
|
-
export {
|
|
55
|
+
//#endregion
|
|
56
|
+
export { uniAutoPages, uniInject };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/** 注入插件配置 */
|
|
3
|
+
interface InjectPluginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* 注入的文件路径
|
|
6
|
+
* @default 'src/App.inject.vue'
|
|
7
|
+
*/
|
|
8
|
+
path?: string;
|
|
9
|
+
}
|
|
10
|
+
/** 自动补全 pages 插件配置 */
|
|
11
|
+
interface AutoPagesPluginOptions {
|
|
12
|
+
/**
|
|
13
|
+
* 扫描目录
|
|
14
|
+
* @default 'pages'
|
|
15
|
+
*/
|
|
16
|
+
dir?: string;
|
|
17
|
+
/**
|
|
18
|
+
* 分包目录
|
|
19
|
+
* @default []
|
|
20
|
+
*/
|
|
21
|
+
subPackages?: string[];
|
|
22
|
+
/**
|
|
23
|
+
* 生成类型声明
|
|
24
|
+
* @default 'src/uni-pages.d.ts'
|
|
25
|
+
*/
|
|
26
|
+
dts?: string;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/inject.d.ts
|
|
30
|
+
/**
|
|
31
|
+
* 注入代码插件
|
|
32
|
+
*/
|
|
33
|
+
declare function uniInject(opts?: InjectPluginOptions): {
|
|
34
|
+
name: string;
|
|
35
|
+
enforce: "pre";
|
|
36
|
+
configResolved(config: {
|
|
37
|
+
root: string;
|
|
38
|
+
}): void;
|
|
39
|
+
transform(code: string, id: string): {
|
|
40
|
+
code: string;
|
|
41
|
+
} | undefined;
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/auto-pages.d.ts
|
|
45
|
+
/**
|
|
46
|
+
* 自动补全 pages.json 插件
|
|
47
|
+
*/
|
|
48
|
+
declare function uniAutoPages(opts?: AutoPagesPluginOptions): {
|
|
49
|
+
name: string;
|
|
50
|
+
enforce: "pre";
|
|
51
|
+
configResolved(config: {
|
|
52
|
+
root: string;
|
|
53
|
+
}): void;
|
|
54
|
+
};
|
|
55
|
+
//#endregion
|
|
56
|
+
export { uniAutoPages, uniInject };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { parse } from "@babel/parser";
|
|
2
|
+
import { parse as parse$1 } from "@vue/compiler-sfc";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import MagicString from "magic-string";
|
|
6
|
+
//#region src/inject.ts
|
|
7
|
+
/**
|
|
8
|
+
* 注入代码插件
|
|
9
|
+
*/
|
|
10
|
+
function uniInject(opts) {
|
|
11
|
+
const { path: injectPath = "App.inject.vue" } = opts || {};
|
|
12
|
+
const pageSet = /* @__PURE__ */ new Set();
|
|
13
|
+
let injectTemplate = "";
|
|
14
|
+
let injectScriptSetup = "";
|
|
15
|
+
return {
|
|
16
|
+
name: "vite-plugin-uni-inject",
|
|
17
|
+
enforce: "pre",
|
|
18
|
+
configResolved(config) {
|
|
19
|
+
const srcRoot = path.resolve(config.root, "src");
|
|
20
|
+
const injPath = path.join(srcRoot, injectPath);
|
|
21
|
+
if (!fs.existsSync(injPath)) return;
|
|
22
|
+
const pagesJsonPath = path.join(srcRoot, "pages.json");
|
|
23
|
+
if (!fs.existsSync(pagesJsonPath)) return;
|
|
24
|
+
const { descriptor } = parse$1(fs.readFileSync(injPath, "utf-8"));
|
|
25
|
+
if (descriptor.template) injectTemplate = descriptor.template.content.trim();
|
|
26
|
+
if (descriptor.scriptSetup) injectScriptSetup = descriptor.scriptSetup.content.trim();
|
|
27
|
+
const pagesJsonContent = fs.readFileSync(pagesJsonPath, "utf-8");
|
|
28
|
+
const pagesJson = JSON.parse(pagesJsonContent);
|
|
29
|
+
const subPackages = pagesJson.subPackages ?? [];
|
|
30
|
+
const collectPages = (pages, rootDir = "") => {
|
|
31
|
+
pages.forEach((page) => {
|
|
32
|
+
const pageVuePath = path.join(srcRoot, rootDir, `${page.path}.vue`);
|
|
33
|
+
pageSet.add(path.normalize(pageVuePath));
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
collectPages(pagesJson.pages);
|
|
37
|
+
subPackages.forEach((sub) => {
|
|
38
|
+
collectPages(sub.pages, sub.root);
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
transform(code, id) {
|
|
42
|
+
if (!id.endsWith(".vue")) return;
|
|
43
|
+
const normalizedId = path.normalize(id);
|
|
44
|
+
if (!pageSet.has(normalizedId)) return;
|
|
45
|
+
let newCode = code;
|
|
46
|
+
const { descriptor } = parse$1(code);
|
|
47
|
+
if (injectTemplate && descriptor.template) {
|
|
48
|
+
const tplContent = descriptor.template.content.trim();
|
|
49
|
+
const injectedTemplate = `${injectTemplate}\n${tplContent}`.trim();
|
|
50
|
+
newCode = newCode.slice(0, descriptor.template.loc.start.offset) + `\n${injectedTemplate}\n` + newCode.slice(descriptor.template.loc.end.offset);
|
|
51
|
+
}
|
|
52
|
+
if (injectScriptSetup && descriptor.scriptSetup) {
|
|
53
|
+
const start = descriptor.scriptSetup.loc.start.offset;
|
|
54
|
+
newCode = newCode.slice(0, start) + `\n${injectScriptSetup}` + newCode.slice(start);
|
|
55
|
+
}
|
|
56
|
+
const { descriptor: newDescriptor } = parse$1(newCode);
|
|
57
|
+
if (newDescriptor.scriptSetup) {
|
|
58
|
+
const start = newDescriptor.scriptSetup.loc.start.offset;
|
|
59
|
+
const end = newDescriptor.scriptSetup.loc.end.offset;
|
|
60
|
+
const scriptCode = newDescriptor.scriptSetup.content;
|
|
61
|
+
const s = new MagicString(scriptCode);
|
|
62
|
+
const ast = parse(scriptCode, {
|
|
63
|
+
sourceType: "module",
|
|
64
|
+
plugins: ["typescript"]
|
|
65
|
+
});
|
|
66
|
+
const imports = [];
|
|
67
|
+
for (const node of ast.program.body) if (node.type === "ImportDeclaration") {
|
|
68
|
+
imports.push(scriptCode.slice(node.start, node.end));
|
|
69
|
+
s.remove(node.start, node.end + 1);
|
|
70
|
+
}
|
|
71
|
+
if (imports.length) s.prepend("\n" + imports.join("\n"));
|
|
72
|
+
newCode = newCode.slice(0, start) + s.toString() + newCode.slice(end);
|
|
73
|
+
}
|
|
74
|
+
return { code: newCode };
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/auto-pages.ts
|
|
80
|
+
function toPosixPath(value) {
|
|
81
|
+
return value.replace(/\\/g, "/");
|
|
82
|
+
}
|
|
83
|
+
function normalizeDir(value) {
|
|
84
|
+
return toPosixPath(value).replace(/^\/+|\/+$/g, "");
|
|
85
|
+
}
|
|
86
|
+
function walkFiles(dirPath, filePaths = []) {
|
|
87
|
+
if (!fs.existsSync(dirPath)) return filePaths;
|
|
88
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
walkFiles(fullPath, filePaths);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
filePaths.push(fullPath);
|
|
96
|
+
}
|
|
97
|
+
return filePaths;
|
|
98
|
+
}
|
|
99
|
+
function collectFileRoutes(srcRoot, routeDirs) {
|
|
100
|
+
const routeSet = /* @__PURE__ */ new Set();
|
|
101
|
+
routeDirs.forEach((dir) => {
|
|
102
|
+
walkFiles(path.resolve(srcRoot, dir)).forEach((filePath) => {
|
|
103
|
+
if (!filePath.endsWith(".vue")) return;
|
|
104
|
+
const noExt = path.relative(srcRoot, filePath).slice(0, -4);
|
|
105
|
+
routeSet.add(toPosixPath(noExt));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
return Array.from(routeSet).sort();
|
|
109
|
+
}
|
|
110
|
+
function getScanDirs(dir, subPackages) {
|
|
111
|
+
const dirSet = /* @__PURE__ */ new Set();
|
|
112
|
+
const normalizedMainDir = normalizeDir(dir);
|
|
113
|
+
if (normalizedMainDir) dirSet.add(normalizedMainDir);
|
|
114
|
+
subPackages.forEach((subRoot) => {
|
|
115
|
+
const normalizedSubRoot = normalizeDir(subRoot);
|
|
116
|
+
if (!normalizedSubRoot) return;
|
|
117
|
+
if (normalizedMainDir) {
|
|
118
|
+
dirSet.add(`${normalizedSubRoot}/${normalizedMainDir}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
dirSet.add(normalizedSubRoot);
|
|
122
|
+
});
|
|
123
|
+
return Array.from(dirSet);
|
|
124
|
+
}
|
|
125
|
+
function readPagesJson(pagesJsonPath) {
|
|
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]));
|
|
132
|
+
return routes.map((route) => {
|
|
133
|
+
return existingMap.get(route) ?? { path: route };
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function hasPagesJsonChanged(current, next) {
|
|
137
|
+
return JSON.stringify({
|
|
138
|
+
pages: next.pages,
|
|
139
|
+
subPackages: next.subPackages ?? []
|
|
140
|
+
}) !== JSON.stringify({
|
|
141
|
+
pages: current.pages ?? [],
|
|
142
|
+
subPackages: current.subPackages ?? []
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function getPagesByFileRoute(routes, pagesJson, subPackageRoots) {
|
|
146
|
+
const mainRoutes = [];
|
|
147
|
+
const subRouteMap = /* @__PURE__ */ new Map();
|
|
148
|
+
const normalizedSubRoots = subPackageRoots.map(normalizeDir);
|
|
149
|
+
for (const route of routes) {
|
|
150
|
+
const matchedRoot = normalizedSubRoots.find((root) => {
|
|
151
|
+
return route.startsWith(root + "/");
|
|
152
|
+
});
|
|
153
|
+
if (matchedRoot) {
|
|
154
|
+
const subPath = route.slice(matchedRoot.length + 1);
|
|
155
|
+
const list = subRouteMap.get(matchedRoot) ?? [];
|
|
156
|
+
list.push(subPath);
|
|
157
|
+
subRouteMap.set(matchedRoot, list);
|
|
158
|
+
} else mainRoutes.push(route);
|
|
159
|
+
}
|
|
160
|
+
const pages = mergePages(mainRoutes, pagesJson.pages ?? []);
|
|
161
|
+
const existSubs = pagesJson.subPackages ?? [];
|
|
162
|
+
const subPackages = normalizedSubRoots.map((root) => {
|
|
163
|
+
return {
|
|
164
|
+
root,
|
|
165
|
+
pages: mergePages(subRouteMap.get(root) ?? [], existSubs.find((s) => {
|
|
166
|
+
return normalizeDir(s.root) === root;
|
|
167
|
+
})?.pages ?? [])
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
const merged = {
|
|
171
|
+
...pagesJson,
|
|
172
|
+
pages,
|
|
173
|
+
subPackages
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
merged,
|
|
177
|
+
changed: hasPagesJsonChanged(pagesJson, merged)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function resolveDtsFilePath(srcRoot, dts) {
|
|
181
|
+
return path.isAbsolute(dts) ? dts : path.resolve(srcRoot, dts);
|
|
182
|
+
}
|
|
183
|
+
function buildRouteDts(routes) {
|
|
184
|
+
return [
|
|
185
|
+
"// Auto-generated by vite-plugin-uni-inject. Do not edit.",
|
|
186
|
+
"",
|
|
187
|
+
"/** 提取路径 */",
|
|
188
|
+
"export type ExtractPath<T extends string, Prefix extends string> = T extends `${Prefix}${infer P}` ? P : never;",
|
|
189
|
+
"",
|
|
190
|
+
"/** 路由路径 */",
|
|
191
|
+
"export type RoutePath =",
|
|
192
|
+
routes.map((route) => ` | "/${route}"`).join("\n") + ";",
|
|
193
|
+
""
|
|
194
|
+
].join("\n");
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 自动补全 pages.json 插件
|
|
198
|
+
*/
|
|
199
|
+
function uniAutoPages(opts) {
|
|
200
|
+
const { dir = "pages", subPackages = [], dts = "uni-pages.d.ts" } = opts || {};
|
|
201
|
+
return {
|
|
202
|
+
name: "vite-plugin-uni-auto-pages",
|
|
203
|
+
enforce: "pre",
|
|
204
|
+
configResolved(config) {
|
|
205
|
+
const srcRoot = path.resolve(config.root, "src");
|
|
206
|
+
const pagesJsonPath = path.join(srcRoot, "pages.json");
|
|
207
|
+
const pagesJson = readPagesJson(pagesJsonPath);
|
|
208
|
+
if (!pagesJson) return;
|
|
209
|
+
const routes = collectFileRoutes(srcRoot, getScanDirs(dir, subPackages));
|
|
210
|
+
const { merged, changed } = getPagesByFileRoute(routes, pagesJson, subPackages);
|
|
211
|
+
if (changed) fs.writeFileSync(pagesJsonPath, JSON.stringify(merged, null, 2) + "\n");
|
|
212
|
+
if (dts) {
|
|
213
|
+
const dtsFilePath = resolveDtsFilePath(srcRoot, dts);
|
|
214
|
+
const dtsContent = buildRouteDts(routes);
|
|
215
|
+
fs.writeFileSync(dtsFilePath, dtsContent);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
export { uniAutoPages, uniInject };
|
package/package.json
CHANGED
|
@@ -1,39 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-uni-inject",
|
|
3
|
-
"type": "module",
|
|
4
|
-
"version": "0.1.0",
|
|
5
3
|
"description": "vite-plugin-uni-inject",
|
|
6
4
|
"author": "Kriac",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"version": "0.3.0",
|
|
7
8
|
"homepage": "https://github.com/Kriac/vite-plugin-uni-inject",
|
|
8
9
|
"repository": {
|
|
9
10
|
"type": "git",
|
|
10
11
|
"url": "git+https://github.com/Kriac/vite-plugin-uni-inject.git"
|
|
11
12
|
},
|
|
12
13
|
"keywords": [
|
|
13
|
-
"vite-plugin"
|
|
14
|
-
"uni-inject"
|
|
14
|
+
"vite-plugin-uni-inject"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
17
|
"lint": "vue-tsc -b --noEmit",
|
|
18
|
-
"build": "
|
|
18
|
+
"build": "tsdown",
|
|
19
19
|
"release": "npm publish --access public"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@vue/compiler-sfc": "3.5.27"
|
|
22
|
+
"@vue/compiler-sfc": "3.5.27",
|
|
23
|
+
"@babel/parser": "7.29.0",
|
|
24
|
+
"magic-string": "0.30.21"
|
|
23
25
|
},
|
|
24
26
|
"devDependencies": {
|
|
25
27
|
"@types/node": "25.0.10",
|
|
26
|
-
"
|
|
28
|
+
"tsdown": "0.21.0",
|
|
27
29
|
"vue-tsc": "3.2.1"
|
|
28
30
|
},
|
|
29
|
-
"license": "MIT",
|
|
30
31
|
"files": [
|
|
31
32
|
"dist"
|
|
32
33
|
],
|
|
33
34
|
"exports": {
|
|
34
35
|
".": {
|
|
35
36
|
"types": "./dist/index.d.ts",
|
|
36
|
-
"import": "./dist/index.
|
|
37
|
+
"import": "./dist/index.mjs",
|
|
37
38
|
"require": "./dist/index.cjs"
|
|
38
39
|
}
|
|
39
40
|
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/** 插件配置 */
|
|
2
|
-
interface PluginOptions {
|
|
3
|
-
/** 要注入的文件路径 - 基于项目 src 目录 */
|
|
4
|
-
injectPath?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
declare function export_default(opts?: PluginOptions): {
|
|
8
|
-
name: string;
|
|
9
|
-
enforce: "pre";
|
|
10
|
-
configResolved(config: {
|
|
11
|
-
root: string;
|
|
12
|
-
}): void;
|
|
13
|
-
transform(code: string, id: string): {
|
|
14
|
-
code: string;
|
|
15
|
-
map: null;
|
|
16
|
-
} | undefined;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export { export_default as default };
|
package/dist/index.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import{parse as d}from"@vue/compiler-sfc";import a from"fs";import o from"path";function k(P){let{injectPath:S="App.inject.vue"}=P||{},g=new Set,p="",l="";return{name:"vite-plugin-uni-inject",enforce:"pre",configResolved(i){let s=o.resolve(i.root,"src"),r=o.join(s,S);if(!a.existsSync(r))return;let e=a.readFileSync(r,"utf-8"),{descriptor:t}=d(e);t.template&&(p=t.template.content.trim()),t.scriptSetup&&(l=t.scriptSetup.content.trim());let n=o.join(s,"pages.json");if(!a.existsSync(n))return;let f=a.readFileSync(n,"utf-8"),m=JSON.parse(f),u=(c,j="")=>{c.forEach(h=>{let v=o.join(s,j,`${h.path}.vue`);g.add(o.normalize(v))})};u(m.pages),(m.subPackages??[]).forEach(c=>{u(c.pages,c.root)})},transform(i,s){if(!s.endsWith(".vue"))return;let r=o.normalize(s);if(!g.has(r))return;let{descriptor:e}=d(i),t=i;if(p&&e.template){let n=e.template.content.trim(),f=`${p}
|
|
2
|
-
${n}`.trim();t=t.slice(0,e.template.loc.start.offset)+`
|
|
3
|
-
${f}
|
|
4
|
-
`+t.slice(e.template.loc.end.offset)}if(l&&e.scriptSetup){let n=e.scriptSetup.loc.start.offset;t=t.slice(0,n)+`
|
|
5
|
-
${l}
|
|
6
|
-
`+t.slice(n)}return{code:t,map:null}}}}export{k as default};
|