vite-plugin-uni-inject 0.2.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 CHANGED
@@ -1,17 +1,15 @@
1
1
  # vite-plugin-uni-inject
2
2
 
3
- 利用 Vite 插件机制,解决 uniapp 项目中无法使用公共组件的问题。
3
+ 利用 Vite 插件机制,实现自动注入代码,解放你的双手。
4
4
 
5
5
  ## 功能特点
6
6
 
7
7
  - 干净的注入任何代码,没有多余的结构。
8
8
  - 支持注入 `<page-meta/>` 这种只能放在页面第一个位置的标签节点。
9
- - 自动识别所有 `page.json` 中的 vue 文件并注入,无需维护注入页面表。
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,3 +1,4 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
1
2
  //#region \0rolldown/runtime.js
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
@@ -28,9 +29,12 @@ let path = require("path");
28
29
  path = __toESM(path);
29
30
  let magic_string = require("magic-string");
30
31
  magic_string = __toESM(magic_string);
31
- //#region src/index.ts
32
- function src_default(opts) {
33
- const { injectPath = "App.inject.vue" } = opts || {};
32
+ //#region src/inject.ts
33
+ /**
34
+ * 注入代码插件
35
+ */
36
+ function uniInject(opts) {
37
+ const { path: injectPath = "App.inject.vue" } = opts || {};
34
38
  const pageSet = /* @__PURE__ */ new Set();
35
39
  let injectTemplate = "";
36
40
  let injectScriptSetup = "";
@@ -79,15 +83,15 @@ function src_default(opts) {
79
83
  if (newDescriptor.scriptSetup) {
80
84
  const start = newDescriptor.scriptSetup.loc.start.offset;
81
85
  const end = newDescriptor.scriptSetup.loc.end.offset;
82
- const code = newDescriptor.scriptSetup.content;
83
- const s = new magic_string.default(code);
84
- const ast = (0, _babel_parser.parse)(code, {
86
+ const scriptCode = newDescriptor.scriptSetup.content;
87
+ const s = new magic_string.default(scriptCode);
88
+ const ast = (0, _babel_parser.parse)(scriptCode, {
85
89
  sourceType: "module",
86
90
  plugins: ["typescript"]
87
91
  });
88
92
  const imports = [];
89
93
  for (const node of ast.program.body) if (node.type === "ImportDeclaration") {
90
- imports.push(code.slice(node.start, node.end));
94
+ imports.push(scriptCode.slice(node.start, node.end));
91
95
  s.remove(node.start, node.end + 1);
92
96
  }
93
97
  if (imports.length) s.prepend("\n" + imports.join("\n"));
@@ -98,4 +102,147 @@ function src_default(opts) {
98
102
  };
99
103
  }
100
104
  //#endregion
101
- module.exports = src_default;
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,12 +1,36 @@
1
1
  //#region src/types.d.ts
2
- /** 插件配置 */
3
- interface PluginOptions {
4
- /** 要注入的文件路径 - 基于项目 src 目录 */
5
- injectPath?: string;
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;
6
27
  }
7
28
  //#endregion
8
- //#region src/index.d.ts
9
- declare function export_default(opts?: PluginOptions): {
29
+ //#region src/inject.d.ts
30
+ /**
31
+ * 注入代码插件
32
+ */
33
+ declare function uniInject(opts?: InjectPluginOptions): {
10
34
  name: string;
11
35
  enforce: "pre";
12
36
  configResolved(config: {
@@ -16,4 +40,17 @@ declare function export_default(opts?: PluginOptions): {
16
40
  code: string;
17
41
  } | undefined;
18
42
  };
19
- export = export_default;
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.d.mts CHANGED
@@ -1,12 +1,36 @@
1
1
  //#region src/types.d.ts
2
- /** 插件配置 */
3
- interface PluginOptions {
4
- /** 要注入的文件路径 - 基于项目 src 目录 */
5
- injectPath?: string;
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;
6
27
  }
7
28
  //#endregion
8
- //#region src/index.d.ts
9
- declare function export_default(opts?: PluginOptions): {
29
+ //#region src/inject.d.ts
30
+ /**
31
+ * 注入代码插件
32
+ */
33
+ declare function uniInject(opts?: InjectPluginOptions): {
10
34
  name: string;
11
35
  enforce: "pre";
12
36
  configResolved(config: {
@@ -17,4 +41,16 @@ declare function export_default(opts?: PluginOptions): {
17
41
  } | undefined;
18
42
  };
19
43
  //#endregion
20
- export { export_default as default };
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 CHANGED
@@ -3,9 +3,12 @@ import { parse as parse$1 } from "@vue/compiler-sfc";
3
3
  import fs from "fs";
4
4
  import path from "path";
5
5
  import MagicString from "magic-string";
6
- //#region src/index.ts
7
- function src_default(opts) {
8
- const { injectPath = "App.inject.vue" } = opts || {};
6
+ //#region src/inject.ts
7
+ /**
8
+ * 注入代码插件
9
+ */
10
+ function uniInject(opts) {
11
+ const { path: injectPath = "App.inject.vue" } = opts || {};
9
12
  const pageSet = /* @__PURE__ */ new Set();
10
13
  let injectTemplate = "";
11
14
  let injectScriptSetup = "";
@@ -54,15 +57,15 @@ function src_default(opts) {
54
57
  if (newDescriptor.scriptSetup) {
55
58
  const start = newDescriptor.scriptSetup.loc.start.offset;
56
59
  const end = newDescriptor.scriptSetup.loc.end.offset;
57
- const code = newDescriptor.scriptSetup.content;
58
- const s = new MagicString(code);
59
- const ast = parse(code, {
60
+ const scriptCode = newDescriptor.scriptSetup.content;
61
+ const s = new MagicString(scriptCode);
62
+ const ast = parse(scriptCode, {
60
63
  sourceType: "module",
61
64
  plugins: ["typescript"]
62
65
  });
63
66
  const imports = [];
64
67
  for (const node of ast.program.body) if (node.type === "ImportDeclaration") {
65
- imports.push(code.slice(node.start, node.end));
68
+ imports.push(scriptCode.slice(node.start, node.end));
66
69
  s.remove(node.start, node.end + 1);
67
70
  }
68
71
  if (imports.length) s.prepend("\n" + imports.join("\n"));
@@ -73,4 +76,146 @@ function src_default(opts) {
73
76
  };
74
77
  }
75
78
  //#endregion
76
- export { src_default as default };
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
@@ -4,7 +4,7 @@
4
4
  "author": "Kriac",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
- "version": "0.2.0",
7
+ "version": "0.3.0",
8
8
  "homepage": "https://github.com/Kriac/vite-plugin-uni-inject",
9
9
  "repository": {
10
10
  "type": "git",