关于package.json的理解

83 阅读6分钟

name

name 是区分 npm 包的唯一标识。当一个 npm 仓库中的包被安装到本地,我们能通过名称引用,而不必写复杂的 node_modules/...

version

major.minor.patch(主版本号.次版本号.修订号)

版本约束

  • ^ 的含义是安装最新的 minor 版本。例如 ^1.2.0 的约束下,会为项目安装最新的 minor 版本 1.X.Y,但不会安装下一个 major 版本 2.0.0
  • ~ 的含义是安装最新的 patch 版本。例如 ~1.2.0 的约束下,会为项目安装最新的 patch 版本 1.2.X,但不会安装下一个 minor 版本 1.3.0
  • 如果版本号前面没有任何标识符,表示固定版本号,无论如何都只安装这个固定版本。

(E) defer | async

defer是“渲染完再执行”【等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成】

async是“下载完就执行”【加载完成,渲染引擎就会中断渲染立即执行。执行完成后,再恢复渲染】

(E) type="module"

异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本

(E) ES6(esm) | CommonJs(cjs)

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
  • CommonJS 加载的是一个对象(即module.exports属性)该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。[ES6 模块是动态引用,并且不会缓存值]\
  • CommonJS 模块使用require()module.exports,ES6 模块使用importexport

(E) AMD、CMD、CommonJS、UMD、ESM

AMD(Asynchronous Module Definition):是由RequireJS提出的一种模块化规范,它主要用于浏览器环境,在加载依赖模块时使用异步方式。AMD使用define函数来定义模块,使用 require 函数来加载模块。

CMD(Common Module Definition):是由 SeaJS 提出的一种模块化规范,与AMD类似,CMD也是用于浏览器环境的模块化。不同之处在于 CMD 强调就近依赖,模块的加载是按需执行的。CMD 使用 define 函数来定义模块,使用 require 函数来加载模块。

CommonJS:是一种模块化规范,主要用于服务器端开发(如 Node.js)。CommonJS规范通过 module.exports 导出模块,通过 require 函数加载模块。CommonJS 模块是同步加载的,这使得它在服务器端开发中非常方便。

UMD(Universal Module Definition):是一种通用的模块化规范,旨在兼容不同的环境。UMD可以同时支持 AMD、CommonJS和全局变量的方式来导入和导出模块。

ESM(ECMAScript Modules):是 ECMAScript 提供的官方模块化规范,从 ECMAScript 6 (ES6)开始引入。ESM 使用 import 和 export 关键字来导入和导出模块。ESM 支持静态分析,可以在编译时进行模块依赖的静态解析,提供更好的性能和可靠性。

AMD 和 CMD 主要用于浏览器环境,强调异步加载。CommonJS 主要用于服务器端开发,采用同步加载。UMD 是通用的模块化规范。 ESM 是官方标准的模块化规范,具有静态分析和更好的性能

(E) .mjs | .umd

Node.js 要求 ES6 模块采用.mjs后缀文件名

package.json 中的exports 和 main

package.json文件有两个字段可以指定模块的入口文件:mainexports

{
  "type": "module",
  "main": "./src/index.js"
}

./src/index.js,它的格式为 ES6 模块。如果没有type字段,index.js就会被解释为 CommonJS 模块。 然后,import命令就可以加载这个模块。

exports字段的别名如果是.,就代表模块的主入口,优先级高于main字段,并且可以直接简写成exports字段的值

{
  "exports": {
    ".": "./main.js"
  }
}

// 等同于
{
  "exports": "./main.js"
}

一个模块同时要支持 CommonJS 和 ES6 两种格式

"exports":{
  "require": "./index.js""import": "./esm/wrapper.js"
}

dependencies | devDependencies | peerDependencies

  • dependencies 还是 devDependencies 都会悉数安装
  • dependencies 与 devDependencies 只有语义化约定的作用,所以你需要做好区分,只要不在项目上线的代码中 使用devDependencies的东西就不会被打包进去
  • dependencies 表示该项目在运行时所需要用到的依赖项
  • devDependencies 表示在开发时所需要用到的或依赖的包
  • peerDependencies 主要用于依赖包中,在项目中不起作用,开发时一般会配合 devDependencies 来实现开发和发包时的版本解耦。【表示安装该包时还需要安装哪些包】

files

files 指定了发布为 npm 包时,哪些文件或目录需要被提交到 npm 服务器中

{
  "files": [
    "LICENSE",
    "README.md",
    "dist"
  ]
}

pnpm 包管理 -workspace 模式

声明在根目录的 package.json - devDependencies 中。-w 选项代表在 monorepo 模式下的根目录进行操作。

pnpm install -wD xxx

卸载公共依赖,在根目录的 package.json - devDependencies 中删去对应声明

pnpm uninstall -w xxx

子包管理操作

-S 和 -D 选项分别可以将依赖安装为正式依赖(dependencies)或者开发依赖(devDependencies)。

# 为 a 包安装 lodash
pnpm --filter a i -S lodash
pnpm --filter a i -D lodash

指定模块之间的互相依赖。下面的例子演示了为 a 包安装内部依赖 b

# 指定 a 模块依赖于 b 模块
pnpm --filter a i -S b

{
  "name": "a",
  // ...
  "dependencies": {
    "b": "workspace:^"
  }
}

在实际发布 `npm` 包时,`workspace:^` 会被替换成内部模块 `b` 的对应版本号(对应 `package.json` 中的 `version` 字段)

{
  "dependencies": {
    "a": "workspace:*", // 固定版本依赖,被转换成 x.x.x
    "b": "workspace:~", // minor 版本依赖,将被转换成 ~x.x.x
    "c": "workspace:^"  // major 版本依赖,将被转换成 ^x.x.x
  }
}

整体构建

pnpm --filter "./packages/**" run build

vite.config.ts 配置

构建工具打包时默认行为,是将所有涉及模块的代码都一并集合到产物 中。这在打包 Web 应用的时候是没问题的,因为浏览器并不能识别 npm 模块,所以产物就需要包含所有代码。可我们正在打包的东西将作为 npm 包给其他应用安装,在工程环境下,构建工具是可以识别模块引入语法的。

因此,我们在为 库 / npm 包 构建产物时,在实践中通常会将依赖项(package.jsondependenciespeerDependencies 字段下的依赖)声明为 external(外部依赖),使这个依赖相关的源码不被整合进产物,而是保留着 import xxx from 'pkg' 的导入语句。

vite.config.ts 文件,增加 rollupOptions 

      rollupOptions: {
            external: [
              // 除了 @openxui/shared,未来可能还会依赖其他内部模块,不如用正则表达式将 @fastui 开头的依赖项一起处理掉
              /@fastui.*/, 
              'vue'
            ],
          },