2

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:

  1. 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;
  2. 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://docs.npmjs.com/

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~


xmanlin
1.4k 声望43 粉丝