自己书写一个npm包并发布到npm上面/卸载

3,567 阅读5分钟

image.png

自己书写一个npm包并发布到npm上面

说到npm包都会给人一种特别高大上的感觉,并且自己写了一个包之后如果有人用那么就会产生莫大的成就感,程序员的快乐就是这么简单。

想必有产生写npm包想法的人都对模块化比较熟悉,并且对于react、vue两者之一都比较熟练了。

下面呢我们就是使用react来写一个自己的npm包,我们呢会使用自己封装的webpack脚手架来写,如果有兴趣同学可以来看一下我的自我沉淀webpack5+react+eslint+tslint文章。 接下来的内容呢也是基于此来说明的。

这里也有现成的脚手架

一、不同点

npm包的目录结构和普通的脚手架结构有所不同

  1. 启动目录不同:以往我们习惯将entry文件写在src中,但是npm包的入口文件不能写在src中,因为npm是将我们的源代码打包,不可以包括html。

    所以将index.jsx和index.html文件提取到example文件中。【注意】example文件要和src同级。

    结构和内容如下

    index.jsx

     import React from 'react';
     import { render } from 'react-dom';
     import ReactDemo from '../src';
    
     const App = () => <ReactDemo />;
     render(<App />, document.getElementById('root'));
     
    

    index.html

     <!DOCTYPE html>
     <html lang="en">
       <head>
         <meta charset="UTF-8" />
         <meta http-equiv="X-UA-Compatible" content="IE=edge" />
         <meta name="viewport" content="width=device-width, initial-scale=1.0" />
         <title>Document</title>
       </head>
       <body>
         <div id="root"></div>
       </body>
     </html>
    

    然后在src/index.jsx文件中 导出

     import App1 from './App';
     export default App1;
    

    二 配置npm包的打包运行文件

    在 config文件夹中新建webpack.npm.js文件

    配置文件内容差不多。 如下: 详细配置请移步 自我沉淀webpack5+react+eslint+tslint

    externals划重点:这个可以告诉npm打包的时候不许将下面几种东西打包进去哦。

     const { resolve } = require('path');
     const cssLoaders = [
       'style-loader',
       {
         loader: 'css-loader',
         options: {
           importLoaders: 1,
           modules: {
             auto: (resourcePath) => resourcePath.endsWith('.less'),
             localIdentName: '[local]_[hash:base64:10]',
           },
         },
       },
       {
         loader: 'postcss-loader',
         options: {
           postcssOptions: {
             plugins: [['autoprefixer'], require('postcss-preset-env')()],
           },
         },
       },
     ];
     module.exports = {
       entry: './src/index.tsx',
       mode: process.env.NODE_ENV,
       externals: {
         antd: 'antd',
         react: 'React',
       },
       output: {
         libraryTarget: 'umd',
         filename: 'index.js',
         path: resolve(resolve(__dirname, '..'), 'dist'),
         clean: true,
       },
       resolve: {
         alias: {
           '@': resolve(resolve(__dirname, '..'), 'src/'),
         },
         extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
         mainFiles: ['index'],
       },
    
       devServer: {
         hot: true,
         port: 3002,
         host: '127.0.0.1',
         compress: true,
         open: true,
         proxy: {
           '/api': {
             target: 'http://127.0.0.1:3002',
             pathRewrite: { '^/api': '' },
             secure: false,
           },
         },
       },
    
       module: {
         rules: [
           {
             test: /\.(js|jsx)$/,
             include: resolve(resolve(__dirname, '..'), ''),
             exclude: /node_modules/,
             enforce: 'pre',
             use: [
               {
                 loader: 'babel-loader',
                 options: {
                   presets: ['@babel/preset-env', '@babel/preset-react'],
                   // 缓存:第二次构建时,会读取之前的缓存
                   cacheDirectory: true,
                 },
               },
             ],
           },
           {
             test: /\.tsx$/,
             loader: 'ts-loader',
             exclude: /node_modules/,
           },
           {
             test: /\.css$/,
             use: [...cssLoaders],
           },
           {
             test: /\.less$/,
             use: [...cssLoaders, 'less-loader'],
           },
           {
             test: /\.s[ac]ss$/,
             use: [...cssLoaders, 'sass-loader'],
           },
           {
             exclude: /.(html|less|css|sass|js|jsx|ts|tsx)$/,
             test: /\.(jpg|jpe|png|gif)$/,
             loader: 'file-loader',
             options: {
               name: 'imgs/[name].[ext]',
               outputPath: 'other',
             },
           },
           {
             test: /\.(ect|ttf|svg|woff)$/,
             use: {
               loader: 'file-loader',
               options: {
                 name: 'icon/[name].[ext]',
               },
             },
           },
         ],
       },
     };
     
     
     
    

    下面着重说一下package.json中的内容

  • name: 包名,后续在npm中搜索全靠它

  • version: 版本号,每发布一次npm包就要增加一个版本,每个版本不能重复。

  • description:描述

  • main: 本包向外暴露的文件,很重要,一定要和你打包出来的文件名一模一样,我的叫做"dist/index.js"

  • private: true/false 是否为私有。 一般为false否则只有自己能使用

  • flies: 暴露的文件夹, 有哪些文件夹提交到npm上面 格式为[ "dist" ]

  • keywords: npm检索的关键字

  • author: 作者

  • license: ISC

  • peerDependencies: 代表着当前npm包依赖下面这几种环境。

    完整配置

       {
        "name": "new_webpack_action2",
        "version": "1.0.24",
        "m": "",
        "main": "dist/index.js",
        "private": false,
        "flies": [
          "dist"
        ],
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
          "dev": "export NODE_ENV=development && npx webpack serve --config config/webpack.dev.js",
          "build": "export NODE_ENV=production && npx webpack   --config config/webpack.prod.js",
          "npm": "export NODE_ENV=production && npx webpack   --config config/webpack.npm.js"
        },
        "keywords": [
          "react",
          "javascript",
          "npm"
        ],
        "author": "919022572@qq.com",
        "license": "ISC",
        "devDependencies": {
          "@ant-design/icons": "4.7.0",
          "@babel/core": "^7.15.0",
          "@babel/preset-env": "^7.15.0",
          "@babel/preset-react": "^7.14.5",
          "@types/lodash": "^4.14.178",
          "@types/react": "^17.0.19",
          "@types/react-dom": "^17.0.11",
          "@types/react-router-dom": "^5.3.3",
          "@typescript-eslint/eslint-plugin": "^5.11.0",
          "@typescript-eslint/parser": "^5.11.0",
          "autoprefixer": "^10.3.2",
          "babel-loader": "^8.2.2",
          "babel-plugin-import": "^1.13.3",
          "css-loader": "^6.2.0",
          "css-minimizer-webpack-plugin": "^3.0.2",
          "eslint": "^8.8.0",
          "eslint-config-airbnb": "^19.0.4",
          "eslint-plugin-import": "^2.25.4",
          "eslint-plugin-jsx-a11y": "^6.5.1",
          "eslint-plugin-react": "^7.28.0",
          "eslint-plugin-react-hooks": "^4.3.0",
          "eslint-webpack-plugin": "^3.1.1",
          "file-loader": "^6.2.0",
          "html-webpack-externals-plugin": "^3.8.0",
          "html-webpack-plugin": "^5.5.0",
          "less": "^4.1.1",
          "less-loader": "^10.0.1",
          "lodash": "^4.17.21",
          "mini-css-extract-plugin": "^2.2.0",
          "postcss-loader": "^6.1.1",
          "postcss-preset-env": "^7.4.2",
          "sass": "^1.38.0",
          "sass-loader": "^12.1.0",
          "speed-measure-webpack-plugin": "^1.5.0",
          "style-loader": "^3.2.1",
          "stylelint": "^13.13.1",
          "stylelint-config-standard": "^22.0.0",
          "terser-webpack-plugin": "^5.1.4",
          "thread-loader": "^3.0.4",
          "ts-loader": "^9.2.5",
          "tslint": "^6.1.3",
          "typescript": "^4.5.5",
          "webpack": "^5.68.0",
          "webpack-cli": "^4.8.0",
          "webpack-dev-server": "^4.0.0",
          "webpack-merge": "^5.8.0",
          "workbox-webpack-plugin": "^6.4.2"
        },
        "dependencies": {
          "antd": "4.18.8",
          "axios": "^0.26.0",
          "react": "17.0.2",
          "react-dom": "17.0.2",
          "react-router-dom": "5.2.0"
        },
        "peerDependencies": {
          "@ant-design/icons": "4.7.0",
          "antd": "4.18.8",
          "bizcharts": "4.1.15",
          "rc-footer": "0.6.6",
          "react": "17.0.2",
          "react-dom": "17.0.2",
          "react-router-dom": "5.2.0"
        },
        "browserslist": {
          "development": [
            "last 1 chrome version",
            "last 1 firefox version",
            "last 1 safari version"
          ],
          "production": [
            ">0.2%",
            "not dead",
            "not op_mini all"
          ]
        }
      }
      
      
    

    三、发布

    如果是第一次发布包,执行以下命令,然后输入前面注册好的NPM账号,密码和邮箱,将提示创建成功

      npm adduser
      
    

    如果不是第一次发布包,执行以下命令进行登录,同样输入NPM账号,密码和邮箱

      npm login
      
    

    注意:npm adduser成功的时候默认你已经登陆了,所以不需要再进行npm login了

    接着先进入项目文件夹下,然后输入以下命令进行发布

      npm publish
      
    

    当终端显示如下面的信息时,就代表版本号为1.0.0(你的package.json中的版本号)的包发布成功啦!前往NPM官网就可以查到你的包

     + 你的文件名@0.1.0
     
    

    四、报错

    1、如果出现

      npm ERR! code E403
      npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/ghost-watermarkdemo - Forbidden
      npm ERR! 403 In most cases, you or one of your dependencies are requesting
      npm ERR! 403 a package version that is forbidden by your security policy, or
      npm ERR! 403 on a server you do not have access to.
    

    以下几种原因会导致

      账号密码错误   (请检查npm官网的账号密码)
      包重名     (请检查npm官网上是否有同名项目,名字取决于 package.js 的项目名字段)
      网络原因   
      镜像源问题 
      新注册的用户邮箱未激活。  登陆你的邮箱去激活(如下)
    
       
    

image.png

2、 如果出现

image.png

需要在你的package.json中 private改为false或者删除

更新已经发布的包

更新包的操作和发布包的操作是一样的

   npm publish
   

但是每次更新时,必须修改版本号后才能更新,比如将1.0.0修改为1.0.1后才能更新发布。

这里的包版本管理规则都是一样的,采用的是semver(语义化版本),意思就是版本号:大改.中改.小改

五、## 从npm上面卸载自己发布的包

进入自己项目的目录执行。npm unpublish --force 出现:

   npm WARN using --force Recommended protections disabled.
-包名@0.1.0

则卸载成功,这时在npm上面就搜索不到了