自定义NPM包

环境初始化

  1. mkdir npm-log
  2. cd npm-log
  3. npm init -y
入口文件
  • 自定义依赖模块:
    • 模块是在 package.json 里通过 main 字段定义这个包对外暴露的入口;
      • 模块起源于node,语法默认支持commonjs规范
      • 模块若使用ES Module语法书写,通过 module 字段定义入口(需要打包工具配合使用)
  • 自定义命令行:
    • 如果是提供命令行工具,则需要通过 pkg#bin 字段来定义暴露的命令名称与实际执行的文件
这篇文章讲述自定义依赖模块的声明,后续会有专门篇幅进行自定义命令行的讲述。
声明示例
  1. 创建lib/index.js
    const Noop = () => {} class Logmi { errorHandler = Noop successHandler = Noopstatic create (options) { return new Logmi(options) } constructor (options = {}) { console.log('---------create------', options) } log (msg, level) { console.log('log: start', msg, level)} } module.exports = Logmi

  2. 更新package.json
    "main": "lib/index.js"

开发环境
  • [ ] 自动日志
  • [ ] 版本更新
  • husky
    npm install husky --save-dev npx husky install

    • 配置run-script:安装依赖后自动启动Git hooks
      "prepare": "husky install"

      • 追加测试钩子
        # Unix系统可用 npx husky add .husky/pre-commit "npm run test" # Windows通过以下命令创建文件(引号在windows下不是正确的语法) npx husky add .husky/pre-commit # Windows去新建的文件中指定命令 #!/bin/sh . "$(dirname "$0")/_/husky.sh" npm run test

  • commitlint
    • commitlint提交信息校验工具
    • 需要和校验规范配合使用,官网默认规范@commitlint/config-conventional —— 可自定义。
    • commitlint绑定@commitlint/config-conventional
      npm i -D commitlint @commitlint/config-conventional # Unix echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js # Windows echo module.exports = {extends: ['@commitlint/config-conventional']} > commitlint.config.js

    • 配置Git hook:在提交commit msg进行参数校验 —— 写在run-script中无效
      # Unix系统可用 npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1" # Windows通过以下命令创建文件(引号在windows下不是正确的语法) npx husky add .husky/commit-msg # Windows去新建的文件中指定命令 #!/bin/sh . "$(dirname "$0")/_/husky.sh"npx --no-install commitlint --edit $1

  • standard-version
    npm i --save-dev standard-version

    • 配置run-script:发布前自动升级版本号 + 生成日志
      "prepublishOnly": "standard-version"

    注意:这里不要使用prepublish钩子,该钩子在npm i时运行,而不是npm publish时运行。
调试
  1. 进入本地NPM
    • npm link创建软链接到全局node环境中
  2. 进入依赖包的项目A中
    • npm link 建立软链接依赖
  3. 在项目A需要调用的文件中
    # 调用 import Logmi from "log"; const LogmiInstance = Logmi.create({ url: 'http://localhost:3000' }) LogmiInstance.log(`paste: ${JSON.stringify(paste)}`, 1)

  4. 启动项目A,即可调试
开发 NPM包是commonJS语法,使用require(),而非import...from...引入依赖。
  • 实例化参数
    • 工厂函数
    • 参数默认值
      • const Noop = () => {}:空语句
    • 传参校验
      • 参数数据实体类型
      • 校验警告
        • 参数合并
  • DTO
    • 校验DTO组成结构参数ParamChecker
    • 统一结构ContentWrapper
  • 配置信息统一分类处理
    module.exports = { EXCEED_TRY_TIMES: 'Exceed try times', }

打包发布 打包需要引入webpack,这里的package.json修改入口文件:
"main": "dist/logmi.js", "module": "lib/index.js",

其中,main是暴露打包后的入口文件;
modulewebpack环境下暴露的入口文件;
package.json
{ "name": "log", "version": "1.0.0", "description": "", "main": "dist/logmi.js", "module": "lib/index.js", "scripts": { "prepare": "husky install", "build": "cross-env NODE_ENV=production webpack --config webpack.config.js --mode=production", "test": "echo \"npm run test\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "@babel/core": "^7.14.3", "@babel/preset-env": "^7.14.4", "@commitlint/cli": "^12.1.4", "@commitlint/config-conventional": "^12.1.4", "babel-loader": "^8.2.2", "cross-env": "^7.0.3", "husky": "^6.0.0", "standard-version": "^9.3.0", "terser-webpack-plugin": "^5.1.3", "webpack": "^5.38.1", "webpack-cli": "^4.7.0" }, "dependencies": { "@babel/polyfill": "^7.12.1", "idb-managed": "^1.0.9" }, "bundledDependencies": [ "idb-managed" ] }

pkg#main
作为第三方依赖包时,包的入口执行文件。
如果没有指定,默认为root目录下的index.js
pkg#bin
作为命令行工具时,包的入口执行文件
安装该包时,node会自动创建硬链接该包到全局执行环境。
  • String:单执行文件
  • Map:多执行文件
pkg#module
非官方配置,rollupwebpack等打包工具提供的配置项。
指向的应该是一个基于 ES6模块规范书写的模块。
pkg#private
设置"private": truenpm拒绝发布该包。
pkg#workspaces
结合monorepo的概念,创建工作区。
pkg#files
安装该包时,目录中包含在pkg.files中指定的文件结构。
默认包含:
package.json README CHANGES / CHANGELOG / HISTORY LICENSE / LICENCE NOTICE The file in the "main" field

pkg#bundledDependencies
通过fptscp等工具传输该包时,需要将该包的依赖打包在一起。
  • bundledDependencies中指定依赖包的列表
    • 只需要指定包名,版本会在dependencies查找
  • 通过npm pack打包
  • 通过传输工具传输打好的*.tgz
  • 通过npm i *.tgz安装该包及其依赖
Note: 定义在bundledDependencies列表内的依赖,安装NPM包时,该依赖会嵌套在包文件下,而不会提升到node_modules目录的根下
pkg#peerDependencies
  • 表明该包对主包/主工具库的兼容性,而不是依赖性,这种关系称之为插件。
    • 主包一般会对插件暴漏的接口指定标准
peerDependencies指定的包@版本号表明,我们的包需要在指定包的环境下执行,需要一同安装。
打包 安装插件
npm i -D webpack-cli webpack cross-env terser-webpack-plugin

npm install --save-dev @babel/core babel-loader @babel/preset-envnpm install --save @babel/polyfill

配置babel.config.json
{ "presets": [ [ "@babel/env", { "targets": { "edge": "17", "firefox": "60", "chrome": "67", "safari": "11.1" }, "useBuiltIns": "usage", "corejs": "3.6.5" } ] ] }

配置run-script
... "build": "cross-env NODE_ENV=production webpack --config webpack.config.js --mode=production", ...

webpack.config.js
const path = require('path') const webpack = require('webpack') const TerserPlugin = require("terser-webpack-plugin")const resolve = dir => path.join(__dirname, '.', dir)const isProd = process.env.NODE_ENV === 'production'module.exports = { entry: { logmi: './lib/index.js' }, output: { path: resolve('dist'), // 输出目录 filename: '[name].js', // 输出文件 libraryTarget: 'umd', // 采用通用模块定义 library: 'logmi', // 库名称 libraryExport: 'default', // 兼容 ES6(ES2015) 的模块系统、CommonJS 和 AMD 模块规范 globalObject: 'this' // 兼容node和浏览器运行,避免window is not undefined情况 }, devtool: 'source-map', module: { rules: [ { test: /\.js$/, exclude: /(node_modules)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] }, optimization: { minimize: true, minimizer: [new TerserPlugin()], } }

配置可见目录结构
... "files": [ "dist/" ] ...

发布 若使用nrm维护多个npm源,需要切换到发布的目标源。或者通过pkg#publishConfig.npmrc指定目标源。
npm为例:
  1. 切换到npm
    nrm use npm

  2. 在NPM官网注册账号
  3. 命令行中登录用户
    npm login

  4. 在项目目录下发布包
    npm publish

补充知识
  • 【自定义NPM包】[ ] Peer Dependencies
    The peerDependencies configuration was originally designed to address the problem of NPM packages that were ‘plugins’ for other frameworks.
删除CHangeLog
  • https://github.com/convention...
  • https://lukasznojek.com/blog/...

    推荐阅读