wb-lib-tool 0.0.1-beta.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.ch.md +198 -0
- package/bin/dev.js +11 -0
- package/bin/index.js +45 -0
- package/bin/lib.js +56 -0
- package/config/PATH.js +17 -0
- package/config/babel.config.json +35 -0
- package/config/formatFitConfig/module.js +7 -0
- package/config/index.html +14 -0
- package/config/output.multi.js +33 -0
- package/config/webpack.base.conf.js +96 -0
- package/config/webpack.dev.conf.js +84 -0
- package/config/webpack.lib.conf.js +245 -0
- package/package.json +84 -0
- package/utils/file.js +157 -0
- package/utils/parserExportFile.js +115 -0
- package/utils/util.js +96 -0
package/README.ch.md
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# wb-lib
|
|
2
|
+
|
|
3
|
+
## 简介
|
|
4
|
+
|
|
5
|
+
使用 webpack 编译可生成多种js标准格式的组件库打包工具,默认支持 react 组件库的打包。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
// 在项目中安装wb-lib
|
|
11
|
+
|
|
12
|
+
npm i wb-lib -D
|
|
13
|
+
or
|
|
14
|
+
yarn add wb-lib -D
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 命令
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
/* 编译组件库 */
|
|
21
|
+
wb-lib lib
|
|
22
|
+
|
|
23
|
+
/* 组件库开发预览
|
|
24
|
+
* 默认以src/(index.jsx|index.tsx)文件为入口,
|
|
25
|
+
* 项目根目录可以创建index.html
|
|
26
|
+
* 如果没有创建则默认使用内置的index.html */
|
|
27
|
+
wb-lib dev
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 使用配置
|
|
31
|
+
|
|
32
|
+
可创建并使用 **`lib.config.js|lib.config.ts`** 配置来自定义打包。
|
|
33
|
+
|
|
34
|
+
#### 可使用属性
|
|
35
|
+
|
|
36
|
+
| 属性 | 类型 | 说明 |
|
|
37
|
+
| ---- | --------------- | ---- |
|
|
38
|
+
**format** | Object|Array|string | 生成格式配置【可配置类型 ['module','commonjs']等】,默认值为 `module` |
|
|
39
|
+
**output** | String | 输出存放文件夹,默认值为 `lib` |
|
|
40
|
+
**clean** | Boolean | 是否在打包前清除存放文件夹,默认值为 `true` |
|
|
41
|
+
**compileDir** | String | 编译目录,默认值为 `src/components` |
|
|
42
|
+
**webpackConf** | Function|Object | 自定义 webpack 配置项, 如传入函数,函数参数为 `function (webpackConf,env,format){ return webpackConf; }`,需要返回值为自定义的 webpack 配置项 `webpackConf(已有的webpack配置项)/env(当前运行模式[development|library])/format(编译格式[development模式下为null])` |
|
|
43
|
+
|
|
44
|
+
#### 配置文件的使用示例
|
|
45
|
+
##### 创建lib.config.ts|lib.config.js选其一就行,同时存在会默认优先加载lib.config.ts
|
|
46
|
+
###### 使用lib.config.ts 示例
|
|
47
|
+
```
|
|
48
|
+
import { merge } from 'webpack-merge';
|
|
49
|
+
import { resolve } from 'path';
|
|
50
|
+
import { fileURLToPath } from 'url';
|
|
51
|
+
|
|
52
|
+
// 在ES模块中获取__dirname
|
|
53
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
54
|
+
const __dirname = resolve(__filename, '..');
|
|
55
|
+
|
|
56
|
+
export default {
|
|
57
|
+
compileDir: 'src/components', // 默认编译目录
|
|
58
|
+
output: 'lib', // 输出存放文件夹
|
|
59
|
+
clean: true, // 是否在打包前清除存放文件夹
|
|
60
|
+
|
|
61
|
+
//【format 使用方式1】
|
|
62
|
+
format: 'module', // 生成 module 格式的文件 同时相关代码文件放入到 [output]目录下【默认生成module格式】
|
|
63
|
+
|
|
64
|
+
//【format 使用方式2】
|
|
65
|
+
format: ['module', 'commonjs'], // 分别生成 module 和 commonjs 类型 同时相关代码文件放入到 [output]/(module|commonjs) 目录下
|
|
66
|
+
|
|
67
|
+
//【format 使用方式3】
|
|
68
|
+
format: {
|
|
69
|
+
// 生成 module 类型 同时相关代码文件放入到 [output]/es 目录下
|
|
70
|
+
module: 'es',
|
|
71
|
+
// 生成 commonjs 类型 同时相关代码文件放入到 [output]/cjs 目录下
|
|
72
|
+
commonjs: 'cjs',
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
//【webpackConf 使用方式1 webpack 配置项】
|
|
76
|
+
webpackConf: {
|
|
77
|
+
// webpack 配置项
|
|
78
|
+
.....
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
//【webpackConf 使用方式2 webpack 配置项】
|
|
82
|
+
webpackConf: function (webpackConf, env,format) {
|
|
83
|
+
// 自定义 webpack 配置项
|
|
84
|
+
return webpackConf;
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
###### 使用lib.config.js
|
|
90
|
+
```
|
|
91
|
+
module.exports = {
|
|
92
|
+
compileDir: 'src/components', // 默认编译目录
|
|
93
|
+
output: 'lib', // 输出存放文件夹
|
|
94
|
+
clean: true, // 是否在打包前清除存放文件夹
|
|
95
|
+
|
|
96
|
+
//【format 使用方式1】
|
|
97
|
+
format: 'module', // 生成 module 格式的文件 同时相关代码文件放入到 [output]目录下【默认生成module格式】
|
|
98
|
+
|
|
99
|
+
//【format 使用方式2】
|
|
100
|
+
format: ['module', 'commonjs'], // 分别生成 module 和 commonjs 类型 同时相关代码文件放入到 [output]/(module|commonjs) 目录下
|
|
101
|
+
|
|
102
|
+
//【format 使用方式3】
|
|
103
|
+
format: {
|
|
104
|
+
// 生成 module 类型 同时相关代码文件放入到 [output]/es 目录下
|
|
105
|
+
module: 'es',
|
|
106
|
+
// 生成 commonjs 类型 同时相关代码文件放入到 [output]/cjs 目录下
|
|
107
|
+
commonjs: 'cjs',
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
//【webpackConf 使用方式1 webpack 配置项】
|
|
111
|
+
webpackConf: {
|
|
112
|
+
// webpack 配置项
|
|
113
|
+
.....
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
//【webpackConf 使用方式2 webpack 配置项】
|
|
117
|
+
webpackConf: function (webpackConf, env,format) {
|
|
118
|
+
// 自定义 webpack 配置项
|
|
119
|
+
return webpackConf;
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 目录结构说明
|
|
126
|
+
|
|
127
|
+
#### 源目录结构
|
|
128
|
+
```
|
|
129
|
+
├── ... # 其他文件
|
|
130
|
+
├── src/ # 源代码目录
|
|
131
|
+
│ ├── components/ # 组件目录
|
|
132
|
+
│ │ ├── button/ # 组件目录
|
|
133
|
+
│ │ │ └── index.tsx # 组件入口文件
|
|
134
|
+
│ │ │ ├── style/ # 组件样式文件的文件夹
|
|
135
|
+
│ │ │ │ ├── index.tsx # 组件样式入口文件[(可不创建)可将index.scss使用 import "./index.scss" 引入可配合组件按需加载使用]
|
|
136
|
+
│ │ ├── ├── └── index.scss # 组件组件样式文件
|
|
137
|
+
│ │ ├── index.tsx # 所有组件的汇集[(可不创建)可以把所有组件出口都放在一处并且所有css都汇集到一个文件中便于后期使用项目一次性引入【export { default as Xxx } from './xxx'】]
|
|
138
|
+
│ └── index.tsx # dev 入口文件
|
|
139
|
+
├── index.html # 项目根目录html文件【可不创建】
|
|
140
|
+
```
|
|
141
|
+
#### 编译后目录结构
|
|
142
|
+
|
|
143
|
+
**<font size="2">format 使用方式1</font>**
|
|
144
|
+
```
|
|
145
|
+
├── ... # 其他文件
|
|
146
|
+
├── [output]/ # 输出存放文件夹
|
|
147
|
+
│ ├── button/ # 组件目录
|
|
148
|
+
│ │ ├── index.js # 组件入口文件
|
|
149
|
+
│ │ ├── style/ # 组件样式目录
|
|
150
|
+
│ │ │ ├── index.js # 组件样式入口文件
|
|
151
|
+
│ │ │ └── index.css # 组件样式默认会把css文件放入到style文件下并命名为index.css
|
|
152
|
+
│ ├── index.min.js # 所有组件的汇集
|
|
153
|
+
│ └── index.min.css # 所有组件的样式汇集
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**<font size="2">format 使用方式2</font>**
|
|
157
|
+
```
|
|
158
|
+
├── ... # 其他文件
|
|
159
|
+
├── [output]/ # 输出存放文件夹
|
|
160
|
+
│ ├── module/ # module 格式代码目录
|
|
161
|
+
│ │ ├── button/ # 组件目录
|
|
162
|
+
│ │ │ ├── index.js # 组件入口文件
|
|
163
|
+
│ │ │ ├── style/ # 组件样式目录
|
|
164
|
+
│ │ │ │ ├── index.js # 组件样式入口文件
|
|
165
|
+
│ │ │ │ └── index.css # 组件样式默认会把css文件放入到style文件下并命名为index.css
|
|
166
|
+
│ │ ├── index.min.js # 所有组件的汇集
|
|
167
|
+
│ │ └── index.min.css # 所有组件的样式汇集
|
|
168
|
+
│ ├── commonjs/ # commonjs 格式代码目录
|
|
169
|
+
│ │ ├── button/ # 组件目录
|
|
170
|
+
│ │ │ ├── index.js # 组件入口文件
|
|
171
|
+
│ │ │ ├── style/ # 组件样式目录
|
|
172
|
+
│ │ │ │ ├── index.js # 组件样式入口文件
|
|
173
|
+
│ │ │ │ └── index.css # 组件样式默认会把css文件放入到style文件下并命名为index.css
|
|
174
|
+
│ │ ├── index.min.js # 所有组件的汇集
|
|
175
|
+
└── └── └── index.min.css # 所有组件的样式汇集
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**<font size="2">format 使用方式3</font>**
|
|
179
|
+
```
|
|
180
|
+
├── ... # 其他文件
|
|
181
|
+
├── [output]/ # 输出存放文件夹
|
|
182
|
+
│ ├── es/ # module 格式代码目录
|
|
183
|
+
│ │ ├── button/ # 组件目录
|
|
184
|
+
│ │ │ ├── index.js # 组件入口文件
|
|
185
|
+
│ │ │ ├── style/ # 组件样式目录
|
|
186
|
+
│ │ │ │ ├── index.js # 组件样式入口文件
|
|
187
|
+
│ │ │ │ └── index.css # 组件样式默认会把css文件放入到style文件下并命名为index.css
|
|
188
|
+
│ │ ├── index.min.js # 所有组件的汇集
|
|
189
|
+
│ │ └── index.min.css # 所有组件的样式汇集
|
|
190
|
+
│ ├── cjs/ # commonjs 格式代码目录
|
|
191
|
+
│ │ ├── button/ # 组件目录
|
|
192
|
+
│ │ │ ├── index.js # 组件入口文件
|
|
193
|
+
│ │ │ ├── style/ # 组件样式目录
|
|
194
|
+
│ │ │ │ ├── index.js # 组件样式入口文件
|
|
195
|
+
│ │ │ │ └── index.css # 组件样式默认会把css文件放入到style文件下并命名为index.css
|
|
196
|
+
│ │ ├── index.min.js # 所有组件的汇集
|
|
197
|
+
└── └── └── index.min.css # 所有组件的样式汇集
|
|
198
|
+
```
|
package/bin/dev.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { spawnPromise } = require('../utils/util.js');
|
|
2
|
+
const { getResolvePath } = require('../utils/file.js');
|
|
3
|
+
|
|
4
|
+
const spawnDev = async () => {
|
|
5
|
+
await spawnPromise('webpack-dev-server', ['--config',
|
|
6
|
+
`${getResolvePath('config/webpack.dev.conf.js')}`,
|
|
7
|
+
'--progress',
|
|
8
|
+
...process.argv.slice(3),
|
|
9
|
+
], { stdio: 'inherit' });
|
|
10
|
+
}
|
|
11
|
+
spawnDev();
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { Command } = require('commander');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { removeFile } = require('../utils/file.js');
|
|
5
|
+
const packageJson = require('../package.json');
|
|
6
|
+
|
|
7
|
+
const program = new Command(packageJson.name);
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
program.version(packageJson.version);
|
|
11
|
+
program.command('lib').action(() => {
|
|
12
|
+
require('./lib.js');
|
|
13
|
+
});
|
|
14
|
+
program.command('dev').action(() => {
|
|
15
|
+
require('./dev.js');
|
|
16
|
+
});
|
|
17
|
+
program.command('clean').option('-n, --name <name>', '删除项目内的文件名').action(({ name }) => {
|
|
18
|
+
removeFile(name);
|
|
19
|
+
});
|
|
20
|
+
program.parse();
|
|
21
|
+
|
|
22
|
+
// 处理程序退出
|
|
23
|
+
// const handleExit = (exitStatus = 0) => {
|
|
24
|
+
// console.error(chalk.red(
|
|
25
|
+
// `进程异常退出.\n`
|
|
26
|
+
// ));
|
|
27
|
+
// // 确保exitStatus是数字类型
|
|
28
|
+
// const numericExitStatus = typeof exitStatus === 'number' ? exitStatus : 1;
|
|
29
|
+
// process.exit(numericExitStatus);
|
|
30
|
+
// }
|
|
31
|
+
|
|
32
|
+
// 处理打印错误信息
|
|
33
|
+
const handleError = (error, _exitStatus = 1) => {
|
|
34
|
+
console.log('')
|
|
35
|
+
error && console.error(chalk.red(
|
|
36
|
+
`进程出错! \n` +
|
|
37
|
+
`${error ? (error.message || error) : ''}`
|
|
38
|
+
));
|
|
39
|
+
// handleExit(exitStatus);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// process.on('SIGINT', handleExit); // 终止监听
|
|
43
|
+
// process.on('SIGTERM', handleExit); // 终止监听
|
|
44
|
+
process.on('uncaughtException', (error) => handleError(error, 1)); // 异常监听
|
|
45
|
+
// process.on('unhandledRejection', (reason, _promise) => handleError(reason, 1)); // 异常监听
|
package/bin/lib.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { timestampToHMS, DateTimeDiffS, spawnPromise, isObject } = require('../utils/util.js');
|
|
3
|
+
const { getResolvePath, getCustomConfig, removeFile } = require('../utils/file.js');
|
|
4
|
+
const { OUTPUT } = require('../config/PATH.js');
|
|
5
|
+
|
|
6
|
+
const customConfig = getCustomConfig();
|
|
7
|
+
|
|
8
|
+
const output = customConfig && customConfig.outputDir ? customConfig.outputDir : OUTPUT;
|
|
9
|
+
const format = customConfig && customConfig.format ? customConfig.format : null;
|
|
10
|
+
const isMerageAll = customConfig && customConfig.merageAll === true;
|
|
11
|
+
|
|
12
|
+
const spawnBuildLib = async () => {
|
|
13
|
+
if (!customConfig || customConfig.clean != false) removeFile(output);
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
let isRun = false;
|
|
16
|
+
console.log(chalk.green(`执行开始: ${timestampToHMS(startTime)}`));
|
|
17
|
+
console.log(' ');
|
|
18
|
+
if (Array.isArray(format)) {
|
|
19
|
+
isRun = true;
|
|
20
|
+
await Promise.all(format.map(type => spawnPromise('webpack', [
|
|
21
|
+
'--config',
|
|
22
|
+
`${getResolvePath('config/webpack.lib.conf.js')}`,
|
|
23
|
+
'--progress', '--color',
|
|
24
|
+
'--env', 'formatType=Array',
|
|
25
|
+
`format=${type}`,
|
|
26
|
+
`output=${output}`,
|
|
27
|
+
...process.argv.slice(3),
|
|
28
|
+
], { stdio: 'inherit' })));
|
|
29
|
+
} else if (isObject(format)) {
|
|
30
|
+
isRun = true;
|
|
31
|
+
await Promise.all(Object.keys(format).map(key => spawnPromise('webpack', [
|
|
32
|
+
'--config',
|
|
33
|
+
`${getResolvePath('config/webpack.lib.conf.js')}`,
|
|
34
|
+
'--progress', '--color',
|
|
35
|
+
'--env', 'formatType=Object',
|
|
36
|
+
`format=${key}`,
|
|
37
|
+
`libName=${format[key]}`,
|
|
38
|
+
`output=${output}`,
|
|
39
|
+
...process.argv.slice(3),
|
|
40
|
+
], { stdio: 'inherit' })));
|
|
41
|
+
}
|
|
42
|
+
if (!isRun || isMerageAll) {
|
|
43
|
+
await spawnPromise('webpack', ['--config',
|
|
44
|
+
`${getResolvePath('config/webpack.lib.conf.js')}`,
|
|
45
|
+
'--progress', '--color',
|
|
46
|
+
'--env', 'formatType=String',
|
|
47
|
+
format && `format=${format}`,
|
|
48
|
+
`output=${output}`,
|
|
49
|
+
...process.argv.slice(3),
|
|
50
|
+
], { stdio: 'inherit' });
|
|
51
|
+
}
|
|
52
|
+
console.log(' ');
|
|
53
|
+
console.log(chalk.green(`执行结束: ${timestampToHMS(Date.now())}, 耗时: ${DateTimeDiffS(Date.now(), startTime)}`));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
spawnBuildLib();
|
package/config/PATH.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const OUTPUT = 'lib'; // 默认输出目录
|
|
4
|
+
const ROOT = process.cwd(); // 项目根目录
|
|
5
|
+
const SRC = path.resolve(ROOT, './src'); // 项目源目录
|
|
6
|
+
const BUILD_FILE_NAME = ['lib.config.ts', 'lib.config.js']; // 构建配置文件名可使用ts或js
|
|
7
|
+
const COMPILE_DIR = path.resolve(ROOT, './src/components'); // 编译文件目录
|
|
8
|
+
const DEFAULT_FORMAT = 'module'; // 默认输出格式
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
OUTPUT,
|
|
12
|
+
ROOT,
|
|
13
|
+
SRC,
|
|
14
|
+
BUILD_FILE_NAME,
|
|
15
|
+
COMPILE_DIR,
|
|
16
|
+
DEFAULT_FORMAT
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"presets": [
|
|
3
|
+
"@babel/preset-react",
|
|
4
|
+
[
|
|
5
|
+
"@babel/preset-typescript",
|
|
6
|
+
{
|
|
7
|
+
"allowNamespaces": true,
|
|
8
|
+
"allowDeclareFields": true
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
[
|
|
12
|
+
"@babel/preset-env",
|
|
13
|
+
{
|
|
14
|
+
"targets": {
|
|
15
|
+
"ie": 11
|
|
16
|
+
},
|
|
17
|
+
"modules": false,
|
|
18
|
+
"corejs": 3,
|
|
19
|
+
"useBuiltIns": "usage"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
],
|
|
23
|
+
"plugins": [
|
|
24
|
+
[
|
|
25
|
+
"@babel/plugin-proposal-decorators",
|
|
26
|
+
{
|
|
27
|
+
"legacy": true
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"@babel/plugin-syntax-dynamic-import",
|
|
31
|
+
"@babel/plugin-transform-class-properties",
|
|
32
|
+
"@babel/plugin-transform-optional-chaining",
|
|
33
|
+
"@babel/plugin-transform-nullish-coalescing-operator"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { merge } = require('webpack-merge');
|
|
2
|
+
const { getResolvePath, inputMultiple, outputLibrary, checkFileExist } = require('../utils/file.js');
|
|
3
|
+
const { ROOT, COMPILE_DIR, DEFAULT_FORMAT } = require('../config/PATH.js');
|
|
4
|
+
|
|
5
|
+
// const presetFormat = {
|
|
6
|
+
// commonjs: 'commonjs',
|
|
7
|
+
// commonjs2: 'commonjs',
|
|
8
|
+
// // module: 'module',
|
|
9
|
+
// }
|
|
10
|
+
|
|
11
|
+
module.exports = function ({ format = DEFAULT_FORMAT, libName, output, compileDir }) {
|
|
12
|
+
const fileDir = compileDir || COMPILE_DIR;
|
|
13
|
+
const inputs = inputMultiple(fileDir, fileDir); // 多入口文件
|
|
14
|
+
const outputPath = getResolvePath(output, ROOT); // 输出路径
|
|
15
|
+
const libraryOptions = outputLibrary(format); // 输出库选项˝
|
|
16
|
+
let formatFitConfig = {}; // 特有
|
|
17
|
+
const formatFitConfigPath = getResolvePath(`./formatFitConfig/${format}.js`, __dirname);
|
|
18
|
+
const formatFitConfigIsExist = checkFileExist(formatFitConfigPath);
|
|
19
|
+
// 获取各格式的特有配置属性
|
|
20
|
+
if (formatFitConfigIsExist) formatFitConfig = require(formatFitConfigPath);
|
|
21
|
+
|
|
22
|
+
const conf = merge({
|
|
23
|
+
entry: inputs,
|
|
24
|
+
output: {
|
|
25
|
+
path: outputPath,
|
|
26
|
+
filename: `${libName || '.'}/[name].js`,
|
|
27
|
+
chunkFilename: `chunk${libName ? `/${libName}` : ''}/[name].js`,
|
|
28
|
+
library: libraryOptions,
|
|
29
|
+
},
|
|
30
|
+
}, formatFitConfig);
|
|
31
|
+
// if (presetFormat[format]) { conf.output.chunkFormat = presetFormat[format]; };
|
|
32
|
+
return conf;
|
|
33
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const { getResolvePath } = require('../utils/file');
|
|
2
|
+
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
3
|
+
|
|
4
|
+
const baseConfig = (env) => {
|
|
5
|
+
const isDev = env === 'development';
|
|
6
|
+
|
|
7
|
+
const config = {
|
|
8
|
+
resolve: {
|
|
9
|
+
extensions: ['.js', '.jsx', '.ts', '.tsx', '.scss', '.less'],
|
|
10
|
+
alias: {
|
|
11
|
+
'src': getResolvePath('src'),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
module: {
|
|
15
|
+
rules: [
|
|
16
|
+
{
|
|
17
|
+
test: /\.(?:png|jpe?g|gif|svg|webp)\??.*$/,
|
|
18
|
+
type: 'asset/inline',
|
|
19
|
+
parser: {
|
|
20
|
+
dataUrlCondition: {
|
|
21
|
+
maxSize: 10 * 1024, // 10kb > 自动使用 asset/inline
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
test: /\.(?:woff|eot|ttf|wav)\??.*$/,
|
|
27
|
+
type: 'asset/inline',
|
|
28
|
+
parser: {
|
|
29
|
+
dataUrlCondition: {
|
|
30
|
+
maxSize: 10 * 1024, // 10kb > 自动使用 asset/inline
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
test: /\.css$/,
|
|
36
|
+
sideEffects: true,
|
|
37
|
+
use: [
|
|
38
|
+
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
|
|
39
|
+
'css-loader',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
test: /\.less$/,
|
|
44
|
+
sideEffects: true,
|
|
45
|
+
use: [
|
|
46
|
+
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
|
|
47
|
+
'css-loader',
|
|
48
|
+
{
|
|
49
|
+
loader: 'less-loader',
|
|
50
|
+
options: {
|
|
51
|
+
lessOptions: {
|
|
52
|
+
javascriptEnabled: true,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
test: /\.scss$/,
|
|
60
|
+
sideEffects: true,
|
|
61
|
+
use: [
|
|
62
|
+
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
|
|
63
|
+
{
|
|
64
|
+
loader: 'css-loader',
|
|
65
|
+
options: {
|
|
66
|
+
esModule: false, // 禁用 ES 模块导出,改用 CommonJS 格式(适配库模式)
|
|
67
|
+
modules: {
|
|
68
|
+
mode: 'global',
|
|
69
|
+
exportLocalsConvention: 'camelCaseOnly' // 确保导出格式兼容
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
loader: 'sass-loader',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
test: /\.(j|t)sx?$/,
|
|
81
|
+
exclude: /node_modules/,
|
|
82
|
+
use: [
|
|
83
|
+
{
|
|
84
|
+
loader: 'babel-loader',
|
|
85
|
+
options: {
|
|
86
|
+
configFile: getResolvePath('babel.config.json', __dirname),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
].filter(Boolean),
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
return config;
|
|
95
|
+
}
|
|
96
|
+
module.exports = baseConfig;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const { merge } = require('webpack-merge');
|
|
2
|
+
const webpack = require('webpack');
|
|
3
|
+
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
4
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
5
|
+
const baseConfig = require('./webpack.base.conf.js');
|
|
6
|
+
const { SRC, ROOT } = require('./PATH.js');
|
|
7
|
+
const { getCustomConfig, getFiles,getResolvePath } = require('../utils/file.js');
|
|
8
|
+
const currentEnv = 'development';
|
|
9
|
+
|
|
10
|
+
const customConfig = getCustomConfig();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 按优先级从文件数组中获取目标文件
|
|
14
|
+
* @param {string[]} filePathArr - 文件路径数组
|
|
15
|
+
* @param {string[]} priorities - 优先级后缀列表(越靠前优先级越高)
|
|
16
|
+
* @returns {string|undefined}
|
|
17
|
+
*/
|
|
18
|
+
function getFileByPriority(filePathArr = indexFiles, priorities = ['index.tsx', 'index.jsx']) {
|
|
19
|
+
for (const target of priorities) {
|
|
20
|
+
// 用 endsWith 更简洁(适合固定后缀)
|
|
21
|
+
const matchFile = filePathArr.find(path => path.endsWith(target));
|
|
22
|
+
if (matchFile) return matchFile;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const customWebpackConf = customConfig && customConfig.webpackConf ? customConfig.webpackConf : null;
|
|
28
|
+
|
|
29
|
+
// 获取并匹配src目录下的index.tsx或index.jsx文件
|
|
30
|
+
const indexFiles = getFiles(SRC, /index\.(j|t)sx$/, 0);
|
|
31
|
+
// 获取根目录总index.html文件
|
|
32
|
+
const rootIndexHtml = getFiles(ROOT, /index\.html$/, 0)[0];
|
|
33
|
+
|
|
34
|
+
const entryFile = getFileByPriority();
|
|
35
|
+
|
|
36
|
+
// 默认获取src目录下的index.tsx
|
|
37
|
+
|
|
38
|
+
const getDevConfig = () => {
|
|
39
|
+
if (!entryFile && (!process.env.TEST || process.env.TEST == 'false')) throw new Error('未找到入口文件,请在src目录下创建index.tsx或index.jsx文件');
|
|
40
|
+
|
|
41
|
+
let buildConfig = merge(baseConfig(currentEnv), {
|
|
42
|
+
mode: 'development',
|
|
43
|
+
cache: true,
|
|
44
|
+
devtool: 'eval-cheap-module-source-map',
|
|
45
|
+
entry: entryFile,
|
|
46
|
+
stats: "errors-only", // 只在发生错误时输出
|
|
47
|
+
devServer: {
|
|
48
|
+
allowedHosts: 'all', // 跳过 host header 检查机制
|
|
49
|
+
historyApiFallback: true, // 配置单页应用在开发中的路由行为 - 页面刷新重定向配置默认为index.html
|
|
50
|
+
server: 'http',
|
|
51
|
+
host: 'localhost',
|
|
52
|
+
port: 8181,
|
|
53
|
+
hot: true, // 开启热更新
|
|
54
|
+
open: true, // 自动打开浏览器
|
|
55
|
+
client: {
|
|
56
|
+
logging: 'warn', // 是否先显示构建信息
|
|
57
|
+
progress: true, // 是否开启编译进度条
|
|
58
|
+
overlay: false // 全屏错误提示开关
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
plugins: [
|
|
62
|
+
new MiniCssExtractPlugin({
|
|
63
|
+
filename: 'css/[name]-[contenthash:7].css',
|
|
64
|
+
chunkFilename: 'css/[name]-[contenthash:7].css',
|
|
65
|
+
ignoreOrder: true,
|
|
66
|
+
}),
|
|
67
|
+
new HtmlWebpackPlugin({
|
|
68
|
+
template: !!rootIndexHtml ? rootIndexHtml : getResolvePath('./index.html', __dirname),
|
|
69
|
+
filename: 'index.html',
|
|
70
|
+
chunksSortMode: 'none',
|
|
71
|
+
}),
|
|
72
|
+
new webpack.NoEmitOnErrorsPlugin(),
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
const isCustomBuildConfFn = typeof customWebpackConf === 'function';
|
|
76
|
+
if (isCustomBuildConfFn) {
|
|
77
|
+
buildConfig = customWebpackConf(buildConfig, currentEnv, null);
|
|
78
|
+
} else {
|
|
79
|
+
buildConfig = merge(buildConfig, customWebpackConf || {});
|
|
80
|
+
};
|
|
81
|
+
return buildConfig;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
module.exports = getDevConfig;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { merge } = require('webpack-merge');
|
|
3
|
+
const { BannerPlugin } = require('webpack');
|
|
4
|
+
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|
5
|
+
const TerserPlugin = require('terser-webpack-plugin');
|
|
6
|
+
const { OUTPUT, DEFAULT_FORMAT, ROOT, COMPILE_DIR } = require('./PATH.js');
|
|
7
|
+
const { getCustomConfig } = require('../utils/file.js');
|
|
8
|
+
const getComponentExternals = require('../utils/parserExportFile.js');
|
|
9
|
+
const baseConfig = require('./webpack.base.conf.js');
|
|
10
|
+
const multiOutputConfig = require('./output.multi.js');
|
|
11
|
+
const { getBeforeLastSlash, getLastFolderAndFile } = require('../utils/util.js');
|
|
12
|
+
|
|
13
|
+
const currentEnv = 'library';
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const customConfig = getCustomConfig();
|
|
17
|
+
|
|
18
|
+
const customWebpackConf = customConfig && customConfig.webpackConf ? customConfig.webpackConf : null;
|
|
19
|
+
const compileDir = customConfig && customConfig.compileDir ? customConfig.compileDir : COMPILE_DIR;
|
|
20
|
+
const componentExternals = getComponentExternals(compileDir, ROOT);
|
|
21
|
+
const getLibConfig = function ({
|
|
22
|
+
format = DEFAULT_FORMAT, libName, formatType,
|
|
23
|
+
output = OUTPUT, generateMinChunk = false
|
|
24
|
+
}) {
|
|
25
|
+
// 是不是module(esm)格式
|
|
26
|
+
const isModule = format == 'module';
|
|
27
|
+
const chunkName = '[name]'; // 默认文件命名
|
|
28
|
+
// 如果只是生成一种格式的库,那么就不需要添加格式别名或者单独放入输出目录
|
|
29
|
+
const outputAliasDirName = formatType == 'String' ? '' : (libName || format || '');
|
|
30
|
+
// 使用自定义的多入口配置
|
|
31
|
+
const outputConfig = multiOutputConfig({ format, libName: outputAliasDirName, output, compileDir });
|
|
32
|
+
const defaultEntry = outputConfig.entry;
|
|
33
|
+
|
|
34
|
+
let buildConfig = merge(baseConfig(currentEnv), {
|
|
35
|
+
target: !isModule ? ['web', 'es5'] : 'web',
|
|
36
|
+
mode: 'production',
|
|
37
|
+
externals: {
|
|
38
|
+
react: 'react',
|
|
39
|
+
'react-dom': 'react-dom',
|
|
40
|
+
'react-router-dom': 'react-router-dom',
|
|
41
|
+
},
|
|
42
|
+
optimization: {
|
|
43
|
+
minimize: false, // 是否开启压缩
|
|
44
|
+
moduleIds: 'deterministic',
|
|
45
|
+
minimizer: [
|
|
46
|
+
new TerserPlugin({
|
|
47
|
+
extractComments: false,
|
|
48
|
+
minify: TerserPlugin.swcMinify,
|
|
49
|
+
parallel: true,
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
splitChunks: {
|
|
53
|
+
maxInitialRequests: 6,
|
|
54
|
+
maxAsyncRequests: 12,
|
|
55
|
+
minRemainingSize: 1024 * 50,
|
|
56
|
+
minSize: 1024 * 50,
|
|
57
|
+
maxSize: 1024 * 500,
|
|
58
|
+
cacheGroups: {
|
|
59
|
+
vendor: {
|
|
60
|
+
name(module) {
|
|
61
|
+
if (!module.context || !module.context.match(/[\\/]node_modules[\\/](.*?)([\\/-]|$)/)) {
|
|
62
|
+
return 'vendor/common';
|
|
63
|
+
}
|
|
64
|
+
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/-]|$)/)[1];
|
|
65
|
+
return `vendor/${packageName.replace('@', '')}`;
|
|
66
|
+
},
|
|
67
|
+
test: /[\/]node_modules[\/]/,
|
|
68
|
+
chunks: 'all',
|
|
69
|
+
minChunks: 2,
|
|
70
|
+
priority: 10,
|
|
71
|
+
reuseExistingChunk: true,
|
|
72
|
+
},
|
|
73
|
+
images: {
|
|
74
|
+
chunks: 'initial',
|
|
75
|
+
test: /\.(png|jpe?g|gif|svg|webp|ico)$/i,
|
|
76
|
+
name: 'static/images',
|
|
77
|
+
priority: 5,
|
|
78
|
+
minChunks: 2,
|
|
79
|
+
enforce: true,
|
|
80
|
+
minSize: 1024 * 10,
|
|
81
|
+
maxSize: 1024 * 200,
|
|
82
|
+
reuseExistingChunk: true,
|
|
83
|
+
},
|
|
84
|
+
fonts: {
|
|
85
|
+
chunks: 'initial',
|
|
86
|
+
test: /\.(woff2?|ttf|otf|eot)$/i,
|
|
87
|
+
name: 'static/fonts',
|
|
88
|
+
priority: 5,
|
|
89
|
+
minChunks: 2,
|
|
90
|
+
enforce: true,
|
|
91
|
+
minSize: 1024 * 50,
|
|
92
|
+
reuseExistingChunk: true,
|
|
93
|
+
},
|
|
94
|
+
js: {
|
|
95
|
+
chunks: 'initial',
|
|
96
|
+
test: /\.(j|t)s$/i,
|
|
97
|
+
priority: 5,
|
|
98
|
+
enforce: true,
|
|
99
|
+
maxSize: 1024 * 200,
|
|
100
|
+
minChunks: 2,
|
|
101
|
+
reuseExistingChunk: true,
|
|
102
|
+
name(module) {
|
|
103
|
+
if (!module.context) return false;
|
|
104
|
+
const { folderName, fileName } = getLastFolderAndFile(module.resource) || {};
|
|
105
|
+
let prefixName = "js";
|
|
106
|
+
if (module.resource && module.resource.indexOf('/node_modules/') != -1) {
|
|
107
|
+
prefixName = "vendor"
|
|
108
|
+
}
|
|
109
|
+
return `${prefixName}/${folderName.replace('@', '')}${fileName ? `/${fileName}` : ''}`;
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
runtimeChunk: {
|
|
115
|
+
name: 'runtime',
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
plugins: [
|
|
119
|
+
new MiniCssExtractPlugin({
|
|
120
|
+
filename: (pathData) => {
|
|
121
|
+
const names = (pathData && pathData.chunk && pathData.chunk.name) ? getBeforeLastSlash(pathData.chunk.name) : null;
|
|
122
|
+
const { folderName, generateFolderName } = names || {};
|
|
123
|
+
return names && generateFolderName
|
|
124
|
+
? `${outputAliasDirName ? `${outputAliasDirName}/` : ''}${generateFolderName}/${folderName}/index.css` : `${outputAliasDirName || ''}/${chunkName}.css`;
|
|
125
|
+
},
|
|
126
|
+
chunkFilename: `chunk/${outputAliasDirName || 'style'}/${chunkName}.css`,
|
|
127
|
+
ignoreOrder: true,
|
|
128
|
+
experimentalUseImportModule: true,
|
|
129
|
+
}),
|
|
130
|
+
// 为每个JavaScript文件添加对应的CSS文件导入语句
|
|
131
|
+
new BannerPlugin({
|
|
132
|
+
banner: (data) => {
|
|
133
|
+
// 只对JavaScript文件添加CSS导入语句
|
|
134
|
+
if (data.filename.endsWith('.js')) {
|
|
135
|
+
// 获取chunk信息
|
|
136
|
+
const chunk = data.chunk;
|
|
137
|
+
if (chunk) {
|
|
138
|
+
// 获取入口文件的原始路径
|
|
139
|
+
const resourcePath = outputConfig.entry && chunk.name ? outputConfig.entry[chunk.name] : null;
|
|
140
|
+
if (resourcePath) {
|
|
141
|
+
try {
|
|
142
|
+
// 读取源文件的内容
|
|
143
|
+
const sourceContent = fs.readFileSync(resourcePath, 'utf8');
|
|
144
|
+
// 定义CSS导入的正则表达式 - 支持多种导入方式
|
|
145
|
+
const cssImportRegex = /import\s+(?:(?:\w+|\{[^}]*\})\s+from\s+)?['"]([^'"]+\.(css|scss|less))['"]/;
|
|
146
|
+
// 检查源文件是否包含CSS导入语句
|
|
147
|
+
const hasCssImport = cssImportRegex.test(sourceContent);
|
|
148
|
+
// 检查文件是否仅仅包含CSS导入语句
|
|
149
|
+
// 去除空白行和注释,然后检查是否所有有效行都是CSS导入
|
|
150
|
+
const lines = sourceContent
|
|
151
|
+
.split('\n')
|
|
152
|
+
.map(line => line.trim())
|
|
153
|
+
.filter(line => line && !line.startsWith('//') && !line.startsWith('/*'));
|
|
154
|
+
const onlyHasCssImports = lines.length > 0 && lines.every(line => cssImportRegex.test(line));
|
|
155
|
+
// 只处理那些包含CSS导入且所有有效行都是CSS导入的文件
|
|
156
|
+
if (hasCssImport && onlyHasCssImports) {
|
|
157
|
+
// 从JavaScript文件名获取对应的CSS文件名
|
|
158
|
+
const jsFilename = data.filename;
|
|
159
|
+
// 根据MiniCssExtractPlugin的逻辑生成对应的CSS文件路径
|
|
160
|
+
let cssPath;
|
|
161
|
+
// 获取文件名部分
|
|
162
|
+
const pathParts = jsFilename.split('/');
|
|
163
|
+
const fileName = pathParts.pop();
|
|
164
|
+
// 检查是否是index.js文件
|
|
165
|
+
if (fileName === 'index.js') {
|
|
166
|
+
// 如果是index.js,尝试找到对应的样式文件
|
|
167
|
+
cssPath = './index.css';
|
|
168
|
+
} else {
|
|
169
|
+
// 否则,生成与JS文件同名的CSS文件路径
|
|
170
|
+
cssPath = './' + fileName.replace(/\.js$/, '.css');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 确保路径格式正确(使用正斜杠)
|
|
174
|
+
cssPath = cssPath.replace(/\\/g, '/');
|
|
175
|
+
|
|
176
|
+
// 根据format类型生成不同的导入语句语法
|
|
177
|
+
switch (format) {
|
|
178
|
+
case 'commonjs':
|
|
179
|
+
case 'commonjs2':
|
|
180
|
+
case 'commonjs-module':
|
|
181
|
+
case 'commonjs-static':
|
|
182
|
+
// CommonJS格式,使用require语法
|
|
183
|
+
return `require('${cssPath}');`;
|
|
184
|
+
case 'amd':
|
|
185
|
+
case 'amd-require':
|
|
186
|
+
// AMD格式,使用define语法
|
|
187
|
+
return `require(['${cssPath}']);`;
|
|
188
|
+
case 'var':
|
|
189
|
+
case 'assign':
|
|
190
|
+
case 'assign-properties':
|
|
191
|
+
case 'this':
|
|
192
|
+
case 'window':
|
|
193
|
+
case 'self':
|
|
194
|
+
case 'global':
|
|
195
|
+
case 'umd':
|
|
196
|
+
case 'umd2':
|
|
197
|
+
// 这些格式通常用于浏览器环境,使用require语法加载CSS
|
|
198
|
+
return `require('${cssPath}');`;
|
|
199
|
+
case 'module':
|
|
200
|
+
default:
|
|
201
|
+
// ES模块格式,使用import语法
|
|
202
|
+
return `import '${cssPath}';`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error('Error reading source file:', error);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return '';
|
|
212
|
+
},
|
|
213
|
+
raw: true, // 保留原始字符串,不添加注释
|
|
214
|
+
entryOnly: true, // 只对入口文件添加
|
|
215
|
+
}),
|
|
216
|
+
]
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const isCustomBuildConfFn = typeof customWebpackConf === 'function';
|
|
220
|
+
if (isCustomBuildConfFn) {
|
|
221
|
+
buildConfig = customWebpackConf(buildConfig, currentEnv, format);
|
|
222
|
+
} else {
|
|
223
|
+
buildConfig = merge(buildConfig, customWebpackConf || {});
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
let getcompoentExternals = componentExternals;
|
|
227
|
+
// 是生成压缩合成文件
|
|
228
|
+
if (generateMinChunk) {
|
|
229
|
+
getcompoentExternals = {};
|
|
230
|
+
// outputConfig.stats = 'errors-warnings';
|
|
231
|
+
if (outputConfig && outputConfig.entry && outputConfig.entry['index']) {
|
|
232
|
+
const entryIndex = outputConfig.entry['index'];
|
|
233
|
+
delete outputConfig.entry['index'];
|
|
234
|
+
outputConfig.entry['index.min'] = entryIndex;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const lastConfig = merge(outputConfig, buildConfig, { entry: defaultEntry, externals: getcompoentExternals });
|
|
238
|
+
return lastConfig;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
module.exports = [
|
|
242
|
+
(arg) => getLibConfig(arg),
|
|
243
|
+
Object.keys(componentExternals).length > 0
|
|
244
|
+
? (arg) => getLibConfig(Object.assign(arg, { generateMinChunk: true })) : false
|
|
245
|
+
].filter(Boolean);
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wb-lib-tool",
|
|
3
|
+
"version": "0.0.1-beta.1",
|
|
4
|
+
"description": "基于webpack库模式编译生成组件库",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wb-lib": "./bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"lib": "node ./bin/index.js lib",
|
|
11
|
+
"dev": "cross-env TEST=true node ./bin/index.js dev",
|
|
12
|
+
"clean": "node ./bin/index.js clean --name lib"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"lib",
|
|
16
|
+
"wb-lib",
|
|
17
|
+
"wb-lib-tool",
|
|
18
|
+
"components",
|
|
19
|
+
"react",
|
|
20
|
+
"react-component"
|
|
21
|
+
],
|
|
22
|
+
"files": [
|
|
23
|
+
"package.json",
|
|
24
|
+
"bin",
|
|
25
|
+
"config",
|
|
26
|
+
"utils",
|
|
27
|
+
"README.ch.md"
|
|
28
|
+
],
|
|
29
|
+
"homepage": "https://gitee.com/my_domain/webpack-lib",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://gitee.com/my_domain/webpack-lib"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://gitee.com/my_domain/webpack-lib/issues"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@babel/core": "^7.28.5",
|
|
39
|
+
"@babel/parser": "^7.28.5",
|
|
40
|
+
"@babel/plugin-proposal-decorators": "^7.28.0",
|
|
41
|
+
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
42
|
+
"@babel/plugin-transform-class-properties": "^7.27.1",
|
|
43
|
+
"@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
|
|
44
|
+
"@babel/plugin-transform-optional-chaining": "^7.28.5",
|
|
45
|
+
"@babel/preset-env": "^7.28.5",
|
|
46
|
+
"@babel/preset-react": "^7.28.5",
|
|
47
|
+
"@swc/core": "^1.15.1",
|
|
48
|
+
"babel-loader": "^10.0.0",
|
|
49
|
+
"chalk": "^4.1.2",
|
|
50
|
+
"commander": "^14.0.2",
|
|
51
|
+
"core-js": "^3.47.0",
|
|
52
|
+
"cross-spawn": "^7.0.6",
|
|
53
|
+
"css-loader": "^7.1.2",
|
|
54
|
+
"html-webpack-plugin": "^5.6.5",
|
|
55
|
+
"less": "^4.4.2",
|
|
56
|
+
"less-loader": "^12.3.0",
|
|
57
|
+
"mini-css-extract-plugin": "^2.9.4",
|
|
58
|
+
"rimraf": "^6.1.0",
|
|
59
|
+
"sass": "^1.93.2",
|
|
60
|
+
"sass-loader": "^16.0.6",
|
|
61
|
+
"style-loader": "^4.0.0",
|
|
62
|
+
"typescript": "^5.9.3",
|
|
63
|
+
"webpack": "^5.102.1",
|
|
64
|
+
"webpack-cli": "^6.0.1",
|
|
65
|
+
"webpack-dev-server": "^5.2.2",
|
|
66
|
+
"webpack-merge": "^6.0.1"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"classnames": "^2.5.1",
|
|
70
|
+
"cross-env": "^10.1.0",
|
|
71
|
+
"react": "^19.2.1",
|
|
72
|
+
"react-dom": "^19.2.1"
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">=20.x"
|
|
76
|
+
},
|
|
77
|
+
"peerDependencies": {
|
|
78
|
+
"webpack": ">=5.x"
|
|
79
|
+
},
|
|
80
|
+
"overrides": {
|
|
81
|
+
"react": "$react",
|
|
82
|
+
"react-dom": "$react-dom"
|
|
83
|
+
}
|
|
84
|
+
}
|
package/utils/file.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const {
|
|
2
|
+
resolve: resolvePath,
|
|
3
|
+
relative: relativePath,
|
|
4
|
+
extname: extnamePath,
|
|
5
|
+
dirname: dirnamePath,
|
|
6
|
+
isAbsolute: isAbsolutePath,
|
|
7
|
+
} = require('path');
|
|
8
|
+
const { readdirSync, statSync, existsSync } = require('fs');
|
|
9
|
+
const { rimraf } = require('rimraf');
|
|
10
|
+
const { ROOT, BUILD_FILE_NAME } = require('../config/PATH.js');
|
|
11
|
+
|
|
12
|
+
// 查询将要使用的文件是否存在
|
|
13
|
+
const checkFileExist = (path) => {
|
|
14
|
+
return existsSync(path);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const regMatch = (str, reg) => {
|
|
18
|
+
if (reg instanceof RegExp) {
|
|
19
|
+
return reg.test(str);
|
|
20
|
+
}
|
|
21
|
+
return reg(str) || false;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// 递归读取目录并生成输入文件列表 level 不等于1 则不查询子目录
|
|
25
|
+
const getFiles = (dir, reg, level = 1) => {
|
|
26
|
+
if (!checkFileExist(dir)) return [];
|
|
27
|
+
const files = readdirSync(dir).map((file) => {
|
|
28
|
+
const filePath = resolvePath(dir, file);
|
|
29
|
+
const isDirectory = statSync(filePath).isDirectory();
|
|
30
|
+
return isDirectory && level === 1 ? getFiles(filePath, reg) : filePath;
|
|
31
|
+
});
|
|
32
|
+
return files.flat().filter((file) => regMatch(file, reg));
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// 递归读取多入口文件目录(并删除inputDir路径)
|
|
36
|
+
const inputMultiple = (inputDir, rmDir = '', options) => {
|
|
37
|
+
const { reg = /\.(t|j)sx?$/, level = 1 } = options || {};
|
|
38
|
+
return Object.fromEntries(
|
|
39
|
+
getFiles(inputDir, reg, level).map((file) => {
|
|
40
|
+
return [
|
|
41
|
+
relativePath(
|
|
42
|
+
rmDir,
|
|
43
|
+
file.slice(0, file.length - extnamePath(file).length)
|
|
44
|
+
),
|
|
45
|
+
resolvePath(process.cwd(), file),
|
|
46
|
+
];
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// 根据设定类型输出output -> library 库属性
|
|
52
|
+
const outputLibrary = (type, options) => {
|
|
53
|
+
// 根据不同的格式类型提供默认配置
|
|
54
|
+
const defaultOptions = {
|
|
55
|
+
// 基础配置
|
|
56
|
+
type
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// 根据不同的格式类型添加特定配置
|
|
60
|
+
switch (type) {
|
|
61
|
+
case 'var':
|
|
62
|
+
case 'assign':
|
|
63
|
+
case 'assign-properties':
|
|
64
|
+
case 'this':
|
|
65
|
+
case 'window':
|
|
66
|
+
case 'self':
|
|
67
|
+
case 'global':
|
|
68
|
+
// 这些格式通常用于浏览器环境,需要一个全局变量名
|
|
69
|
+
defaultOptions.name = options?.name || 'lib';
|
|
70
|
+
break;
|
|
71
|
+
case 'commonjs':
|
|
72
|
+
case 'commonjs2':
|
|
73
|
+
case 'commonjs-module':
|
|
74
|
+
case 'commonjs-static':
|
|
75
|
+
// CommonJS格式,不需要全局变量名
|
|
76
|
+
break;
|
|
77
|
+
case 'amd':
|
|
78
|
+
case 'amd-require':
|
|
79
|
+
// AMD格式,需要一个模块名
|
|
80
|
+
defaultOptions.name = options?.name || 'lib';
|
|
81
|
+
break;
|
|
82
|
+
case 'umd':
|
|
83
|
+
case 'umd2':
|
|
84
|
+
// UMD格式,需要同时支持浏览器和CommonJS环境
|
|
85
|
+
defaultOptions.name = options?.name || 'lib';
|
|
86
|
+
defaultOptions.auxiliaryComment = {
|
|
87
|
+
amd: 'This is an AMD module',
|
|
88
|
+
commonjs: 'This is a CommonJS module',
|
|
89
|
+
commonjs2: 'This is a CommonJS2 module',
|
|
90
|
+
root: 'This is a global variable'
|
|
91
|
+
};
|
|
92
|
+
break;
|
|
93
|
+
case 'module':
|
|
94
|
+
// ES模块格式,不需要额外配置,特别是不能设置name属性
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
// 未知格式,使用默认配置
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
...defaultOptions,
|
|
103
|
+
...options,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// 获取指定文件 (从构建目录开始)
|
|
108
|
+
const getResolvePath = (
|
|
109
|
+
path,
|
|
110
|
+
dir = dirnamePath(__dirname)
|
|
111
|
+
) => {
|
|
112
|
+
const getPath = isAbsolutePath(path) ? path : resolvePath(dir, path);
|
|
113
|
+
return getPath;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// 获取项目自定义配置文件 lib.config.(t|j)s
|
|
117
|
+
const getCustomConfig = () => {
|
|
118
|
+
// 尝试加载的配置文件列表
|
|
119
|
+
const configFileNames = BUILD_FILE_NAME;
|
|
120
|
+
for (const fileName of configFileNames) {
|
|
121
|
+
const buildConfigPath = getResolvePath(fileName, ROOT);
|
|
122
|
+
// 如果文件不存在,继续尝试下一个
|
|
123
|
+
if (!checkFileExist(buildConfigPath)) continue;
|
|
124
|
+
let conf;
|
|
125
|
+
try {
|
|
126
|
+
// 清除缓存,确保每次都重新加载文件
|
|
127
|
+
if (require.cache[require.resolve(buildConfigPath)]) {
|
|
128
|
+
delete require.cache[require.resolve(buildConfigPath)];
|
|
129
|
+
}
|
|
130
|
+
// 直接使用require加载配置文件
|
|
131
|
+
conf = require(buildConfigPath);
|
|
132
|
+
// 如果是ES模块导出,提取default属性
|
|
133
|
+
conf = conf.default || conf;
|
|
134
|
+
return conf;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(' 配置文件加载失败 => ', error);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// 删除项目内的文件
|
|
144
|
+
const removeFile = (name) => {
|
|
145
|
+
const path = getResolvePath(name, ROOT);
|
|
146
|
+
if (name && name !== './' && name !== '/' && checkFileExist(path) && path != './') rimraf(path);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
inputMultiple,
|
|
151
|
+
outputLibrary,
|
|
152
|
+
getResolvePath,
|
|
153
|
+
getCustomConfig,
|
|
154
|
+
checkFileExist,
|
|
155
|
+
removeFile,
|
|
156
|
+
getFiles
|
|
157
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// 动态检测所有通过index文件导出的自有文件,并将它们声明为外部依赖
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { parse } = require('@babel/parser');
|
|
5
|
+
|
|
6
|
+
// 读取文件内容
|
|
7
|
+
const readFile = (filePath) => {
|
|
8
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 解析文件,提取所有通过
|
|
12
|
+
// export { default as xxx } from 'xxx'或export { xxx }
|
|
13
|
+
// 相对路径导出的自有文件
|
|
14
|
+
const extractExportedFiles = (filePath) => {
|
|
15
|
+
const exportedFiles = [];
|
|
16
|
+
const importMap = {}; // 存储import语句的映射关系
|
|
17
|
+
const fileContent = readFile(filePath);
|
|
18
|
+
// 根据文件扩展名自动选择解析插件
|
|
19
|
+
const fileExtension = path.extname(filePath).toLowerCase();
|
|
20
|
+
const plugins = fileExtension === '.ts' || fileExtension === '.tsx' ? ['typescript'] : [];
|
|
21
|
+
const ast = parse(fileContent, {
|
|
22
|
+
sourceType: 'module',
|
|
23
|
+
plugins: plugins
|
|
24
|
+
});
|
|
25
|
+
// 遍历AST树,找到所有的import/export语句
|
|
26
|
+
ast.program.body.forEach(node => {
|
|
27
|
+
if (node?.type === 'ImportDeclaration' && node?.specifiers?.length > 0 && node?.source) {
|
|
28
|
+
// 存储import语句的映射关系
|
|
29
|
+
node.specifiers.forEach(specifier => {
|
|
30
|
+
if (specifier?.type === 'ImportDefaultSpecifier') {
|
|
31
|
+
// import xxx from './xxx';
|
|
32
|
+
importMap[specifier?.local?.name] = node?.source?.value;
|
|
33
|
+
} else if (specifier?.type === 'ImportSpecifier') {
|
|
34
|
+
// import { xxx } from './xxx';
|
|
35
|
+
importMap[specifier?.local?.name] = node?.source?.value;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// 处理导出语句
|
|
40
|
+
if (node?.type === 'ExportNamedDeclaration' || node?.type === 'ExportAllDeclaration') {
|
|
41
|
+
// 处理 export * from 'xxx' 这种形式的导出
|
|
42
|
+
if (node?.type === 'ExportAllDeclaration' && node?.source?.value?.startsWith('./')) {
|
|
43
|
+
exportedFiles.push(node.source.value);
|
|
44
|
+
}
|
|
45
|
+
// 处理 export { xxx as xx } from 'xxx' 这种形式的导出
|
|
46
|
+
else if (node?.type === 'ExportNamedDeclaration' && node?.source?.value?.startsWith('./')) {
|
|
47
|
+
// 只要是从相对路径导出的,不管是默认导出还是命名导出,都需要添加到externals中
|
|
48
|
+
exportedFiles.push(node.source.value);
|
|
49
|
+
}
|
|
50
|
+
// 处理 export { xxx } 这种形式的导出
|
|
51
|
+
else if (node?.type === 'ExportNamedDeclaration' && !node?.source && node?.specifiers?.length > 0) {
|
|
52
|
+
node.specifiers.forEach(specifier => {
|
|
53
|
+
if (specifier?.type === 'ExportSpecifier') {
|
|
54
|
+
const importPath = importMap[specifier?.local?.name];
|
|
55
|
+
if (importPath && importPath.startsWith('./')) {
|
|
56
|
+
// 提取相对路径
|
|
57
|
+
exportedFiles.push(importPath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return exportedFiles;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 找到所有将要编译的文件夹下的index文件
|
|
68
|
+
const findIndexFiles = (dirPath, options) => {
|
|
69
|
+
const {
|
|
70
|
+
isPollingChild = false, // 是否递归遍历子目录
|
|
71
|
+
findFiles = ['index.ts', 'index.js', 'index.tsx', 'index.jsx'] // 要查找的文件名
|
|
72
|
+
} = options || {};
|
|
73
|
+
const indexFiles = [];
|
|
74
|
+
const traverseDir = (currentDir) => {
|
|
75
|
+
const files = fs.readdirSync(currentDir);
|
|
76
|
+
files.forEach(file => {
|
|
77
|
+
const filePath = path.join(currentDir, file);
|
|
78
|
+
const fileStat = fs.statSync(filePath);
|
|
79
|
+
if (fileStat.isDirectory() && isPollingChild) {
|
|
80
|
+
// 递归遍历子目录
|
|
81
|
+
traverseDir(filePath);
|
|
82
|
+
} else if (fileStat.isFile() && findFiles.includes(file)) {
|
|
83
|
+
// 找到文件
|
|
84
|
+
indexFiles.push(filePath);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
traverseDir(dirPath);
|
|
89
|
+
return indexFiles;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const getComponentExternals = (compileDir, ROOT) => {
|
|
93
|
+
const componentsDir = path.isAbsolute(compileDir) ? compileDir : path.join(ROOT, compileDir);
|
|
94
|
+
// 检查目录是否存在
|
|
95
|
+
if (!fs.existsSync(componentsDir) || !fs.statSync(componentsDir).isDirectory()) return {};
|
|
96
|
+
const indexFiles = findIndexFiles(componentsDir);
|
|
97
|
+
const allExportedFiles = [];
|
|
98
|
+
// 提取所有index文件中导出的自有文件
|
|
99
|
+
indexFiles.forEach(indexFile => {
|
|
100
|
+
const exportedFiles = extractExportedFiles(indexFile);
|
|
101
|
+
allExportedFiles.push(...exportedFiles);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// 去重
|
|
105
|
+
const uniqueExportedFiles = [...new Set(allExportedFiles)];
|
|
106
|
+
// 将所有导出的自有文件加入到externals中
|
|
107
|
+
const componentExternals = uniqueExportedFiles.reduce((externals, file) => {
|
|
108
|
+
externals[file] = file;
|
|
109
|
+
return externals;
|
|
110
|
+
}, {});
|
|
111
|
+
|
|
112
|
+
return componentExternals;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = getComponentExternals;
|
package/utils/util.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const spawn = require('cross-spawn');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
4
|
+
// 时间戳转换为 HH:mm:ss 格式
|
|
5
|
+
const timestampToHMS = (timestamp) => {
|
|
6
|
+
const date = new Date(timestamp);
|
|
7
|
+
const hours = date.getHours().toString().padStart(2, '0');
|
|
8
|
+
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
9
|
+
const seconds = date.getSeconds().toString().padStart(2, '0');
|
|
10
|
+
return `${hours}:${minutes}:${seconds}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 计算时间差(单位:秒)
|
|
14
|
+
const DateTimeDiffS = (endTime, startTime) => {
|
|
15
|
+
if (typeof startTime == 'number' && typeof endTime == 'number') {
|
|
16
|
+
// 向上取整
|
|
17
|
+
return endTime - startTime >= 1000 ? `${Math.round(((endTime - startTime) / 1000) * 100) / 100}s` : `${endTime - startTime}ms`;
|
|
18
|
+
}
|
|
19
|
+
return 0 - 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// spawn promise 封装
|
|
23
|
+
const spawnPromise = (command, args, options = {}) => {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
const cross = spawn(command, args, options);
|
|
26
|
+
cross.on('close', (code) => {
|
|
27
|
+
if (code != 0) {
|
|
28
|
+
return reject(new Error(`进程[${command}]命令执行失败:退出码 ${code}`));
|
|
29
|
+
}
|
|
30
|
+
resolve(1);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
cross.on('exit', function (code, signal) {
|
|
34
|
+
if (signal) {
|
|
35
|
+
console.log(chalk.bold.red(`进程 [${command}] 命令退出:信号 ${signal} 退出码 ${code}`));
|
|
36
|
+
reject(new Error(`进程 [${command}] 命令退出:信号 ${signal} 退出码 ${code}`));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 判断传入变量是否是对象
|
|
43
|
+
const isObject = (obj) => {
|
|
44
|
+
return Object.prototype.toString.call(obj) === '[object Object]';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 获取css引入的最后一个 / 前的字符
|
|
49
|
+
* @param {string} str 目标字符串(如路径、URL)
|
|
50
|
+
* @returns {fileName, folderName, generateFolderName} fileName: 文件名, folderName: 文件夹名, generateFolderName: 生成的文件夹名
|
|
51
|
+
*/
|
|
52
|
+
const getBeforeLastSlash = (str) => {
|
|
53
|
+
// // 捕获最后一个 / 之前的所有字符(文件夹名)
|
|
54
|
+
let folderInfo = null;
|
|
55
|
+
if (str.indexOf('/') !== -1) {
|
|
56
|
+
const strArr = str.split('/');
|
|
57
|
+
// 移除最后一个元素(文件名)
|
|
58
|
+
const fileName = strArr[strArr.length - 1];
|
|
59
|
+
const lastButOneStr = strArr.length > 2 ? strArr[strArr.length - 2] : 'style';
|
|
60
|
+
folderInfo = {
|
|
61
|
+
fileName,
|
|
62
|
+
folderName: lastButOneStr,
|
|
63
|
+
generateFolderName: strArr[0],
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return folderInfo;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 从完整路径中提取最后一级文件夹名和文件名
|
|
71
|
+
* @param fullPath 完整文件路径
|
|
72
|
+
* @returns { folderName: string, fileName: string } | null
|
|
73
|
+
*/
|
|
74
|
+
function getLastFolderAndFile(fullPath) {
|
|
75
|
+
// 核心正则:兼容 / \ 分隔符,排除文件后缀
|
|
76
|
+
const regex = /[\\/]([^\\/]+)[\\/]([^\\/.]+)(?:\.[^\\/]+)?$/;
|
|
77
|
+
const match = fullPath.match(regex);
|
|
78
|
+
|
|
79
|
+
if (match) {
|
|
80
|
+
return {
|
|
81
|
+
folderName: match[1],
|
|
82
|
+
fileName: match[2]
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null; // 路径格式不合法时返回 null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
getLastFolderAndFile,
|
|
91
|
+
getBeforeLastSlash,
|
|
92
|
+
timestampToHMS,
|
|
93
|
+
DateTimeDiffS,
|
|
94
|
+
spawnPromise,
|
|
95
|
+
isObject,
|
|
96
|
+
}
|