引言
在前端项目开发过程中,有大量重复的内容,比如布局相似的模块,较多的功能表单等,我们可以提炼成组件来提升效率,减少重复建设。文章以实际工作中的项目为例,介绍如何将项目中常用的组件进行封装并发布到npm中。
1 前提要求
在开始前你需要具备以下条件:
- 安装了Node & npm
- 安装了Git
- 基本掌握npm ,git使用方法
- 熟练使用 JavaScript & ES6 & CSS
- 基本掌握React
- 熟悉React ,antd
2 开始
2.1 配置git和npm
本次将我的组件库命名为nsc-components
首先配置git ,在github上创建一个新的远程仓库nsc-components,在本地运行git clone 命令,将nsc-components克隆到本地
$ git clone https://github.com/你的账户名/nsc-components
配置 npm,运行npm login,登录你的npm账号。然后运行npm init 初始化生成一个新的package.json
文件.
$ npm init
name: (nsc-components)
version: (1.0.0) 0.1.0
description: an example component library with React!
entry point: (index.js)
test command:
git repository:
keywords:
Author:
license: (ISC)
About to write to /Users/alanbsmith/personal-projects/trash/package.json:
{
"name": "nsc-components",
"version": "0.1.0",
"description": "an example component library with React!",
"main": "dist/index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"author": ,
"license": "ISC"
}
Is this ok? (yes)
在根目录下添加以下配置文件
touch .babelrc .eslintrc .gitignore .npmignore README.md
- .babelrc包含编译阶段一些有用的转转码规则(presets)
- .eslintrc包含linter配置
- .gitignore和.npmignore分别用于忽略来自 git 和 npm 的文件
- README.md也非常重要。这是我们和开源社区交流的主要方式
.babelrc
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
["import", {"libraryName": "antd", "libraryDirectory": "lib", "style":"css"
}],
]
}
注:这里踩了很多坑
加入["import", {"libraryName": "antd", "libraryDirectory": "lib", "style": true }]配置是必要的,该配置使用babel-plugin-import 按需加载 antd样式,不加该配置信息antd组建的样式会无法显示。
babel-plugin-import 在 babel 运行时,将类似import { ModuleName } from 'libiaryName'
的代码转化为组件所在的路径,这样实际引用的就是这个组件的模块而不是整个 Library
// babel 前
import { Icon } from 'antd';
// babel 后
import _Icon from 'antd/lib/icon';
import 'antd/lib/dropdown/style/css';
.gitignore 添加不需要push的文件
.DS_Store
dist
node_modules
*.log
.npmignore用于忽略不用发布到npm的文件
.babelrc
src
CODE_OF_CONDUCT.md
node_modules
.gitignore
webpack.config.js
yarn.lock
.eslintrc
在初始化的package.json文件里添加以下命令
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"build": "webpack --config webpack.config.js -p",
"prepublish": "npm run build"
},
"files": [
"dist"
],
- build将运行根目录下webpack.config.js文件对src目录下的内容如何进行转码然后导出到dist目录。需要在webpack.config.js文件设置入口src/index.js。
- npm会在我们运行npm publish之前执行这个脚本。 这将确保我们在dist的资源是最新的版本。
- "files"属性的值是一个数组,内容是模块下文件名或者文件夹名,也可以在模块根目录下创建一个".npmignore"文件(windows下无法直接创建以"."开头的文件,使用linux命令行工具创建如git bash),写在这个文件里边的文件即便被写在files属性里边也会被排除在外,写法与".gitignore"类似。
2.2 安装依赖
package.json文件中添加以下字段:
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.5.5",
"@babel/plugin-proposal-class-properties": "^7.2.3",
"@babel/plugin-proposal-object-rest-spread": "^7.2.0",
"@babel/preset-env": "^7.5.5",
"@babel/preset-react": "^7.0.0",
"babel-core": "^6.26.3",
"babel-loader": "^8.0.6",
"babel-preset-es2015": "^6.24.1",
"babel-preset-stage-1": "^6.24.1",
"babel-plugin-import": "^1.12.0",
"classnames": "^2.2.6",
"css-loader": "^3.2.0",
"eslint": "^6.2.1",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.14.3",
"eslint-watch": "^6.0.0",
"less": "^3.10.2",
"less-loader": "^5.0.0",
"polished": "^3.4.1",
"prop-types": "^15.7.2",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"style-loader": "^1.0.0",
"webpack": "^4.39.3",
"webpack-cli": "^3.3.7",
"webpack-dev-server": "^3.8.0"
},
"dependencies": {
"antd": "^3.22.0",
"axios": "^0.19.0",
"lodash": "^4.17.15",
},
"peerDependencies": {
"antd": "^3.22.0",
"prop-types": "^15.7.2",
"react": "^16.9.0",
"react-dom": "^16.9.0"
}
dependencies
字段指定了项目运行所依赖的模块,该类型依赖一般属于运行项目业务逻辑需要依赖的第三方库。devDependencies
指定项目开发所需要的模块。peerDependencies
中声明的依赖,如果项目没有显式依赖并安装,则不会被npm 自动安装,转而输出warning
日志,告诉项目开发者,你需要显式依赖了,不要再依靠我了。
2.3 添加组件
创建src/components目录,在目录下依次添加TooltipButton.js,Toolbar.js,Toolbar.css文件
TooltipButton.js
import React from 'react'
import PropTypes from 'prop-types'
import { Tooltip, Button } from 'antd'
const TooltipButton = props => {
const { children, tip, placement, ...restProps } = props
return (
<Tooltip title={tip} placement={placement}>
<Button {...restProps}>{children}</Button>
</Tooltip>
)
}
TooltipButton.propTypes = {
tip: PropTypes.string.isRequired,
placement: PropTypes.string.isRequired
}
TooltipButton.defaultProps = {
placement: 'top'
}
export default TooltipButton
Toolbar.js
import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import styles from './Toolbar.css'
const Toolbar = ({ children, inline, compact, ...restProps }) => {
const classes = classNames({
[styles.toolbarInline]: inline,
[styles.toolbar]: !inline,
[styles.compact]: compact
})
return (
<div className={classes} {...restProps}>
{children}
</div>
)
}
Toolbar.propTypes = {
inline: PropTypes.bool.isRequired,
compact: PropTypes.bool.isRequired
}
Toolbar.defaultProps = {
inline: false,
compact: false
}
const Group = ({ children }) => {
return (
<span className={styles.group}>
{children}
</span>
)
}
const Tool = ({ children }) => {
return (
<span className={styles.tool}>
{children}
</span>
)
}
Toolbar.Group = Group
Toolbar.Tool = Tool
export default Toolbar
Toolbar.css
.toolbar {
display: block;
}
.toolbarInline {
display: inline-block;
}
.tool {
margin: 0 2px 0 0;
}
.group {
margin: 0 8px 0 0;
}
.compact .tool {
margin: 0 1px;
}
.compact .group {
margin: 0 4px
}
src/index.js
import Toolbar from './lib/components/Toolbar'
import TooltipButton from './lib/components/TooltipButton'
2.4 webpack配置
利用webpack将文件打包
在根目录下添加webpack.config.js文件,添加配置信息如下
var path = require('path')
var webpack = require('webpack')
module.exports = {,
entry:'./src/index.js', //入口文件路径
output: {
filename: 'main.js',
library: 'nsc-components',
libraryTarget: 'umd',
libraryExport: 'default'
},
module: {
rules: [
{
test: /.js[x]?$/, // 用正则来匹配文件路径,这段意思是匹配 js 或者 jsx
exclude: /node_modules/,
include: [path.resolve(__dirname, 'src')],
loader: 'babel-loader' // 加载模块 "babel-loader"
},
{
test: /.less$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { importLoaders: 1 } },
'less-loader',
{ loader: 'less-loader', options: { javascriptEnabled: true } }
]
},
{
test: /.css$/,
use: [{
loader: "style-loader"
},
{
loader: 'css-loader',
options:{
modules: {
mode: 'local',
localIdentName: '[name]-[local]',
},
}
}
]
},
]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
}
- entry:是入口文件所处的目录的绝对路径的字符串
- output:
output.filename
:输出文件名output.library
:此配置适用于webpack 用于打包 JavaScript library的时候,例如本例中开发一个名为'nsc-components' 的library,配置output.library设置 library 的名称为'nsc-components'output.libraryTarget
:配置了output.library后,还需要在配置文件中添加libraryTarget
属性,控制 library 如何以不同方式暴露的选项。
可以通过以下方式暴露 library:-
- 变量:作为一个全局变量,通过
script
标签来访问(libraryTarget:'var'
) - this:通过
this
对象访问(libraryTarget:'this'
) - window:通过
window
对象访问,在浏览器中(libraryTarget:'window'
) - UMD:在 AMD 或 CommonJS 的
require
之后可访问(libraryTarget:'umd'
)
- 变量:作为一个全局变量,通过
-
- module.rules:
创建模块时,匹配请求的规则数组,这些规则能够对模块(module)应用 loader。
2.5 打包文件
运行以下命令,将文件打包输出
npm run build
3 创建测试应用
我们需要一个本地环境去试用我们的组件,在本地利用脚手架或者手动新建一个小应用项目,本次我手动新建了一个测试应用,你需要从GitHub上把它clone下来并安装依赖。
$ git clone https://github.com/aaxuyun/demo.git
运行npm run start或yarn start,在你的浏览器上打开http://localhost:9100,页面出现‘hello',修改test/index.js文件,将热加载更新。
在之前组件库的根目录运行
pwd
复制组件库地址
在当前测试应用的根目录运行
npm link [组件库地址]
在测试应用调用组件
在test/index.js中我们将像使用其他外部库一样引入和调用我们的组件:
import React from 'react';
import { render} from 'react-dom';
import { TooltipButton } from 'nsc-components'
const App = () => {
return (
<div style={{position:"absolute",top:'100px',left:'50%'}}>
<TooltipButton tip='hi' icon='edit'/>
</div>
)};
render(<App />, document.getElementById("root"));
保存之后,在页面中将看到以下按钮在界面中显示
4 发布到npm
运行以下命令将组件发布到npm
npm publish