@bams-app/configs 0.1.3 → 0.1.4

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.
@@ -0,0 +1,105 @@
1
+ const { resolveComponent } = require("./resolve-component");
2
+ const globalComponentDefaults = require("./component-defaults.js");
3
+
4
+ /**
5
+ * 简单的深度合并对象
6
+ * @param {Object} target 目标对象
7
+ * @param {...Object} sources 源对象
8
+ * @returns {Object} 合并后的对象
9
+ */
10
+ function merge(target, ...sources) {
11
+ if (!sources.length) return target;
12
+ const source = sources.shift();
13
+
14
+ if (source !== undefined && source !== null) {
15
+ Object.keys(source).forEach(key => {
16
+ const targetValue = target[key];
17
+ const sourceValue = source[key];
18
+
19
+ if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
20
+ target[key] = targetValue.concat(sourceValue);
21
+ } else if (typeof targetValue === 'object' && targetValue !== null &&
22
+ typeof sourceValue === 'object' && sourceValue !== null &&
23
+ !Array.isArray(sourceValue)) {
24
+ target[key] = merge({ ...targetValue }, sourceValue);
25
+ } else {
26
+ target[key] = sourceValue;
27
+ }
28
+ });
29
+ }
30
+
31
+ return merge(target, ...sources);
32
+ }
33
+
34
+ /**
35
+ * 将环境变量名转换为可配置插槽名
36
+ * @param {string} envKey 环境变量名
37
+ * @returns {string} 插槽名,如 login、home-card
38
+ */
39
+ function normalizeConfigurableSlotName(envKey) {
40
+ return envKey
41
+ .replace(/^(VUE_APP_COMPONENT_|COMPONENT_)/, "")
42
+ .toLowerCase()
43
+ .replace(/_/g, "-");
44
+ }
45
+
46
+ /**
47
+ * 根据环境变量和默认值生成可配置组件 alias
48
+ * @param {Object} options 配置项
49
+ * @param {Object} options.env 环境变量对象
50
+ * @param {string} options.rootDir 项目根目录
51
+ * @param {Object} options.packageJson 根 package.json
52
+ * @param {Object} [options.defaults] 额外默认插槽组件映射,会覆盖全局默认值
53
+ * @returns {Object} webpack alias 对象
54
+ */
55
+ function getConfigurableComponentAliases({ env, rootDir, packageJson, defaults = {} }) {
56
+ const slotToComponent = { ...globalComponentDefaults, ...defaults };
57
+
58
+ Object.entries(env).forEach(([key, value]) => {
59
+ if (!/^(VUE_APP_COMPONENT_|COMPONENT_).+$/.test(key) || !value) {
60
+ return;
61
+ }
62
+
63
+ slotToComponent[normalizeConfigurableSlotName(key)] = value;
64
+ });
65
+
66
+ return Object.entries(slotToComponent).reduce((aliases, [slotName, componentName]) => {
67
+ aliases[`@configurable/${slotName}`] = resolveComponent(componentName, rootDir, packageJson);
68
+ return aliases;
69
+ }, {});
70
+ }
71
+
72
+ /**
73
+ * 生成通用的 webpack alias 片段,包含所有可配置组件插槽
74
+ * @param {Object} [options]
75
+ * @param {string} options.rootDir 项目根目录
76
+ * @param {Object} [options.env=process.env] 环境变量对象
77
+ * @param {Object} options.packageJson 根 package.json
78
+ * @param {Object} [options.defaults] 额外默认插槽组件映射
79
+ * @returns {{rootDir: string, packageJson: object, aliases: Object}}
80
+ */
81
+ function applyCommonConfig(options = {}) {
82
+ const { rootDir, env = process.env, packageJson, defaults } = options;
83
+
84
+ if (!rootDir || !packageJson) {
85
+ throw new Error("applyCommonConfig requires rootDir and packageJson");
86
+ }
87
+
88
+ const configurableAliases = getConfigurableComponentAliases({
89
+ env,
90
+ rootDir,
91
+ packageJson,
92
+ defaults
93
+ });
94
+
95
+ return {
96
+ rootDir,
97
+ packageJson,
98
+ aliases: configurableAliases
99
+ };
100
+ }
101
+
102
+ module.exports = {
103
+ merge,
104
+ applyCommonConfig
105
+ };
package/empty-entry.js ADDED
@@ -0,0 +1 @@
1
+ export { };
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@bams-app/configs",
3
3
  "private": false,
4
- "version": "0.1.3",
4
+ "version": "0.1.4",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",
8
8
  "component-defaults.js",
9
- "app-config.json"
9
+ "app-config.json",
10
+ "resolve-component.js",
11
+ "config-utils.js",
12
+ "vue.config.app-base.js",
13
+ "empty-entry.js"
10
14
  ],
11
- "description": "BAMS-Work 公共配置(应用配置、可配置组件插槽默认值)"
15
+ "description": "BAMS-Work 公共配置(应用配置、可配置组件插槽默认值、宿主应用 vue-cli 基础配置与组件别名解析)"
12
16
  }
@@ -0,0 +1,220 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ /**
5
+ * 判断目标路径是否为目录
6
+ * @param {string} targetPath - 目标路径
7
+ * @returns {boolean} 是否为目录
8
+ */
9
+ function isDirectory(targetPath) {
10
+ return fs.existsSync(targetPath) && fs.statSync(targetPath).isDirectory();
11
+ }
12
+
13
+ /**
14
+ * 标准化路径用于显示(统一使用 / 分隔符)
15
+ * @param {string} targetPath - 目标路径
16
+ * @returns {string} 标准化后的路径
17
+ */
18
+ function normalizePathForDisplay(targetPath) {
19
+ return targetPath.split(path.sep).join('/');
20
+ }
21
+
22
+ /**
23
+ * 将目录名添加到 scope 映射中
24
+ * @param {Object} scopes - scope 到目录的映射
25
+ * @param {string} dirName - 目录名
26
+ */
27
+ function addScopeDir(scopes, dirName) {
28
+ // 排除不是 ui 相关的目录
29
+ if (['work', 'bams-components', 'bams-ui'].includes(dirName)) {
30
+ if (!scopes['bams-app']) scopes['bams-app'] = [];
31
+ if (!scopes['bams-app'].includes(dirName)) {
32
+ scopes['bams-app'].push(dirName);
33
+ }
34
+ } else {
35
+ // 其他目录用目录名作为 scope
36
+ if (!scopes[dirName]) scopes[dirName] = [];
37
+ if (!scopes[dirName].includes(dirName)) {
38
+ scopes[dirName].push(dirName);
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * 从项目根目录查找 -ui 结尾的目录并补充到 scope 映射
45
+ * @param {Object} scopes - scope 到目录的映射
46
+ * @param {string} rootDir - 项目根目录
47
+ */
48
+ function appendUiDirsFromRoot(scopes, rootDir) {
49
+ if (!rootDir || !isDirectory(rootDir)) {
50
+ return;
51
+ }
52
+
53
+ const dirEntries = fs.readdirSync(rootDir, { withFileTypes: true });
54
+ dirEntries.forEach((entry) => {
55
+ if (!entry.isDirectory() || !entry.name.endsWith('-ui')) {
56
+ return;
57
+ }
58
+
59
+ addScopeDir(scopes, entry.name);
60
+ });
61
+ }
62
+
63
+ /**
64
+ * 从 workspaces 配置中解析出 scope 和目录映射
65
+ * @param {Object} packageJson - package.json 对象
66
+ * @param {string} rootDir - 项目根目录
67
+ * @returns {Object} scope 到目录的映射
68
+ */
69
+ function parseScopesFromWorkspaces(packageJson, rootDir) {
70
+ const workspaces = packageJson.workspaces || [];
71
+ const scopes = {};
72
+
73
+ workspaces.forEach(pattern => {
74
+ // 跳过排除项
75
+ if (pattern.startsWith('!')) return;
76
+
77
+ // 处理类似 "pds-ui/*" 或 "pds-ui" 这样的模式
78
+ const match = pattern.match(/^([^/*]+)(\/\*)?$/);
79
+ if (match) {
80
+ addScopeDir(scopes, match[1]);
81
+ }
82
+ });
83
+
84
+ appendUiDirsFromRoot(scopes, rootDir);
85
+
86
+ // 默认总是包含 bams-app 的 fallback
87
+ if (!scopes['bams-app']) scopes['bams-app'] = [];
88
+ if (!scopes['bams-app'].includes('work/packages')) {
89
+ scopes['bams-app'].push('work/packages');
90
+ }
91
+
92
+ return scopes;
93
+ }
94
+
95
+ /**
96
+ * 获取所有 workspace 条目
97
+ * @param {Object} packageJson - package.json 对象
98
+ * @param {string} rootDir - 项目根目录
99
+ * @returns {Array<{scope: string, dir: string}>} workspace 条目列表
100
+ */
101
+ function getWorkspaceEntries(packageJson, rootDir) {
102
+ const scopes = parseScopesFromWorkspaces(packageJson, rootDir);
103
+ const entries = [];
104
+
105
+ for (const [scope, dirs] of Object.entries(scopes)) {
106
+ for (const dir of dirs) {
107
+ entries.push({ scope, dir });
108
+ }
109
+ }
110
+
111
+ return entries;
112
+ }
113
+
114
+ /**
115
+ * 创建组件候选对象
116
+ * @param {string} scope - scope 名称
117
+ * @param {string} dir - 目录路径
118
+ * @param {string} componentDirName - 组件目录名
119
+ * @param {string} componentPath - 组件完整路径
120
+ * @param {string} requestedName - 用户请求的原始组件名
121
+ * @returns {Object} 组件候选对象
122
+ */
123
+ function createCandidate(scope, dir, componentDirName, componentPath, requestedName) {
124
+ return {
125
+ scope,
126
+ dir,
127
+ requestedName,
128
+ componentDirName,
129
+ componentPath,
130
+ packageName: `@${scope}/${componentDirName}`,
131
+ displayPath: normalizePathForDisplay(path.join(dir, componentDirName))
132
+ };
133
+ }
134
+
135
+ /**
136
+ * 根据组件名在所有 workspaces 中查找候选组件
137
+ * @param {string} componentName - 组件名
138
+ * @param {string} rootDir - 项目根目录
139
+ * @param {Object} packageJson - package.json 对象
140
+ * @returns {Array<Object>} 组件候选列表
141
+ */
142
+ function findComponentCandidates(componentName, rootDir, packageJson) {
143
+ const candidates = [];
144
+ const seen = new Set();
145
+ const searchNames = [componentName];
146
+
147
+ // 如果没有前缀,自动尝试添加 ui- 和 page- 前缀
148
+ if (!componentName.startsWith('ui-') && !componentName.startsWith('page-')) {
149
+ searchNames.push(`ui-${componentName}`, `page-${componentName}`);
150
+ }
151
+
152
+ for (const { scope, dir } of getWorkspaceEntries(packageJson, rootDir)) {
153
+ for (const searchName of searchNames) {
154
+ const componentPath = path.resolve(rootDir, dir, searchName);
155
+
156
+ // 跳过不存在或不是目录的路径
157
+ if (!isDirectory(componentPath)) {
158
+ continue;
159
+ }
160
+
161
+ const packageName = `@${scope}/${searchName}`;
162
+ // 跳过重复的 packageName
163
+ if (seen.has(packageName)) {
164
+ continue;
165
+ }
166
+
167
+ seen.add(packageName);
168
+ candidates.push(createCandidate(scope, dir, searchName, componentPath, componentName));
169
+ }
170
+ }
171
+
172
+ return candidates;
173
+ }
174
+
175
+ /**
176
+ * 根据 componentName 自动识别作用域
177
+ * @param {string} componentName - 组件名
178
+ * @param {string} rootDir - 根目录路径
179
+ * @param {Object} packageJson - package.json 对象
180
+ * @returns {string} 完整的包名
181
+ */
182
+ function getComponentScope(componentName, rootDir, packageJson) {
183
+ const candidates = findComponentCandidates(componentName, rootDir, packageJson);
184
+
185
+ if (candidates.length > 0) {
186
+ return candidates[0].packageName;
187
+ }
188
+
189
+ // 默认回退到 bams-app
190
+ return `@bams-app/${componentName}`;
191
+ }
192
+
193
+ /**
194
+ * 解析组件名,返回完整的包名或路径
195
+ * @param {string} componentName - 组件名
196
+ * @param {string} rootDir - 根目录路径
197
+ * @param {Object} packageJson - package.json 对象
198
+ * @returns {string} 完整的包名或路径
199
+ */
200
+ function resolveComponent(componentName, rootDir, packageJson) {
201
+ // 优先识别完整包名(以 @ 开头)
202
+ if (componentName.startsWith('@')) {
203
+ return componentName;
204
+ }
205
+ // 再识别路径(包含 / 但不是包名)
206
+ if (componentName.includes('/')) {
207
+ return path.resolve(rootDir, componentName);
208
+ }
209
+ // 最后尝试自动识别 scope
210
+ return getComponentScope(componentName, rootDir, packageJson);
211
+ }
212
+
213
+ module.exports = {
214
+ parseScopesFromWorkspaces,
215
+ getWorkspaceEntries,
216
+ createCandidate,
217
+ findComponentCandidates,
218
+ getComponentScope,
219
+ resolveComponent
220
+ };
@@ -0,0 +1,100 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { merge, applyCommonConfig } = require('./config-utils');
4
+
5
+ /** 内置默认开发代理前缀,可被项目根 vue.config.dev.js 覆盖 */
6
+ const BUILTIN_PROXY_PREFIXES = ['/bams-assets', '/bams-ui-umd', '/bams-app', '/admin-api', '/preview'];
7
+
8
+ /**
9
+ * 解析项目根开发代理配置
10
+ * 优先读取项目根 vue.config.dev.js,其声明的代理键序优先(精确 path 置前),
11
+ * 内置默认前缀仅补充未声明项。
12
+ * @param {string} projectRoot 项目根目录
13
+ * @returns {Object} devServer 配置
14
+ */
15
+ function resolveDevServer(projectRoot) {
16
+ const devConfigPath = path.join(projectRoot, 'vue.config.dev.js');
17
+ let devServer = {};
18
+
19
+ if (fs.existsSync(devConfigPath)) {
20
+ devServer = require(devConfigPath);
21
+ } else if (process.env.NODE_ENV === 'development') {
22
+ console.error('\x1b[31m%s\x1b[0m', '错误: 必须配置 vue.config.dev.js 文件以启动开发服务器。');
23
+ console.error('\x1b[33m%s\x1b[0m', '请按以下步骤操作:');
24
+ console.error('\x1b[33m%s\x1b[0m', '1. 复制模板生成配置: cp vue.config.dev_template.js vue.config.dev.js');
25
+ console.error('\x1b[33m%s\x1b[0m', '2. 按需修改 vue.config.dev.js 中的代理 target,或在 .envs/.env.* 中配置 PROXY_TARGET');
26
+ console.error('\x1b[33m%s\x1b[0m', '3. 修改后需重启开发服务才生效');
27
+ process.exit(1);
28
+ }
29
+
30
+ const builtinProxy = Object.fromEntries(
31
+ BUILTIN_PROXY_PREFIXES.map((prefix) => [prefix, { target: process.env.PROXY_TARGET }])
32
+ );
33
+ const devProxy = devServer.proxy || {};
34
+ const mergedProxy = Object.fromEntries([
35
+ ...Object.entries(devProxy),
36
+ ...Object.entries(builtinProxy).filter(([key]) => !(key in devProxy))
37
+ ]);
38
+
39
+ return merge({}, devServer, { proxy: mergedProxy });
40
+ }
41
+
42
+ /**
43
+ * 生成宿主应用(vue-cli)通用基础配置
44
+ *
45
+ * 该配置与仓库根 vue.config.js 同源,供仓库根配置与 work/apps/* 共同复用:
46
+ * - 仓库内:projectRoot 指向仓库根,行为与原根配置一致
47
+ * - 用户项目:projectRoot 指向用户项目根(由 CLI 通过 WORK_PROJECT_ROOT 注入),
48
+ * 使应用安装到 node_modules 后仍可独立构建,不再 require 仓库根文件
49
+ *
50
+ * @param {Object} [options]
51
+ * @param {string} [options.projectRoot] 项目根目录,缺省取 WORK_PROJECT_ROOT 或 cwd
52
+ * @returns {Object} vue-cli 基础配置(含 devServer)
53
+ */
54
+ function getAppBaseConfig(options = {}) {
55
+ const projectRoot = options.projectRoot || process.env.WORK_PROJECT_ROOT || process.cwd();
56
+ const packageJson = require(path.join(projectRoot, 'package.json'));
57
+
58
+ // 动态检测组件入口文件,实现“无感”加载的基础配置
59
+ const componentsEntryPath = path.resolve(projectRoot, 'build-components-entry.js');
60
+ const hasComponentsEntry = fs.existsSync(componentsEntryPath);
61
+
62
+ const commonContext = applyCommonConfig({ rootDir: projectRoot, packageJson });
63
+
64
+ const baseConfig = {
65
+ transpileDependencies: true,
66
+ publicPath: process.env.BASE_URL || '/',
67
+ configureWebpack: {
68
+ externals: {
69
+ vue: 'Vue',
70
+ 'vue-router': 'VueRouter',
71
+ pinia: 'Pinia'
72
+ // 'devextreme-vue': 'DevExtremeVue',
73
+ // 'devextreme': 'DevExtreme',
74
+ },
75
+ module: {
76
+ rules: [
77
+ {
78
+ test: /\.md$/,
79
+ use: 'raw-loader'
80
+ }
81
+ ]
82
+ },
83
+ resolve: {
84
+ alias: {
85
+ // 设置别名,如果文件不存在则指向空文件,保证编译不报错
86
+ '@components-entry': hasComponentsEntry ? componentsEntryPath : require.resolve('./empty-entry.js'),
87
+ ...commonContext.aliases
88
+ }
89
+ }
90
+ },
91
+ devServer: {}
92
+ };
93
+
94
+ return merge({}, baseConfig, { devServer: resolveDevServer(projectRoot) });
95
+ }
96
+
97
+ module.exports = {
98
+ getAppBaseConfig,
99
+ resolveDevServer
100
+ };