@mpilk/mp-route 2.0.0 → 3.0.1

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 ADDED
@@ -0,0 +1,76 @@
1
+ # @mpilk/mp-route
2
+
3
+ 用于根据 route 和 tabbar 配置文件生成 uni-app 的 `pages.json` 路由配置。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -D @mpilk/mp-route
9
+ ```
10
+
11
+ `@mpilk/mp-route` 依赖 `@mpilk/mp-shared`,安装时会自动安装共享工具包。
12
+
13
+ ## CLI 用法
14
+
15
+ ```bash
16
+ mp-route --help
17
+ mp-route build
18
+ mp-route watch
19
+ ```
20
+
21
+ 也可以使用别名命令:
22
+
23
+ ```bash
24
+ uni-route --help
25
+ urc --help
26
+ ```
27
+
28
+ ## Route 配置
29
+
30
+ ```ts
31
+ import { defineMiniRouter } from '@mpilk/mp-route'
32
+
33
+ export default defineMiniRouter({
34
+ title: '首页',
35
+ path: 'pages/home/index',
36
+ isIndex: true
37
+ })
38
+ ```
39
+
40
+ ## TabBar 配置
41
+
42
+ ```ts
43
+ import { defineMiniTabbar } from '@mpilk/mp-route'
44
+
45
+ export default defineMiniTabbar({
46
+ color: '#666666',
47
+ selectedColor: '#007aff',
48
+ backgroundColor: '#ffffff',
49
+ list: [
50
+ {
51
+ pagePath: 'pages/home/index',
52
+ text: '首页'
53
+ }
54
+ ]
55
+ })
56
+ ```
57
+
58
+ ## 库 API
59
+
60
+ ```ts
61
+ import {
62
+ builder,
63
+ builderWithWatch,
64
+ defineMiniRouter,
65
+ defineMiniTabbar
66
+ } from '@mpilk/mp-route'
67
+ ```
68
+
69
+ ## 包格式
70
+
71
+ 当前包同时提供 ESM 和 CommonJS 产物:
72
+
73
+ - ESM:`dist/index.js`
74
+ - CommonJS:`dist/index.cjs`
75
+ - 类型声明:`dist/index.d.ts`
76
+ - CLI 入口:`bin/cli.js`
package/bin/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict'
3
- import '../dist/cli.js'
3
+ require('../dist/cli.cjs')
@@ -1,238 +1,262 @@
1
- import path from 'node:path';
2
- import fs from 'node:fs';
3
- import pino from 'pino';
4
- import { loadFileContent, loadJson, outputFile } from '@vsilk/mp-shared';
5
- import { isEmpty, isArray, isPlainObject } from 'lodash-es';
6
- import chokidar from 'chokidar';
7
-
8
- // src/builder.ts
9
- var _indexPath = "";
10
- var logger = pino({
11
- transport: {
12
- target: "pino-pretty",
13
- options: {
14
- colorize: true
15
- // ignore: 'hostname,pid',
16
- }
17
- }
18
- });
19
- var verboseLogger = logger.child({ verbose: true });
20
- function builderWithWatch(options) {
21
- let timer = null;
22
- function run() {
23
- if (timer)
24
- clearTimeout(timer);
25
- timer = setTimeout(() => {
26
- timer = null;
27
- return builder(options);
28
- }, options?.delay || 200);
29
- }
30
- return chokidar.watch([
31
- `src/**/${options?.routeName || "route"}.{ts,js}`,
32
- `src/${options?.tabbarName || "tabbar"}.{ts,js}`
33
- ]).on("all", (eventName) => {
34
- switch (eventName) {
35
- case "add":
36
- case "change":
37
- case "unlink":
38
- run();
39
- break;
40
- }
41
- });
42
- }
43
- async function builder(options = {}) {
44
- const { input = "./src", routeName = "route", tabbarName = "tabbar", pagesPath = "./src/pages.json", verbose } = options;
45
- logger.info("\u5F00\u59CB\u751F\u6210\u8DEF\u7531\u6587\u4EF6...");
46
- const filePaths = loadDirFiles(input, routeName);
47
- logger.info(`\u6839\u636E ${routeName} \u627E\u5230 ${filePaths.length} \u4E2A\u6587\u4EF6`);
48
- const routeConfigs = [];
49
- for (const filePath of filePaths) {
50
- verbose && verboseLogger.info(`\u5F00\u59CB\u89E3\u6790\u6587\u4EF6 ${filePath}`);
51
- let content = await loadFileContent(filePath);
52
- if (!content || typeof content !== "object" || isEmpty(content)) {
53
- verbose && verboseLogger.info(`\u6587\u4EF6 ${filePath} \u4E3A\u7A7A\uFF0C\u8DF3\u8FC7`);
54
- continue;
55
- }
56
- if (!isArray(content))
57
- content = [content];
58
- verbose && verboseLogger.info(`\u6587\u4EF6 ${filePath} \u89E3\u6790\u5B8C\u6210\uFF1A${JSON.stringify(content)}`);
59
- routeConfigs.push(...content);
60
- }
61
- const rs = buildRoute(routeConfigs);
62
- if (_indexPath) {
63
- const idx2 = rs.pages.findIndex((page) => page.path === _indexPath);
64
- if (idx2 !== -1)
65
- rs.pages.unshift(...rs.pages.splice(idx2, 1));
66
- }
67
- logger.info("\u8DEF\u7531\u6587\u4EF6\u89E3\u6790\u5B8C\u6210");
68
- if (!rs.condition.list.length)
69
- rs.condition = void 0;
70
- else if (rs.condition.list.length && rs.condition.current === -1)
71
- rs.condition.current = 0;
72
- const idx = rs.pages.findIndex((page) => page["_index"]);
73
- if (idx !== -1)
74
- rs.pages.unshift(...rs.pages.splice(idx, 1));
75
- let tabbarObj;
76
- const [tabbarPath] = loadDirFiles("./src", tabbarName);
77
- if (tabbarPath && !isEmpty(tabbarPath)) {
78
- logger.info(`\u6839\u636E ${tabbarName} \u627E\u52301\u4E2A\u6587\u4EF6`);
79
- tabbarObj = await loadFileContent(tabbarPath);
80
- if (tabbarObj && !isEmpty(tabbarObj))
81
- rs.tabBar = tabbarObj;
82
- }
83
- const {
84
- debug: _debug = false,
85
- pages,
86
- subPackages,
87
- preloadRule,
88
- condition,
89
- tabBar,
90
- ...otherProps
91
- } = await loadJson(pagesPath);
92
- const writeContent = {
93
- debug: _debug,
94
- pages: rs.pages || pages,
95
- subPackages: rs.subPackages || subPackages,
96
- preloadRule: rs.preloadRule || preloadRule,
97
- condition: rs.condition || condition,
98
- tabBar: tabbarObj || tabBar,
99
- ...otherProps
100
- };
101
- await outputFile(pagesPath, JSON.stringify(writeContent, null, 2));
102
- logger.info("\u8DEF\u7531\u6587\u4EF6\u751F\u6210\u5B8C\u6210\uFF01");
103
- }
104
- function buildRoute(content, ext = {}) {
105
- const result = {
106
- pages: [],
107
- subPackages: [],
108
- preloadRule: {},
109
- condition: {
110
- current: -1,
111
- list: []
112
- }
113
- };
114
- const { parentName = "", parentPath = "", subpackageRoot = "" } = ext;
115
- for (const route of content) {
116
- const { subPackages = false, isSubpackage = subPackages, path: path2, title, name, children } = route;
117
- const style = {
118
- navigationBarTitleText: title || name || parentName,
119
- ...route.style
120
- };
121
- const _path = pathJoin(parentPath, path2);
122
- if (isSubpackage && !subpackageRoot) {
123
- let { preLoadPath, preLoadNetwork } = route;
124
- const { pages, preloadRule, condition } = buildRoute(children ?? [], {
125
- parentName: style.navigationBarTitleText,
126
- subpackageRoot: _path
127
- });
128
- const subPackage = {
129
- root: _path,
130
- pages
131
- };
132
- result.preloadRule = deepMerge(result.preloadRule, preloadRule);
133
- result.condition = deepMerge(result.condition, condition);
134
- if (condition.current > -1)
135
- result.condition.current = result.condition?.list?.indexOf(condition.list[condition.current]);
136
- result.subPackages.push(subPackage);
137
- if (preLoadPath) {
138
- if (!isArray(preLoadPath))
139
- preLoadPath = [preLoadPath];
140
- const preLoadRule = preLoadPath.reduce((acc, cur) => {
141
- acc[pathJoin(cur)] = {
142
- network: preLoadNetwork,
143
- packages: [_path]
144
- };
145
- return acc;
146
- }, {});
147
- result.preloadRule = deepMerge(result.preloadRule, preLoadRule);
148
- }
149
- continue;
150
- }
151
- if (children?.length) {
152
- const { pages, subPackages: subPackages2, preloadRule, condition } = buildRoute(
153
- children,
154
- {
155
- parentName: style.navigationBarTitleText,
156
- parentPath: _path,
157
- subpackageRoot
158
- }
159
- );
160
- result.pages = deepMerge(result.pages, pages);
161
- result.subPackages = deepMerge(result.subPackages, subPackages2);
162
- result.preloadRule = deepMerge(result.preloadRule, preloadRule);
163
- result.condition = deepMerge(result.condition, condition);
164
- if (condition.current > -1)
165
- result.condition.current = result.condition?.list?.indexOf(condition.list[condition.current]);
166
- } else {
167
- const { conditionActive, conditionQuery } = route;
168
- const item = {
169
- path: _path,
170
- style
171
- };
172
- route.isIndex && (_indexPath = _path);
173
- result.pages.push(item);
174
- if (conditionQuery || conditionActive) {
175
- const conditionItem = {
176
- name: style.navigationBarTitleText,
177
- path: _path,
178
- query: conditionQuery
179
- };
180
- result.condition.list.push(conditionItem);
181
- if (conditionActive)
182
- result.condition.current = result.condition.list?.indexOf(conditionItem);
183
- }
184
- }
185
- }
186
- return result;
187
- }
188
- var extension = [".ts", ".js", ".mjs", ".json"];
189
- function loadDirFiles(roots, name, exitByFirst = false) {
190
- if (!Array.isArray(roots))
191
- roots = [roots];
192
- const files = [];
193
- for (let root of roots) {
194
- root = path.resolve(root);
195
- if (!fs.existsSync(root))
196
- continue;
197
- const stat = fs.statSync(root);
198
- const regex = new RegExp(`${name}(${extension.join("|")})$`);
199
- if (stat.isFile() && regex.test(root)) {
200
- files.push(root);
201
- if (exitByFirst)
202
- break;
203
- }
204
- if (stat.isDirectory()) {
205
- const dirents = fs.readdirSync(root).map((o) => path.resolve(root, o));
206
- files.push(...loadDirFiles(dirents, name));
207
- }
208
- }
209
- return files;
210
- }
211
- function pathJoin(...paths) {
212
- return paths.join("/").split("/").filter(Boolean).join("/");
213
- }
214
- function deepMerge(target, ...sources) {
215
- if (typeof target !== "object" || target === null)
216
- target = {};
217
- if (!sources.length)
218
- return target;
219
- for (const source of sources) {
220
- if (typeof source !== "object" || source === null)
221
- continue;
222
- if (isArray(target) && isArray(source)) {
223
- const set = /* @__PURE__ */ new Set([...target, ...source]);
224
- return [...set];
225
- } else if (isPlainObject(target) && isPlainObject(source)) {
226
- for (const key in source) {
227
- if (key in target && typeof target[key] === "object" && typeof source[key] === "object") {
228
- target[key] = deepMerge(target[key], source[key]);
229
- } else {
230
- target[key] = source[key] || target[key];
231
- }
232
- }
233
- }
234
- }
235
- return target;
236
- }
237
-
238
- export { builder, builderWithWatch };
1
+ import path from 'node:path'
2
+ import fs from 'node:fs'
3
+ import pino from 'pino'
4
+ import { loadFileContent, loadJson, outputFile } from '@mpilk/mp-shared'
5
+ import { isEmpty, isArray, isPlainObject } from 'lodash-es'
6
+ import chokidar from 'chokidar'
7
+
8
+ // src/builder.ts
9
+ var _indexPath = ''
10
+ var logger = pino({
11
+ transport: {
12
+ target: 'pino-pretty',
13
+ options: {
14
+ colorize: true
15
+ // ignore: 'hostname,pid',
16
+ }
17
+ }
18
+ })
19
+ var verboseLogger = logger.child({ verbose: true })
20
+ function builderWithWatch(options) {
21
+ let timer = null
22
+ function run() {
23
+ if (timer) clearTimeout(timer)
24
+ timer = setTimeout(() => {
25
+ timer = null
26
+ return builder(options)
27
+ }, options?.delay || 200)
28
+ }
29
+ return chokidar
30
+ .watch([
31
+ `src/**/${options?.routeName || 'route'}.{ts,js}`,
32
+ `src/${options?.tabbarName || 'tabbar'}.{ts,js}`
33
+ ])
34
+ .on('all', (eventName) => {
35
+ switch (eventName) {
36
+ case 'add':
37
+ case 'change':
38
+ case 'unlink':
39
+ run()
40
+ break
41
+ }
42
+ })
43
+ }
44
+ async function builder(options = {}) {
45
+ const {
46
+ input = './src',
47
+ routeName = 'route',
48
+ tabbarName = 'tabbar',
49
+ pagesPath = './src/pages.json',
50
+ verbose
51
+ } = options
52
+ logger.info('\u5F00\u59CB\u751F\u6210\u8DEF\u7531\u6587\u4EF6...')
53
+ const filePaths = loadDirFiles(input, routeName)
54
+ logger.info(
55
+ `\u6839\u636E ${routeName} \u627E\u5230 ${filePaths.length} \u4E2A\u6587\u4EF6`
56
+ )
57
+ const routeConfigs = []
58
+ for (const filePath of filePaths) {
59
+ verbose &&
60
+ verboseLogger.info(`\u5F00\u59CB\u89E3\u6790\u6587\u4EF6 ${filePath}`)
61
+ let content = await loadFileContent(filePath)
62
+ if (!content || typeof content !== 'object' || isEmpty(content)) {
63
+ verbose &&
64
+ verboseLogger.info(
65
+ `\u6587\u4EF6 ${filePath} \u4E3A\u7A7A\uFF0C\u8DF3\u8FC7`
66
+ )
67
+ continue
68
+ }
69
+ if (!isArray(content)) content = [content]
70
+ verbose &&
71
+ verboseLogger.info(
72
+ `\u6587\u4EF6 ${filePath} \u89E3\u6790\u5B8C\u6210\uFF1A${JSON.stringify(
73
+ content
74
+ )}`
75
+ )
76
+ routeConfigs.push(...content)
77
+ }
78
+ const rs = buildRoute(routeConfigs)
79
+ if (_indexPath) {
80
+ const idx2 = rs.pages.findIndex((page) => page.path === _indexPath)
81
+ if (idx2 !== -1) rs.pages.unshift(...rs.pages.splice(idx2, 1))
82
+ }
83
+ logger.info('\u8DEF\u7531\u6587\u4EF6\u89E3\u6790\u5B8C\u6210')
84
+ if (!rs.condition.list.length) rs.condition = void 0
85
+ else if (rs.condition.list.length && rs.condition.current === -1)
86
+ rs.condition.current = 0
87
+ const idx = rs.pages.findIndex((page) => page['_index'])
88
+ if (idx !== -1) rs.pages.unshift(...rs.pages.splice(idx, 1))
89
+ let tabbarObj
90
+ const [tabbarPath] = loadDirFiles('./src', tabbarName)
91
+ if (tabbarPath && !isEmpty(tabbarPath)) {
92
+ logger.info(`\u6839\u636E ${tabbarName} \u627E\u52301\u4E2A\u6587\u4EF6`)
93
+ tabbarObj = await loadFileContent(tabbarPath)
94
+ if (tabbarObj && !isEmpty(tabbarObj)) rs.tabBar = tabbarObj
95
+ }
96
+ const {
97
+ debug: _debug = false,
98
+ pages,
99
+ subPackages,
100
+ preloadRule,
101
+ condition,
102
+ tabBar,
103
+ ...otherProps
104
+ } = await loadJson(pagesPath)
105
+ const writeContent = {
106
+ debug: _debug,
107
+ pages: rs.pages || pages,
108
+ subPackages: rs.subPackages || subPackages,
109
+ preloadRule: rs.preloadRule || preloadRule,
110
+ condition: rs.condition || condition,
111
+ tabBar: tabbarObj || tabBar,
112
+ ...otherProps
113
+ }
114
+ await outputFile(pagesPath, JSON.stringify(writeContent, null, 2))
115
+ logger.info('\u8DEF\u7531\u6587\u4EF6\u751F\u6210\u5B8C\u6210\uFF01')
116
+ }
117
+ function buildRoute(content, ext = {}) {
118
+ const result = {
119
+ pages: [],
120
+ subPackages: [],
121
+ preloadRule: {},
122
+ condition: {
123
+ current: -1,
124
+ list: []
125
+ }
126
+ }
127
+ const { parentName = '', parentPath = '', subpackageRoot = '' } = ext
128
+ for (const route of content) {
129
+ const {
130
+ subPackages = false,
131
+ isSubpackage = subPackages,
132
+ path: path2,
133
+ title,
134
+ name,
135
+ children
136
+ } = route
137
+ const style = {
138
+ navigationBarTitleText: title || name || parentName,
139
+ ...route.style
140
+ }
141
+ const _path = pathJoin(parentPath, path2)
142
+ if (isSubpackage && !subpackageRoot) {
143
+ let { preLoadPath, preLoadNetwork } = route
144
+ const { pages, preloadRule, condition } = buildRoute(children ?? [], {
145
+ parentName: style.navigationBarTitleText,
146
+ subpackageRoot: _path
147
+ })
148
+ const subPackage = {
149
+ root: _path,
150
+ pages
151
+ }
152
+ result.preloadRule = deepMerge(result.preloadRule, preloadRule)
153
+ result.condition = deepMerge(result.condition, condition)
154
+ if (condition.current > -1)
155
+ result.condition.current = result.condition?.list?.indexOf(
156
+ condition.list[condition.current]
157
+ )
158
+ result.subPackages.push(subPackage)
159
+ if (preLoadPath) {
160
+ if (!isArray(preLoadPath)) preLoadPath = [preLoadPath]
161
+ const preLoadRule = preLoadPath.reduce((acc, cur) => {
162
+ acc[pathJoin(cur)] = {
163
+ network: preLoadNetwork,
164
+ packages: [_path]
165
+ }
166
+ return acc
167
+ }, {})
168
+ result.preloadRule = deepMerge(result.preloadRule, preLoadRule)
169
+ }
170
+ continue
171
+ }
172
+ if (children?.length) {
173
+ const {
174
+ pages,
175
+ subPackages: subPackages2,
176
+ preloadRule,
177
+ condition
178
+ } = buildRoute(children, {
179
+ parentName: style.navigationBarTitleText,
180
+ parentPath: _path,
181
+ subpackageRoot
182
+ })
183
+ result.pages = deepMerge(result.pages, pages)
184
+ result.subPackages = deepMerge(result.subPackages, subPackages2)
185
+ result.preloadRule = deepMerge(result.preloadRule, preloadRule)
186
+ result.condition = deepMerge(result.condition, condition)
187
+ if (condition.current > -1)
188
+ result.condition.current = result.condition?.list?.indexOf(
189
+ condition.list[condition.current]
190
+ )
191
+ } else {
192
+ const { conditionActive, conditionQuery } = route
193
+ const item = {
194
+ path: _path,
195
+ style
196
+ }
197
+ route.isIndex && (_indexPath = _path)
198
+ result.pages.push(item)
199
+ if (conditionQuery || conditionActive) {
200
+ const conditionItem = {
201
+ name: style.navigationBarTitleText,
202
+ path: _path,
203
+ query: conditionQuery
204
+ }
205
+ result.condition.list.push(conditionItem)
206
+ if (conditionActive)
207
+ result.condition.current =
208
+ result.condition.list?.indexOf(conditionItem)
209
+ }
210
+ }
211
+ }
212
+ return result
213
+ }
214
+ var extension = ['.ts', '.js', '.mjs', '.json']
215
+ function loadDirFiles(roots, name, exitByFirst = false) {
216
+ if (!Array.isArray(roots)) roots = [roots]
217
+ const files = []
218
+ for (let root of roots) {
219
+ root = path.resolve(root)
220
+ if (!fs.existsSync(root)) continue
221
+ const stat = fs.statSync(root)
222
+ const regex = new RegExp(`${name}(${extension.join('|')})$`)
223
+ if (stat.isFile() && regex.test(root)) {
224
+ files.push(root)
225
+ if (exitByFirst) break
226
+ }
227
+ if (stat.isDirectory()) {
228
+ const dirents = fs.readdirSync(root).map((o) => path.resolve(root, o))
229
+ files.push(...loadDirFiles(dirents, name))
230
+ }
231
+ }
232
+ return files
233
+ }
234
+ function pathJoin(...paths) {
235
+ return paths.join('/').split('/').filter(Boolean).join('/')
236
+ }
237
+ function deepMerge(target, ...sources) {
238
+ if (typeof target !== 'object' || target === null) target = {}
239
+ if (!sources.length) return target
240
+ for (const source of sources) {
241
+ if (typeof source !== 'object' || source === null) continue
242
+ if (isArray(target) && isArray(source)) {
243
+ const set = /* @__PURE__ */ new Set([...target, ...source])
244
+ return [...set]
245
+ } else if (isPlainObject(target) && isPlainObject(source)) {
246
+ for (const key in source) {
247
+ if (
248
+ key in target &&
249
+ typeof target[key] === 'object' &&
250
+ typeof source[key] === 'object'
251
+ ) {
252
+ target[key] = deepMerge(target[key], source[key])
253
+ } else {
254
+ target[key] = source[key] || target[key]
255
+ }
256
+ }
257
+ }
258
+ }
259
+ return target
260
+ }
261
+
262
+ export { builder, builderWithWatch }