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 模块使用import和export。
(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文件有两个字段可以指定模块的入口文件:main和exports
{
"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.json 中 dependencies、peerDependencies 字段下的依赖)声明为 external(外部依赖),使这个依赖相关的源码不被整合进产物,而是保留着 import xxx from 'pkg' 的导入语句。
vite.config.ts 文件,增加 rollupOptions
rollupOptions: {
external: [
// 除了 @openxui/shared,未来可能还会依赖其他内部模块,不如用正则表达式将 @fastui 开头的依赖项一起处理掉
/@fastui.*/,
'vue'
],
},