从搭建脚手架到在 npm 上发布 react 组件
最近公司给公司里架设了私有的 npm 仓库,相应地也需要一个用来发布 react 组件用的脚手架,在这个过程中又又又又复习了一下 webpack,在这里分享下脚手架搭建的过程。
首先,我们预期的脚手架具有如下功能
开发组件时可以实时预览
对组件各种资源进行打包(js/css/ 图片等)
一键打包发布
1. 创建项目
脚手架的名字暂时取 react-simple-component-boilerplate。
首先创建一个新目录用于放我们的文件:
mkdir react-simple-component-boilerplate
cd react-simple-component-boilerplate
使用 npm 命令创建一个项目
npm init
接下来会提示你输入项目的名称、版本号、作者等,也可以一路回车,稍后修改。这一步完成后,你的项目文件夹里应该有一个 package.json 文件了,这个文件保存了我们项目和组件的各种信息。
接下来创建如下的目录结构
react-simple-component-boilerplate
|– config // webpack 配置
|– demo // 开发时预览用
|– dist // 打包结果
|– src // 源文件目录
| — assets // 存放图片等媒体文件
| — style // 存放样式,项目使用的是 less 来编写样式
2. 安装依赖
既然我们要发布的是 react 组件,那依赖里肯定少不了 react。使用 npm install 安装下面的依赖
npm install react react-dom –save
打包工具选择的是 webpack,下面是开发依赖,也需要一并安装
“devDependencies”: {
// babel 用于将你写的 es6+ 的代码转换到 es5
“@babel/cli”: “^7.0.0”,
“@babel/core”: “^7.0.0”,
“@babel/plugin-proposal-class-properties”: “^7.0.0”, // 用于支持 class 属性
“@babel/plugin-proposal-decorators”: “^7.0.0”, // 支持 decorator
“@babel/plugin-transform-modules-commonjs”: “^7.0.0”,
“@babel/plugin-transform-runtime”: “^7.0.0”, // 自动 polyfill es5 不支持的 api 特性
“@babel/preset-env”: “^7.0.0”, // 根据目标环境来按需转码
“@babel/preset-react”: “^7.0.0”, // 让 babel 支持 react 语法
“babel-loader”: “^8.0.0”,
“css-loader”: “^1.0.0”,
“file-loader”: “^2.0.0”,
“html-loader”: “^0.4.4”,
“less-loader”: “^4.1.0”, // 使用 less 来编写样式
“mini-css-extract-plugin”: “^0.5.0”, // 将 css 提取成一个单独的文件
“style-loader”: “^0.23.0”,
“webpack”: “^4.26.0”,
“webpack-cli”: “^3.1.2”, // webpack4 之后需要额外安装 webpack-cli
“webpack-dev-server”: “^3.1.14”, // 开发时预览组件所用的服务,在文件变化时会自动刷新页面
“webpack-merge”: “^4.1.4″ // 用于合并 webpack 配置
},
3. 编写组件
在 /src 目录下新建一个 index.js,这就是我们组件的入口文件了。如果项目中要使用图片、css 等,分类放到 assets、style 文件夹下就好。
下面我们就在 index.js 中写一个简单的组件
/* src/index.js */
import React from ‘react’;
import ‘./style/style.less’; // 使用 less 的情况
import testPng from ‘./assets/test.png’; // 使用图片的情况
export default class MyComponent extends Component {
render(){
return (<div>A new Component</div>)
}
}
接下来,我们在 /demo 目录下新建 index.html 和 demo.js 这两个文件用于在开发组件时预览组件效果。index.html 内容如下
<!DOCTYPE html>
<html>
<head>
<meta charset=”UTF-8″>
<title>Title</title>
</head>
<body>
<div id=”root”></div>
<script src=”demo.bundle.js”></script>
</body>
</html>
在 demo.js 中,我们要使用一下刚刚写的组件 (位于 /src/index.js) 看一下效果,开发中这个 demo.js 文件会被打包成 demo.bundle.js,就是在上面 index.html 中引用的 js。
import React from ‘react’;
import ReactDom from ‘react-dom’;
import MyComponent from ‘../src/index’
const Demo = () => {
return <div>
<h1> 组件预览:</h1>
<MyComponent />
</div>
}
ReactDom.render(<Demo />, document.getElementById(‘root’));
4. 配置 webpack 和 babel
4.1 配置 webpack
在 /config 下我们建立三个 webpack 配置文件
webpack.base.js
webpack.config.dev.js // 开发时的配置
webpack.config.prod.js // 打包发布时的配置
由于开发和发布打包时 webpack 的配置有一部分是公共而且重复的,我们把这部分的配置单独拿出来放到 webpack.base.js 中。首先是公共配置 webpack.base.js:
module.exports = {
module: {
rules: [
{// 在 webpack 中使用 babel 需要 babel-loader
test: /\.js?$/,
loader: ‘babel-loader’,
exclude: ‘/node_modules/’,
},
{// 用于加载组件或者 css 中使用的图片
test: /\.(jpg|jpeg|png|gif|cur|ico|svg)$/,
use: [{
loader: ‘file-loader’, options: {
name: “images/[name][hash:8].[ext]”
}
}]
}
]
}
}
下面是开发时所用的 webpack 配置,写在 webpack.config.dev.js 中
const path = require(‘path’);
const merge = require(‘webpack-merge’);
const baseConfig = require(‘./webpack.base.js’); // 引用公共的配置
const devConfig = {
entry: ‘./demo/demo.js’, // 入口文件
mode: ‘development’, // 打包为开发模式
output: {
filename: ‘demo.bundle.js’, // 输出的文件名称
path: path.resolve(__dirname, ‘../demo’) // 输出的文件目录
},
devServer: {// 该字段用于配置 webpack-dev-server
contentBase: path.join(__dirname, ‘../demo’),
compress: true,
port: 9000, // 端口 9000
open: true // 自动打开浏览器
},
module: {
rules: [
{// 编译 less
test: /\.less$/,
exclude: ‘/node_modules/’,
use: [{
loader: ‘style-loader’
}, {
loader: ‘css-loader’
}, {
loader: ‘less-loader’
}]
},
]
},
}
module.exports = merge(devConfig, baseConfig); // 将 baseConfig 和 devConfig 合并为一个配置
需要注意的是,等会使用 webpack-dev-sevrer 启动开发服务时,并不会实际在 demo 文件夹下生成 demo.bundle.js,打包好的文件是在内存中的,但并不影响我们使用。
下面是打包发布时所用的 webpack 配置,写在 webpack.config.prod.js 中
const path = require(‘path’);
const merge = require(‘webpack-merge’);
const baseConfig = require(‘./webpack.base.js’);
const MiniCssExtractPlugin = require(“mini-css-extract-plugin”); // 用于将组件的 css 打包成单独的文件输出到 `dist` 目录中
const devConfig = {
entry: ‘./src/index.js’,
mode: ‘production’,
output: {
path: path.resolve(__dirname, ‘../dist’),
filename: ‘index.js’, // 输出文件
libraryTarget: ‘umd’, // 采用通用模块定义, 注意 webpack 到 4.0 为止依然不提供输出 es module 的方法,所以输出的结果必须使用 npm 安装到 node_modules 里再用,不然会报错
library: ‘react-simple-component-boilerplate’, // 库名称
libraryExport: ‘default’, // 兼容 ES6(ES2015) 的模块系统、CommonJS 和 AMD 模块规范
},
externals: {
react: {
root: “React”,
commonjs2: “react”,
commonjs: “react”,
amd: “react”
},
“react-dom”: {
root: “ReactDOM”,
commonjs2: “react-dom”,
commonjs: “react-dom”,
amd: “react-dom”
}
},
module: {
rules: [{
test: /\.(le|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
“css-loader”,
{
loader: “less-loader”,
options: {
sourceMap: false
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: “main.min.css” // 提取后的 css 的文件名
})
],
}
module.exports = merge(devConfig, baseConfig);
上面我们配置了 externals 字段,这一点非常重要。externals 定义了外部依赖。将 react 和 react-dom 添加进该字段,说明我们的组件将依赖外部的 react 和 react-dom,这样就可以避免把 react 和 react-dom 打包进去(不然组件会很大)
4.1 配置 babel 我们需要用 babel 把我们的代码编译成 es5 版本。在项目根目录新建一个.babelrc 文件,输入以下内容。
{
“presets”: [
[
“@babel/preset-env”,
{
“targets”: “> 0.25%, not dead”
}
],
“@babel/preset-react”
],
“plugins”: [
“@babel/plugin-transform-runtime”,
“@babel/plugin-transform-modules-commonjs”,
[
“@babel/plugin-proposal-decorators”,
{
“legacy”: true
}
],
“@babel/plugin-proposal-class-properties”,
“@babel/plugin-proposal-object-rest-spread”
]
}
我们在 presets 其中使用了 preset-env, 规定了输出的代码目标环境是份额大于 0.25% 的浏览器。另外由于我们的项目里使用了 react,presets 中就要加入 preset-react。同时,plugins 配置了一些 babel 插件,用于支持装饰器展开操作符等类内直接定义属性等新的 es 特性。
4.3 配置启动命令我们再次回到项目根目录下的 package.json 中,编辑如下
“scripts”: {
“build”: “set NODE_ENV=production && webpack –config ./config/webpack.config.prod.js”,
“pub”: “npm run build && npm publish”,
“dev”: “webpack-dev-server –config ./config/webpack.config.dev.js”
},
“main”: “dist/index.js”,
“files”: [“dist”]
build 命令用于打包组件
dev 命令会使用 webpack-dev-server 启动一个开发服务用于预览组件效果
pub 命令进行打包组件并且发布到 npm 上
main 字段指定了我们的组件的入口文件,files 字段用于指定我们的 npm 包的文件目录。
5. 试用和发布
要发布一个 npm 包,我们需使用如下命令添加一个 npm 的账号,如果已经添加过的这一步可以跳过。
npm adduser
如果已经有 npm 账号,可以使用 npm login 登陆。如果不知道自己是否已经添加过了 npm 账号,使用 npm whoami 查看登陆信息即可
接下来就编辑 package.json 把组件的名称 / 版本 / 介绍等字段都填写一下。
好了,接下我们先使用 npm run dev 命令,此时会自动打开默认浏览器预览组件。如果没什么问题的话,接下来使用 npm run pub 进行打包和发布。等待发布完成后,我们就下载安装一下。
npm i your-component // 假设你的包名字叫 your-component
使用自己发布的组件
import YourComponent from ‘your-component’;
import ‘your-component/dist/main.min.css’; // 如果给组件写了样式,需要手动导入 css 文件
6. 总结
到这里,一个非常非常简单的用于发布 react 小组件的脚手架就搭好了,总结一下其中要注意的地方:
webpack 打包时 libraryTarget 要使用 umd
externals 里要把外部依赖配置好
如果还要生成 es module,可以额外使用 gulp 或 rollup 等工具
webpack4 之后建议使用 MiniCssExtractPlugin 来提取 css