大家好,我是柒八九
。从今天起,我们又双叒叕yòu shuāng ruò zhuó, 开辟了一个新的领域:「前端工程化」。可能大家对这个词,比较陌生,但是如果把这个问题具象化,那就会感觉到倍感亲切。我简单罗列几个概念和技术
SouceMap
Babel/SWC
Gulp/Webpack/Rollup/Vite
打包框架DevServer
- HMR/LiveReloadThree Shaking/DCE/Mangle
有的概念很熟悉,毕竟每天都用npm run dev/start
启动了一个,然后利用某种打包工具对网站资源进行增量更新,并推送到浏览器端,实现HMR
,省去了刷新全页来获取最新的资源信息。使开发变得更加「丝滑顺畅」。
而刚才讲的只是「前端工程化」的冰山一角,在很多地方还有很多的细节点和设计模式需要去挖掘和探讨。既然,这是一个需要探索和研究的领域,并且和我们平时工作息息相关。那就有必要做一个专题来研究。
前端工程化
正如上面所逻辑的技术点和框架,都是一些「大而全」的技术,所以,我们有必要厘清一些比较基础且容易被忽略的基础概念。「万丈高楼平地起」也需要一个夯实的地基做支撑。
那我们就闲话少叙,开车走起。
文章主要点:
脚手架一词最早来源于建筑工程领域。
❝脚手架作为一种创建项目「初始文件」的工具被广泛地应用于「新项目」或者「跌代初始阶段」 ❞
使用工具替代人工操作能够避免人为失误引起的低级错误,同时结合整体前端工程化方案,快速生成功能「模块配置」、「自动安装依赖」等,降低了时间成本。
简单概括脚手架的功能就是:「创建项目初始文件」。
而项目初始化文件,一般都是有一些既定的格式或者模板。
package.json 1) npm 项目文件
package-lock.json 2) npm 依赖 lock 文件
public/ 3) 预设的静态目录
src/ 3) 源代码目录
main.ts 3) 源代码中的初始入口文件
router.ts 3) 源代码中的路由文件
store/ 3) 源代码中的数据流模块目录
webpack/ 4) webpack 配置目录
common.config.js 4) webpack 通用配置文件
dev.config.js 4) webpack 开发环境配置文件
prod.config.js 4) webpack 生产环境配置文件
.browserlistrc 5) 浏览器兼容描述 browserlist 配置文件
babel.config.js 5) ES 转换工具 babel 配置文件
tsconfig.json 5) TypeScript 配置文件
postcss.config.js 5) CSS 后处理工具 postcss 配置文件
.eslintrc 6) 代码检查工具 eslint 配置文件
jest.config.js 6) 单元测试工具 jest 配置文件
.gitignore 7) Git 忽略配置文件
README.md 7) 默认文档文件
简单来解释各个文件的含义
文件/文件夹 | 文件含义 |
---|---|
1) package.json | npm 依赖管理体系下的基础配置文件 |
2) 包管理器 | 使用 npm 或 Yarn,会在项目里添加上对应的 lock 文件,「确保在不同环境下部署项目时的依赖稳定性」 |
3) 确定项目技术栈 | 可以采用React/Vue来构建视图 |
4) 构建工具 | 构建工具的主流选择还是 webpack增加相关的 webpack 配置文件: 开发环境/生产环境 |
5) 构建流程 | 安装与配置各种 Loader 、插件和其他配置项 |
6) 选择和调试辅助工具 | 代码检查工具/单元测试工具 |
7) 收尾工作 | 编写说明文档 README.md不需要纳入版本管理的文件目录记入 .gitignore |
整个流程可以简单归纳为
现在主流的前端脚手架工具有两类:
名称 | 模板框架 | 多选项生产 | 支持自定义模板 | 特点 |
---|---|---|---|---|
Create-React-App | React | 否 | 是 | React 官方维护 |
Vue CLI | Vue | 是 | 是 | Vue官方维护 |
react-app-rewired
:利用config-overrides.js
文件来对 webpack
配置进行扩展customize-cra
:利用react-app-rewired
的配置文件config-overrides.js
对webpack
配置进行修改create-react-app
用于选择脚手架「创建项目」react-scripts
则作为所创建项目中的「运行时依赖包」,提供了封装后的项目「启动、编译、测试」等基础工具@vue/cli
@vue/cli-service
@vue/cli-plugin
当然,CRA
/Vue CLI
是现在比较流行的框架级别的脚手架,其实还有一些老牌通用的脚手架,只不过它们的应用市场很少了,但是有些场景也是有点用的。例如:Yeoman
,由 Google I/O
在 2012 年首次发布,基于特定生成器(Generator
)来创建项目基础代码的功能,有庞大的生成器仓库。
在实际开发中,我们可以通过创建脚手架对应的模板对项目进行「定制化处理」。
定制化模板可以「弥补」官方提供基础工具集不满足特定需求的场景。
定制化有如下优点(但有不仅限这些优点)
webpack
配置优化,来提升开发环境的效率和生产环境的性能等我们简单的为CRA
配置一个最简单的模板。
作为一个最简化的 CRA 模板,模板中包含如下必要文件
README.md
:用于在 npm 仓库中显示的「模板说明」package.json
:用于描述模板本身的「元信息」, 例如名称、运行脚本、依赖包名和版本等template.json
:用于描述基于模板创建的项目中的 package.json
信息template
目录:用于「复制」到创建后的项目中,gitignore
在复制后重命名为 .gitignore
,「public/index.html和src/index 为运行 react-scripts 的必要文件」对应的目录结构如下:
cra-template-[template-name]/
README.md (for npm)
template.json
package.json
template/
README.md (for projects created from this template)
gitignore
public/
index.html
src/
index.js (or index.tsx)
在使用时,还是需要将模板通过 npm link
命令「映射」到全局依赖中或发布到 npm
仓库中。
然后执行创建项目的命令
npx create-react-app [app-name] --template [template-name]
❝脚手架的「功能和本质」:功能是「创建项目初始文件」,本质是「方案的封装」 ❞
❝
Source Map
:「映射」转换后的代码与源代码之间的关系
❞
一段转换后的代码,通过转换过程中生成的 Source Map
文件就可以「逆向解析」得到对应的源代码。
Source Map
是一个 JSON
格式的文件,这个 JSON
里面记录的就是「转换后和转换前」代码之间的映射关系。
你可以认为:
❝「Souce Map 就是存储于JSON文件中的Map(哈希表)」 ❞
在编译器(Babel/SWC
)编译处理的过程中,在生成产物代码的同时,也生成产物代码中被转换的部分与源代码中相应部分的「映射关系表」。
有了完整的映射表,就可以通过 Chrome
控制台中的"Enable Javascript source map"来实现调试时的显示与定位源代码功能。
「chrome-setting」
「perference-Enable Javascript source map」
Source map的格式
{
version : 3,
file: "out.js",
sourceRoot : "",
sources: ["foo.js", "bar.js"],
names: ["src", "maps", "are", "fun"],
mappings: "AAgBC,SAAQ,CAAEA"
}
整个文件就是一个「JavaScript对象」,可以被解释器读取。
字段名 | 含义 |
---|---|
version | Source Map的「版本」,目前为3最常用的方法是使用Google的Closure「编译器」 |
file | 转换后的文件名 |
sourceRoot | 「转换前」的文件所在的目录如果与转换前的文件在同一目录,该项为空 |
sources | 记录的是转换前的源文件名称 「因为有可能出现多个文件打包转换为一个文件的情况,所以这里是一个数组」 |
names | 源代码中使用的一些「成员名称」 压缩代码时会将「开发阶段」编写的有意义的「变量名」替换为一些「简短的字符」这个属性中记录的就是「原始的名称」 |
mappings | 它是一个叫作 base64-VLQ 编码的字符串 里面记录的信息就是转换后代码中的「字符」与转换前代码中的字符之间的「映射关系」 |
❝这里额外对
mappings
字段做一个介绍 这是一个很长的字符串,它分成「三层」
VLQ
编码表示,「代表该位置对应的转换前的源码位置」mappings:"AAAAA,BBBBB;CCCCC"
:转换后的源码分成「两行」,第一行有两个位置,第二行有一个位置。
❝一般我们会在「转换后的代码中」通过「添加一行注释」的方式来去「引入 Source Map 文件」 ❞
对于同一个源文件,根据不同的目标,可以生成不同效果的 Source Map
。在开发环境和生产环境下,对于 source map 功能的期望也有所不同。
Source Map
/生成的文件大小/访问方式是否会对页面性能造成影响,最后才考虑质量和构建速度由于,现在在打包领域,Webpack
还是一个绕不过去的大山,所以,在了解到基础的知识点后,需要将知识配合实际项目进行分析和学习。
根据不同规则,实际上 Webpack
是从三种「插件」中选择其一作为 source map 的处理插件。
EvalDevToolModulePlugin
sourceURL=webpack:///
+ 模块引用路径eval()
封装EvalSourceMapDevToolPlugin
base64
格式的 source map 并「附加在模块代码之后」sourceURL=webpack:///
+ 模块引用路径eval()
封装SourceMapDevToolPlugin
.map
文件❝插件中有
eval
字段的,模块产物是通过eval()
封装的,只有SourceMapDevToolPlugin
(AKA:SMDP
)生成.map
文件 ❞
在 Webpack
中,通过设置 devtool
来选择 Source Map
的预设类型。文档中共有 20 余种 Source Map
的预设。这些预设通常包含了 eval
/cheap
/module
/inline
/hidden
/nosource
/source-map
等关键字的组合。
❝
devtool
的值匹配「并非精确匹配」,某个关键字只要包含在赋值中即可获得匹配。 ❞
例如:
foo-eval-bar
等同于 eval
;wl-source-map
等同于 cheap-source-map
Webpack
预设Source Map
各个字段的含义 即(devtool:xxx
)
字段名 | 作用 |
---|---|
false | 不开启 Source Map 功能 |
eval | 在编译器中使用 EvalDevToolModulePlugin 作为 Source Map 的处理插件 |
[xxx-...]source-map | 根据 devtool 对应值中「是否有」 eval 关键字来决定使用 1. 有eval:使用EvalSourceMapDevToolPlugin作为处理插件2. 没有eval:使用SourceMapDevToolPlugin作为处理插件` |
inline | 作用是决定「单独生成」 source map 文件还是在「行内显示」 |
hidden | 作用是判断是否添加 SourceMappingURL 的注释 |
module | 作用是为加载器(Loaders)生成 source map |
cheap | 它决定插件 columns 参数的取值,作用是决定生成的 source map 中「是否包含列信息」在不包含列信息的情况下,调试时只能定位到指定代码所在的行而定位不到所在的列 |
分别从「质量」/「构建速度」/「包大小和生成方式」三个角度来分析
生成的 source map 的质量分为 「5 个级别」,对应的「调试便捷性」依次降低。
级别 | 解释 |
---|---|
5 | 「源码且包含列信息」 |
4) | 「缺少列信息的源代码」 |
3) | 「Loader 转换后的代码」 |
2) | 「生成后的产物代码」 |
1) | 「无法显示代码」 |
❝「再次构建」速度都要显著快于初次构建速度 ❞
不同环境下关注的速度也不同
devServer
,再次构建的速度对我们的效率影响远大于初次构建的速度eval-
对应的 EvalSourceMapDevToolPlugin
整体要「快于」不带 eval- 的 SourceMapDevToolPlugin
eval-source-map
的再次构建速度要远快于其他几种在「开发环境」下我们并不需要关注这些因素。
通常来说,「开发环境」首选哪一种预设取决于 source map 对于我们的帮助程度。
针对不同场景,我们可以大致分为以下三种:
eval-cheap-module-source-map
eval-source-map
eval-cheap-source-map
利用EvalSourceMapDevToolPlugin
对开发环境提速。
在开发阶段,调试的是开发的业务代码部分,而非依赖的第三方模块部分,所以在生成 source map
的时候如果可以「排除第三方模块」的部分,而只生成业务代码的 source map,无疑能进一步提升构建的速度。
webpack.config.js
...
//devtool: 'eval-source-map',
devtool: false,
plugins: [
new webpack.EvalSourceMapDevToolPlugin({
exclude: /node_modules/,
module: true,
columns: false
})
],
...
将 devtool
设为 false,就是丢弃webpack
或者CRA
的默认配置,而是直接使用 EvalSourceMapDevToolPlugin
,通过传入 module: true
和 column:false
,达到和预设 eval-cheap-module-source-map
一样的质量。但是,我们传入 exclude
参数,「排除第三方依赖包的 source map 生成」。进一步提升了构建的速度。
❝在控制台的「网络面板」中通常看不到 source map 文件的请求,其原因是出于安全考虑 Chrome 「隐藏」了 source map 的请求 ❞
我们可以通过一些抓包工具进行文件的抓取
Chrome
自带的 chrome://net-export/
whistle
WireShark
既然能通过抓包工具进行Source Map
文件的捕获,那么在前面我们就说了,「Source Map:映射转换后的代码与源代码之间的关系」,那么我们反其道而行,我们是不是可以通过Source Map
来反向推出源代码。然后,找到了源代码,我们是不是可以肆无忌惮的做一个合格的CV
工程师。
这里推荐一个库shuji
。可以通过x.map
(x可以是js
也可以是css
),反编译出源代码。