前言

在咱们平时的开发工作中,咱们能够把很多能够专用的组件和办法抽离进去,以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

<!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

这里须要留神的是:

  1. 记得在发包之前把npm源地址改成:http://registry.npmjs.org ,很多人会用淘宝镜像或者公有源,这样是公布不到npm上的;
  2. 记得要先登录,而后再发包。

更新

版本更新很简略,批改 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://docs.npmjs.com/

https://react.docschina.org/d...

最初

本文从我的项目搭建到理论公布,以实际为根底,置信对一些小伙伴还是有帮忙的。咱们开发的插件不仅能够发到npm上,如果有公司的公有源或者本人搭建的公有源,都能够进行打包公布,咱们只须要改一下发包地址就行。

文章有值得改良或有问题的中央,欢送一起探讨~