构建NodeJS库--前端项目的打包发布

1. 前言

学习如何打包发布前端项目,需要学习以下相关知识:

  • package.json 如何初始化配置,以及学习npm配置项
  • webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具,使用说明,推荐阅读
  • babel-loader JavaScript通常需要做语法转化和polyfills以便可以使用高级语法而不必担心浏览器兼容性问题,Babel的作用便在于此,而babel-loader正好可以与webpack结合使用;
  • eslint 一个好的项目离不开代码格式规范
  • jest 一款js测试框架,写好测试用例覆盖测试的功能点,确保软件质量,推荐阅读

2. 概念区别

Node.js是JavaScript的一种运行环境,是对Google V8引擎进行的封装。是一个服务器端的JavaScript的解释器。npm(Node Package Manager)是nodejs的包管理器。

有一些概念容易混淆,注意区分。

2.1 CommonJS vs Es module

关于type的配置值有:

  • module 可以指示 Node.js 通过使用.cjs扩展名命名特定文件,将其解释为CommonJS
  • commonjs 可以通过使用.mjs扩展名命名特定文件,指示 Node.js 将其解释为 ES module

ps:js文件两种类型都能识别。

ES module 更加现代化和灵活,支持动态导入、异步加载、静态作用域等特性,

而 CommonJS 更加简单和适用于早期的 Node.js 环境。

在实际开发中,需要根据具体的项目需求和环境来选择使用哪种模块系统。

看到深入浅出 Commonjs 和 Es Module一文描述的很详细,感兴趣可以详细了解。

2.2 package.json入口mainmodulebrower

总结:其他项目引用时,会根据项目自身的type来选择定义的lib的入口文件,三个配置的主要区别在于优先级。

一般通常认为browser = browser+mjs > module > browser+cjs > main

推荐阅读入口文件配置的区别一文。

2.3 ES5 vs ES6

  • ES5指的是ECMScript的第五个版本,发布于2009年,是目前最广泛使用的JavaScript版本。
  • ES6是ECMScript的第六个版本,也成为ES2015,发布于2015年,引入了许多新的语言特性和语法糖。
  • ES2015是ES6的官方名称,但是由于ES6引入了太多的新特性,因此人们通常使用ES2015来指代ES6。

推荐阅读ES5和ES6的区别以及ES6常用特性

2.4 webpack config中的 output.library.type

官方使用说明中配置可选项很多,这里介绍:

  • commonjs
  • module
  • umd 统一模块定义,这种模块语法,兼容了以上的CommonJS、AMD、ES Module使用方式,也就是Vue脚手lib模式打包的这种模式,设置改值后,注意globalObject配置项可设置值为'this'

推荐阅读CommonJS/AMD/UMD/ES Module介绍和区别

3. 项目实战

源码:https://github.com/SkylerHu/js-enum

3.1 目录结构

3.2 主要配置文件

3.2.1 .babelrc

json 复制代码
{
  "presets": [
    "@babel/preset-env"
  ],
  "plugins": [
    "@babel/plugin-proposal-class-properties"
  ]
}

3.2.2 .eslintignore

text 复制代码
# node_modules
node_modules/

# build
build/

# dist
dist/
docs/

3.2.3 .estlintrc.json

json 复制代码
{
  "env": {
    "browser": true,
    "es6": true,
    "mocha": true,
    "jest": true,
    "node": true
  },
  "globals": {
    "dashjs": true,
    "WebKitMediaSource": true,
    "MediaSource": true,
    "WebKitMediaKeys": true,
    "MSMediaKeys": true,
    "MediaKeys": true
  },
  "parser": "babel-eslint",
  "rules": {
    "no-caller": 2,
    "no-undef": 2,
    "no-unused-vars": 2,
    "no-use-before-define": 0,
    "object-curly-spacing": ["error", "always"],
    "strict": 0,
    "semi": 2,
    "no-loop-func": 0,
    "no-multi-spaces": "error",
    "keyword-spacing": [
      "error",
      {
        "before": true,
        "after": true
      }
    ],
    "quotes": [
      "error",
      "single",
      {
        "allowTemplateLiterals": true
      }
    ],
    "indent": [
      "error",
      2,
      {
        "SwitchCase": 1
      }
    ]
  },
  "ignorePatterns": ["releases/**/*"],
  "overrides": [
    {
      "files": ["tests/**/*"],
      "env": {
        "jest": true
      }
    }
  ]
}

3.2.3 jest.config.json

json 复制代码
{
  "verbose": true,
  "collectCoverage": true,
  "coverageDirectory": "./.coverage",
  "moduleFileExtensions": [
    "js",
    "json"
  ],
  "testMatch": [
    "**/tests/**/*.js"
  ],
  "collectCoverageFrom": [
    "src/**/*.{js,jsx}",
    "!**/node_modules/**"
  ],
  "testEnvironment": "node"
}

3.2.2 package.json

json 复制代码
{
  "author": "SkylerHu",
  "name": "js-enumerate",
  "version": "1.0.2",
  "private": false,
  "type": "module",
  "main": "dist/index.js",
  "files": [
    "dist"
  ],
  "publishConfig": {
    "access": "public",
    "registry": "https://registry.npmjs.org/"
  },
  "engines": {
    "node": "^14.21.3"
  },
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "build": "webpack --config webpack.config.js",
    "test": "jest"
  },
  "dependencies": {},
  "devDependencies": {
    "@babel/core": "^7.24.4",
    "@babel/plugin-proposal-class-properties": "^7.18.6",
    "@babel/preset-env": "^7.24.4",
    "@jest/globals": "^29.7.0",
    "babel-eslint": "^10.1.0",
    "babel-loader": "^9.1.3",
    "clean-webpack-plugin": "^4.0.0",
    "eslint": "7.32.0",
    "eslint-loader": "^4.0.2",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "webpack": "^5.91.0",
    "webpack-cli": "^5.1.4"
  },
  "description": "Enum is a javascript enumeration module. It works with Node.js and the browser.",
  "keywords": [
    "enum",
    "enumerate",
    "javascript",
    "js-enum",
    "js-enumerate",
    "react-enum",
    "vue-enum"
  ],
  "homepage": "https://github.com/SkylerHu/js-enum",
  "repository": {
    "type": "git",
    "url": "git@github.com:SkylerHu/js-enum.git"
  },
  "bugs": {
    "url": "https://github.com/SkylerHu/js-enum/issues"
  },
  "license": "MIT"
}

3.2.3 webpack.config.js

javascript 复制代码
import path from 'path';
import { CleanWebpackPlugin } from 'clean-webpack-plugin';

import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));


const PATHS = {
  src: path.join(__dirname, 'src'),
  build: path.join(__dirname, 'dist'),
};

const config = {
  mode: 'production',
  // devtool: 'source-map',
  entry: path.join(PATHS.src, 'index.js'),
  output: {
    path: PATHS.build,
    clean: true,
    filename: 'index.js',
    library: {
      name: 'Enum',
      type: 'umd', // 采用通用模块定义
      export: 'default', // 兼容 ES6 的模块系统、CommonJS 和 AMD 模块规范
    },
    globalObject: 'this',
  },
  resolve: {
    extensions: ['.js', '.jsx', '.json'],
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/, //排除node_modules文件夹
        enforce: 'pre', //提前加载使用
        use: { //使用eslint-loader解析
          loader: 'eslint-loader'
        }
      },
      {
        // 使用 babel-loader 来编译处理 js 和 jsx 文件
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
  ],
  optimization: {
    minimize: true,
  },
};

export default config;

3.3 如何构建发版

  • 安装依赖npm install .
  • 代码格式npm run lint
  • 测试用例npm run test
  • 构建npm run build
  • 发版npm publish ,具体命令可以npm help查看,也可以查看官方文档
    • 需要在nodejs.org上注册账号,可以npm adduser通过命令行操作;
    • publish前需要npm login登录账号;
    • 也可以直接npmrc配置中配置好账号,或者创建auth_token配置

3.4 其他注意的问题

  • 3.4.1 jestwebpack版本对node版本的要求,node版本可以通过nvm控制;
shell 复制代码
> jest
./node_modules/jest/node_modules/jest-cli/build/run.js:135
    if (error?.stack) {
              ^
SyntaxError: Unexpected token '.'

升级node版本解决,该项目使用的node 14+

语法标准中,可选链运算符(?.) 要求node版本14+

  • 3.4.2 Babel编译缺少plugin
shell 复制代码
> jest

 FAIL  tests/test_enum.js
  ● Test suite failed to run
    Jest encountered an unexpected token
    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    ...

    Details:
    SyntaxError: ./src/index.js: Missing class properties transform.
       8 |
       9 | export default class Enum {
    > 10 |   #items = [];
         |   ^^^^^^^^^^^^
      11 |   #config = {};
      12 |   /**
      13 |    *

通过安装和配置@babel/plugin-proposal-class-properties解决,参考

  • 3.4.3 webpack配置output.type为umd时,注意配置globalObject: 'this'
shell 复制代码
file:///.../node_modules/js-enumerate/dist/index.js:1
ReferenceError: self is not defined
    at file:///Users/skyler/Documents/github/test/node_modules/js-enumerate/dist/index.js:1:190
    at ModuleJob.run (internal/modules/esm/module_job.js:183:25)
    at async Loader.import (internal/modules/esm/loader.js:178:24)
    at async Object.loadESM (internal/process/esm_loader.js:68:5)
    at async handleMainPromise (internal/modules/run_main.js:59:12)

报错参考typescript-webpack-library-generates-referenceerror-self-is-not-defined解决。

  • 3.4.4 当nodejs.org上仅publish一个版本,操作npm unpublish后,导致无法找寻项目名,24小时内无法再次publish
shell 复制代码
npm ERR! code E403
npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/js-enumerate - js-enumerate cannot be republished until 24 hours have passed.
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.

npm ERR! A complete log of this run can be found in:
npm ERR!     ./.npm/_logs/2024-04-21T18_27_39_505Z-debug.log
相关推荐
汪子熙31 分钟前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ39 分钟前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.5 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。6 小时前
案例-表白墙简单实现
前端·javascript·css
数云界6 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd6 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常6 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer6 小时前
Vite:为什么选 Vite
前端
小御姐@stella6 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing6 小时前
【React】增量传输与渲染
前端·javascript·面试