preface
In our usual development work, we can pull out 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 the reuse effect.
Taking a react plug-in as an example, this article will go through a series of steps, such as development project construction, plug-in writing, npm packaging and release, and develop an npm plug-in with my partners.
Engineering 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
Some files will be briefly described below.
package.json
Project dependencies and configurations. You can directly:
npm install
Here are two fields: files and types. These two fields may be used less in our normal development, but they are very useful in developing npm plug-ins.
The first is files, which can specify the array of folders or files that we need to upload to npm after our development. It can be said that it has the opposite effect to. npmignore.
The second is types, the entry file of typescript, where we can specify the file address where we place xx.d.ts. Without this, the npm plug-in we uploaded may report that the type file cannot be found after it is 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 is not listed one by one. You may be different. If you are interested, you can see it Project source code.
tsconfig.json
This can also be done according to your own preferences.
{ "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 some configurations of webpack. The configuration file is 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 combined. This depends on yourself.
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 the webpack is configured, we can cooperate with our commands in package.json:
"scripts": { "build": "webpack --config ./scripts/webpack.prod.config.js", "start": "webpack serve --config ./scripts/webpack.dev.config.js" }
Why should I mention it here alone, because the configurations here are slightly different from webpack5 + and webpack4 +.
In webpack4 + (this can also be configured in webpack5, but the webpack cli should be reduced to version 3 +):
"start": "webpack-dev-server --config ./scripts/webpack.dev.config.js"
At the same time, the webpack cli is reduced to version 3 +.
Plug in development
After the development project is built, we can start the development of plug-ins.
Debug file
public/index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>My components</title> </head> <body> <div id="root" class="root"></div> </body> </html>
src/index.tsx
Here is mainly a blank page to introduce the plug-ins we are developing. We can develop while watching 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="Card one" content="Here is the content" /> </div> ) } ReactDOM.render(<App />, document.getElementById('root'));
Plug in code
Here is the source code of our plug-in. There is not much code.
src/component/index.tsx
Entry file when packaging plug-ins
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 plug-in, we need to conduct local debugging:
npm link (in package dir) npm link [<@scope>/]<pkg>[@<version>] alias: npm ln
You can see the specific usage Official documents , you can also have a look This article , it's very clear
Publish to npm
There must be an npm account before contracting. Just register one on the npm official website.
release
Log in to npm
Log in to npm, type the command and fill in the prompt:
npm login
Release package
Enter the following command in the project root directory:
npm publish
Note here:
- Remember to change the npm source address to: http://registry.npmjs.org , many people will use Taobao image or private source, which can not be published on npm;
- Remember to log in before contracting out.
to update
Version update is very simple. Modify the version field in package.json, and then:
npm publish
delete
Delete a version:
npm unpublish [<@scope>/]<pkg>[@<version>]
For example, we want to delete a version:
npm unpublish message-card@1.0.0
Delete entire package:
npm unpublish [<@scope>/]<pkg> --force
reference resources
https://github.com/XmanLin/message-card
https://webpack.docschina.org/concepts/
https://docs.npmjs.com/
https://react.docschina.org/docs/getting-started.html
last
This paper is based on practice from project construction to actual release. I believe it is helpful to some small partners. The plug-ins we developed can not only be sent to npm, but also can be packaged and released if there are private sources of the company or built by ourselves. We just need to change the contracting address.
The article is worth improving or has problems. Welcome to discuss it together~