本文主要介绍react组件库开发时通过webpack打包过程的关键注意点。
组件打包要求:
- 针对公共组件打包处理,并且保证按源文件结构输出。
- 支持ES6 module、AMD、CommonJs、Script标签 引入使用组件。
js部分webpack配置
const webpackConfig = {
// 要打包的组件,多入口配置
entry: {
'button1': path.resolve(__dirname, `../src/components/button1/index.tsx`),
'button2': path.resolve(__dirname, `../src/components/button2/index.tsx`)
},
output: {
// filename指定为一个方法可以拿到chunk.name,分别是'button1'和'button2'
// filename最终的结果 => 'button1/index.js'
filename: (pathData) => {
return `${pathData.chunk.name}/index.js`
},
// 输出到根目录的 lib文件夹下面
// 最终的路径就是 path + filename => '../lib/button1/index.js', 如此js可以按照源文件路径输出了
path: path.resolve(__dirname, '../lib'),
libraryTarget: 'umd',
library: '[name]',
libraryExport: "default"
},
modules: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: [
'babel-loader',
{
loader: 'ts-loader',
options: {
transpileOnly: true,
},
}
]
}
]
}
}
libraryTarget:'umd'。 可以在ES6 module、CommonJS、AMD 环境下运行,也可以通过全局访问。
library:暴露出模块的变量名称,上述配置的名称取的是文件名'button1'、'button2',可根据需求自己定义,具体使用如下。
- ES2015 模块。例如 import button1 from 'button1'。
- CommonJS 模块。例如 require('button1')。
- 全局变量,在通过 script 标签引入可以直接访问到button1这个变量。 libraryExport:'default', 如果不给libraryExport赋值默认导出整个命名空间对象Module,例如想要调用button1(这里button1是个方法),需要这样button1.default()。如果定义libraryExport:'default',那么可以直接调用button1()。
到这里js部分的打包处理完成了,同时最开始需求的第2点也满足了,下面开始处理css。
css部分webpack配置
module: {
rules: [
{
test: /\.(scss|sass)$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: (pathData) => {
return `${pathData.chunk.name}/style/index.css`
}
})
]
css配置相对比较简单,filename和js配置类似,都是通过chunk.name拼接出最终要输出的路径。
images部分webpack配置
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
exclude: /node_modules/,
use: {
loader: 'file-loader',
options: {
context: path.resolve(__dirname, '../src/components'),
// 这里name最终结果 => 文件名.jpg
name: '[name].[ext]',
outputPath: (url, resourcePath, context) => {
const relativePath = path.relative(context, resourcePath);
return `../lib/${relativePath}`
},
publicPath: '../images',
esModule: false
}
}
}
]
}
context: 当前执行上下文,可以理解为当前打包时nodeJs执行所在的工作目录,默认时webpack的context,如果指定一个目录那么工作目录就是指定的文件夹位置。
outputPath:打包结果输出路径。这里要保持打包后的目录结构和打包前一致所以outputPath使用来一个方法来处理,此时之前设置的context就可以用上了。如下:
outputPath: (url, resourcePath, context) => {
// 以我电脑输出为例
// resourcePath => /Users/admin/Documents/webpack/src/components/button1/images/dog1.png
// context => /Users/admin/Documents/webpack/src/components
// 这里的context就是上面配置的context: path.resolve(__dirname, '../src/components')
// 通过path.relative就能得到相对路径,这个路径就是要输出的路径 => 'button1/images/dog1.png'
const relativePath = path.relative(context, resourcePath);
// 返回最终图片打包输出的路径 => '../lib/button1/images/dog1.png'
return `../lib/${relativePath}`
}
publicPath: 打包后静态资源的访问路径。例如在css中引用了一个图片:
background-image: url(../images/dog1.png);
如果打包出来dog1.png位于lib/static中,那么可以这样配置publicPath
options: {
name: '[name].[ext]',
publicPath: '../static',
}
可以通过配置publicPath来自定义打包后静态资源的引用路径,这样打包后的图片引用路径就会被修改,结果如下
background-image: url(../static/dog1.png);
esModule:false。当前file-loader版本为v6.2.0,默认打包输出结果是使用ES模块语法的JS模块,如下
background-image:url([object Module])
所以要想显示正常的url,需要把esModule置为false。
排除react、react-dom
因为是组件打包,所以不能把react相关的代码打包进去,所以最后需要把不需要的代码排除掉。
externals: {
'react': {
commonjs: 'react', // CommonJS 模块
commonjs2: 'react', // CommonJS 模块
amd: 'react', // AMD 模块
root: 'React', // 全局变量访问
},
'react-dom': {
commonjs: 'react-dom',
commonjs2: 'react-dom',
amd: 'react-dom',
root: 'ReactDOM',
},
}
上面externals配置解决了2个问题:
- 排除react和react-dom,让组件打包时不会把这两个库的代码打进去。
- 解决组件内使用react和react-dom问题。 组件内通常这么引用react
import React from 'react'
externals配置就是保证了组件内部引用react时能够正常运行,如上配置使用require或者全局变量访问react此处也是可以的。
到此组件打包基础功能已经实现,看下图:左侧是src下的源码,右侧是打包后的输出文件
本片文章的相关代码webpack-build-components有兴趣可以参考。如果文章内容有误欢迎指正,谢谢。