从项目搭建到发布插件到npm

前言 在我们平时的开发工作中,我们可以把很多可以公用的组件和方法抽离出来,以npm插件的形式发布在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

这里提一下两个字段:filestypings ,这两个字段在我们平时开发的时候可能用的比较少,但是在开发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
我的组件 - 锐客网

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 () }ReactDOM.render(, document.getElementById('root'));

插件代码
这里就是我们插件源代码,代码不多。
src/component/index.tsx
打包插件时的入口文件
import MessageCard from './message-card'; export default MessageCard;

src/component/message-card.tsx
import Reactfrom 'react'; import './index.less'; export interface ICard { title: string; content?: string; }const MessageCard: React.FC = (props) => {const { title, content } = props; return ({title} {content}) } 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>/][@]alias: npm ln

具体用法可以看官方文档,也可以看看这篇文章,写的很清楚
发布到npm 发包之前肯定要有一个npm账号啦,到npm官网注册一个就行。
发布
登录npm 登录npm,敲完命令跟着提示填就是了:
npm login

发布包 在项目根目录输入以下命令:
npm publish

这里需要注意的是:
  1. 记得在发包之前把npm源地址改成:http://registry.npmjs.org ,很多人会用淘宝镜像或者私有源,这样是发布不到npm上的;
  2. 记得要先登录,然后再发包。
更新
版本更新很简单,修改 package.json 里的 version 字段,然后再:
npm publish

删除
删除某个版本:
npm unpublish [<@scope>/][@]

例如我们想要删除某个版本:
npm unpublish message-card@1.0.0

删除整个包:
npm unpublish [<@scope>/] --force

参考 https://github.com/XmanLin/me...
https://webpack.docschina.org...
https://docs.npmjs.com/
https://react.docschina.org/d...
最后 本文从项目搭建到实际发布,以实践为基础,相信对一些小伙伴还是有帮助的。我们开发的插件不仅可以发到npm上,如果有公司的私有源或者自己搭建的私有源,都可以进行打包发布,我们只需要改一下发包地址就行。
文章有值得改进或有问题的地方,欢迎一起讨论~

    推荐阅读