前言
在我们平时的开发工作中,我们可以把很多可以公用的组件和方法抽离出来,以npm插件的形式发布在npm或者自己的npm私库上,以达到复用效果。
本文会以一个react插件为例,经历开发工程搭建—插件编写—npm打包发布等一系列步骤,和小伙伴们一起开发一个npm插件。
工程搭建
项目工程以为webpack5+、react17+、less、TypeScript为主体进行搭建。
项目结构
|-- demo
|-- .babelrc
|-- .gitignore
|-- package.json
|-- tsconfig.json
|-- README.md
|-- dist
|-- types
|-- public
| |-- index.html
|-- scripts
| |-- webpack.base.config.js
| |-- webpack.dev.config.js
| |-- webpack.prod.config.js
|-- src
|-- index.less
|-- index.tsx
|-- component
|-- index.less
|-- index.tsx
|-- message-card.tsx
下面会对一些文件进行一个简单的说明。
package.json
项目依赖和配置。可以直接:
npm install
这里提一下两个字段:files
和 typings
,这两个字段在我们平时开发的时候可能用的比较少,但是在开发npm插件的时候用处很大。
首先是 files
,这个可以在我们开发完成后,指定我们需要上传到npm的文件夹或文件的数组,可以说是和 .npmignore
相反的效果。
其次是 typings
, TypeScript 的入口文件 , 这里可以指定我们放置 xx.d.ts
的文件地址。没有这个的话,我们上传的npm插件,在被下载下来后可能会报找不到类型文件的错误。
{
"name": "message-card",
"version": "1.0.1",
"main": "dist/message-card.js",
"scripts": {
"build": "webpack --config ./scripts/webpack.prod.config.js",
"start": "webpack serve --config ./scripts/webpack.dev.config.js"
},
"repository": "https://github.com/XmanLin/message-card",
"keywords": [
"react",
"component"
],
"author": "Xmanlin",
"license": "MIT",
"files": [
"dist",
"types"
],
"typings": "types/index.d.ts",
"devDependencies": {
"@babel/cli": "^7.14.5",
"@babel/core": "^7.14.5",
"@babel/preset-env": "^7.14.5",
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.14.5",
"@types/react": "^17.0.11",
"@types/react-dom": "^17.0.7",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^4.0.0-alpha.0",
"css-loader": "^5.2.6",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.1",
"less": "^4.1.1",
"less-loader": "^9.1.0",
"optimize-css-assets-webpack-plugin": "^6.0.0",
"style-loader": "^2.0.0",
"terser-webpack-plugin": "^5.1.3",
"typescript": "^4.3.2",
"url-loader": "^4.1.1",
"webpack": "^5.38.1",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
.babelrc
babel相关配置。
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}
.gitignore
这个就不一一列举了,大家可能不一样,有兴趣可以看项目源码。
tsconfig.json
这个也可以按照自己的喜好来吧。
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
"strictNullChecks": true,
"moduleResolution": "node",
"esModuleInterop": true,
"experimentalDecorators": true,
"jsx": "react",
"noUnusedParameters": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"target": "es6",
"lib": ["dom", "es2017"],
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
scripts
这里主要是webpack的一些配置,把配置文件拆成了三份,一个是开发和生产公用基础配置,另外两个就是开发和生产单独的配置。当然也可以合在一块,这个看自己。
webpack.base.config.js
const webpackBaseConfig = {
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node-modules/,
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
},
},
{
test: /\.(css|less)$/,
use: [
{
loader: "style-loader",
},
{
loader: "css-loader",
options: {
importLoaders: 1,
},
},
{
loader: "less-loader"
}
]
},
{
test: /\.(png|jpg|gif)$/i,
type: 'asset/resource'
}
]
}
}
module.exports = webpackBaseConfig
webpack.dev.config.js
const path = require('path');
const webpack = require('webpack');
const webpackBaseConfig = require('./webpack.base.config');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { merge } = require('webpack-merge');
function resolve(relatedPath) {
return path.join(__dirname, relatedPath)
}
const webpackDevConfig = {
mode: 'development',
entry: {
app: resolve('../src/index.tsx')
},
output: {
path: resolve('../dist'),
filename: 'message-card.js',
},
devtool: 'eval-cheap-module-source-map',
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.css', '.less']
},
devServer: {
contentBase: resolve('../dist'),
hot: true,
open: true,
host: 'localhost',
port: 8080,
},
plugins: [
new HtmlWebpackPlugin({template: './public/index.html'}),
new webpack.HotModuleReplacementPlugin()
]
}
module.exports = merge(webpackBaseConfig, webpackDevConfig)
webpack.prod.config.js
const path = require('path');
const webpack = require('webpack');
const webpackBaseConfig = require('./webpack.base.config');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { merge } = require('webpack-merge');
function resolve(relatedPath) {
return path.join(__dirname, relatedPath)
}
const webpackProdConfig = {
mode: 'production',
entry: {
app: [resolve('../src/component/index.tsx')]
},
output: {
filename: 'message-card.js',
path: resolve('../dist'),
library: {
type: 'commonjs2'
}
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.css', '.less']
},
devtool: 'source-map',
optimization: {
minimizer: [
new TerserJSPlugin({
parallel: 4,
terserOptions: {
compress: {
drop_console: true
},
},
}),
new OptimizeCSSAssetsPlugin()
],
},
plugins:[
new CleanWebpackPlugin()
]
}
module.exports = merge(webpackBaseConfig, webpackProdConfig)
webpack配置好之后,我们就可以在 package.json
中配合我们的命令:
"scripts": {
"build": "webpack --config ./scripts/webpack.prod.config.js",
"start": "webpack serve --config ./scripts/webpack.dev.config.js"
}
为什么这里还要单独拎出来说一下呢,因为这里的配置webpack5+和webpack4+有些许不一一样。
在webpack4+(在webpack5中也可以这样配置,但是webpack-cli要降到 3+版本)中:
"start": "webpack-dev-server --config ./scripts/webpack.dev.config.js"
同时webpack-cli降到 3+ 版本。
插件开发
开发工程搭建好之后,我们就可以开始插件的开发了。
调试文件
public/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>我的组件</title>
</head>
<body>
<div id="root" class="root"></div>
</body>
</html>
src/index.tsx
这里主要是一个空白页面,用来引入我们正在开发的插件,我们可以一边看效果一边进行开发,很直观。
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import MessageCard from './component/index';
import './index.less'
const App = () => {
return (
<div className="container">
<MessageCard
title="卡片一"
content="这里是内容"
/>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
插件代码
这里就是我们插件源代码,代码不多。
src/component/index.tsx
打包插件时的入口文件
import MessageCard from './message-card';
export default MessageCard;
src/component/message-card.tsx
import React from 'react';
import './index.less';
export interface ICard {
title: string;
content?: string;
}
const MessageCard: React.FC<ICard> = (props) => {
const { title, content } = props;
return (
<div className="card">
<div className="title">{title}</div>
<div className="content">{content}</div>
</div>
)
}
export default MessageCard
src/component/index.less
.card {
border: 1px solid #eee;
border-radius: 4px;
padding: 20px 20px;
.title {
min-height: 50px;
border-bottom: 1px solid #eee;
font-size: 20px;
font-weight: bold;
}
.content {
min-height: 100px;
font-size: 16px;
padding: 10px 0;
}
}
打包
插件开发完,我们可以执行命令进行打包:
npm run build
打包完毕我们就可以得到我们的 dist
文件夹和里面的 message-card.js
文件了。
调试
在我们发布我们的npm插件之前,我们需要先进行本地调试:
npm link (in package dir)
npm link [<@scope>/]<pkg>[@<version>]
alias: npm ln
发布到npm
发包之前肯定要有一个npm账号啦,到npm官网注册一个就行。
发布
登录npm
登录npm,敲完命令跟着提示填就是了:
npm login
发布包
在项目根目录输入以下命令:
npm publish
这里需要注意的是:
- 记得在发包之前把npm源地址改成:http://registry.npmjs.org ,很多人会用淘宝镜像或者私有源,这样是发布不到npm上的;
- 记得要先登录,然后再发包。
更新
版本更新很简单,修改 package.json
里的 version
字段,然后再:
npm publish
删除
删除某个版本:
npm unpublish [<@scope>/]<pkg>[@<version>]
例如我们想要删除某个版本:
npm unpublish message-card@1.0.0
删除整个包:
npm unpublish [<@scope>/]<pkg> --force
参考
https://github.com/XmanLin/me...
https://webpack.docschina.org...
https://react.docschina.org/d...
最后
本文从项目搭建到实际发布,以实践为基础,相信对一些小伙伴还是有帮助的。我们开发的插件不仅可以发到npm上,如果有公司的私有源或者自己搭建的私有源,都可以进行打包发布,我们只需要改一下发包地址就行。
文章有值得改进或有问题的地方,欢迎一起讨论~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。