Preface
In our usual development work, we can extract many common components and methods and publish them in the form of npm plug-ins on npm or our own npm private library to achieve reuse effects.
This article will take a react plug-in as an example, go through a series of steps such as development project construction-plug-in writing-npm packaging and publishing, and develop an npm plug-in with your friends.
Project construction
The project is built with webpack5+, react17+, less, and TypeScript as the main body.
Project structure
|-- 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
A brief description of some files will be given below.
package.json
Project dependencies and configuration. You can directly:
npm install
Here are two fields: files
and typings
. These two fields may be used less in our usual development, but they are very useful when developing npm plugins.
The first is files
, which can specify the folder or array of files that we need to upload to npm after our development is completed. It can be said to be the opposite effect of .npmignore
Next is typings
, the entry file of TypeScript, where we can specify the file address xx.d.ts
Without this, the npm plugin we uploaded may report an error that the type file cannot be found after being downloaded.
{
"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 related configuration.
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
"@babel/plugin-proposal-class-properties"
]
}
.gitignore
This will not list them, we may not be the same, are interested can look project source code .
tsconfig.json
You can do this according to your liking.
{
"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
Here are mainly some configurations of webpack. The configuration files are divided into three parts, one is the common basic configuration for development and production, and the other two are separate configurations for development and production. Of course, it can also be put together, it's up to you.
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)
After webpack is configured, we can cooperate with our command package.json
"scripts": {
"build": "webpack --config ./scripts/webpack.prod.config.js",
"start": "webpack serve --config ./scripts/webpack.dev.config.js"
}
Why do we need to take it out separately here, because the configuration here is slightly different from webpack5+ and webpack4+.
In webpack4+ (this can also be configured in webpack5, but webpack-cli needs to be downgraded to version 3+):
"start": "webpack-dev-server --config ./scripts/webpack.dev.config.js"
At the same time, webpack-cli is downgraded to version 3+.
Plug-in development
After the development project is set up, we can start the development of the plug-in.
Debug file
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
This is mainly a blank page, used to introduce the plug-in we are developing, we can develop while looking at the effect, which is very intuitive.
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'));
Plug-in code
Here is our plug-in source code, not much code.
src/component/index.tsx
Entry file when packaging the plugin
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;
}
}
Pack
After the plug-in is developed, we can execute the command to package:
npm run build
After packaging, we can get our dist
folder and the message-card.js
file inside.
debugging
Before we release our npm plugin, we need to perform local debugging first:
npm link (in package dir)
npm link [<@scope>/]<pkg>[@<version>]
alias: npm ln
Specific usage can be seen official document , you can also look at this article , very clearly written
Publish to npm
You must have an npm account before sending the package, just register one on the npm official website.
release
Log in to npm
Log in to npm, type the command and follow the prompts to fill in:
npm login
Release package
Enter the following command in the project root directory:
npm publish
The things to note here are:
- Remember to change the npm source address to: 161429019e2ef2 http://registry.npmjs.org package. Many people will use Taobao mirrors or private sources, which will not be released on npm;
- Remember to log in first, and then send the package.
renew
Very simple version of the update, modify package.json
in the version
field, then:
npm publish
delete
Delete a version:
npm unpublish [<@scope>/]<pkg>[@<version>]
For example, we want to delete a certain version:
npm unpublish message-card@1.0.0
Delete the entire package:
npm unpublish [<@scope>/]<pkg> --force
refer to
https://github.com/XmanLin/message-card
https://webpack.docschina.org/concepts/
https://react.docschina.org/docs/getting-started.html
finally
From the project construction to the actual release, this article is based on practice, and I believe it will be helpful to some small partners. The plug-ins we develop can not only be sent to npm, but if there are company private sources or private sources built by ourselves, they can be packaged and released. We only need to change the package address.
There are areas worthy of improvement or problems in the article, welcome to discuss together~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。