webpack的另一个核心就是plugin,官方对其有这样一段描述:
- Loader是用于特定的模块类型进行转换
- Plugin可以用于执行更加广泛的任务,比如打包优化、资源管理、环境变量注入等
CleanWebpackPlugin
这是一个非常常用的plugin,作用是每次打包前,清除上一次打包的文件。在webpack中,如果没有配置这个plugin,每次打包会创建新的文件,覆盖掉原有的文件,但是如果在打包后新增的文件,则无法处理,而CleanWebpackPlugin插件可以删除全部文件。
- 安装
npm i clean-webpack-plugin -D
- 使用
- 引入插件:
- 配置使用:
HtmlWebpackPlugin
我们打包后的文件想要发布,需要有一个index.html作为入口文件,引入打包出来的js文件,HtmlWebpackPlugin就是用于帮助我们生成这个html文件的。
- 安装 npm i html-webpack-plugin -D
- 使用
- 引入插件:
和CleanWebpackPlugin稍有不同,这是因为插件本身导出方式不同决定的
- 配置使用:
使用模板生成指定的html文件
可以配置一个html模板,生成一个指定格式的html文件
这样配置即可生成一个title为“jujuul webpack”,引用模板路径为“./public/index.html”的html文件
DefinePlugin的介绍
我们将vue的模板html文件作为模板使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
这个时候打包报错
这是因为模板中的 BASE_URL 这个常量找不到,所以报错,而DefinePlugin就是用于定义常量的
DefinePlugin插件允许在编译时创建配置的全局常量,是webpack内置插件(不需单独安装)
Mode配置
Mode配置选项,可以告诉webpack使用相应模式的内部优化
默认值是production
- 可选值有:‘none’|’development’|’production’
Webpack的模块化
webpack打包的代码,允许我们使用各种模块化方案,但是最常用的是CommonJS、ESModule。
那么它是如何帮助我们实现了代码中支持模块化呢?
我们来研究一下它的原理,包括如下原理:
- CommonJS模块化实现原理
- ES Module实现原理
- CommonJS加载ES Module的原理
-
sourcemap
我们的代码通常运行在浏览器上时,是通过打包压缩的:
也就是真实跑在浏览器上的代码,和我们编写的代码其实是有差异的
- 比如ES6的代码可能被转换成ES5
- 比如对应的代码行号、列号在经过编译后肯定会不一致
- 比如代码进行丑化压缩时,会将编码名称等修改
- 比如我们使用了TypeScript等方式编写的代码,最终会被转换成JavaScript
但是,当我们代码报错需要调试时(debug),调试转换后的代码是很困难的。
我们如何才能调试这种转换后不一致的代码呢?答案就是source-map
- 根据源文件,生成source-map文件,webpack在打包时,可以通过配置生成source-map
- 在转换后的代码,最后添加一个注释,它指向source-map
bundle.js最后添加_//# sourceMappingURL=bundle.js.map_
浏览器会根据我们的注释,查找相应的source-map,并且根据source-map还原我们的代码,方便进行调试
最初source-map生成的文件大小是原始文件的10被,第二版减少了50%,第三版又减少了50%,所以目前一个133kb的文件,最终的source-map的大小大概是300kb
生成source-map
如何在使用webpack打包时,生成对应的source-map呢?
- webpack为我们提供了非常多的选项(26个)来处理source-map
- 选择不同的值,生成的source-map会稍微有差异,打包的过程也会有性能差异,可以根据不同的情况进行选择
下面几个值不会生成source-map
- false:不使用source-map,也就是没有任何和source-map相关的内容
- none:mode值为production时的默认值,不生成source-map
eval:mode值为deveopment模式下的默认值,不生成source-map
生成一个独立的source-map文件,并且在bundle文件中有一个注释,指向source-map文件
bundle文件中有如下注释
- 开发工具会根据这个注释找到source-map文件,并且解析
组合的规则如下:
- inline-|hidden-|eval:这三个值可以三选一
- nosources:可选值
- cheap可选值,并且可以跟随module的值
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
开发中的最佳实践
开发阶段:推荐使用source-map或者cheap-module-source-map
- 这分别是vue和react使用的值,可以获取调试信息,方便快速开发
测试阶段:推荐使用source-map或者cheap-module-source-map
- 测试阶段和上面一致
Babel的深入解析
babel是前端开发不可缺少的一部分
- 开发中,我们想要使用ES6+的语法,想要使用TypeScript,开发React项目,他们都是离不开babel的
Babel到底是什么?
- babel是一个工具链,主要用于将ESMAScript2015+代码转换为向后先容版本的JavaScript
- 包括:语法转换、源代码转换、Polyfill实现目标环境缺少的功能等
babel命令行使用
babel本身可以作为一个独立的工具(和postcss一样),不和webpack等构建工具配置来单独使用
如果我们希望在命令行使用babel,需安装如下库
@babel/core:babel核心代码,必须安装
@babel/cli:可以让我们在命令行使用babel
使用babel处理我们的源代码:
- src:源文件目录
-
Babel的的底层原理
babel如何将我们的一段代码(ES6\TS\JSX)转换成另一段代码(ES5)呢?
从一种源代码(原生语言)转换成另一种源代码(目标语言),需要通过编译器来实现
- babel就是一个编译器
- babel编译器的作用就是将我们的源代码,转换成浏览器可以直接识别的另一段源代码
babel编译器的工作流程:
- 解析阶段(Parsing)
- 转换阶段(Transformation)
- 生成阶段(Code Generation)
Babel的配置文件
我们可以将babel的配置信息放到一个独立的文件中,babel给我们提供了两种配置文件的编写方式:
- babel.config.json(或者.js,.cjs,.mjs)文件
- .babelrc.json(或者.babelrc,.js,.cjs,.mjs)文件
它们有什么区别呢?目前很多项目都采用了多包管理的方式(babel本身、element-plus、umi等)
- .babelrc.json:早期使用较多的配置方式,但是配置Monorepos项目是比较麻烦的
babel.config.json(babel7):可以直接用于Monorepos项目的子包,更加推荐
认识polyfill
polyfill是什么?
polyfill是一种垫片,一个补丁,可以帮助我们更好的使用JavaScript
什么时候用到polyfill?
- 当我们使用了一些新的语法特性(例如:Promise,Generator,Symbol等)
- 但是某些低版本浏览器无法解析这些特性
- 我们可以使用polyfill来帮助我们实现该特性
如何使用polyfill?
在babel7.4.0之前,可以使用@babel/polyfill的包,但是7.4.0之后该包不推荐使用了
如果要使用polyfill,需要安装下面两个包
npm i core-js regenerator-runtime
然后在配置babel-loader时使用useBuiltIns属性值
一共有三种模式
- false 不使用 babel
- usage
当代码中出现需要polyfill的时候才使用,需要注意,当使用 usage 模式时,因为项目包含很多其他包,其他包内部可能自行实现了polyfill的功能,所以为了不改变其他包的内容,需要给loader设置exclude
- entry
手动在入口文件中导入 core-js/regenerator-runtime,根据目标浏览器引入所有对应的polyfill
在需要使用polyfill处理的文件中引入
这样webpack在打包时就会使用polyfill
认识Plugin-transform-runtime
polyfill默认情况下添加的特性都是全局的
- 如果我们编写一个工具库,这个工具库使用了polyfill
- 别人在使用我们的工具时,工具库通过polyfill添加的特性,可能会污染他们的代码
- 所以当编写工具时,babel更推荐我们使用一个插件:@babel/plugin-transform-runtime来完成polyfill的功能
何时使用useBuiltIns何时使用@babel/plugin-transform-runtime?
官方给的答案,通过useBuiltIns添加的特性会影响全局,@babel/plugin-transform-runtime影响局部,所以编写工具库时使用@babel/plugin-transform-runtime,平时开发使用useBuiltIns,两者不可一起使用。安装使用
npm i @babel/plugin-transform-runtime -D
作为插件使用
注意:因为使用了corejs3,所以需要安装对应的库
React的jsx支持
babel对于react有预设(preset)支持,只需要安装 @babel/preset-react 就可以处理react具体步骤
安装好 @babel/preset-react 后,打包即可TypeScript的编译
ts-loader
项目开发中,使用TypeScript开发,ts代码需要转换成js代码。
这时就需要使用ts-loader
这样还不够,还需要一个tsconfig.json文件,通过 tsc —init 创建
然后打包即可,不过这样有个问题,如果想要给ts文件使用polyfill,这种方法无法实现polyfill效果。
babel-loader
除了可以使用TypeScript Compiler 来编译TypeScript之外,我们还可以使用Babel:
- Babel是有对TypeScript进行支持的
- 我们可以使用插件:@babel/transform-typescript
- 但是更推荐直接使用preset:@babel/preset-typescript
在babel.config.js中配置preset
打包即可实现对ts的编译
使用babel-loader的缺点在于无法在打包编译阶段对错误的语法进行提示,需要IDE支持或额外安装插件
ts-loader和babel-loader的选择
没有一个统一的答案,两种方式各有优劣,TS官网有如下建议
- 如果你当前代码输出的文件大部分情况下和输入的文件相似的情况下(使用了一些新语法的情况下)使用 ts-loader
- 如果你的代码中有使用新语法的情况下推荐使用babel-loader,并使用tsc做ts语法检测
使用babel-loader的情况下,在package中配置一个启动项,在build之前运行检测语法错误
如果有语法错误会提示
另外需要注意的是,这样检测会在被检测文件目录下生成同名的JS文件
如果不想生成该文件,可如下配置
认识ESLint
ESLint是一个静态代码分析工具(在没有任何程序执行的情况下,对代码进行分析)
ESLint可以帮助我们在项目中建立统一的团队代码规范,保持正确、统一的代码风格,提高代码的可读性、可维护性
并且ESLint的规则是可配置的,我们可以自定义属于自己的规则
使用ESLint
安装
npm install eslint -D
创建配置文件
npx eslint --int
在生成的 .eslintrc.js 文件中配置相应的规则