要写好npm包,Package.json 是需要掌握的

2023年 10月 14日 48.6k 0

Package 知识体系

熟练掌握 Package.json 各个字段的意思以及用途,对于开发 npm 包和日常开发将如虎添翼。希望阅读本文后你可以解决以下问题。

  • ^1.2.3 和 ~1.2.3 版本范围是什么
  • 如何防止业务项目被发布到 npm 仓库
  • 当 npm 包版本出现冲突了,应该如何解决
  • 如果限制团队成员使用相同的 node 和包管理工具「npm、yarn、pnpm」,解决方案是多样的
  • Vite、Webpack 加载模块优先级是什么,注意:webpack 的优先级是可以配置的
  • 你可以利用 pre、post 钩子做些什么什么sao操作?
  • 如何开发一个 script 脚本

知识获取网站 rushjs.io/zh-cn/

官方文档 :docs.npmjs.com/cli/v9/conf…,以下的这些都配置都可以通过 process.env 中获取。

npm_package_name: 'my_package_name',
npm_package_scripts_start: 'node ./index.js',
​
格式: npm_package_ + package.json 中的 key值。

name

为项目的名称,它需要遵循 npm 包名规范

  • 如果是一个 npm 包,这个字段是必填的,并具备唯一性。
  • 否则,非必填。

version

为版本号,它需要遵循 Semantic Versioning 规范

  • 如果是一个 npm 包,这个字段是必填的,每次发版本需要改变版本号。
  • 否则,非必填。

homepage

官网,npm docs reactnpm home react 即可快速访问。

script

为执行脚本,通过运行 npm run start,yarn start 即可,它还有一些内置的 script,但是很少用,本质是 npm 会建立一个shell 以及环境变量「会处理局部、全局 node_modules 的bin」。

添加变量

如环境变量,NODE_ENV=production ,在执行命令时读取环境变量 NODE_ENVproduction

注意:添加变量的方式,是和当前运行的环境有关系,比如以下的webpack 和 rollup 添加环境变量的方式不一样,具体实现可以参考示例和官网。以及 node 运行的时候也可以传参数的,在windows 系统中,无法通过此方式读取环境变量,需要借助工具 cross-env实现跨系统 。

{
  "scripts": {
    "start": "node server.js myDevelopment myPrams", // 通过 process.argv 访问如下
     // argv: [ 
     //  '/usr/local/bin/node', // Node.js 的执行路径
     //  '~/Desktop/myDemo/server', // 脚本文件的路径
     //  'myDevelopment', // 参数1
     //  'myPrams'  // 参数2
     // ],
    // 通过 `process.env.NODE_ENV`
    "dev": "NODE_ENV=production webpack",
    "dev:cross-platform": "cross-env NODE_ENV=production webpack", 
    "build": "rollup --config rollup.config.js --environment NODE_ENV:production --bundleConfigAsCjs", 
    "build": "webpack",
    "test": "mocha tests/*.js",
    "lint": "eslint src/*.js",
​
  }
}
​

pre/post 钩子:允许执行脚本前的前后执行,可以用于执行一些预处理或后处理任务

当我们在手动执行 npm run xxx 时,如果 prexxxpostxxxscripts 存在时,它将会自动执行 npm run prexxx 以及 npm run postxxx

npm yarn1.x 是支持的。

pnpm 需要设置npm 配置

npm config set enable-pre-post-scripts=true
还有一些默认的钩子是有问题的,比如 preinstall postinstall。 它们都会在脚本结束后一起执行
自定义的钩子执行逻辑是符合预期的

注意:pre/post 的安全问题,黑客可以通过修改package.json 中的script 字段的 preinstall 和 postinstall 这样可以执行一些脚本,带来安全性问题。

如何自己开发一个 script

可以参考去实现 github.com/kentcdodds/…,kentcdodds.com/blog/tools-…

repository

为仓库地址,string 或 object

{
  "repository": {
    "type": "git",
    "url": "https://github.com/facebook/react.git"
  }
}
​
{
  "repository": "https://github.com/facebook/react.git",
}

type

用于指定模块的类型。

  • "module":表示该包是一个 ES 模块,适用于支持 ES 模块规范的环境。
  • "commonjs":表示该包是一个 CommonJS 模块,适用于 Node.js 或不支持 ES 模块规范的环境。

没有默认值,

{
  "type": "module"
}

注意:"type" 字段的使用是为了提供更准确的模块类型信息,以帮助使用者根据不同的环境选择正确的模块导入方式。但这并不影响实际的模块导入和包的使用,因为最终还是取决于具体的模块加载器和环境支持。

在缺少 "type" 字段的情况下,模块的类型将根据具体的上下文和环境进行推断和处理

文件后缀也可以定义模块规范,demo.cjs、demo.ejs

模块规范

参考模块查找解析规范,但具体的实现规则根据的打包器又不一样。

Node.js

imports「私有映射」 > exports「require > default , 找不到模块报错」> main ,其他的模块也不会去找了。

Webpack

  • 在 Web 浏览器环境配置下,优先选择:exports > browser > module > main

注意:在 webpack 中需要配置"type": "module" 才能生效,webpack 4 和5 还有一定的区别,4 只会查 import,并不会忽略其他配置,webpack5和vite是类似的 参考

Vite

  • 在 Web 浏览器环境配置下,exports「import >browser > default ,找不到模块报错,因为require不加载」 > module > browser > main

在 Vite 中 配置了exports 字段就会忽略其他的配置,找不到就会报错。

注意:exports 中 的 default 最好是 cjs 和 esm 都兼容的

总的来说 exports 优先级高,配置后就会忽略其他的配置,其中webpack 和 vite 实现略有不同,如果把browser字段去掉,规律就是 module > main

main

cjs规范导出的模块,当使用 const foo = require("foo"),模块解析器会去优先找到定义在 package.json 文件中的 main 字段,并返回。

If main is not set, it defaults to index.js in the package's root folder.

{
  "main": "dist/index.js",
}

module

esm规范导出的模块,当使用 import xx from 'antd' 时,模块解析器会去优先找到定义在 package.json 文件中的 module 字段,并返回,如果没有定义会尝试 main 字段

{
  "module": "dist/index.js",
}

browser

在浏览器环境中,用于指定替代模块的路径或文件。它的作用和 main 字段是一样的,并且语意更强,注意它在 node.js 中是不能用的

{
  "browser": "dist/index.js",
}

exports

用于指定模块的导出方式和入口文件。通过定义 "exports" 字段,你可以为你的包提供灵活的模块导入方式,以适应不同的模块系统和环境需求。

"exports": {
  "require": "./index.js",
  "import": "./index.mjs",
  "./style": "./dist/css/index.css'
 }
}
​
// 以前的使用方式 import `packageA/dist/css/index.css`;
// 现在的使用方式 import `packageA/style`;
​
也可以使用这种方式
 "." 表示默认导出方式。
require 代表相应环境的 cjs 规范,import 代表 esm,default就是默认。
 "exports": {
    ".": {
      "default": {
        "import": "./dist/index-exports.esm",
        "browser": "./dist/index-browser-exports.js",
        "require": "./dist/index-exports-require.js",
        "default": "./dist/index-exports-default.js"
      }
    }
  },
​

注意:npm v7、yarn v2 才可以使用,它的优先级最高。

imports

用于指定 ECMAScript 模块的导入映射的配置。它用于告诉 JavaScript 运行时或构建工具如何解析模块的导入路径。 JavaScript 解析模块将会从 imports 中查找模块映射,进而导入成功。

注意:它是写在项目的 package.json 下面的,由项目开发者决定需要怎么查找模块「私有映射」,去映射到那个包,imports 是优先级最高的

在以下场景中特别有用:

  • 解决模块路径的别名:可以使用 "imports" 字段来为常用模块路径或目录路径指定别名,以简化导入语句。例如,将较长的路径映射为短的别名路径,或者将相对路径映射为绝对路径别名。
  • 处理非标准模块路径:有时,某些模块可能具有非标准的导入路径。通过在 "imports" 字段中定义相应的映射,可以让 JavaScript 运行时或构建工具正确解析这些非标准路径。
// demo
{
  "dependencies": {
    "rollup-package-demo-juice": "^1.0.4"
  },
  "imports": {
    "#rollup-package-demo-juice": {
      "import":"rollup-package-demo-juice",
      "require":"rollup-package-demo-juice",
      "node": "rollup-package-demo-juice",
      "default": "rollup-package-demo-juice"
    }}
  
}
​
开源的用法
https://www.npmjs.com/package/esbuild-loader?activeTab=code
"imports": {
  "#esbuild-loader": {
    "types": "./src/index.d.ts",
    "development": "./src/index.ts",
    "default": "./dist/index.cjs"
  }
},
​
使用「未验证」 
import typesModule from '#esbuild-loader/types';
import developmentModule from '#esbuild-loader/development';
import defaultModule from '#esbuild-loader/default';

注意:目前这个模块是和 webpack 绑定的,并且 imports 字段。

由 ECMAScript 提案 Import Conditions 引入的。这个提案旨在为 ECMAScript 模块系统提供一种配置方式,以解决模块导入路径的问题。

提案的第一版草案(Stage 1)于 2020 年 7 月提出,并在接下来的 TC39 会议上进行了讨论和改进。然后,在 2021 年 9 月的 TC39 会议上,该提案进入了第二版草案(Stage 2)。

unpkg、jsdelivr

用于指定在 unpkg、jsdelivr(一个用于在浏览器中直接引入 npm 包的 CDN)上的入口文件路径。

"jsdelivr": "dist/axios.min.js",
"unpkg": "dist/axios.min.js",

bin

用于注册命令。npm 会将这些可执行文件链接到环境变量,使用户可以在命令行中直接运行这些命令。

  • 当我们下载包的时候 npm i xxx
  • 包管理工具会在 package.json 找到 bin 对象,将这个对象指向的文件软链到全局path「系统的可执行路径」
  • 添加一个可执行权限「x」
  • // package.json
    "bin":{
        "mybin":"./binbin/index.js"
    }
    ​
    ​
    bin/index.js  // 脚本文件
    #!/usr/bin/env node  // 解释器声明
    ​
    console.log( '执行' )
    ​
    ​
    // 将会出现两种情况,可以通过 npm link 调式
    ​
    1.局部安装执行方式 node node_modules/.bin/mybin
    2.全局安装执行方式 mybin
    

    注意配合 npx 使用会更方便,yarn link 是不可以的,因为bin文件没有链接处理,node_modules 中的 .bin 文件没有生成

    files

    这个字段允许你控制在使用 npm publish 命令发布你的包时,哪些文件将被包含在发布的 tarball 中。

    可选值,默认值["*"],默认所有文件都可发布,这样会有非必要的文件也会发布,浪费储存空间。

    "files": [
      "bin",
      "build"
    ],
    ​
    这样解压出来的文件,就只有 build 一个文件夹,以及其他的证书、CHANGELOG.md,package.json README.md
    

    dependencies

    dependencies

    项目依赖:这些依赖都会成为线上生产环境中的代码组成部分。当它依赖的 npm 包被下载时,dependencies 下的模块也会作为依赖,一起被下载。

    npm add the-answer // --save
    yarn add the-answer
    pnpm add the-answer
    

    devDependencies

    开发依赖:一般是只用于开发环境的开发库。当引用线上库的时候,devDependencies 不会被自动下载,因为它对于使用者来说是不需要的。

    注意:并不是只有在 dependencies 中的模块才会被一起打包,而在 devDependencies 中的依赖一定不会被打包。实际上,依赖是否被打包,完全取决于项目里是否被引入了该模块。dependencies 和 devDependencies 在业务中更多的只是一个规范作用,我们自己的应用项目中,使用 npm install 命令安装依赖时,dependencies 和 devDependencies 内容都会被下载。

    npm add the-answer -D // --dev-save 
    yarn add the-answer -D 
    pnpm add the-answer -D 
    

    peerDependencies

    它确保使用者在安装你的包时也安装了指定的依赖包,并在不一致的依赖环境下提供警告或错误信息。它的作用是让开发者和包的开发者建立一种合作和依赖的关系

    • 避免重复安装,因为这个库很经常被安装,比如 react 项目,引用了一个 antd,antd 就配置了 peerDependencies
    • 框架或库的插件,由于是强依赖,所以通常是可以使用 peerDependencies 去解决重复安装的,比如 babel monorepo 体系下面的 babel-preset-env

    举个例子,假设 react-ui@1.2.2 只提供一套基于 React 的 UI 组件库,它需要宿主环境提供指定的 React 版本来搭配使用,因此我们需要在 React-ui 的 package.json 中配置:

    "peerDependencies": {
        "React": "^17.0.0"
    }
    

    插件不能单独运行,插件正确运行的前提是核心依赖库必须先下载安装。我们不希望核心依赖库被重复下载,在项目中,同一插件体系下,核心依赖库版本最好相同

    npm --no-save the-answer
    yarn add --peer the-answer
    pnpm add --peer the-answer
    

    注意:npm versions 3-6 和 yarn 中的 peerDependencies 不会自动下载,只会提示警告,v7 将会默认自动下载。

    peerDependenciesMeta

    对 peerDependencies 中的包更多的描述信息,帮助包的使用者更好地了解和处理 peerDependencies。

    以下 "react" 的元数据指定了该对等依赖是可选的,而"react-dom" 的元数据提供了对该包的描述信息。

    {
      "peerDependencies": {
        "react": "^17.0.0",
        "react-dom": "^17.0.0"
      },
      "peerDependenciesMeta": {
        "react": {
          "optional": true
        },
        "react-dom": {
          "description": "DOM rendering for React"
        }
      }
    }
    

    bundledDependencies

    指定需要捆绑(bundle)到你的包中的依赖项,会增加包的大小,因此选择那些必要的、无法通过用户自行安装的依赖项

    通常默认情况下,依赖项不会被包含在你的包中。而是期望用户在安装你的包时自行安装这些依赖项。然而,有时你可能希望将特定的依赖项直接捆绑(bundle)到你的包中,以便用户在安装你的包时无需单独安装这些依赖项。

    // 当执行 npm pack 时,会产生 my-package-1.0.0.tgz,你的包将捆绑(bundle) "lodash" 和 "axios" 两个依赖项。这意味着当用户安装你的包时,这些依赖项会作为你的包的一部分直接被安装,而不需要用户手动安装它们。
    {
      "name":"my-package",
       "version": "1.0.0",
      "bundledDependencies": ["lodash", "axios"]
    }
    

    注意:在 bundledDependencies 中指定的依赖包,必须先在 dependencies 和 devDependencies 声明过,否则在 npm pack 阶段会进行报错。

    optionalDependencies

    可选依赖,即使对应依赖项安装失败了,也不会影响整个安装过程。

    resolution

    用于指定解决依赖项冲突或版本选择的规则和策略。

    当一个项目具有多个依赖项,而这些依赖项之间存在版本冲突或不兼容时,resolution 字段可以帮助开发者指定优先使用的版本,或者通过特定规则解决依赖冲突。

    以下是 resolution 字段的一些常见用途:

  • 解决依赖冲突:当多个依赖项要求使用不同版本的同一个软件包时,可能会出现依赖冲突。通过 resolution 字段,开发者可以指定要使用的特定版本,以解决冲突并确保依赖项能够正常工作。
  • 锁定版本:resolution 字段可以用于锁定依赖项的特定版本,以确保在后续安装或更新时不会自动升级到其他版本。这对于确保项目的稳定性和一致性非常有用。
  • 版本范围管理:resolution 字段还可以用于管理依赖项的版本范围。通过指定特定的版本范围,可以确保在安装或更新依赖项时仅选择符合规定范围的版本。
  • 注意:它并不是 npm 官方的标准字段,所以只有 yarn 或 pnpm 是支持解析这个字段的

    sideEffects

    用于给打包工具「Webpack」优化处理,可以告诉它这个包是否有副作用。

    "sideEffects": true // 表示有副作用
    "sideEffects": false // 表示没有副作用
    "sideEffects": [] // 表示该路径下的文件是有副作用的,不用 tree shaking,其他模块是没有副作用的,但是具体实际情况以打包工具的实际逻辑为准。
    

    engines

    指定项目 node 版本 和 npm、yarn、pnpm 版本

    注意 npm 要配置 npm config set engine-strict=true,或者在 .npmrc 文件中配置

    "engines": {
    "node": ">=12",
    "npm": ">=6",
    "yarn":">2",
    "pnpm":"

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论