从 0 开始发布一个 react 组件到 npm

从 0 开始发布一个 react 组件到 npm

翻译自: A guide to building a React component with Webpack 4, publishing to npm, with a demo on GitHub…

当你开发完一个 React 组件后也许你想过把它贡献给社区,但是你又不知道如何让其他人能使用,这篇文章将指导你完成这个目标

假设你是通过 create-react-app 创建的项目,然后你要发布的组件是包含在你项目中的,要发布该组件的话可能需要对你的工作流做一些特殊的处理

在这个教程中包含了从项目搭建到发布整个过程,在开始前你最好先了解一下 react-scripts,这样的话你才能更好的理解我讲的

主要目标包括:

  1. 在本地创建一个 demo , 通过 webpack4 的配置实现文件改变后页面自动刷新
  2. 将编译后的组件发布到 npm 上去,用户直接可以使用
  3. 在 GitHub Pages 上发布一个可以在线预览的 Demo

创建组件和DEMO

创建项目文件夹并初始化 npm package ,确保你创建的组件名称没有在 [npm](npm) 上被使用过, 这里我们用 my-component作为示例

mkdir my-component
cd my-component
npm init

运行 npm init 问题提示列表可以采用默认的选项 (译者注:默认配置可以使用 npm init -y)

对于本地 demo 组件我们需要 react,所以接下来我们安装 react 到我们项目开发依赖中来, 后面我会介绍怎样让用户知道我们发布的组件有需要 react 依赖

npm i react react-dom -D

我们的项目将通过 webpack进行构建, Babel 进行编译,webpack-dev-server 作为本地开发服务器,接下来我们将他们添加到项目的开发依赖中去

npm i webpack webpack-cli webpack-dev-server html-webpack-plugin style-loader css-loader babel-core babel-loader babel-preset-env babel-preset-react -D

这时上面安装的依赖已经被添加到根目录下的 package.json中了,接下来我们添加一个 start的脚本,用于启动我们本地开发的服务器, start如下:

{
 "name": "my-component",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "start": "webpack-dev-server --mode development"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
 "babel-core": "^6.26.3",
 "babel-loader": "^7.1.4",
 "babel-preset-env": "^1.7.0",
 "babel-preset-react": "^6.24.1",
 "css-loader": "^0.28.11",
 "html-webpack-plugin": "^3.2.0",
 "react": "^16.4.0",
 "react-dom": "^16.4.0",
 "style-loader": "^0.21.0",
 "webpack": "^4.9.1",
 "webpack-cli": "^2.1.4",
 "webpack-dev-server": "^3.1.4"
 }
}

现在让在我们的项目中创建组件和示例代码目录,目录树结构如下

├── example // 示例代码存放目录
│  └── src
├── node_modules
├── package.json
└── src // 组件源代码和样式存放目录


下面我将创建一个非常简单的组件以作为示例


/*** src/index.js  ***/
import React from 'react';
import './styles.css';
const MyComponent = () => (
 <h1>Hello from My Component</h1>
);
export default MyComponent;


/*** src/styles.css ***/
h1 {
 color: red;
}


( 社区一定会爱上它的

接下来添加一个 demo

<!-- examples/src/index.html -->
<html>
<head>
 <title>My Component Demo</title>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body>
 <noscript>
 You need to enable JavaScript to run this app.
 </noscript>
 <div id="root"></div>
</body>
</html>


/*** examples/src/index.js ***/
import React from 'react';
import { render} from 'react-dom';
import MyComponent from '../../src';
const App = () => (
 <MyComponent />
);
render(<App />, document.getElementById("root"));


注意 demo 中的 MyComponent 是从 ../../src中导入的

接下来配置 webpack, 在项目根路径下创建 webpack.config.js文件

/*** webpack.config.js ***/
const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const htmlWebpackPlugin = new HtmlWebpackPlugin({
 template: path.join(__dirname, "examples/src/index.html"),
 filename: "./index.html"
});
module.exports = {
 entry: path.join(__dirname, "examples/src/index.js"),
 module: {
   rules: [{
     test: /\.(js|jsx)$/,
   use: "babel-loader",
   exclude: /node_modules/
 },{
   test: /\.css$/,
   use: ["style-loader", "css-loader"]
 }]
},
 plugins: [htmlWebpackPlugin],
 resolve: {
   extensions: [".js", ".jsx"]
 },
 devServer: {
   port: 3001
}};


Webpack 的配置文件主要做了如下事情:

  • 使用 example/src/index.js作为项目入口,处理资源文件的依赖关系
  • 通过 babel-loader来编译处理 js和jsx文件
  • 通过style-loader 和 css-loader来处理 css 依赖和注入内联样式
  • 通过html-webpack-plugin自动注入编译打包好的脚本文件
  • 为 demo 启动端口为 3001 的服务

最后需要指定 Babel 需要对哪些文件进行编译,毫无疑问 React 中使用的 JSX 文件需要被编译,让它转换成被主流浏览器都支持的 ES5 ,通用的配置也很简单,只需要添加一对 presets,在项目根目录下添加文件.babelrc

{
 "presets": ["env", "react"]
}


接下来运行 demo


npm start


启动完成后打开浏览器输入 http://localhost:3001,你将会在页面上看到你写的组件,你可以修改你的代码并保存,页面将会自动刷新,我们的开发环境已经处于监控模式


接下来继续完成第二项目标


发布用户能直接使用的组件到 npm 上


发布到 npm 是一个很简单自动化的工作,只是在发布前需要做如下工作


我们要发布被 babel 编译且被压缩后的版本,要让没有使用 babel 的项目也能够正常的使用,比如不能出现 JSX 语法


首先需要安装 babel cli


npm i babel-cli -D


现在我们添加 transpile脚本,以便使用 Babel 编译我们的源代码,同时拷贝一些静态文件(如:css 文件)到目标打包目录dist下


同时指定被编译后的版本为组件的主入口,更改后的 package.json如下


{
 "name": "my-component",
 "version": "1.0.0",
 "description": "",
 "main": "dist/index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "start": "webpack-dev-server --mode development",
   "transpile": "babel src -d dist --copy-files"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
   "babel-cli": "^6.26.0",
   "babel-core": "^6.26.0",
   "babel-loader": "^7.1.4",
   "babel-preset-env": "^1.6.1",
   "babel-preset-react": "^6.24.1",
   "css-loader": "^0.28.11",
   "html-webpack-plugin": "^3.2.0",
   "react": "^16.3.2",
   "react-dom": "^16.3.2",
   "style-loader": "^0.20.3",
   "webpack": "^4.5.0",
   "webpack-cli": "^2.0.14",
   "webpack-dev-server": "^3.1.3"
 }
}


尝试编译

npm run transpile

现在在我们项目根目录下面会有一个 dist 目录,包含了 index.js 的编译版本,和拷贝的样式文件styles.css,这些文件是用户可以直接 可以import到他们项目的文件,接下来我们再在我们的工作流中添加一个脚本prepublishOnly ,这个脚本会在每次我们需要发布我们的组件到 npm上去的时候会自动执行,他能确保我们每次发布上去的代码都是最新代码编译的


另外我们需要告诉用户在用户我们的组件的时候对于 React 版本的要求,peerDependency 能够很好的表达这个信息,同时在我们的发布的组件包中不会包含 react , 这样也减小了包的大小,更加重要是可以避免在用户的项目中存在多个 react 版本,更改后的 package.json如下

{
 "name": "my-component",
 "version": "1.0.0",
 "description": "",
 "main": "dist/index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "start": "webpack-dev-server --mode development",
   "transpile": "babel src -d dist",
   "prepublishOnly": "npm run transpile"
 },
 "author": "",
 "license": "ISC",
 "peerDependencies": {
   "react": "^16.3.0",
   "react-dom": "^16.3.0"
 },
 "devDependencies": {
   "babel-cli": "^6.26.0",
   "babel-core": "^6.26.0",
   "babel-loader": "^7.1.4",
   "babel-preset-env": "^1.6.1",
   "babel-preset-react": "^6.24.1",
   "css-loader": "^0.28.11",
   "html-webpack-plugin": "^3.2.0",
   "react": "^16.3.1",
   "react-dom": "^16.3.1",
   "style-loader": "^0.20.3",
   "webpack": "^4.5.0",
   "webpack-cli": "^2.0.14",
   "webpack-dev-server": "^3.1.3"
 }
}


最后让我们在项目的根目录下添加.npmignore文件,告诉 npm,我们项目中哪些文件和文件夹是在发布的包中被忽略掉的


# .npmignore 
src
examples
.babelrc
.gitignore
webpack.config.js


发布我们的组件到 npm 上

npm publish


这时你去浏览器的 npm 主页,应该就能看到我们刚才发布的新包了,恭喜你,你的组件已经成功发布!


接下来让我们完成最后一项目标


在 GitHub Pages 上发布一个在线 demo


在 GitHub Pages 托管在线 Demo 是免费的,需要使用 webpack 来构建我们的生产环境版本,然后发布到 GitHub 仓库指定的分支上去,接下来让我们自动化完成这些吧!


首先,我们需要借助一个帮助维护特性分支的包,我们还没有对我们的项目添加 git 代码版本控制,稍等片刻


 npm i gh-pages -D


然后在 package.json 中添加三个脚本


{
 "name": "my-component",
 "version": "1.0.0",
 "description": "",
 "main": "dist/index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "start": "webpack-dev-server --mode development",
   "transpile": "babel src -d dist --copy-files",
   "prepublishOnly": "npm run transpile",
   "build": "webpack --mode production",
   "deploy": "gh-pages -d examples/dist",
   "publish-demo": "npm run build && npm run deploy"
 },
 "author": "",
 "license": "ISC",
 "peerDependencies": {
   "react": "^16.3.0",
   "react-dom": "^16.3.0"
 },
 "devDependencies": {
   "babel-cli": "^6.26.0",
   "babel-core": "^6.26.0",
   "babel-loader": "^7.1.4",
   "babel-preset-env": "^1.6.1",
   "babel-preset-react": "^6.24.1",
   "css-loader": "^0.28.11",
   "gh-pages": "^1.1.0",
   "html-webpack-plugin": "^3.2.0",
   "react": "^16.3.2",
   "react-dom": "^16.3.2",
   "style-loader": "^0.20.3",
   "webpack": "^4.5.0",
   "webpack-cli": "^2.0.14",
   "webpack-dev-server": "^3.1.3"
 }
}

build 脚本目的是用 webpack 帮我们构建一个 boundled, 和压缩生产环境代码,这里我们需要告诉 webpack 哪个文件是我们项目输出的结果


/*** webpack.config.js ***/
const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const htmlWebpackPlugin = new HtmlWebpackPlugin({
 template: path.join(__dirname, "examples/src/index.html"),
 filename: "./index.html"
});
module.exports = {
 entry: path.join(__dirname, "examples/src/index.js"),
 output: {
   path: path.join(__dirname, "examples/dist"),
   filename: "bundle.js"
 },
 module: {
   rules: [{
     test: /\.(js|jsx)$/,
     use: "babel-loader",
     exclude: /node_modules/
   },{
     test: /\.css$/,
     use: ["style-loader", "css-loader"]
   }]
 },
 plugins: [htmlWebpackPlugin],
 resolve: {
   extensions: [".js", ".jsx"]
 },
 devServer: {
   port: 3001
 }
};


来,试一试


npm run build


你会发现生成版本的代码已经打包到了 examples/dist


现在来对项目添加 git 版本控制,在项目的根目录下添加 .gitignore文件,需要对一些中间过程代码文件进行排除


# .gitignore
node_modules
dist


接着去 GitHub 为它创建一个仓库,按照随后屏幕上出现的提示执行命令行 ...or create a new respository on the command line,将会在本地初始化一个本地仓库,并连接到远程仓库上


现在我们的本地和远程的仓库都已经创建并连接上了,准备将 demo 发布到托管环境了,这时,首先需要去这个项目的仓库中为它新建一个 gh-pages 的分支,deploy 脚本就是为了帮我们干这个事的


npm run deploy


点击设置连接到你的 github 仓库页面,然后滚动到 github pages 栏目,你将会看到你的 demo 在线连接地址,恭喜你 上线了!


最后我们使用 publish-demo脚本叫 build 和 deploy 脚本合并在一起简化我们的工作流


npm run publish-demo


后话


后面当你需要发布一个新的版本时,你只需要更新一下 package.json 里面的 version 版本号,然后执行 npm publish 和 npm run publish-demo,发布新版本到 npm 是分分钟的事儿,发布新的 demo 到 GitHub Pages 最多也只需要 20分钟

当然你想让你的组件能在社区被发现,你还需要做一些其他事情,由于文章篇幅有限,这里就不展开讲了,就简单列举一下吧

  • 添加 README.md ,描述你的组件是做什么的,在线 demo 的链接,使用示例,以及组件 API
  • 添加自动化测试
  • 在 package.json 中填充 description 和 repository字段
  • 考虑给你的组件添加许可


感谢你的阅读!

编辑于 2018-05-27