@bams-app/ui-dev-server 0.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/bin/build-app.js +44 -0
- package/bin/build-umd.js +96 -0
- package/bin/ui-dev-server.js +45 -0
- package/config-utils.js +105 -0
- package/empty-entry.js +1 -0
- package/package.json +106 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +41 -0
- package/src/App.vue +56 -0
- package/src/main.js +34 -0
- package/src/router/index.js +30 -0
- package/src/themeConfig.js +32 -0
- package/src/useLayout.js +15 -0
- package/src/views/ConfigView.vue +36 -0
- package/src/views/HomeView.vue +9 -0
- package/src/views/LoginView.vue +22 -0
- package/utils.js +466 -0
- package/vue.config.base.js +75 -0
- package/vue.config.build.js +35 -0
- package/vue.config.dev.js +44 -0
- package/vue.config.js +20 -0
- package/vue.config.umd.js +31 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<UiLoginComponent :callback-url="callbackUrl" :sys-code="sysCode" @login-success="onLoginSuccess" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup>
|
|
6
|
+
import { computed } from "vue";
|
|
7
|
+
import { useRoute } from "vue-router";
|
|
8
|
+
import { auth } from "@bams-app/utils";
|
|
9
|
+
// 编译阶段根据环境变量通过通用插槽别名选择组件
|
|
10
|
+
import UiLoginComponent from "@configurable/login";
|
|
11
|
+
|
|
12
|
+
const route = useRoute();
|
|
13
|
+
const sysCode = computed(() => auth.getSysCode());
|
|
14
|
+
const callbackUrl = computed(() => route.query.callback_url || "/");
|
|
15
|
+
console.log("回调地址:", decodeURIComponent(callbackUrl.value));
|
|
16
|
+
|
|
17
|
+
const onLoginSuccess = ({ token, userData }) => {
|
|
18
|
+
console.log("登录成功回调", { token, userData });
|
|
19
|
+
};
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<style scoped></style>
|
package/utils.js
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
/** 启用颜色输出的条件 */
|
|
5
|
+
const ENABLE_COLOR = process.stdout.isTTY && process.env.NO_COLOR === undefined;
|
|
6
|
+
/** ANSI 颜色码映射 */
|
|
7
|
+
const ANSI = {
|
|
8
|
+
reset: '\x1b[0m',
|
|
9
|
+
bold: '\x1b[1m',
|
|
10
|
+
red: '\x1b[31m',
|
|
11
|
+
green: '\x1b[32m',
|
|
12
|
+
yellow: '\x1b[33m',
|
|
13
|
+
blue: '\x1b[34m',
|
|
14
|
+
cyan: '\x1b[36m',
|
|
15
|
+
gray: '\x1b[90m'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 获取项目根目录
|
|
20
|
+
* - 优先使用 WORK_PROJECT_ROOT 环境变量(由 work-cli 注入)
|
|
21
|
+
* - 回退为包目录上溯三层(仓库内 work/cli/ui-dev-server 或作为依赖安装时均指向项目根)
|
|
22
|
+
* @returns {string} 项目根目录
|
|
23
|
+
*/
|
|
24
|
+
function getRootDir() {
|
|
25
|
+
return process.env.WORK_PROJECT_ROOT || path.resolve(__dirname, '../../../');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 加载项目根 package.json
|
|
30
|
+
* @param {string} [rootDir] - 项目根目录,缺省时使用 getRootDir()
|
|
31
|
+
* @returns {Object} package.json 内容
|
|
32
|
+
*/
|
|
33
|
+
function getProjectPackageJson(rootDir) {
|
|
34
|
+
const projectDir = rootDir || getRootDir();
|
|
35
|
+
return require(path.join(projectDir, 'package.json'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 判断目标路径是否为目录
|
|
40
|
+
* @param {string} targetPath - 目标路径
|
|
41
|
+
* @returns {boolean} 是否为目录
|
|
42
|
+
*/
|
|
43
|
+
function isDirectory(targetPath) {
|
|
44
|
+
return fs.existsSync(targetPath) && fs.statSync(targetPath).isDirectory();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 标准化路径用于显示(统一使用 / 分隔符)
|
|
49
|
+
* @param {string} targetPath - 目标路径
|
|
50
|
+
* @returns {string} 标准化后的路径
|
|
51
|
+
*/
|
|
52
|
+
function normalizePathForDisplay(targetPath) {
|
|
53
|
+
return targetPath.split(path.sep).join('/');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 将目录名添加到 scope 映射中
|
|
58
|
+
* @param {Object} scopes - scope 到目录的映射
|
|
59
|
+
* @param {string} dirName - 目录名
|
|
60
|
+
*/
|
|
61
|
+
function addScopeDir(scopes, dirName) {
|
|
62
|
+
// 排除不是 ui 相关的目录
|
|
63
|
+
if (['work', 'bams-components', 'bams-ui'].includes(dirName)) {
|
|
64
|
+
if (!scopes['bams-app']) scopes['bams-app'] = [];
|
|
65
|
+
if (!scopes['bams-app'].includes(dirName)) {
|
|
66
|
+
scopes['bams-app'].push(dirName);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
// 其他目录用目录名作为 scope
|
|
70
|
+
if (!scopes[dirName]) scopes[dirName] = [];
|
|
71
|
+
if (!scopes[dirName].includes(dirName)) {
|
|
72
|
+
scopes[dirName].push(dirName);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 从项目根目录查找 -ui 结尾的目录并补充到 scope 映射
|
|
79
|
+
* @param {Object} scopes - scope 到目录的映射
|
|
80
|
+
* @param {string} rootDir - 项目根目录
|
|
81
|
+
*/
|
|
82
|
+
function appendUiDirsFromRoot(scopes, rootDir) {
|
|
83
|
+
if (!rootDir || !isDirectory(rootDir)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const dirEntries = fs.readdirSync(rootDir, { withFileTypes: true });
|
|
88
|
+
dirEntries.forEach((entry) => {
|
|
89
|
+
if (!entry.isDirectory() || !entry.name.endsWith('-ui')) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
addScopeDir(scopes, entry.name);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 从 workspaces 配置中解析出 scope 和目录映射
|
|
99
|
+
* @param {Object} packageJson - package.json 对象
|
|
100
|
+
* @param {string} rootDir - 项目根目录
|
|
101
|
+
* @returns {Object} scope 到目录的映射
|
|
102
|
+
*/
|
|
103
|
+
function parseScopesFromWorkspaces(packageJson, rootDir) {
|
|
104
|
+
const workspaces = packageJson.workspaces || [];
|
|
105
|
+
const scopes = {};
|
|
106
|
+
|
|
107
|
+
workspaces.forEach(pattern => {
|
|
108
|
+
// 跳过排除项
|
|
109
|
+
if (pattern.startsWith('!')) return;
|
|
110
|
+
|
|
111
|
+
// 处理类似 "pds-ui/*" 或 "pds-ui" 这样的模式
|
|
112
|
+
const match = pattern.match(/^([^/*]+)(\/\*)?$/);
|
|
113
|
+
if (match) {
|
|
114
|
+
addScopeDir(scopes, match[1]);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
appendUiDirsFromRoot(scopes, rootDir);
|
|
119
|
+
|
|
120
|
+
// 默认总是包含 bams-app 的 fallback
|
|
121
|
+
if (!scopes['bams-app']) scopes['bams-app'] = [];
|
|
122
|
+
if (!scopes['bams-app'].includes('work/packages')) {
|
|
123
|
+
scopes['bams-app'].push('work/packages');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return scopes;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 获取所有 workspace 条目
|
|
131
|
+
* @param {Object} packageJson - package.json 对象
|
|
132
|
+
* @param {string} rootDir - 项目根目录
|
|
133
|
+
* @returns {Array<{scope: string, dir: string}>} workspace 条目列表
|
|
134
|
+
*/
|
|
135
|
+
function getWorkspaceEntries(packageJson, rootDir) {
|
|
136
|
+
const scopes = parseScopesFromWorkspaces(packageJson, rootDir);
|
|
137
|
+
const entries = [];
|
|
138
|
+
|
|
139
|
+
for (const [scope, dirs] of Object.entries(scopes)) {
|
|
140
|
+
for (const dir of dirs) {
|
|
141
|
+
entries.push({ scope, dir });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return entries;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 创建组件候选对象
|
|
150
|
+
* @param {string} scope - scope 名称
|
|
151
|
+
* @param {string} dir - 目录路径
|
|
152
|
+
* @param {string} componentDirName - 组件目录名
|
|
153
|
+
* @param {string} componentPath - 组件完整路径
|
|
154
|
+
* @param {string} requestedName - 用户请求的原始组件名
|
|
155
|
+
* @returns {Object} 组件候选对象
|
|
156
|
+
*/
|
|
157
|
+
function createCandidate(scope, dir, componentDirName, componentPath, requestedName) {
|
|
158
|
+
return {
|
|
159
|
+
scope,
|
|
160
|
+
dir,
|
|
161
|
+
requestedName,
|
|
162
|
+
componentDirName,
|
|
163
|
+
componentPath,
|
|
164
|
+
packageName: `@${scope}/${componentDirName}`,
|
|
165
|
+
displayPath: normalizePathForDisplay(path.join(dir, componentDirName))
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 根据组件名在所有 workspaces 中查找候选组件
|
|
171
|
+
* @param {string} componentName - 组件名
|
|
172
|
+
* @param {string} rootDir - 项目根目录
|
|
173
|
+
* @param {Object} packageJson - package.json 对象
|
|
174
|
+
* @returns {Array<Object>} 组件候选列表
|
|
175
|
+
*/
|
|
176
|
+
function findComponentCandidates(componentName, rootDir, packageJson) {
|
|
177
|
+
const candidates = [];
|
|
178
|
+
const seen = new Set();
|
|
179
|
+
const searchNames = [componentName];
|
|
180
|
+
|
|
181
|
+
// 如果没有前缀,自动尝试添加 ui- 和 page- 前缀
|
|
182
|
+
if (!componentName.startsWith('ui-') && !componentName.startsWith('page-')) {
|
|
183
|
+
searchNames.push(`ui-${componentName}`, `page-${componentName}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const { scope, dir } of getWorkspaceEntries(packageJson, rootDir)) {
|
|
187
|
+
for (const searchName of searchNames) {
|
|
188
|
+
const componentPath = path.resolve(rootDir, dir, searchName);
|
|
189
|
+
|
|
190
|
+
// 跳过不存在或不是目录的路径
|
|
191
|
+
if (!isDirectory(componentPath)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const packageName = `@${scope}/${searchName}`;
|
|
196
|
+
// 跳过重复的 packageName
|
|
197
|
+
if (seen.has(packageName)) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
seen.add(packageName);
|
|
202
|
+
candidates.push(createCandidate(scope, dir, searchName, componentPath, componentName));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return candidates;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 根据 componentName 自动识别作用域
|
|
211
|
+
* @param {string} componentName - 组件名
|
|
212
|
+
* @param {string} rootDir - 根目录路径
|
|
213
|
+
* @param {Object} packageJson - package.json 对象
|
|
214
|
+
* @returns {string} 完整的包名
|
|
215
|
+
*/
|
|
216
|
+
function getComponentScope(componentName, rootDir, packageJson) {
|
|
217
|
+
const candidates = findComponentCandidates(componentName, rootDir, packageJson);
|
|
218
|
+
|
|
219
|
+
if (candidates.length > 0) {
|
|
220
|
+
return candidates[0].packageName;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 默认回退到 bams-app
|
|
224
|
+
return `@bams-app/${componentName}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 解析组件名,返回完整的包名或路径
|
|
229
|
+
* @param {string} componentName - 组件名
|
|
230
|
+
* @param {string} rootDir - 根目录路径
|
|
231
|
+
* @param {Object} packageJson - package.json 对象
|
|
232
|
+
* @returns {string} 完整的包名或路径
|
|
233
|
+
*/
|
|
234
|
+
function resolveComponent(componentName, rootDir, packageJson) {
|
|
235
|
+
// 优先识别完整包名(以 @ 开头)
|
|
236
|
+
if (componentName.startsWith('@')) {
|
|
237
|
+
return componentName;
|
|
238
|
+
}
|
|
239
|
+
// 再识别路径(包含 / 但不是包名)
|
|
240
|
+
if (componentName.includes('/')) {
|
|
241
|
+
return path.resolve(rootDir, componentName);
|
|
242
|
+
}
|
|
243
|
+
// 最后尝试自动识别 scope
|
|
244
|
+
return getComponentScope(componentName, rootDir, packageJson);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 从组件名或路径中提取组件入口名(用于前端渲染)
|
|
249
|
+
* @param {string} componentName - 组件名或路径
|
|
250
|
+
* @returns {string} 组件入口名
|
|
251
|
+
*/
|
|
252
|
+
function getComponentEntryName(componentName) {
|
|
253
|
+
if (!componentName) {
|
|
254
|
+
return 'ui-component-demo';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 完整包名:@scope/package-name -> package-name
|
|
258
|
+
if (componentName.startsWith('@')) {
|
|
259
|
+
const [, packageName] = componentName.split('/');
|
|
260
|
+
return packageName || componentName;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 路径形式:/path/to/component -> component
|
|
264
|
+
return path.basename(componentName);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 将 kebab-case 组件名转换为 camelCase(用于前端变量名)
|
|
269
|
+
* @param {string} componentName - 组件名或路径
|
|
270
|
+
* @returns {string} camelCase 组件名
|
|
271
|
+
*/
|
|
272
|
+
function toCamelComponentName(componentName) {
|
|
273
|
+
return getComponentEntryName(componentName).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ==================== CLI 相关辅助方法 ====================
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 给文本添加 ANSI 颜色(仅在 TTY 环境且未禁用颜色时生效)
|
|
280
|
+
* @param {string} text - 原始文本
|
|
281
|
+
* @param {string} color - 颜色名称(对应 ANSI 码键名)
|
|
282
|
+
* @returns {string} 带颜色的文本
|
|
283
|
+
*/
|
|
284
|
+
function colorize(text, color) {
|
|
285
|
+
if (!ENABLE_COLOR || !ANSI[color]) {
|
|
286
|
+
return text;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return `${ANSI[color]}${text}${ANSI.reset}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 解析命令行参数
|
|
294
|
+
* @param {string[]} argv - process.argv
|
|
295
|
+
* @returns {Object} 解析结果
|
|
296
|
+
* @returns {string} returns.componentName - 组件名
|
|
297
|
+
* @returns {string} returns.layout - 布局类型
|
|
298
|
+
* @returns {string[]} returns.vueCliArgs - 传递给 Vue CLI 的参数
|
|
299
|
+
*/
|
|
300
|
+
function parseCliArgs(argv) {
|
|
301
|
+
const args = argv.slice(2);
|
|
302
|
+
let componentName = 'ui-component-demo';
|
|
303
|
+
let layout = 'pc';
|
|
304
|
+
const vueCliArgs = [];
|
|
305
|
+
|
|
306
|
+
let i = 0;
|
|
307
|
+
while (i < args.length) {
|
|
308
|
+
const arg = args[i];
|
|
309
|
+
|
|
310
|
+
// --component 或 -c 参数
|
|
311
|
+
if (arg === '--component' || arg === '-c') {
|
|
312
|
+
componentName = args[i + 1];
|
|
313
|
+
i += 2;
|
|
314
|
+
} else if (arg.startsWith('--component=')) {
|
|
315
|
+
componentName = arg.split('=')[1];
|
|
316
|
+
i++;
|
|
317
|
+
} else if (arg.startsWith('-c=')) {
|
|
318
|
+
componentName = arg.split('=')[1];
|
|
319
|
+
i++;
|
|
320
|
+
// --layout 或 -l 参数
|
|
321
|
+
} else if (arg === '--layout' || arg === '-l') {
|
|
322
|
+
layout = args[i + 1];
|
|
323
|
+
i += 2;
|
|
324
|
+
} else if (arg.startsWith('--layout=')) {
|
|
325
|
+
layout = arg.split('=')[1];
|
|
326
|
+
i++;
|
|
327
|
+
} else if (arg.startsWith('-l=')) {
|
|
328
|
+
layout = arg.split('=')[1];
|
|
329
|
+
i++;
|
|
330
|
+
// Vue CLI 参数(以 -- 开头)
|
|
331
|
+
} else if (arg.startsWith('--')) {
|
|
332
|
+
vueCliArgs.push(arg);
|
|
333
|
+
// 处理带值的选项
|
|
334
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
335
|
+
vueCliArgs.push(args[i + 1]);
|
|
336
|
+
i += 2;
|
|
337
|
+
} else {
|
|
338
|
+
i++;
|
|
339
|
+
}
|
|
340
|
+
// 其他参数:作为组件名
|
|
341
|
+
} else {
|
|
342
|
+
componentName = arg;
|
|
343
|
+
i++;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
componentName,
|
|
349
|
+
layout,
|
|
350
|
+
vueCliArgs
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 判断输入是否为文件系统路径
|
|
356
|
+
* @param {string} input - 输入字符串
|
|
357
|
+
* @returns {boolean} 是否为文件系统路径
|
|
358
|
+
*/
|
|
359
|
+
function isFileSystemPath(input) {
|
|
360
|
+
return path.isAbsolute(input)
|
|
361
|
+
|| input.startsWith('./')
|
|
362
|
+
|| input.startsWith('../')
|
|
363
|
+
|| (input.includes('/') && !input.startsWith('@'));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 动态加载 inquirer(用于交互式选择)
|
|
368
|
+
* @returns {Promise<Object>} inquirer 实例
|
|
369
|
+
*/
|
|
370
|
+
async function loadInquirer() {
|
|
371
|
+
const inquirerModule = await import('inquirer');
|
|
372
|
+
return inquirerModule.default || inquirerModule;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 使用 inquirer 提示用户选择候选组件
|
|
377
|
+
* @param {string} componentInput - 用户输入的原始组件名
|
|
378
|
+
* @param {Array<Object>} candidates - 组件候选列表
|
|
379
|
+
* @returns {Promise<Object>} 用户选中的候选对象
|
|
380
|
+
*/
|
|
381
|
+
async function promptForCandidate(componentInput, candidates) {
|
|
382
|
+
const inquirer = await loadInquirer();
|
|
383
|
+
const prompt = inquirer.createPromptModule ? inquirer.createPromptModule() : inquirer.prompt;
|
|
384
|
+
const choices = candidates.map((candidate) => ({
|
|
385
|
+
name: `${colorize(candidate.packageName, 'cyan')} ${colorize(`(${candidate.displayPath})`, 'gray')}`,
|
|
386
|
+
value: candidate
|
|
387
|
+
}));
|
|
388
|
+
|
|
389
|
+
// 显示提示信息
|
|
390
|
+
console.log(colorize(`[ui-dev-server] 发现多个同名组件 "${componentInput}"`, 'yellow'));
|
|
391
|
+
console.log(colorize('[ui-dev-server] 使用方向键选择,按回车确认。', 'blue'));
|
|
392
|
+
|
|
393
|
+
// 显示选择菜单
|
|
394
|
+
const answers = await prompt([
|
|
395
|
+
{
|
|
396
|
+
type: 'list',
|
|
397
|
+
name: 'selectedCandidate',
|
|
398
|
+
loop: false,
|
|
399
|
+
pageSize: Math.min(Math.max(candidates.length, 2), 10),
|
|
400
|
+
message: colorize('请选择要启动的组件', 'bold'),
|
|
401
|
+
choices
|
|
402
|
+
}
|
|
403
|
+
]);
|
|
404
|
+
|
|
405
|
+
return answers.selectedCandidate;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* 解析用户输入的组件名
|
|
410
|
+
* - 如果是路径或完整包名:直接返回
|
|
411
|
+
* - 如果是短名:在所有 workspaces 中查找,有多个时交互式选择
|
|
412
|
+
* @param {string} componentInput - 用户输入的组件名
|
|
413
|
+
* @param {string} rootDir - 项目根目录
|
|
414
|
+
* @param {Object} packageJson - package.json 对象
|
|
415
|
+
* @returns {Promise<string>} 最终解析后的组件名或路径
|
|
416
|
+
*/
|
|
417
|
+
async function resolveComponentInput(componentInput, rootDir, packageJson) {
|
|
418
|
+
// 路径或完整包名直接返回
|
|
419
|
+
if (!componentInput || isFileSystemPath(componentInput) || componentInput.startsWith('@')) {
|
|
420
|
+
return componentInput;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const candidates = findComponentCandidates(componentInput, rootDir, packageJson);
|
|
424
|
+
|
|
425
|
+
// 只有一个候选:直接返回
|
|
426
|
+
if (candidates.length === 1) {
|
|
427
|
+
return candidates[0].packageName;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// 多个候选:交互式选择
|
|
431
|
+
if (candidates.length > 1) {
|
|
432
|
+
// 非 TTY 环境无法交互:报错并退出
|
|
433
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
434
|
+
console.error(colorize(`[ui-dev-server] 发现多个同名组件 "${componentInput}",当前环境无法交互式选择:`, 'red'));
|
|
435
|
+
candidates.forEach((candidate) => {
|
|
436
|
+
console.error(` - ${candidate.packageName} (${candidate.displayPath})`);
|
|
437
|
+
});
|
|
438
|
+
console.error(colorize('[ui-dev-server] 请改为传入完整包名或绝对路径后重试。', 'yellow'));
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const selected = await promptForCandidate(componentInput, candidates);
|
|
443
|
+
return selected.packageName;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// 没有候选:原样返回
|
|
447
|
+
return componentInput;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
module.exports = {
|
|
451
|
+
getRootDir,
|
|
452
|
+
getProjectPackageJson,
|
|
453
|
+
parseScopesFromWorkspaces,
|
|
454
|
+
getWorkspaceEntries,
|
|
455
|
+
createCandidate,
|
|
456
|
+
findComponentCandidates,
|
|
457
|
+
getComponentScope,
|
|
458
|
+
resolveComponent,
|
|
459
|
+
getComponentEntryName,
|
|
460
|
+
toCamelComponentName,
|
|
461
|
+
colorize,
|
|
462
|
+
parseCliArgs,
|
|
463
|
+
isFileSystemPath,
|
|
464
|
+
promptForCandidate,
|
|
465
|
+
resolveComponentInput
|
|
466
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { merge, applyCommonConfig } = require('./config-utils');
|
|
4
|
+
const { getRootDir, getProjectPackageJson } = require('./utils');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 内置默认开发代理,可被项目级配置覆盖
|
|
8
|
+
*/
|
|
9
|
+
const builtinProxy = Object.fromEntries(
|
|
10
|
+
['/bams-assets', '/bams-ui-umd', '/bams-app', '/admin-api', '/preview'].map((p) => [p, { target: process.env.PROXY_TARGET }])
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 生成通用基础配置(原仓库根 vue.config.js 的核心逻辑)
|
|
15
|
+
* - externals(vue / vue-router / pinia)
|
|
16
|
+
* - md 文件 raw-loader
|
|
17
|
+
* - @components-entry 动态别名(存在 build-components-entry.js 时指向它,否则指向空入口)
|
|
18
|
+
* - @configurable/* 可配置组件别名(基于环境变量与默认插槽映射)
|
|
19
|
+
* - devServer 代理合并(项目配置优先 + 内置默认补充缺失前缀)
|
|
20
|
+
* @param {Object} [options]
|
|
21
|
+
* @param {string} options.rootDir 项目根目录
|
|
22
|
+
* @param {Object} options.packageJson 项目根 package.json
|
|
23
|
+
* @param {Object} [options.devServer] 项目级 devServer 配置
|
|
24
|
+
* @returns {Object} 基础 vue 配置对象
|
|
25
|
+
*/
|
|
26
|
+
function getBaseConfig(options = {}) {
|
|
27
|
+
const rootDir = options.rootDir || getRootDir();
|
|
28
|
+
const packageJson = options.packageJson || getProjectPackageJson(rootDir);
|
|
29
|
+
const devServer = options.devServer || {};
|
|
30
|
+
|
|
31
|
+
// 项目配置优先,覆盖内置默认值;代理键序以项目配置为准,内置默认仅补充缺失前缀
|
|
32
|
+
const devProxy = devServer.proxy || {};
|
|
33
|
+
const mergedProxy = Object.fromEntries([
|
|
34
|
+
...Object.entries(devProxy),
|
|
35
|
+
...Object.entries(builtinProxy).filter(([k]) => !(k in devProxy)),
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
// 动态检测组件入口文件,实现“无感”加载的基础配置
|
|
39
|
+
const componentsEntryPath = path.resolve(rootDir, 'build-components-entry.js');
|
|
40
|
+
const hasComponentsEntry = fs.existsSync(componentsEntryPath);
|
|
41
|
+
|
|
42
|
+
const commonContext = applyCommonConfig({ rootDir, packageJson });
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
transpileDependencies: true,
|
|
46
|
+
publicPath: process.env.BASE_URL || '/',
|
|
47
|
+
configureWebpack: {
|
|
48
|
+
externals: {
|
|
49
|
+
vue: 'Vue',
|
|
50
|
+
'vue-router': 'VueRouter',
|
|
51
|
+
pinia: 'Pinia',
|
|
52
|
+
},
|
|
53
|
+
module: {
|
|
54
|
+
rules: [
|
|
55
|
+
{
|
|
56
|
+
test: /\.md$/,
|
|
57
|
+
use: 'raw-loader',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
resolve: {
|
|
62
|
+
// 保留默认相对查找,并追加用户项目 node_modules(组件/能力包位于用户项目时从该处解析)
|
|
63
|
+
modules: ['node_modules', path.resolve(rootDir, 'node_modules')],
|
|
64
|
+
alias: {
|
|
65
|
+
// 设置别名,如果文件不存在则指向空文件,保证编译不报错
|
|
66
|
+
'@components-entry': hasComponentsEntry ? componentsEntryPath : path.resolve(__dirname, 'empty-entry.js'),
|
|
67
|
+
...commonContext.aliases,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
devServer: merge({}, devServer, { proxy: mergedProxy }),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { getRootDir, getProjectPackageJson, getBaseConfig };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { defineConfig } = require('@vue/cli-service')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const { getBaseConfig } = require('./vue.config.base')
|
|
4
|
+
const { getRootDir, getProjectPackageJson, resolveComponent, findComponentCandidates, getComponentEntryName } = require('./utils')
|
|
5
|
+
|
|
6
|
+
const rootDir = getRootDir();
|
|
7
|
+
const packageJson = getProjectPackageJson(rootDir);
|
|
8
|
+
const baseConfig = getBaseConfig({ rootDir, packageJson });
|
|
9
|
+
|
|
10
|
+
const componentName = process.env.COMPONENT || 'ui-component-demo';
|
|
11
|
+
|
|
12
|
+
const resolvedComponent = resolveComponent(componentName, rootDir, packageJson);
|
|
13
|
+
// COMPONENT 环境变量可能是 @scope/name 完整包名,查找候选时需用短名
|
|
14
|
+
const candidates = findComponentCandidates(getComponentEntryName(componentName), rootDir, packageJson);
|
|
15
|
+
// 命中候选时别名指向组件目录绝对路径(避免从包目录解析用户项目组件失败)
|
|
16
|
+
const component = candidates.length > 0 ? candidates[0].componentPath : resolvedComponent;
|
|
17
|
+
console.log('🎉 当前开发组件:', resolvedComponent);
|
|
18
|
+
|
|
19
|
+
module.exports = defineConfig({
|
|
20
|
+
transpileDependencies: baseConfig.transpileDependencies,
|
|
21
|
+
outputDir: path.resolve(rootDir, 'dist'),
|
|
22
|
+
publicPath: process.env.BASE_URL || '/',
|
|
23
|
+
configureWebpack: {
|
|
24
|
+
externals: baseConfig.configureWebpack.externals,
|
|
25
|
+
module: baseConfig.configureWebpack.module,
|
|
26
|
+
resolve: {
|
|
27
|
+
modules: baseConfig.configureWebpack.resolve?.modules || ['node_modules'],
|
|
28
|
+
alias: {
|
|
29
|
+
...(baseConfig.configureWebpack.resolve?.alias || {}),
|
|
30
|
+
'@target-component': component
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
devServer: baseConfig.devServer
|
|
35
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const { defineConfig } = require('@vue/cli-service')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const { getBaseConfig } = require('./vue.config.base')
|
|
4
|
+
const { getRootDir, getProjectPackageJson, resolveComponent, findComponentCandidates, getComponentEntryName, toCamelComponentName } = require('./utils')
|
|
5
|
+
|
|
6
|
+
const rootDir = getRootDir();
|
|
7
|
+
const packageJson = getProjectPackageJson(rootDir);
|
|
8
|
+
const baseConfig = getBaseConfig({ rootDir, packageJson });
|
|
9
|
+
|
|
10
|
+
const componentName = process.env.COMPONENT || 'ui-component-demo';
|
|
11
|
+
const layout = process.env.LAYOUT || 'pc';
|
|
12
|
+
const camelComponentName = toCamelComponentName(componentName);
|
|
13
|
+
|
|
14
|
+
const resolvedComponent = resolveComponent(componentName, rootDir, packageJson);
|
|
15
|
+
// COMPONENT 环境变量可能是 @scope/name 完整包名,查找候选时需用短名
|
|
16
|
+
const candidates = findComponentCandidates(getComponentEntryName(componentName), rootDir, packageJson);
|
|
17
|
+
// 命中候选时别名指向组件目录绝对路径(避免从包目录解析用户项目组件失败)
|
|
18
|
+
const component = candidates.length > 0 ? candidates[0].componentPath : resolvedComponent;
|
|
19
|
+
console.log('🎉 当前开发组件:', resolvedComponent);
|
|
20
|
+
|
|
21
|
+
module.exports = defineConfig({
|
|
22
|
+
transpileDependencies: baseConfig.transpileDependencies,
|
|
23
|
+
outputDir: path.resolve(rootDir, 'dist'),
|
|
24
|
+
publicPath: '/',
|
|
25
|
+
configureWebpack: {
|
|
26
|
+
externals: baseConfig.configureWebpack.externals,
|
|
27
|
+
module: baseConfig.configureWebpack.module,
|
|
28
|
+
resolve: {
|
|
29
|
+
modules: baseConfig.configureWebpack.resolve?.modules || ['node_modules'],
|
|
30
|
+
alias: {
|
|
31
|
+
...(baseConfig.configureWebpack.resolve?.alias || {}),
|
|
32
|
+
'@target-component': component
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
chainWebpack: (config) => {
|
|
37
|
+
config.plugin('define').tap((args) => {
|
|
38
|
+
args[0]['process.env.VUE_APP_COMPONENT_NAME'] = JSON.stringify(camelComponentName);
|
|
39
|
+
args[0]['process.env.VUE_APP_LAYOUT'] = JSON.stringify(layout);
|
|
40
|
+
return args;
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
devServer: baseConfig.devServer
|
|
44
|
+
})
|
package/vue.config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { defineConfig } = require('@vue/cli-service')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const rootDir = path.resolve(__dirname, '../../../')
|
|
4
|
+
|
|
5
|
+
// 根据环境加载不同的配置文件
|
|
6
|
+
const NODE_ENV = process.env.NODE_ENV || 'development'
|
|
7
|
+
const isDev = NODE_ENV === 'development'
|
|
8
|
+
const isProd = NODE_ENV === 'production'
|
|
9
|
+
const isUmd = process.env.BUILD_TYPE === 'umd'
|
|
10
|
+
|
|
11
|
+
if (isUmd) {
|
|
12
|
+
// 打包umd
|
|
13
|
+
module.exports = require('./vue.config.umd.js')
|
|
14
|
+
} else if (isProd) {
|
|
15
|
+
// 生产环境
|
|
16
|
+
module.exports = require('./vue.config.build.js')
|
|
17
|
+
} else {
|
|
18
|
+
// 开发环境
|
|
19
|
+
module.exports = require('./vue.config.dev.js')
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { defineConfig } = require('@vue/cli-service')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const { getBaseConfig } = require('./vue.config.base')
|
|
4
|
+
const { getRootDir, getProjectPackageJson } = require('./utils')
|
|
5
|
+
|
|
6
|
+
const rootDir = getRootDir();
|
|
7
|
+
const packageJson = getProjectPackageJson(rootDir);
|
|
8
|
+
const baseConfig = getBaseConfig({ rootDir, packageJson });
|
|
9
|
+
|
|
10
|
+
const entryFile = process.env.ENTRY_FILE || path.resolve(rootDir, 'build-components-entry.js');
|
|
11
|
+
const outputDir = process.env.OUTPUT_DIR || path.resolve(rootDir, 'dist/umd');
|
|
12
|
+
const outputName = process.env.OUTPUT_NAME || 'bams-components';
|
|
13
|
+
|
|
14
|
+
console.log('🎉 当前打包配置:');
|
|
15
|
+
console.log(' 入口文件:', entryFile);
|
|
16
|
+
console.log(' 输出目录:', outputDir);
|
|
17
|
+
console.log(' 输出名称:', outputName);
|
|
18
|
+
|
|
19
|
+
module.exports = defineConfig({
|
|
20
|
+
transpileDependencies: baseConfig.transpileDependencies,
|
|
21
|
+
publicPath: './',
|
|
22
|
+
outputDir: outputDir,
|
|
23
|
+
configureWebpack: {
|
|
24
|
+
externals: baseConfig.configureWebpack.externals,
|
|
25
|
+
module: baseConfig.configureWebpack.module
|
|
26
|
+
},
|
|
27
|
+
css: {
|
|
28
|
+
// 当作为一个库构建时,你也可以将其设置为 false 免得用户自己导入 CSS。
|
|
29
|
+
extract: false
|
|
30
|
+
}
|
|
31
|
+
})
|