前端工程化详解 & 包管理工具
- 1、工程化体系介绍
- 1.1、 什么是前端工程化
- 1.2、 前端工程化发展
- 2、脚手架能力
- 2.1 准备阶段
- 2.2 开发阶段
- 2.3 发布流程
- 3、npm能力
- 3.1 剖析package.json
- 3.1.1 必备属性
- 3.1.2 描述信息
- 3.1.3 依赖配置
- 3.1.4 协议
- 3.1.5 目录&文件相关
- 3.1.5.1 程序入口
- 3.1.5.2 命令行工具入口
- 3.1.5.3 发布文件配置
- 3.1.5.4 man
- 3.1.5.5 规范项目目录
- 3.1.6 脚本配置
- 3.1.6.1 script
- 3.1.6.2 config
- 3.1.7 发布配置
- 3.1.7.1 preferGlobal
- 3.1.7.2 private
- 3.1.7.3 publishConfig
- 3.1.7.4 os
- 3.1.7.5 cpu
- 3.2 剖析包版本管理机制
- 3.2.1 查看npm包版本
- 3.2.2 SemVer规范
- 3.2.3 版本工具使用
- 3.2.4 依赖版本管理
- 3.2.4 锁定依赖版本
- 4、前端打包工具对比
- 4.1 Yarn
- 4.1.1 Yarn介绍
- 4.1.2 Yarn 和 NPM 之间的相似之处
- 4.1.3 Yarn 和 NPM 之间的区别
- 依赖管理
- 性能和速度
- 安全
- 趋势
- 指令
- 4.1.3 如何选择
- 4.2 pnpm
- 4.2.1 pnpm是什么
- 4.2.2 性能对比
- 4.2.3 节省磁盘空间并提升安装速度
- 4.2.4 node_modules目录的优化
- 4.2.5 小结
1、工程化体系介绍
1.1、 什么是前端工程化
- 前端工程化 = 前端 + 软件工程;
- 前端工程化 = 将工程方法系统化地应用到前端开发中;
- 前端工程化 = 系统、严谨、可量化的方法开发、运营和维护前端应用程序;
- 前端工程化 = 基于业务诉求,梳理出最符合当前需要的架构设计;
软件工程:将工程方法系统化地软件工程应用到软件开发中;
工程方法:以系统、严谨、可量化的方法开发、运营和维护软件;
1.2、 前端工程化发展
- 前后端分离:B/S架构兴起,有了前后端之分;
- 模块化:随着前端复杂度上升,模块复用、实践规范重要性提升;
- 自动化:管理&简化前端开发过程,前端框架、自动化、构建系统应运而生;
- 最佳实践:基于行业内最佳实践,开箱即用的框架(dva)、工具体系逐渐建立起来;
- 好、快、稳:依赖vite、esm、wasm、低代码等能力;
2、脚手架能力
2.1 准备阶段
- 技术选型;
- 代码规范:
- 分支管理规范;
- 项目初始资源规范;
- UI规范;
- 物料市场规范;
2.2 开发阶段
- 开发、打包流程;
- 本地mock服务;
- 代码质量;
- 单元测试&E2E测试;
2.3 发布流程
- git commit规范;
- changeLog规范;
- 打包构建;
- 部署、验收;
3、npm能力
3.1 剖析package.json
3.1.1 必备属性
package.json
中有非常多的属性,其中必须填写的只有两个:name
和 version
,这两个属性组成一个 npm 模块的唯一标识。
3.1.2 描述信息
{
"description": "An enterprise-class UI design language and React components implementation",
"keywords": [
"ant",
"component",
"components",
"design",
"framework",
"frontend",
"react",
"react-component",
"ui"
]
}
description用于添加模块的的描述信息,方便别人了解你的模块。
keywords用于给你的模块添加关键字。
当然,他们的还有一个非常重要的作用,就是利于模块检索。
描述开发人员的字段有两个:author 和 contributors
homepage 用于指定该模块的主页;
repository 用于指定模块的代码仓库;
3.1.3 依赖配置
我们的项目可能依赖一个或多个外部依赖包,根据依赖包的不同用途,我们将他们配置在下面几个属性下:dependencies
、devDependencies
、peerDependencies
、bundledDependencies
、optionalDependencies
。
dependencies
指定了项目运行所依赖的模块,开发环境和生产环境的依赖模块都可以配置到这里,例如:
"dependencies": {
"lodash": "^4.17.13",
"moment": "^2.24.0",
}
devDependencies
:有一些包有可能你只是在开发环境中用到,例如你用于检测代码规范的 eslint
,用于进行测试的 jest
,这些依赖照样会在你本地进行 npm install
时被安装和管理,但是不会被安装到生产环境。
"devDependencies": {
"jest": "^24.3.1",
"eslint": "^6.1.0",
}
peerDependencies
用于指定你正在开发的模块所依赖的版本以及用户安装的依赖包版本的兼容性。
"peerDependencies": {
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
optionalDependencies
:依赖包可能不是强依赖的,这个依赖包的功能可有可无,当这个依赖包无法被获取到时,你希望 npm install
继续运行,而不会导致失败,你可以将这个依赖放到 optionalDependencies
中,注意 optionalDependencies
中的配置将会覆盖掉 dependencies
所以只需在一个地方进行配置。
bundledDependencies
:和以上几个不同,bundledDependencies
的值是一个数组,数组里可以指定一些模块,这些模块将在这个包发布时被一起打包。
"bundledDependencies": ["package1" , "package2"]
3.1.4 协议
license
字段用于指定软件的开源协议,开源协议里面详尽表述了其他人获得你代码后拥有的权利,可以对你的的代码进行何种操作,何种操作又是被禁止的。
- MIT:只要用户在项目副本中包含了版权声明和许可声明,他们就可以拿你的代码做任何想做的事情,你也无需承担任何责任;
- Apache:类似于 MIT,同时还包含了贡献者向用户提供专利授权相关的条款;
- GPL:修改项目代码的用户再次分发源码或二进制代码时,必须公布他的相关修改;
3.1.5 目录&文件相关
3.1.5.1 程序入口
{
"main": "lib/index.js",
}
main
属性可以指定程序的主入口文件
3.1.5.2 命令行工具入口
当你的模块是一个命令行工具时,你需要为命令行工具指定一个入口,即指定你的命令名称和本地可指定文件的对应关系。
{
"bin": {
"chenghuai": "./bin/index.js"
}
}
3.1.5.3 发布文件配置
{
"files": [
"dist",
"lib",
"es"
]
}
3.1.5.4 man
man 命令是 Linux 下的帮助指令,通过 man 指令可以查看 Linux 中的指令帮助、配置文件帮助和编程帮助等信息。
如果你的 node.js 模块是一个全局的命令行工具,在 package.json 通过 man 属性可以指定 man 命令查找的文档地址。
{
"man" : [
"/Users/isaacs/dev/npm/cli/man/man1/npm-access.1",
"/Users/isaacs/dev/npm/cli/man/man1/npm-audit.1"
]
}
3.1.5.5 规范项目目录
一个 node.js 模块是基于 CommonJS 模块化规范实现的,严格按照 CommonJS 规范,模块目录下除了必须包含包描述文件 package.json 以外,还需要包含以下目录:
- bin:存放可执行二进制文件的目录;
- lib:存放js代码的目录;
- doc:存放文档的目录;
- test:存放单元测试用例代码的目录;
- …
在模块目录中你可能没有严格按照以上结构组织或命名,你可以通过在 package.json 指定 directories 属性来指定你的目录结构和上述的规范结构的对应情况。除此之外 directories 属性暂时没有其他应用。
{
"directories": {
"lib": "src/lib/",
"bin": "src/bin/",
"man": "src/man/",
"doc": "src/doc/",
"example": "src/example/"
}
}
3.1.6 脚本配置
3.1.6.1 script
{
"scripts": {
"test": "jest --config .jest.js --no-cache",
"dist": "antd-tools run dist",
"compile": "antd-tools run compile",
"build": "npm run compile && npm run dist"
}
}
scripts 用于配置一些脚本命令的缩写,各个脚本可以互相组合使用,这些脚本可以覆盖整个项目的生命周期,配置后可使用 npm run command
进行调用。如果是 npm 关键字,则可以直接调用。例如,上面的配置制定了以下几个命令:npm run test
、npm run dist
、npm run compile
、npm run build
。
3.1.6.2 config
config
字段用于配置脚本中使用的环境变量,例如下面的配置,可以在脚本中使用process.env.npm_package_config_port
进行获取。
{
"config" : { "port" : "8080" }
}
3.1.7 发布配置
3.1.7.1 preferGlobal
如果你的 node.js 模块主要用于安装到全局的命令行工具,那么该值设置为 true ,当用户将该模块安装到本地时,将得到一个警告。这个配置并不会阻止用户安装,而是会提示用户防止错误使用而引发一些问题。
3.1.7.2 private
如果将 private 属性设置为 true,npm将拒绝发布它,这是为了防止一个私有模块被无意间发布出去。
3.1.7.3 publishConfig
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
发布模块时更详细的配置,例如你可以配置只发布某个 tag、配置发布到的私有 npm 源。
3.1.7.4 os
假如你开发了一个模块,只能跑在 linux 系统下,你需要保证 windows 用户不会安装到你的模块,从而避免发生不必要的错误。
使用 os 属性可以帮助你完成以上的需求,你可以指定你的模块只能被安装在某些系统下,或者指定一个不能安装的系统黑名单:
"os" : [ "darwin", "linux" ]
"os" : [ "!win32" ]
在node环境下可以使用 process.platform 来判断操作系统。
3.1.7.5 cpu
和上面的 os 类似,我们可以用 cpu 属性更精准的限制用户安装环境:
"cpu" : [ "x64", "ia32" ]
"cpu" : [ "!arm", "!mips" ]
在node环境下可以使用 process.arch 来判断 cpu 架构。
3.2 剖析包版本管理机制
3.2.1 查看npm包版本
你可以执行 npm view package version
查看某个 package 的最新版本。
执行npm view encode-fe-lint versions
查看某个 package 在npm服务器上所有发布过的版本。
3.2.2 SemVer规范
npm包 中的模块版本都需要遵循 SemVer规范
——由 Github 起草的一个具有指导意义的,统一的版本号表示规则。实际上就是 Semantic Version
(语义化版本)的缩写。
SemVer规范官网: semver.org/
SemVer规范的标准版本号采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数,且禁止在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须以数值来递增。
- 主版本号(major):当你做了不兼容的API 修改;
- 次版本号(minor):当你做了向下兼容的功能性新增;
- 修订号(patch):当你做了向下兼容的问题修正;
例如:1.9.1 -> 1.10.0 -> 1.11.0
3.2.3 版本工具使用
在开发中肯定少不了对一些版本号的操作,如果这些版本号符合 SemVer
规范 ,我们可以借助用于操作版本的npm包semver来帮助我们进行比较版本大小、提取版本信息等操作。
Npm 也使用了该工具来处理版本相关的工作。
npm install semver
- 比较版本号大小
semver.gt('1.2.3', '9.8.7') // false
- 判断版本号是否符合规范,返回解析后符合规范的版本号
semver.valid('1.2.3') // '1.2.3'
- 将其他版本号强制转换成semver版本号
semver.valid(semver.coerce('v2')) // '2.0.0'
- 一些其他用法
semver.clean(' =v1.2.3 ') // '1.2.3'
3.2.4 依赖版本管理
我们经常看到,在 package.json 中各种依赖的不同写法:
- “signale”: “1.4.0”: 固定版本号;
- “figlet”: “*”: 任意版本(>=0.0.0);
- “react”: “16.x”: 匹配主要版本(>=16.0.0 <17.0.0);
- “react”: “16.3.x”: 匹配主要版本和次要版本(>=16.3.0 <16.4.0);
当版本号中引用了 ~ 和 ^ 符号: - ~: 当安装依赖时获取到有新版本时,安装到 x.y.z 中 z 的最新的版本。即保持主版本号、次版本号不变的情况下,保持修订号的最新版本;
- ^: 当安装依赖时获取到有新版本时,安装到 x.y.z 中 y 和 z 都为最新版本。 即保持主版本号不变的情况下,保持次版本号、修订版本号为最新版本;
在 package.json 文件中最常见的应该是 “yargs”: “^14.0.0” 这种格式的 依赖, 因为我们在使用 npm install package 安装包时,npm 默认安装当前最新版本,然后在所安装的版本号前加 ^ 号。
注意,当主版本号为 0 的情况,会被认为是一个不稳定版本,情况与上面不同:
- 主版本号和次版本号都为 0: ^0.0.z、~0.0.z 都被当作固定版本,安装依赖时均不会发生变化;
- 主版本号为 0: ^0.y.z 表现和 ~0.y.z 相同,只保持修订号为最新版本;
3.2.4 锁定依赖版本
lock文件
实际开发中,经常会因为各种依赖不一致而产生奇怪的问题,或者在某些场景下,我们不希望依赖被更新,建议在开发中使用 package-lock.json。
定期更新依赖
我们的目的是保证团队中使用的依赖一致或者稳定,而不是永远不去更新这些依赖。
使用 npm outdated
可以帮助我们列出有哪些还没有升级到最新版本的依赖:
- 黄色表示不符合我们指定的语意化版本范围 - 不需要升级
- 红色表示符合指定的语意化版本范围 - 需要升级
执行npm update
会升级所有的红色依赖。
4、前端打包工具对比
4.1 Yarn
4.1.1 Yarn介绍
Yarn
,或 Yet Another Resource Navigator,是 Facebook 开发的一个相对较新的包管理器。它的开发是为了提供 NPM 当时缺乏的更高级的功能(例如版本锁定,但后续的功能npm都补充上了),同时也使其更安全、更可靠和更高效。
Yarn 现在更像是 NPM 的替代品,由于 Yarn 没有预装 Node.js,因此需要显式安装:
npm install yarn -g
全局安装后,您可以通过在项目中设置版本来在每个项目的基础上使用它,如下所示:
yarn set version <version-name>
yarn的特点:
- 即插即用:从 Yarn 版本 2 开始,不再使用
node_modules
文件夹。相反,它会生成一个映射项目依赖关系的.pnp.cjs
文件。这会导致更优化的依赖树和更快的项目启动和包安装; - 零安装:此功能与 Plug’n’Play 结合使用,后者使用
.pnp.cjs
文件来映射离线缓存中的包。这使您可以快速检索和安装已保存的软件包; - 检查器:Yarn 带有一个内置的检查器,用于下载和安装包;
4.1.2 Yarn 和 NPM 之间的相似之处
- Yarn 和 NPM 都会自动生成一个版本锁定文件,用于跟踪项目使用的依赖项的确切列表;
- Yarn 和 NPM 都提供了将依赖项保存在离线缓存中的选项,即使您处于离线状态,您也可以安装依赖项;
- Yarn 和 NPM 都支持工作空间,允许您从单个存储库管理多个项目的依赖关系;
- 使用 NPM 中的 npx 命令和 Yarn 中的 yarn dlx 命令,您可以在两个管理器中远程运行脚本;
4.1.3 Yarn 和 NPM 之间的区别
依赖管理
Yarn 也支持 NPM 创建的 package-lock.json 文件,方便将版本数据从 NPM 迁移到 Yarn
性能和速度
下图显示了 NPM 和 Yarn 在各种情况下安装依赖项所用时间的比较:
安全
使用 NPM,您还可以执行手动审核以查找任何漏洞并解决它。要查找漏洞,您可以使用 npm audit
趋势
虽然 Yarn 比 NPM是NPM后出来的,但它似乎正在迅速普及。
指令
4.1.3 如何选择
Yarn
优点
- 支持并行安装、即插即用和零安装等功能,从而获得更好的性能;
- 更安全;
- 庞大的活跃用户社区;
缺点 - 不适用于旧版本的 Node.js(低于版本 5);
- 安装本机模块的问题;
NPM
优点 - 易于使用,特别适用于习惯工作流旧版本的开发人员;
- 优化本地包安装,节省硬盘空间;
缺点 - 需要网络访问才能从在线注册表安装软件包;
- 安全漏洞仍然存在;
总结:NPM 更受习惯于旧版本工作流程并对当前工作流程感到满意的开发人员的青睐。它提供了不错的用户体验,同时还节省了硬盘空间。另一方面,Yarn 具有高级功能,例如即插即用和零安装,可以略微提高性能和安全性,但会以硬盘空间为代价。
4.2 pnpm
4.2.1 pnpm是什么
pnpm,即performant npm,高性能的npm。相比起目前主流的包管理器,pnpm是速度快、节省磁盘空间的包管理器。
4.2.2 性能对比
可以看出,与目前主流的包管理器npm、yarn相比,无论有无cache、有无lockfile、有无node_modules,pnpm的安装速度都有明显的优势。
4.2.3 节省磁盘空间并提升安装速度
为什么pnpm这么快呢?最主要的原因是,pnpm通过hard link
(硬连接)机制,节省磁盘空间并提升安装速度。
当使用 npm 或 Yarn 时,如果你有 100 个项目, 并且所有项目都有一个相同的依赖包,那么, 你在 硬盘上就需要保存 100 份该相同package(依赖包)的副本。
而使用 pnpm,package将被存放在一个统一的位置(如 Mac是 ~/.pnpm-store )。当安装软件包时,其包含的所有文件都会硬链接自此位置,而不会占用额外的硬盘空间。
4.2.4 node_modules目录的优化
除了节省磁盘空间,pnpm还有另一个重要特点是,建立非扁平的node_modules目录,并在引用依赖的时候通过sybolic link
机制找到对应.pnpm目录的地址。
使用pnpm安装依赖,打开node_modules,我们可以看到,node_modules目录是非扁平的(与npm@2一样),同时还多了一个.pnpm的目录。而node_modules下的依赖包都是sybolic link
(软链接:类似于windows系统中的快捷方式)
这样,pnpm 实现了相同模块不同版本之间隔离和复用。而且,node_modules目录是非扁平的,不存在由于提升带来的幽灵依赖问题。
4.2.5 小结
- pnpm 通过
hard link
在全局里面搞个store
目录来存储node_modules
依赖里面的 hard link 地址。这样节省了磁盘空间,并提升安装速度; - node_modules目录是非扁平的,在引用依赖的时候,则是通过
sybolic link
去找到对应虚拟磁盘目录下(.pnpm 目录)的依赖地址。这样,避免了之前npm和yarn扁平/非扁平安装带来的一系列问题;