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

前端车站

共 19168字,需浏览 39分钟

 · 2021-06-22

前言

在我们平时的开发工作中,我们可以把很多可以公用的组件和方法抽离出来,以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: {
                    cacheDirectorytrue,
                    cacheCompressionfalse,
                    presets: ['@babel/preset-env''@babel/preset-react''@babel/preset-typescript'],
                },
            },
            {
                test/\.(css|less)$/,
                use: [
                    {
                        loader"style-loader",
                    },
                    {
                        loader"css-loader",
                        options: {
                            importLoaders1,
                        },
                    },
                    {
                        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'),
        hottrue,
        opentrue,
        host'localhost',
        port8080,
    },
    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({
                parallel4,
                terserOptions: {
                    compress: {
                        drop_consoletrue
                    },
                },
            }),
            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/message-card

https://webpack.docschina.org/concepts/

https://docs.npmjs.com/

https://react.docschina.org/docs/getting-started.html

最后

本文从项目搭建到实际发布,以实践为基础,相信对一些小伙伴还是有帮助的。我们开发的插件不仅可以发到npm上,如果有公司的私有源或者自己搭建的私有源,都可以进行打包发布,我们只需要改一下发包地址就行。

文章有值得改进或有问题的地方,欢迎一起讨论~


浏览 23
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报