akfun 5.2.13 → 5.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/module/index.js CHANGED
@@ -33,7 +33,6 @@ let argv = yargs
33
33
  '创建一个项目',
34
34
  (yargs) => {
35
35
  yargs
36
- .reset()
37
36
  .usage(titleTip('Usage') + ': $0 init [options]')
38
37
  .option('type', {
39
38
  alias: 't',
@@ -106,7 +105,6 @@ let argv = yargs
106
105
  '初始化AKFun配置文件',
107
106
  (yargs) => {
108
107
  yargs
109
- .reset()
110
108
  .usage(titleTip('Usage') + ': $0 config init')
111
109
  .alias('h', 'help');
112
110
  },
@@ -119,7 +117,6 @@ let argv = yargs
119
117
  '开启本地调试模式',
120
118
  (yargs) => {
121
119
  yargs
122
- .reset()
123
120
  .usage(titleTip('Usage') + ': $0 dev')
124
121
  .alias('h', 'help');
125
122
  },
@@ -132,7 +129,6 @@ let argv = yargs
132
129
  '构建生产环境代码',
133
130
  (yargs) => {
134
131
  yargs
135
- .reset()
136
132
  .usage(titleTip('Usage') + ': $0 build')
137
133
  .alias('h', 'help');
138
134
  },
@@ -145,7 +141,6 @@ let argv = yargs
145
141
  '构建 Library 库(UMD 模块)',
146
142
  (yargs) => {
147
143
  yargs
148
- .reset()
149
144
  .usage(titleTip('Usage') + ': $0 build2lib')
150
145
  .alias('h', 'help');
151
146
  },
@@ -158,7 +153,6 @@ let argv = yargs
158
153
  '构建 ESM 模块',
159
154
  (yargs) => {
160
155
  yargs
161
- .reset()
162
156
  .usage(titleTip('Usage') + ': $0 build2esm')
163
157
  .alias('h', 'help');
164
158
  },
@@ -171,7 +165,6 @@ let argv = yargs
171
165
  '构建 Node 模块',
172
166
  (yargs) => {
173
167
  yargs
174
- .reset()
175
168
  .usage(titleTip('Usage') + ': $0 build2node')
176
169
  .alias('h', 'help');
177
170
  },
@@ -184,7 +177,6 @@ let argv = yargs
184
177
  '输出当前配置文件',
185
178
  (yargs) => {
186
179
  yargs
187
- .reset()
188
180
  .usage(titleTip('Usage') + ': $0 inspect')
189
181
  .option('type', {
190
182
  alias: 't',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akfun",
3
- "version": "5.2.13",
3
+ "version": "5.3.0",
4
4
  "description": "前端脚手架:支持Vue技术栈和react技术栈",
5
5
  "keywords": [
6
6
  "前端工程",
@@ -51,7 +51,6 @@
51
51
  "dependencies": {
52
52
  "@babel/cli": "^7.17.0",
53
53
  "@babel/core": "^7.17.0",
54
- "@babel/polyfill": "^7.10.1",
55
54
  "@babel/eslint-parser": "^7.17.0",
56
55
  "@babel/plugin-proposal-class-properties": "^7.16.7",
57
56
  "@babel/plugin-proposal-decorators": "^7.17.0",
@@ -59,41 +58,44 @@
59
58
  "@babel/plugin-proposal-function-sent": "^7.16.7",
60
59
  "@babel/plugin-proposal-json-strings": "^7.16.7",
61
60
  "@babel/plugin-proposal-numeric-separator": "^7.16.7",
62
- "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.0",
63
- "@babel/plugin-transform-optional-chaining": "^7.23.0",
64
61
  "@babel/plugin-proposal-throw-expressions": "^7.16.7",
65
62
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
66
63
  "@babel/plugin-syntax-import-meta": "^7.10.4",
64
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.0",
65
+ "@babel/plugin-transform-optional-chaining": "^7.23.0",
67
66
  "@babel/plugin-transform-runtime": "^7.27.1",
68
- "@babel/runtime": "^7.27.1",
67
+ "@babel/polyfill": "^7.10.1",
69
68
  "@babel/preset-env": "^7.16.11",
70
69
  "@babel/preset-react": "^7.16.7",
71
70
  "@babel/preset-typescript": "^7.16.7",
72
71
  "@babel/register": "^7.17.0",
72
+ "@babel/runtime": "^7.27.1",
73
+ "@baiducloud/sdk": "^1.0.7",
73
74
  "@mapbox/stylelint-processor-arbitrary-tags": "^0.4.0",
74
75
  "@rollup/plugin-alias": "^5.1.1",
75
76
  "@rollup/plugin-babel": "^6.1.0",
76
77
  "@rollup/plugin-commonjs": "^29.0.0",
77
78
  "@rollup/plugin-image": "^3.0.3",
78
- "@svgr/rollup": "^8.1.0",
79
79
  "@rollup/plugin-json": "^6.1.0",
80
80
  "@rollup/plugin-node-resolve": "^16.0.3",
81
- "@rollup/plugin-typescript": "^12.3.0",
82
81
  "@rollup/plugin-terser": "^0.4.4",
82
+ "@rollup/plugin-typescript": "^12.3.0",
83
+ "@svgr/rollup": "^8.1.0",
83
84
  "@svgr/webpack": "^6.2.1",
84
85
  "@typescript-eslint/eslint-plugin": "^5.10.2",
85
86
  "@typescript-eslint/parser": "^5.10.2",
86
87
  "@vue/compiler-sfc": "^3.2.29",
87
88
  "@vue/eslint-config-typescript": "^10.0.0",
89
+ "ali-oss": "^6.17.0",
88
90
  "autoprefixer": "^10.4.2",
89
91
  "babel-loader": "^8.2.3",
90
92
  "babel-plugin-import": "^1.13.3",
91
93
  "chalk": "^4.0.0",
92
- "compression-webpack-plugin": "^9.2.0",
94
+ "compression-webpack-plugin": "^11.1.0",
93
95
  "connect-history-api-fallback": "^2.0.0",
94
- "copy-webpack-plugin": "^10.2.4",
96
+ "copy-webpack-plugin": "^13.0.1",
95
97
  "css-loader": "^6.6.0",
96
- "css-minimizer-webpack-plugin": "^3.4.1",
98
+ "css-minimizer-webpack-plugin": "^7.0.4",
97
99
  "cssnano": "^5.0.16",
98
100
  "deepmerge": "^4.2.2",
99
101
  "del": "^6.0.0",
@@ -113,35 +115,33 @@
113
115
  "express": "^4.17.2",
114
116
  "figlet": "^1.5.2",
115
117
  "file-loader": "^6.2.0",
116
- "git-clone": "0.1.0",
118
+ "git-clone": "^0.2.0",
117
119
  "glob": "^7.2.0",
118
120
  "html-loader": "^3.1.0",
119
121
  "html-webpack-plugin": "^5.5.0",
120
122
  "http-proxy-middleware": "^2.0.2",
121
- "inquirer": "^8.2.0",
122
- "rollup-plugin-jsx": "^1.0.3",
123
+ "inquirer": "^13.3.0",
124
+ "less": "^4.1.3",
125
+ "less-loader": "^11.0.0",
123
126
  "mini-css-extract-plugin": "^2.5.3",
124
127
  "open": "^8.4.0",
125
128
  "ora": "^4.0.4",
126
129
  "params-replace-loader": "^1.1.6",
127
130
  "portfinder": "^1.0.28",
128
- "postcss-loader": "^6.2.1",
131
+ "postcss-loader": "^7.3.4",
129
132
  "postcss-nested": "^5.0.6",
130
133
  "postcss-preset-env": "^7.3.1",
131
134
  "postcss-simple-vars": "^6.0.3",
132
135
  "progress-bar-webpack-plugin": "^2.1.0",
133
- "qs": "^6.10.3",
136
+ "qs": "^6.14.2",
134
137
  "rimraf": "^3.0.2",
135
- "rollup": "^4.53.3",
138
+ "rollup": "^4.59.0",
139
+ "rollup-plugin-jsx": "^1.0.3",
136
140
  "rollup-plugin-node-externals": "^8.1.2",
137
141
  "rollup-plugin-postcss": "^4.0.2",
138
142
  "rollup-plugin-vue": "^6.0.0",
139
143
  "sass": "^1.94.2",
140
144
  "sass-loader": "^12.4.0",
141
- "less": "^4.1.3",
142
- "less-loader": "^11.0.0",
143
- "stylus": "^0.57.0",
144
- "stylus-loader": "^7.0.0",
145
145
  "sass-resources-loader": "^2.2.4",
146
146
  "semver": "^7.3.5",
147
147
  "shelljs": "^0.8.5",
@@ -150,32 +150,39 @@
150
150
  "stylelint": "^14.3.0",
151
151
  "stylelint-config-standard": "^24.0.0",
152
152
  "stylelint-webpack-plugin": "^3.1.1",
153
+ "stylus": "^0.57.0",
154
+ "stylus-loader": "^7.0.0",
153
155
  "ts-import-plugin": "^2.0.0",
154
156
  "ts-loader": "^9.2.6",
155
157
  "typescript": "^4.5.5",
156
158
  "url-loader": "^4.1.1",
157
159
  "vue-eslint-parser": "^8.2.0",
158
- "vue-loader": "^15.9.8",
160
+ "vue-loader": "^15.11.1",
159
161
  "vue-style-loader": "^4.1.3",
160
- "vue-template-compiler": "^2.6.14",
161
- "webpack": "^5.68.0",
162
+ "vue-template-compiler": "^2.7.16",
163
+ "webpack": "^5.105.3",
162
164
  "webpack-bundle-analyzer": "^4.5.0",
163
- "webpack-cli": "^4.9.2",
165
+ "webpack-cli": "^5.1.4",
164
166
  "webpack-dev-middleware": "^5.3.1",
165
167
  "webpack-hot-middleware": "^2.25.1",
166
168
  "webpack-merge": "^5.8.0",
167
169
  "webpack-node-externals": "^3.0.0",
168
- "yargs": "^12.0.5",
169
- "@baiducloud/sdk": "^1.0.7",
170
- "ali-oss": "^6.17.0"
170
+ "yargs": "^17.7.2"
171
171
  },
172
172
  "devDependencies": {
173
- "@commitlint/cli": "^16.1.0",
174
- "@commitlint/config-conventional": "^16.0.0",
173
+ "@commitlint/cli": "^20.4.2",
174
+ "@commitlint/config-conventional": "^20.4.2",
175
175
  "husky": "^4.3.8",
176
176
  "lint-staged": "^12.3.3",
177
177
  "prettier": "^2.5.1"
178
178
  },
179
+ "overrides": {
180
+ "serialize-javascript": "^7.0.3",
181
+ "ajv": "^8.18.0",
182
+ "lodash": "^4.17.22",
183
+ "minimatch": "^3.1.3",
184
+ "diff": "^4.0.4"
185
+ },
179
186
  "engines": {
180
187
  "node": ">= 16.0.0",
181
188
  "npm": ">= 8.0.0"
@@ -0,0 +1 @@
1
+ # 此处存放 webpack 自定义 loader
@@ -0,0 +1,429 @@
1
+ const path = require('path');
2
+ const curConfig = require('../config/index'); // 获取当前项目根目录下的配置文件
3
+
4
+ const DEFAULT_COMPONENTS_DIR = curConfig.componentsDir || './src/components';
5
+ const DEFAULT_SCOPE_KEY = 'data-scope';
6
+
7
+ function normalizePath(targetPath) {
8
+ return targetPath ? path.normalize(targetPath) : '';
9
+ }
10
+
11
+ /**
12
+ * 将驼峰命名转换为 kebab-case(短横线连接)
13
+ * @param {string} str - 输入字符串
14
+ * @returns {string} kebab-case 格式
15
+ */
16
+ function camelToKebab(str) {
17
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
18
+ }
19
+
20
+ /**
21
+ * 将驼峰命名转换为 snake_case(下划线连接)
22
+ * @param {string} str - 输入字符串
23
+ * @returns {string} snake_case 格式
24
+ */
25
+ function camelToSnake(str) {
26
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
27
+ }
28
+
29
+ /**
30
+ * 生成组件名的所有可能变体(包括大小写、命名风格、container 后缀等)
31
+ * @param {string} componentName - 组件名称,如 simpleCmp__c
32
+ * @returns {string[]} 所有可能的变体数组
33
+ */
34
+ function generateComponentNameVariants(componentName) {
35
+ if (!componentName) {
36
+ return [];
37
+ }
38
+
39
+ const variants = new Set();
40
+
41
+ // 原始名称
42
+ variants.add(componentName);
43
+
44
+ // 首字母大写
45
+ const capitalized = componentName.charAt(0).toUpperCase() + componentName.slice(1);
46
+ variants.add(capitalized);
47
+
48
+ // 全小写
49
+ variants.add(componentName.toLowerCase());
50
+
51
+ // 全大写
52
+ variants.add(componentName.toUpperCase());
53
+
54
+ // kebab-case(短横线连接)
55
+ const kebabCase = camelToKebab(componentName);
56
+ variants.add(kebabCase);
57
+
58
+ // snake_case(下划线连接)
59
+ const snakeCase = camelToSnake(componentName);
60
+ variants.add(snakeCase);
61
+
62
+ // 首字母大写的 kebab-case
63
+ const capitalizedKebab = kebabCase.charAt(0).toUpperCase() + kebabCase.slice(1);
64
+ variants.add(capitalizedKebab);
65
+
66
+ // 首字母大写的 snake_case
67
+ const capitalizedSnake = snakeCase.charAt(0).toUpperCase() + snakeCase.slice(1);
68
+ variants.add(capitalizedSnake);
69
+
70
+ // 生成带 container 后缀的变体
71
+ const containerSuffixes = ['-container', 'Container', '_container'];
72
+ const baseVariants = Array.from(variants);
73
+ baseVariants.forEach((variant) => {
74
+ containerSuffixes.forEach((suffix) => {
75
+ variants.add(variant + suffix);
76
+ });
77
+ });
78
+
79
+ return Array.from(variants);
80
+ }
81
+
82
+ /**
83
+ * 检查选择器是否匹配组件名(忽略大小写和命名风格)
84
+ * @param {string} selector - 选择器,如 .simpleCmp__c 或 #simpleCmp__c
85
+ * @param {string[]} componentNameVariants - 组件名的所有变体
86
+ * @returns {boolean} 是否匹配
87
+ */
88
+ function isComponentRootSelector(selector, componentNameVariants) {
89
+ if (!selector || !componentNameVariants || componentNameVariants.length === 0) {
90
+ return false;
91
+ }
92
+
93
+ const trimmedSelector = selector.trim();
94
+
95
+ // 提取类选择器或 ID 选择器的名称部分
96
+ // 匹配 .className 或 #idName 格式
97
+ const match = trimmedSelector.match(/^[.#]([a-zA-Z0-9_-]+)/);
98
+ if (!match) {
99
+ return false;
100
+ }
101
+
102
+ const selectorName = match[1];
103
+
104
+ // 检查是否匹配任何组件名变体(忽略大小写)
105
+ return componentNameVariants.some((variant) => {
106
+ return selectorName.toLowerCase() === variant.toLowerCase();
107
+ });
108
+ }
109
+
110
+ /**
111
+ * 为样式选择器添加作用域前缀
112
+ * @param {string} selector - 选择器字符串
113
+ * @param {string} scopeSelector - 作用域选择器,如 [data-scope="componentName"]
114
+ * @param {string[]} componentNameVariants - 组件名的所有变体,用于判断是否是最外层选择器
115
+ * @returns {string} 添加了作用域的选择器
116
+ */
117
+ function addScopeToSelector(selector, scopeSelector, componentNameVariants = []) {
118
+ if (!selector || !selector.trim()) {
119
+ return selector;
120
+ }
121
+
122
+ const trimmedSelector = selector.trim();
123
+
124
+ // 处理 :global() 语法,不添加作用域
125
+ if (trimmedSelector.includes(':global(')) {
126
+ return selector;
127
+ }
128
+
129
+ // 如果选择器已经是作用域选择器,直接返回
130
+ if (trimmedSelector.startsWith('[') && trimmedSelector.includes(scopeSelector)) {
131
+ return selector;
132
+ }
133
+
134
+ // 处理多个选择器(逗号分隔)
135
+ if (trimmedSelector.includes(',')) {
136
+ return trimmedSelector
137
+ .split(',')
138
+ .map((sel) => addScopeToSelector(sel.trim(), scopeSelector, componentNameVariants))
139
+ .join(', ');
140
+ }
141
+
142
+ // 处理伪类和伪元素(如 :hover, ::before, :nth-child(2))
143
+ // 匹配格式:baseSelector:pseudo 或 baseSelector::pseudo
144
+ const pseudoMatch = trimmedSelector.match(
145
+ /^(.+?)(::?[a-zA-Z-]+(?:\([^)]*\))?(?:::?[a-zA-Z-]+(?:\([^)]*\))?)*)$/
146
+ );
147
+ if (pseudoMatch) {
148
+ const baseSelector = pseudoMatch[1].trim();
149
+ const pseudo = pseudoMatch[2];
150
+ // 为 baseSelector 添加作用域
151
+ const scopedBase = addScopeToSelector(baseSelector, scopeSelector, componentNameVariants);
152
+ return `${scopedBase}${pseudo}`;
153
+ }
154
+
155
+ // 检查是否是最外层组件选择器(类选择器或 ID 选择器)
156
+ const isRootSelector = isComponentRootSelector(trimmedSelector, componentNameVariants);
157
+
158
+ // 处理普通选择器
159
+ if (trimmedSelector.startsWith('.') || trimmedSelector.startsWith('#')) {
160
+ // 如果是最外层组件选择器,直接拼接(无空格)
161
+ // 否则添加空格(包含在 scope 里面)
162
+ if (isRootSelector) {
163
+ return `${scopeSelector}${trimmedSelector}`;
164
+ } else {
165
+ return `${scopeSelector} ${trimmedSelector}`;
166
+ }
167
+ }
168
+
169
+ // 如果选择器以 [ 开头(属性选择器),直接拼接
170
+ if (trimmedSelector.startsWith('[')) {
171
+ return `${scopeSelector}${trimmedSelector}`;
172
+ }
173
+
174
+ // 处理标签选择器和其他选择器(添加空格)
175
+ return `${scopeSelector} ${trimmedSelector}`;
176
+ }
177
+
178
+ /**
179
+ * 解析样式文件并添加作用域
180
+ * @param {string} source - 原始样式内容
181
+ * @param {string} scopeSelector - 作用域选择器
182
+ * @param {string[]} componentNameVariants - 组件名的所有变体
183
+ * @returns {string} 添加了作用域的样式内容
184
+ */
185
+ function addScopeToStyles(source, scopeSelector, componentNameVariants = []) {
186
+ if (!source || !source.trim()) {
187
+ return source;
188
+ }
189
+
190
+ let result = '';
191
+ let i = 0;
192
+ let depth = 0; // 大括号嵌套深度
193
+ let inString = false; // 是否在字符串中
194
+ let stringChar = ''; // 字符串的引号类型
195
+ let inComment = false; // 是否在注释中
196
+ let commentType = ''; // 注释类型:'//' 或 '/*'
197
+ let currentSelector = '';
198
+ let currentRule = '';
199
+ let braceStart = -1;
200
+
201
+ // 不需要添加作用域的 @ 规则(这些规则应该保持原样)
202
+ const skipScopeAtRules = ['@import', '@charset', '@namespace', '@font-face'];
203
+ // 需要递归处理的 @ 规则(内部的选择器也需要添加作用域)
204
+ const nestedAtRules = ['@media', '@supports', '@keyframes', '@page', '@viewport'];
205
+ let inAtRule = false;
206
+ let atRuleName = '';
207
+ let atRuleType = ''; // 'skip' 或 'nested'
208
+
209
+ while (i < source.length) {
210
+ const char = source[i];
211
+ const nextChar = source[i + 1] || '';
212
+
213
+ // 处理字符串
214
+ if (!inComment && (char === '"' || char === "'")) {
215
+ if (!inString) {
216
+ inString = true;
217
+ stringChar = char;
218
+ } else if (char === stringChar && source[i - 1] !== '\\') {
219
+ inString = false;
220
+ stringChar = '';
221
+ }
222
+ currentRule += char;
223
+ i++;
224
+ continue;
225
+ }
226
+
227
+ // 处理注释
228
+ if (!inString) {
229
+ if (!inComment && char === '/' && nextChar === '/') {
230
+ inComment = true;
231
+ commentType = '//';
232
+ currentRule += char;
233
+ i++;
234
+ continue;
235
+ }
236
+ if (!inComment && char === '/' && nextChar === '*') {
237
+ inComment = true;
238
+ commentType = '/*';
239
+ currentRule += char;
240
+ i++;
241
+ continue;
242
+ }
243
+ if (inComment && commentType === '//' && char === '\n') {
244
+ inComment = false;
245
+ commentType = '';
246
+ currentRule += char;
247
+ i++;
248
+ continue;
249
+ }
250
+ if (inComment && commentType === '/*' && char === '*' && nextChar === '/') {
251
+ inComment = false;
252
+ commentType = '';
253
+ currentRule += char + nextChar;
254
+ i += 2;
255
+ continue;
256
+ }
257
+ if (inComment) {
258
+ currentRule += char;
259
+ i++;
260
+ continue;
261
+ }
262
+ }
263
+
264
+ // 处理 @ 规则
265
+ if (!inString && !inComment && char === '@' && depth === 0) {
266
+ const remaining = source.substring(i);
267
+ // 检查是否是跳过作用域的规则
268
+ for (const atRule of skipScopeAtRules) {
269
+ if (remaining.startsWith(atRule)) {
270
+ inAtRule = true;
271
+ atRuleName = atRule;
272
+ atRuleType = 'skip';
273
+ break;
274
+ }
275
+ }
276
+ // 检查是否是嵌套规则
277
+ if (!inAtRule) {
278
+ for (const atRule of nestedAtRules) {
279
+ if (remaining.startsWith(atRule)) {
280
+ inAtRule = true;
281
+ atRuleName = atRule;
282
+ atRuleType = 'nested';
283
+ break;
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ // 处理左大括号
290
+ if (!inString && !inComment && char === '{') {
291
+ if (depth === 0) {
292
+ // 保存当前选择器
293
+ currentSelector = currentRule.trim();
294
+ currentRule = '';
295
+ braceStart = i;
296
+ }
297
+ depth++;
298
+ currentRule += char;
299
+ i++;
300
+ continue;
301
+ }
302
+
303
+ // 处理右大括号
304
+ if (!inString && !inComment && char === '}') {
305
+ depth--;
306
+ currentRule += char;
307
+
308
+ if (depth === 0) {
309
+ // 处理完整的规则块
310
+ if (inAtRule) {
311
+ if (atRuleType === 'skip') {
312
+ // 跳过作用域的 @ 规则直接添加
313
+ result += currentSelector + currentRule;
314
+ } else if (atRuleType === 'nested') {
315
+ // 嵌套 @ 规则:为内部的选择器添加作用域
316
+ const innerContent = currentRule.slice(1, -1); // 去掉外层大括号
317
+ const scopedInnerContent = addScopeToStyles(
318
+ innerContent,
319
+ scopeSelector,
320
+ componentNameVariants
321
+ );
322
+ result += currentSelector + '{' + scopedInnerContent + '}';
323
+ } else {
324
+ result += currentSelector + currentRule;
325
+ }
326
+ inAtRule = false;
327
+ atRuleName = '';
328
+ atRuleType = '';
329
+ } else {
330
+ // 普通规则,为选择器添加作用域
331
+ const scopedSelector = addScopeToSelector(
332
+ currentSelector,
333
+ scopeSelector,
334
+ componentNameVariants
335
+ );
336
+ result += scopedSelector + currentRule;
337
+ }
338
+ currentSelector = '';
339
+ currentRule = '';
340
+ braceStart = -1;
341
+ }
342
+ i++;
343
+ continue;
344
+ }
345
+
346
+ // 其他字符
347
+ currentRule += char;
348
+ i++;
349
+ }
350
+
351
+ // 处理文件末尾可能剩余的内容
352
+ if (currentRule.trim()) {
353
+ if (depth === 0 && currentSelector) {
354
+ const scopedSelector = addScopeToSelector(
355
+ currentSelector,
356
+ scopeSelector,
357
+ componentNameVariants
358
+ );
359
+ result += scopedSelector + currentRule;
360
+ } else {
361
+ result += currentRule;
362
+ }
363
+ }
364
+
365
+ return result || source;
366
+ }
367
+ /**
368
+ * 自动添加组件样式作用域
369
+ * @param {string} source - 原始样式内容
370
+ * @returns {string} 添加了作用域的样式内容
371
+ * 实现关键路径:
372
+ * 1. 判断是否是目标文件
373
+ * 2. 获取组件名
374
+ * 3. 生成组件名的所有可能变体
375
+ * 4. 为选择器添加作用域
376
+ * 5. 返回添加了作用域的样式内容
377
+ *
378
+ * 特别说明,组件根节点的样式 需要和组件目录名称 产生关联,不然其根节点的样式会失效。
379
+ */
380
+ module.exports = function componentScopeStyleLoader(source) {
381
+ if (typeof this?.cacheable === 'function') {
382
+ this.cacheable();
383
+ }
384
+
385
+ const options =
386
+ (typeof this?.getOptions === 'function' && this.getOptions()) || this?.query || {};
387
+ const scopeKey = options.scopeKey || DEFAULT_SCOPE_KEY;
388
+ const componentsDir = options.componentsDir || DEFAULT_COMPONENTS_DIR;
389
+
390
+ const resourcePath = this.resourcePath || '';
391
+ const isTargetFile = /(index|style)\.(scss|less)$/i.test(resourcePath);
392
+ if (!isTargetFile) {
393
+ return source;
394
+ }
395
+
396
+ const componentsDirAbs = path.isAbsolute(componentsDir)
397
+ ? componentsDir
398
+ : path.resolve(process.cwd(), componentsDir);
399
+
400
+ const normalizedResourcePath = normalizePath(resourcePath);
401
+ const normalizedCmpDir = normalizePath(componentsDirAbs + path.sep);
402
+
403
+ // 仅处理位于组件目录下的样式文件
404
+ if (!normalizedResourcePath.startsWith(normalizedCmpDir)) {
405
+ return source;
406
+ }
407
+
408
+ // 组件目录的第一层子目录名即组件名
409
+ const relativePath = path.relative(componentsDirAbs, resourcePath);
410
+ const pathParts = relativePath.split(path.sep);
411
+ const cmpName = pathParts[0];
412
+
413
+ if (!cmpName) {
414
+ return source;
415
+ }
416
+
417
+ // 非组件直接子目录下的样式也不处理
418
+ if (pathParts.length > 2) {
419
+ return source;
420
+ }
421
+
422
+ // 生成组件名的所有可能变体
423
+ const componentNameVariants = generateComponentNameVariants(cmpName);
424
+
425
+ const scopeSelector = `[${scopeKey}="${cmpName}"]`;
426
+ const scopedContent = addScopeToStyles(source, scopeSelector, componentNameVariants);
427
+
428
+ return scopedContent;
429
+ };
@@ -1,15 +1,28 @@
1
1
  /**
2
- * Babel 插件:自动在 React 组件的最外层元素添加 data-scope 属性
2
+ * Babel 插件:自动在 React 组件的最外层元素添加 scopeKey 属性
3
3
  *
4
4
  * 使用场景:
5
- * - 在构建 React 组件时,自动为组件的根元素添加 data-scope 属性
5
+ * - 在构建 React 组件时,自动为组件的根元素添加 scopeKey 属性
6
6
  * - 支持函数组件和类组件
7
7
  * - 如果组件返回 Fragment 或数组,会在 Fragment 的第一个子元素或数组的第一个元素上添加
8
- * - data-scope 的值默认为当前组件所在文件目录的名称
8
+ * - scopeKey 的值默认为当前组件所在文件目录的名称
9
9
  */
10
10
  const path = require('path');
11
11
 
12
- module.exports = function ({ types: t }) {
12
+ /**
13
+ * @param {*} babelParams
14
+ * babelTypes(即 @babel/types)提供:
15
+ * 节点类型检查:babelTypes.isJSXElement(node)
16
+ * 创建节点:babelTypes.jsxAttribute(...)
17
+ * 节点构造器:babelTypes.stringLiteral('value')
18
+ * 节点操作:path.get('body')、path.traverse({})
19
+ *
20
+ * @param {*} options 插件配置对象
21
+ * scopeKey 数据作用域的键名,默认为 'data-scope'
22
+ */
23
+ module.exports = function (babelParams, options) {
24
+ const { types: babelTypes } = babelParams;
25
+ const scopeKey = (options || {}).scopeKey || 'data-scope';
13
26
  /**
14
27
  * 获取文件所在目录的名称
15
28
  */
@@ -25,6 +38,275 @@ module.exports = function ({ types: t }) {
25
38
  return '';
26
39
  }
27
40
  }
41
+ /**
42
+ * 判断 callee 是否是 React.createElement
43
+ */
44
+ function isReactCreateElementCallee(calleePath) {
45
+ if (!calleePath) {
46
+ return false;
47
+ }
48
+ if (
49
+ calleePath.isMemberExpression() &&
50
+ babelTypes.isIdentifier(calleePath.get('object').node, { name: 'React' }) &&
51
+ babelTypes.isIdentifier(calleePath.get('property').node, { name: 'createElement' })
52
+ ) {
53
+ return true;
54
+ }
55
+ return calleePath.isIdentifier({ name: 'createElement' });
56
+ }
57
+
58
+ /**
59
+ * 判断 callee 是否是 React 17 自动 JSX 运行时生成的 jsx/jsxs/jsxDEV 调用
60
+ * 支持 _jsx/_jsxs 形式(ts 编译后的别名)
61
+ */
62
+ const jsxRuntimeCalleeNames = new Set(['jsx', 'jsxs', 'jsxDEV', '_jsx', '_jsxs', '_jsxDEV']);
63
+ function isReactJSXRuntimeCallee(calleePath) {
64
+ return (
65
+ calleePath && calleePath.isIdentifier() && jsxRuntimeCalleeNames.has(calleePath.node.name)
66
+ );
67
+ }
68
+
69
+ /**
70
+ * 判断是否是 React 元素生成调用(createElement 或 jsx/jsxs/jsxDEV)
71
+ */
72
+ function isReactElementCall(callPath) {
73
+ if (!callPath || !callPath.isCallExpression()) {
74
+ return false;
75
+ }
76
+ const calleePath = callPath.get('callee');
77
+ return isReactCreateElementCallee(calleePath) || isReactJSXRuntimeCallee(calleePath);
78
+ }
79
+
80
+ /**
81
+ * 判断元素类型是否是 Fragment
82
+ */
83
+ function isFragmentType(typePath) {
84
+ if (!typePath || !typePath.node) {
85
+ return false;
86
+ }
87
+ if (babelTypes.isIdentifier(typePath.node, { name: 'Fragment' })) {
88
+ return true;
89
+ }
90
+ if (
91
+ babelTypes.isMemberExpression(typePath.node) &&
92
+ babelTypes.isIdentifier(typePath.node.object, { name: 'React' }) &&
93
+ babelTypes.isIdentifier(typePath.node.property, { name: 'Fragment' })
94
+ ) {
95
+ return true;
96
+ }
97
+ return false;
98
+ }
99
+
100
+ /**
101
+ * 判断 ObjectExpression 中是否已有 scopeKey
102
+ */
103
+ function hasScopeKeyInProps(objectExpressionNode) {
104
+ return objectExpressionNode.properties.some((prop) => {
105
+ if (!babelTypes.isObjectProperty(prop) || prop.computed) {
106
+ return false;
107
+ }
108
+ if (babelTypes.isIdentifier(prop.key) && prop.key.name === scopeKey) {
109
+ return true;
110
+ }
111
+ return babelTypes.isStringLiteral(prop.key) && prop.key.value === scopeKey;
112
+ });
113
+ }
114
+
115
+ /**
116
+ * 创建一个 scopeKey 的 object property
117
+ */
118
+ function createScopeProperty(scopeValue) {
119
+ const key = babelTypes.isValidIdentifier(scopeKey)
120
+ ? babelTypes.identifier(scopeKey)
121
+ : babelTypes.stringLiteral(scopeKey);
122
+ return babelTypes.objectProperty(key, babelTypes.stringLiteral(scopeValue));
123
+ }
124
+
125
+ /**
126
+ * 为 props 对象添加 scopeKey
127
+ */
128
+ function addScopeToPropsObject(propsPath, scopeValue) {
129
+ if (!propsPath) {
130
+ return;
131
+ }
132
+
133
+ // props 为 null/undefined 时直接替换为对象
134
+ if (propsPath.isNullLiteral() || propsPath.isIdentifier({ name: 'undefined' })) {
135
+ propsPath.replaceWith(babelTypes.objectExpression([createScopeProperty(scopeValue)]));
136
+ return;
137
+ }
138
+
139
+ if (propsPath.isObjectExpression()) {
140
+ if (hasScopeKeyInProps(propsPath.node)) {
141
+ return;
142
+ }
143
+ propsPath.node.properties.unshift(createScopeProperty(scopeValue));
144
+ return;
145
+ }
146
+
147
+ // 其他类型的 props,使用 Object.assign 合并
148
+ propsPath.replaceWith(
149
+ babelTypes.callExpression(
150
+ babelTypes.memberExpression(
151
+ babelTypes.identifier('Object'),
152
+ babelTypes.identifier('assign')
153
+ ),
154
+ [babelTypes.objectExpression([createScopeProperty(scopeValue)]), propsPath.node]
155
+ )
156
+ );
157
+ }
158
+
159
+ /**
160
+ * 处理 Fragment 的子元素,找到首个可注入的元素
161
+ */
162
+ function processFragmentChildren(callPath, scopeValue) {
163
+ const args = callPath.get('arguments');
164
+ // jsx/jsxs 调用的 children 在第二个参数(props.children)
165
+ const propsPath = args[1];
166
+ if (propsPath && propsPath.isObjectExpression()) {
167
+ const propPaths = propsPath.get('properties');
168
+ for (const propPath of propPaths) {
169
+ if (
170
+ propPath.isObjectProperty() &&
171
+ !propPath.node.computed &&
172
+ ((babelTypes.isIdentifier(propPath.node.key) && propPath.node.key.name === 'children') ||
173
+ (babelTypes.isStringLiteral(propPath.node.key) &&
174
+ propPath.node.key.value === 'children'))
175
+ ) {
176
+ const valuePath = propPath.get('value');
177
+ if (Array.isArray(valuePath)) {
178
+ // safety: ObjectProperty value returns NodePath, not array
179
+ continue;
180
+ }
181
+ if (processJSXLikePath(valuePath, scopeValue)) {
182
+ return true;
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ // createElement 形式的 children 在第三个参数开始
189
+ for (let i = 2; i < args.length; i++) {
190
+ if (processJSXLikePath(args[i], scopeValue)) {
191
+ return true;
192
+ }
193
+ }
194
+ return false;
195
+ }
196
+
197
+ /**
198
+ * 处理 createElement/jsx 调用,向 props 注入 scopeKey
199
+ */
200
+ function processCreateElementCall(callPath, scopeValue) {
201
+ if (!isReactElementCall(callPath)) {
202
+ return false;
203
+ }
204
+
205
+ const args = callPath.get('arguments');
206
+ const typePath = args[0];
207
+ const isFragment = isFragmentType(typePath);
208
+
209
+ if (isFragment) {
210
+ // Fragment 需要把 scopeKey 添加到首个子元素
211
+ const handled = processFragmentChildren(callPath, scopeValue);
212
+ if (handled) {
213
+ return true;
214
+ }
215
+ // 如果没有子元素可处理,则回退到为 Fragment props 添加
216
+ }
217
+
218
+ // jsx/jsxs/createElement 的 props 都在第二个参数
219
+ const propsIndex = 1;
220
+ if (!args[propsIndex]) {
221
+ callPath.node.arguments[propsIndex] = babelTypes.objectExpression([]);
222
+ }
223
+ const propsPath = callPath.get('arguments')[propsIndex];
224
+ addScopeToPropsObject(propsPath, scopeValue);
225
+ return true;
226
+ }
227
+
228
+ /**
229
+ * 递归判断表达式中是否包含 JSX 或 React 元素调用
230
+ */
231
+ function containsJSXLike(nodePath) {
232
+ if (!nodePath || !nodePath.node) {
233
+ return false;
234
+ }
235
+ if (nodePath.isJSXElement() || nodePath.isJSXFragment()) {
236
+ return true;
237
+ }
238
+ if (nodePath.isArrayExpression()) {
239
+ return nodePath.get('elements').some((element) => containsJSXLike(element));
240
+ }
241
+ if (nodePath.isConditionalExpression()) {
242
+ return (
243
+ containsJSXLike(nodePath.get('consequent')) || containsJSXLike(nodePath.get('alternate'))
244
+ );
245
+ }
246
+ if (nodePath.isLogicalExpression()) {
247
+ return containsJSXLike(nodePath.get('left')) || containsJSXLike(nodePath.get('right'));
248
+ }
249
+ if (nodePath.isCallExpression()) {
250
+ if (isReactElementCall(nodePath)) {
251
+ return true;
252
+ }
253
+ // 进一步查看参数里是否有 JSX 结构
254
+ return nodePath.get('arguments').some((argPath) => containsJSXLike(argPath));
255
+ }
256
+ return false;
257
+ }
258
+
259
+ /**
260
+ * 递归处理可能的 JSX/React 元素表达式
261
+ */
262
+ function processJSXLikePath(nodePath, scopeValue) {
263
+ if (!nodePath || !nodePath.node) {
264
+ return false;
265
+ }
266
+
267
+ if (nodePath.isJSXElement()) {
268
+ addDataScopeAttribute(nodePath.node, scopeValue);
269
+ return true;
270
+ }
271
+
272
+ if (nodePath.isJSXFragment()) {
273
+ const children = nodePath.get('children');
274
+ for (const child of children) {
275
+ if (processJSXLikePath(child, scopeValue)) {
276
+ return true;
277
+ }
278
+ }
279
+ return false;
280
+ }
281
+
282
+ if (nodePath.isArrayExpression()) {
283
+ const elements = nodePath.get('elements');
284
+ for (const element of elements) {
285
+ if (processJSXLikePath(element, scopeValue)) {
286
+ return true;
287
+ }
288
+ }
289
+ return false;
290
+ }
291
+
292
+ if (nodePath.isConditionalExpression()) {
293
+ const handledConsequent = processJSXLikePath(nodePath.get('consequent'), scopeValue);
294
+ const handledAlternate = processJSXLikePath(nodePath.get('alternate'), scopeValue);
295
+ return handledConsequent || handledAlternate;
296
+ }
297
+
298
+ if (nodePath.isLogicalExpression()) {
299
+ const handledLeft = processJSXLikePath(nodePath.get('left'), scopeValue);
300
+ const handledRight = processJSXLikePath(nodePath.get('right'), scopeValue);
301
+ return handledLeft || handledRight;
302
+ }
303
+
304
+ if (nodePath.isCallExpression()) {
305
+ return processCreateElementCall(nodePath, scopeValue);
306
+ }
307
+
308
+ return false;
309
+ }
28
310
  /**
29
311
  * 检查函数是否返回 JSX(用于判断是否是 React 组件)
30
312
  */
@@ -37,18 +319,17 @@ module.exports = function ({ types: t }) {
37
319
  body.traverse({
38
320
  ReturnStatement(returnPath) {
39
321
  const argument = returnPath.get('argument');
40
- if (
41
- argument.node &&
42
- (argument.isJSXElement() || argument.isJSXFragment() || argument.isArrayExpression())
43
- ) {
322
+ if (argument.node && containsJSXLike(argument)) {
44
323
  hasJSXReturn = true;
45
324
  returnPath.stop();
46
325
  }
47
326
  }
48
327
  });
49
328
  return hasJSXReturn;
50
- } else if (body.isJSXElement() || body.isJSXFragment()) {
51
- // 箭头函数直接返回 JSX
329
+ }
330
+
331
+ if (containsJSXLike(body)) {
332
+ // 箭头函数或表达式直接返回 JSX/React 元素调用
52
333
  return true;
53
334
  }
54
335
 
@@ -56,24 +337,24 @@ module.exports = function ({ types: t }) {
56
337
  }
57
338
 
58
339
  /**
59
- * 在 JSX 元素上添加 data-scope 属性
340
+ * 在 JSX 元素上添加 scopeKey 属性
60
341
  * @param {Object} jsxElement - JSX 元素节点
61
- * @param {string} scopeValue - data-scope 的值,默认为空字符串
342
+ * @param {string} scopeValue - scopeKey 的值,默认为空字符串
62
343
  */
63
344
  function addDataScopeAttribute(jsxElement, scopeValue = '') {
64
345
  const openingElement = jsxElement.openingElement;
65
346
  const attributes = openingElement.attributes;
66
347
 
67
- // 检查是否已经存在 data-scope 属性
348
+ // 检查是否已经存在 scopeKey 属性
68
349
  const hasDataScope = attributes.some(
69
- (attr) => t.isJSXAttribute(attr) && attr.name.name === 'data-scope'
350
+ (attr) => babelTypes.isJSXAttribute(attr) && attr.name.name === scopeKey
70
351
  );
71
352
 
72
353
  if (!hasDataScope) {
73
- // 创建 data-scope 属性,值为目录名
74
- const dataScopeAttr = t.jsxAttribute(
75
- t.jsxIdentifier('data-scope'),
76
- t.stringLiteral(scopeValue)
354
+ // 创建 scopeKey 属性,值为目录名
355
+ const dataScopeAttr = babelTypes.jsxAttribute(
356
+ babelTypes.jsxIdentifier(scopeKey),
357
+ babelTypes.stringLiteral(scopeValue)
77
358
  );
78
359
  // 添加到属性列表的开头
79
360
  attributes.unshift(dataScopeAttr);
@@ -83,7 +364,7 @@ module.exports = function ({ types: t }) {
83
364
  /**
84
365
  * 处理返回语句中的 JSX
85
366
  * @param {Object} path - Babel path 对象
86
- * @param {string} scopeValue - data-scope 的值
367
+ * @param {string} scopeValue - scopeKey 的值
87
368
  */
88
369
  function processReturnStatement(path, scopeValue) {
89
370
  const argument = path.get('argument');
@@ -92,84 +373,7 @@ module.exports = function ({ types: t }) {
92
373
  return;
93
374
  }
94
375
 
95
- // 处理 JSX 元素
96
- if (argument.isJSXElement()) {
97
- addDataScopeAttribute(argument.node, scopeValue);
98
- return;
99
- }
100
-
101
- // 处理 JSX Fragment
102
- if (argument.isJSXFragment()) {
103
- const children = argument.get('children');
104
- // 找到第一个 JSX 元素子节点
105
- for (const child of children) {
106
- if (child.isJSXElement()) {
107
- addDataScopeAttribute(child.node, scopeValue);
108
- return;
109
- }
110
- // 如果子节点是 Fragment,递归处理
111
- if (child.isJSXFragment()) {
112
- processReturnStatement(child, scopeValue);
113
- return;
114
- }
115
- }
116
- }
117
-
118
- // 处理数组返回(多个元素)
119
- if (argument.isArrayExpression()) {
120
- const elements = argument.get('elements');
121
- // 找到第一个 JSX 元素
122
- for (const element of elements) {
123
- if (element.isJSXElement()) {
124
- addDataScopeAttribute(element.node, scopeValue);
125
- return;
126
- }
127
- // 如果元素是 Fragment,递归处理
128
- if (element.isJSXFragment()) {
129
- processReturnStatement(element, scopeValue);
130
- return;
131
- }
132
- }
133
- }
134
-
135
- // 处理条件表达式(三元运算符)
136
- if (argument.isConditionalExpression()) {
137
- // 处理 true 分支
138
- const consequent = argument.get('consequent');
139
- if (consequent.isJSXElement()) {
140
- addDataScopeAttribute(consequent.node, scopeValue);
141
- } else if (consequent.isJSXFragment() || consequent.isArrayExpression()) {
142
- processReturnStatement(consequent, scopeValue);
143
- }
144
-
145
- // 处理 false 分支
146
- const alternate = argument.get('alternate');
147
- if (alternate.isJSXElement()) {
148
- addDataScopeAttribute(alternate.node, scopeValue);
149
- } else if (alternate.isJSXFragment() || alternate.isArrayExpression()) {
150
- processReturnStatement(alternate, scopeValue);
151
- }
152
- }
153
-
154
- // 处理逻辑表达式(&& 或 ||)
155
- if (argument.isLogicalExpression()) {
156
- const left = argument.get('left');
157
- const right = argument.get('right');
158
-
159
- // 处理左侧(可能是 JSX)
160
- if (left.isJSXElement()) {
161
- addDataScopeAttribute(left.node, scopeValue);
162
- } else if (left.isJSXFragment() || left.isArrayExpression()) {
163
- processReturnStatement(left, scopeValue);
164
- }
165
-
166
- // 处理右侧(可能是 JSX)
167
- if (right.isJSXElement()) {
168
- addDataScopeAttribute(right.node, scopeValue);
169
- } else if (right.isJSXFragment() || right.isArrayExpression()) {
170
- processReturnStatement(right, scopeValue);
171
- }
172
- }
376
+ processJSXLikePath(argument, scopeValue);
173
377
  }
174
378
 
175
379
  return {
@@ -194,29 +398,72 @@ module.exports = function ({ types: t }) {
194
398
  processReturnStatement(returnPath, scopeValue);
195
399
  }
196
400
  });
197
- } else if (body.isJSXElement()) {
198
- // 箭头函数直接返回 JSX
199
- addDataScopeAttribute(body.node, scopeValue);
200
- } else if (body.isJSXFragment()) {
201
- // 箭头函数直接返回 Fragment
202
- const children = body.get('children');
203
- for (const child of children) {
204
- if (child.isJSXElement()) {
205
- addDataScopeAttribute(child.node, scopeValue);
206
- return;
207
- }
208
- // 如果子节点是 Fragment,递归处理
209
- if (child.isJSXFragment()) {
210
- processReturnStatement(child, scopeValue);
211
- return;
401
+ } else {
402
+ // 箭头函数或表达式体直接返回 React 元素调用
403
+ processJSXLikePath(body, scopeValue);
404
+ }
405
+ },
406
+
407
+ // 处理类组件(推荐方式:使用 Class visitor 更可靠)
408
+ Class(classPath) {
409
+ // 检查是否是 React 组件类(继承自 React.Component、React.PureComponent 或 BaseCmp)
410
+ const superClass = classPath.node.superClass;
411
+ if (!superClass) {
412
+ return;
413
+ }
414
+
415
+ // 检查是否是 React 组件相关的类
416
+ // 支持:React.Component、React.PureComponent、BaseCmp 等
417
+ const isReactComponent =
418
+ (babelTypes.isIdentifier(superClass) &&
419
+ (superClass.name === 'Component' ||
420
+ superClass.name === 'PureComponent' ||
421
+ superClass.name === 'BaseCmp')) ||
422
+ (babelTypes.isMemberExpression(superClass) &&
423
+ babelTypes.isIdentifier(superClass.object) &&
424
+ superClass.object.name === 'React');
425
+
426
+ if (!isReactComponent) {
427
+ return;
428
+ }
429
+
430
+ // 获取当前文件路径并提取目录名
431
+ const filePath = classPath.hub?.file?.opts?.filename || '';
432
+ const scopeValue = getDirectoryName(filePath);
433
+
434
+ // 遍历类的方法,查找 render 方法
435
+ classPath.traverse({
436
+ ClassMethod(methodPath) {
437
+ const methodKey = methodPath.node.key;
438
+ // 检查是否是 render 方法(排除 getter/setter)
439
+ if (
440
+ methodPath.node.kind === 'method' &&
441
+ babelTypes.isIdentifier(methodKey) &&
442
+ methodKey.name === 'render'
443
+ ) {
444
+ const body = methodPath.get('body');
445
+ if (body.isBlockStatement()) {
446
+ body.traverse({
447
+ ReturnStatement(returnPath) {
448
+ processReturnStatement(returnPath, scopeValue);
449
+ }
450
+ });
451
+ }
212
452
  }
213
453
  }
214
- }
454
+ });
215
455
  },
216
456
 
217
- // 处理类组件的 render 方法
457
+ // 处理类组件的 render 方法(备选方案:直接使用 ClassMethod visitor)
218
458
  ClassMethod(classMethodPath) {
219
- if (classMethodPath.node.kind === 'method' && classMethodPath.node.key.name === 'render') {
459
+ // 放宽条件判断,只排除 getter/setter
460
+ const methodKey = classMethodPath.node.key;
461
+ const isRenderMethod =
462
+ classMethodPath.node.kind === 'method' &&
463
+ babelTypes.isIdentifier(methodKey) &&
464
+ methodKey.name === 'render';
465
+
466
+ if (isRenderMethod) {
220
467
  // 获取当前文件路径并提取目录名
221
468
  const filePath = classMethodPath.hub?.file?.opts?.filename || '';
222
469
  const scopeValue = getDirectoryName(filePath);
@@ -0,0 +1,88 @@
1
+ // 空函数
2
+ export function noop(): void {}
3
+
4
+ /**
5
+ * 加载远程 JS脚本
6
+ * 备注:用于动态加载自定义组件脚本
7
+ * @param jsSrc js脚本地址
8
+ * @param callback 加载完成后的回调函数
9
+ */
10
+ export function loadRemoteJs(jsSrc: string, callback?: (success?: boolean) => void): void {
11
+ const curCallback = callback || noop;
12
+
13
+ // 检查脚本是否已存在
14
+ const existingScript = document.querySelector(
15
+ `script[src='${jsSrc}']`
16
+ ) as HTMLScriptElement | null;
17
+
18
+ if (existingScript) {
19
+ // 检查脚本是否已加载完成
20
+ const loadStatus = existingScript.getAttribute('script-load-status');
21
+
22
+ if (loadStatus === 'true') {
23
+ // 已加载完成,直接执行回调
24
+ curCallback(true);
25
+ } else {
26
+ // 未加载完成,等待加载完成
27
+ const waitLoadAction = (): void => {
28
+ existingScript.removeEventListener('load', waitLoadAction);
29
+ existingScript.setAttribute('script-load-status', 'true');
30
+ curCallback(true);
31
+ };
32
+
33
+ const waitErrorAction = (): void => {
34
+ existingScript.removeEventListener('error', waitErrorAction);
35
+ curCallback(false);
36
+ };
37
+
38
+ existingScript.addEventListener('load', waitLoadAction);
39
+ existingScript.addEventListener('error', waitErrorAction);
40
+ }
41
+ } else {
42
+ // 创建新的脚本标签
43
+ const script = document.createElement('script');
44
+
45
+ const waitLoadAction = (): void => {
46
+ script.removeEventListener('load', waitLoadAction);
47
+ script.setAttribute('script-load-status', 'true');
48
+ curCallback(true);
49
+ };
50
+
51
+ const waitErrorAction = (): void => {
52
+ script.removeEventListener('error', waitErrorAction);
53
+ curCallback(false);
54
+ };
55
+
56
+ script.addEventListener('load', waitLoadAction);
57
+ script.addEventListener('error', waitErrorAction);
58
+ script.src = jsSrc;
59
+ document.body.appendChild(script);
60
+ }
61
+ }
62
+
63
+ export const loadRemoteJsPromise = (url: string) =>
64
+ new Promise<boolean>((resolve) => {
65
+ loadRemoteJs(url, (loadResult) => {
66
+ resolve(loadResult ?? false);
67
+ });
68
+ });
69
+
70
+ /**
71
+ * 加载远程 CSS
72
+ * @param cssSrc css地址
73
+ */
74
+ export function loadRemoteCSS(cssSrc: string): void {
75
+ const loadedLink = document.querySelector(`link[href='${cssSrc}']`) as HTMLLinkElement | null;
76
+ if (loadedLink) {
77
+ // 避免重复加载
78
+ loadedLink.disabled = false;
79
+ return;
80
+ }
81
+
82
+ const head = document.getElementsByTagName('head')[0];
83
+ const link = document.createElement('link');
84
+ link.rel = 'stylesheet';
85
+ link.type = 'text/css';
86
+ link.href = cssSrc;
87
+ head.appendChild(link);
88
+ }
@@ -1,47 +0,0 @@
1
- // 空函数
2
- export function noop() {}
3
-
4
- /**
5
- * 加载远程 JS脚本
6
- * @param JsSrc: js脚本地址
7
- * @param callback
8
- */
9
- export function loadRemoteJs(JsSrc, callback) {
10
- const curCallback = callback || noop;
11
- if (document.querySelector(`script[src='${JsSrc}']`)) {
12
- curCallback();
13
- return;
14
- }
15
-
16
- const script = document.createElement('script');
17
-
18
- const loadAction = () => {
19
- script.removeEventListener('load', loadAction);
20
- curCallback();
21
- };
22
-
23
- script.addEventListener('load', loadAction);
24
- script.src = JsSrc;
25
- document.body.appendChild(script);
26
- }
27
-
28
- /**
29
- * 加载远程 CSS
30
- * @param cssSrc: css地址
31
- * @param callback
32
- */
33
- export function loadRemoteCSS(cssSrc) {
34
- const loadedLink = document.querySelector(`link[href='${cssSrc}']`);
35
- if (loadedLink) {
36
- // 避免重复加载
37
- loadedLink.disabled = false;
38
- return;
39
- }
40
-
41
- const head = document.getElementsByTagName('head')[0];
42
- const link = document.createElement('link');
43
- link.rel = 'stylesheet';
44
- link.type = 'text/css';
45
- link.href = src;
46
- head.appendChild(link);
47
- }