这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战
写在前面
本系列文章核心内容:
webpack核心配置深入解析;webpack常用Loaders和Plugins深入学习;- 自定义
webpack中自己的Loaders和Plugins; Babel各种用法以及polyfill、TypeScript的支持;ESLint的配置规则以及在VSCode、webpack中的使用;- 各种性能优化方案:打包抽取分包、
Tree Shaking、动态链接库、CDN、gzip压缩等等; webpack模块化原理解析、打包原理实现;- 掌握其它流行构建工具:
gulp、rollup、Vite;
一 (了解)为什么会出现 webpack
1.1 前端发展的几个阶段
- 无论是作为专业的开发者还是接触互联网的普通人,其实都能深刻地感知到
Web前端的发展是非常快速的- 对于开发者来说我们会更加深有体会;
- 从后端渲染的
JSP、PHP,到前端原生JavaScript,再到jQuery开发,再到目前的三大框架Vue、React、Angular; - 开发方式也从原来的
JavaScript的ES5语法,到ES6、ES7、ES8、ES9、ES10、ES11、ES12,到TypeScript,包括编写CSS的预处理器less、scss等等;
1.2 前端开发的复杂化
- 前端开发目前我们面临哪些复杂的问题呢?
- 比如开发过程中我们需要通过模块化的方式来开发;
- 比如会使用一些高级特性来加快我们的开发效率或安全性,比如通过
ES6+、TypeScript开发脚本逻辑,通过sass、less等方式编写css样式代码; - 比如开发过程中,我们还希望实时地监听文件的变化并且反映到浏览器上,提高开发的效率;
- 比如开发完成后我们还需要将代码进行压缩、合并以及其它相关的优化;
- 等等
1.3 前端三个框架的脚手架
- 目前前端流行的三大框架:
Vue、React、Angular;- 但是事实上,这三大框架的创建过程都是借助于脚手架(
CLI)的; - 事实上
Vue-CLI、create-react-app、Angular-CLI都是基于webpack来帮助我们支持模块化、less、TypeScript、打包优化等的; - 三个框架中关于
webpack配置的文件:
- 但是事实上,这三大框架的创建过程都是借助于脚手架(
可见,我们日常工作根本是无法离开 webpack 的。
二 (理解)webpack 是什么以及工作中应用
2.1 webpack 是什么
Webpack is a static module bundler for modern JavaScript applications.
webpack是一个静态的模块化打包工具,用于现代的JavaScript应用程序;- 我们来对上面的解释进行拆解:
- 打包(
bundler):webpack可以帮助我们进行打包,所以它是一个打包工具; - 静态的(
static):这样表述的原因是我们最终可以将代码打包成最终的静态资源(部署到静态服务器); - 模块化(
module):webpack默认支持各种模块化开发,ES Module、CommonJS、AMD等; - 现代的(
modern):我们前面说过,正是因为现代前端开发面临各种各样的问题,才催生了webpack;
- 打包(
webpack 官网图片:
2.2 工作中的 webpack
- 日常工作中,比如在开发
vue、react、angular等项目的过程中,我们需要一些特殊的配置,比如给某些目录结构起别名,让我们的项目支持sass、less等预处理器,希望在项目中手动的添加TypeScript的支持,都需要对webpack进行一些特殊的配置工作。 - 除了日常工作之外,如果我们希望在原有的脚手架上进行一些自己的特殊配置以提升性能,比如:安装性能分析工具、使用
gzip压缩代码、引用cdn的资源、公共代码抽取等等,甚至包括编写属于自己的loader和plugin。 - 对于想要在前端领域进阶成为高级前端开发工程师,甚至是架构师的前端开发者来说,
webpack等构建工具是必须学习的,包括其中的一些高级特性和原理,都是要熟练掌握的。企业在招聘高级前端工程师或者架构师时,必然会对webpack和其它的构建工具有比较高的要求。
三 (理解)webpack 会被 Vite 取代吗
3.1 webpack 和 Vite
- 我们来提一个问题:
webpack会被Vite取代吗?Vite推出后确实引起了很多的反响,也有很多人看好Vite的发展(俺也是);
- 但是目前
Vite取代webpack还有很长的路要走- 目前
vue的项目支持使用Vite,也支持使用webpack; React、Angular的脚手架目前没有支持,暂时也没有转向Vite的打算;Vite最终打包的过程,依然需要借助rollup来完成;
- 目前
Vite的核心思想并不是首创- 事实上,
Vite的很多思想和之前的snowpack是重合的,而且相对目前来说,snowpack会更加成熟; - 当然,后续发展来看,
Vite可能会超越snowpack;
- 事实上,
webpack的更新迭代webpack在发展的过程中,也会不断改进自己,借鉴其它工具的一些优势和思想;- 在这么多年的发展中,无论是自身的优势还是生态都是非常强大的;
红元老师的个人观点:
- 学习任何东西,重要的是学习核心思想:
- 我们不能学会了
JavaScript,有一天学习TypeScript、Java或者其它语言,所有的内容都是从零开始的; - 我们在掌握了
React之后,再去学习Vue、Angular框架,也应该是可以快速上手的;
- 我们不能学会了
- 任何工具的出现,都是更好地服务于我们开发:
- 无论是
Vite的出现,还是以后新的工具的出现,不要有任何排斥的思想; - 我们要深刻地明白,工具都是为了更好地给我们提供服务;
- 不可能出现了某个工具,让我们的开发效率变得更低,而这个工具却可以变得非常流行,这是不存在的;
- 无论是
- 在后续的学习中,也会在讲完
webpack核心知识后学习gulp、rollup、Vite工具。
四 (掌握)如何阅读 webpack 官方文档
webpack官网地址:webpack.js.orgwebpack的中文官网地址:webpack.docschina.orgDOCUMENTATION:文档,也是我们最关注的;
- 点击
DOCUMENTATION来到文档页:Api:提供相关的接口,可以自定义编译的过程(比如自定义loader和plugin时就可以来这个位置查阅);Concepts:概念,主要是介绍一些webpack的核心概念,比如入口、出口、Loaders、Plugins等等,但是这里并没有对它们进行详细解析,详细的解析以及配置要去Configuration中查阅;Configuration:配置,webpack详细的配置选项,都可以在这里查询到,更多的时候是作为查询手册;Guides:指南,更像是webpack提供的教程,我们可以按照这个教程一步步去学习webpack的使用;Loaders:webpack的核心之一,常见的loader都可以在这里查询到用法,比如css-loader、babel-loader、less-loader等等;Migrate:迁移,可以通过这里的教程将webpac4迁移到webpack5等等;Plugins:webpack的核心之一,常见的plugin都可以在这里查询到用法,比如BannerPlugin、CleanWebpackPlugin、MiniCssExtractPlugin等等;
五 (掌握)webpack 和 webpack-cli 的关系和安装
5.1 webpack 的依赖
webpack的运行时依赖 Node 环境,所以电脑上必须有Node环境:- 需要先安装
Node.js,并且同时会安装npm; - 我当前电脑上的
node版本是v15.4.0,npm版本是7.0.15(你也可以使用nvm或n来管理node版本); Node的官网地址:nodejs.org
- 需要先安装
5.2 webpack 的安装
webpack的安装目前分为两个:webpack、webpack-cli;- 实际上,
webpack-cli不是必须安装的,像vue、react框架中就都没有安装,它们都是自定义了cli,我们也可以自定义。只不过当我们使用某些命令,比如webpack --config w.config.js时,--config参数的解析以及w.config.js文件的加载默认都是交给webpack-cli去执行的。
- 实际上,
- 那么它们是什么关系呢?
- 执行
webpack命令,会执行node_modules下的.bin目录下的webpack; webpack在执行时是依赖webpack-cli的,如果没有安装就会报错;- 而
webpack-cli中的runCli()函数执行时,才是真正利用webpack进行编译和打包的过程; - 所以在安装
webpack时,我们需要同时安装webpack-cli(但第三方的脚手架事实上是没有使用webpack-cli的,而是类似于自己的vue-service-cli的东西);
- 执行
5.2.1 安装命令
- 全局安装:
npm install webpack webpack-cli -g - 局部安装:
npm install webpack webpack-cli -D
注:全局安装是安装到电脑上(电脑的很多地方都能用),局部安装是安装到当前项目中(只在当前项目中可用)。
关于使用 npm 命令下载东西卡住的问题,有以下几种解决方案:
npm设置镜像- 使用
cnpm - 电脑连热点安装
- 使用
VPN
六 (掌握)webpack 的基本打包过程
我们使用 Visual Studio Code 这一代码编辑器来编写代码。
在 Visual Studio Code 中安装扩展:Live Server,方便后面跑 html 页面。
创建项目“01_webpack初体验”,项目目录结构如下:
相关文件的内容如下:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./src/index.js" type="module"></script>
</body>
</html>
index.js:
// ES6 的模块化
import { sum, mul } from "./js/math.js";
console.log(sum(20, 30));
console.log(mul(20, 30));
math.js:
/* ES6 的模块化 */
export const sum = (num1, num2) => {
return num1 + num2;
}
export const mul = (num1, num2) => {
return num1 * num2;
}
然后,在 index.js 文件中”右键“--”Open with Liver Server“,然后在打开的浏览器页面按 F12 打开开发者工具,点击”Console“标签切换到控制台,就可以看到程序运行的结果了:
虽然代码成功运行了,也获得了预期的结果。但是这里仍然存在很大的问题:
-
首先,在使用
index.js这个脚本时,我们必须告诉它它是个模块(type="module"); -
其次,当前所使用的浏览器是支持
ES6的模块化的,但是一些旧的浏览器并不支持ES6的模块化; -
此外,浏览器是不支持
CommonJS的,如果使用CommonJS进行模块化,浏览器也会报错:比如在
js目录下新建format.js文件,文件内容如下:const dateFormat = (date) => { return "2021-01-16"; }; const priceFormat = price => "100.00"; // CommonJS 的模块化 module.exports = { dateFormat, priceFormat };然后修改
index.js的内容如下(新增了关于CommonJS模块化的使用):// ES6 的模块化 import { sum, mul } from "./js/math.js"; // CommonJS 的模块化 const { dateFormat, priceFormat } = require('./js/format'); console.log(sum(20, 30)); console.log(mul(20, 30)); console.log(dateFormat(123)); console.log(priceFormat(123));再回到浏览器中,可以看到控制台报错了:
require是CommonJS的东西,可见,浏览器是不支持CommonJS的。
正是因为存在这些问题,我们才需要借助某些工具(如 webpack)对编写好的原生的代码进行打包,打包成浏览器能正常识别的其它代码,再将这些打包后的代码引入到 index.js 文件中去。
下面,我们就可以借助 webpack 对代码进行打包了,在这里,可以使用全局的 webpack,也可以使用当前项目中的 webpack。我们先使用全局的 webpack 对代码进行打包,在 Visual Studio Code 中打开终端,在命令行中输入 webpack 后回车(注意:前面已经安装好了 webpack),成功打包后结果如下:
(注:使用 webpack 这一命令进行打包时,对于出现的 WARNING,现阶段先忽略。)
这时,我们来看项目目录结构,会发现多了一个 dist 文件夹(dist 其实就是 distribution,这里可以理解为“发布”的意思),下面生成了一个 main.js 文件:
打开 main.js 文件,就可以看到 webpack 打包压缩后的代码了。
下面,我们再去 index.html 文件中引入这个 main.js 文件,用它替换掉之前的 index.js 文件,index.html 中修改后的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- <script src="./src/index.js" type="module"></script> -->
<script src="./dist/main.js"></script>
</body>
</html>
注意,这个时候因为没有直接使用 ES6 的 module,所以 script 中也不需要添加 type 属性了。然后,我们再回到浏览器中,可以看到控制台中输出了预期的结果:
可见,我们之前编写的原生的代码中,不管是 ES6 module 还是 CommonJS module,在经 webpack 打包后,都能在浏览器中正常运行起来了。
当然,上面打包后的代码中其实仍然存在着 ES6 的内容(比如箭头函数、const 等等),这是因为我们还没有对 babel-loader 进行配置,后面我们会讲到,babel 可以对代码进行一些转化。在没有配置 babel 的情况下,webpack 打包出来的就是一些 ES6 的代码。这在官方文档中也说明了:
Note that webpack will not alter any code other than
importandexportstatements. If you are using other ES2015 features, make sure to use a transpiler such as Babel or Bublé via webpack's loader system.
七 (掌握)全局和局部 webpack 的不同用法
首先,我们需要知道一个叫 package.json 的文件,package.json 文件中记录了当前项目所依赖的库以及它们对应的版本信息,通常,我们在拿到一个项目后,会通过执行 npm install 命令来安装 package.json 中记录的各个相应版本的依赖(比如 webpack、webpack-cli、axios、vue、react 等等项目正常运行需要依赖的东西),以保证在任何电脑上运行的都是各个相同版本的依赖,就不会出现电脑因为某个依赖的版本不对而产生问题,即保证了版本的一致。
因此,在实际开发中,我们想要对某一东西做编译时,不会使用全局的 webpack,而是使用局部的 webpack,也就是说项目中应该单独安装 webpack。那怎么使用 package.json 来记录安装的 webpack 的版本呢?我们可以在命令行中执行 npm init 命令生成 package.json 文件:
注意:除了第一项“package name”进行了重命名设置(规避中文可能导致的错误),后续一直回车到底就行了。
这时,我们来看项目目录结构,会发现项目根目录下多了一个 package.json 文件:
package.json 文件可以帮助我们记录当前项目所依赖的东西的版本,也有很多其它的东西,因为这些东西属于 node 的范畴,这里就不详细讲了。
有了 package.json 文件后,我们再在当前项目中安装依赖,这些依赖的版本信息就会被记录到 package.json 文件中去。
我们通过以下命令安装 webpack、webpack-cli 两个开发时依赖:
npm install webpack webpack-cli -D
其中,-D 表示开发时依赖,是 --save-dev 的简写
安装完成后,项目根目录下会生成一个 node_modules 文件夹:
可以看到,node_modules 文件夹下有很多文件夹,这是因为 webpack 也依赖了很多东西。
下面,我们就可以使用 node_modules/.bin 目录下的 webpack 命令来执行当前项目中的 webpack 对代码进行打包了,在终端中(项目根目录下)执行如下命令:./node_modules/.bin/webpack,使用的就是局部的(当前项目中的) webpack 命令进行的打包(注意:如果直接输入 webpack 执行,使用的还是全局的 webpack)。
不难发现,这条命令有点长,有没有简单点的执行方式呢?当然,还有两种:
- 使用
npx webpack命令; - 在
package.json文件中的 "script" 一项内容中设置要执行的脚本命令的别名后,再使用npm run 别名命令,举例如下:
第 1 种方式的原理是:npx 会执行 node_modules 目录下的命令,比如当我们执行 npx webpack 时,实际就会执行 node_modules 目录下的 .bin 目录下的 webpack(Windows 中是 webpack.cmd) 命令;第 2 种方式的原理是:执行 npm run script 时,会找到 package.json 下 scripts 中对应"script"中的命令,然后会优先去找 node_modules 目录下的 .bin 目录下对应的命令。
八 实践中可能出现的问题:
-
浏览器控制台报错:“
Uncaught SyntaxError: Cannot use import statement outside a module”- 报错的意思:不能在模块外使用
import语句; - 原因:
script标签中没有指定type属性的值为module; - 解决办法:在
script标签中指定type属性的值为module;
- 报错的意思:不能在模块外使用
-
浏览器控制台报错:“
GET http://127.0.0.1:5500/01_webpack%E5%88%9D%E4%BD%93%E9%AA%8C/src/js/math net::ERR_ABORTED 404 (Not Found)”- 报错的意思:未找到
math文件; - 原因:编写原生的代码时,文件的后缀名必须加上(
node、webpack中引用js文件时如果没有加后缀,会自动地去寻找对应的js文件,而在编写原生的代码时,则不会); - 解决办法:添加上文件的后缀名;
- 报错的意思:未找到
-
浏览器控制台报错:“
Uncaught ReferenceError: require is not defined”- 报错的意思:
require未定义; - 原因:浏览器是不认识
CommonJS的,所以使用CommonJS中的内容(如require、module.exports等)会报错; - 解决办法:借助某些工具(如
webpack)对编写好的原生的代码进行打包,打包成浏览器能正常识别的其它代码,再将这些打包后的代码引入到index.js文件中;
- 报错的意思:
-
命令行中运行
webpack这一命令时,出现如下报错:-
报错的意思:未能解析当前目录下的
src目录; -
原因:当前目录下没有
src目录或者有src目录但src目录下没有index.js文件,而webpack这一命令本质上会去寻找当前目录下的src目录下的index.js(./src/index.js)文件,然后将其作为入口,通过这一入口去查看对其它文件的引入,最后对其和引入的其它文件一起进行打包的。官方文档也有说明:
webpack开箱即用,可以无需使用任何配置文件。然而,webpack会假定项目的入口起点为src/index.js,然后会在dist/main.js输出结果,并且在生产环境开启压缩和优化。- 解决办法:切换到正确的项目目录下(项目目录下要存在
src目录,src目录下要存在index.js文件)再执行webpack;
-
-
webpack打包完代码,在index.html页面引入dist目录下的main.js后”右键“--”Open with Liver Server“,出现如下报错:- 可能的原因:
CommonJS的导出写的有问题,比如module.exports漏写了“s”等等;
- 可能的原因:
-
使用命令
npm uninstall webpack webpack-cli -g卸载全局的webpack和webpack-cli后,仍然能查看到webpack的版本,截图如下:- 原因:
webpack的全局卸载没有成功,可能是node环境安装的问题; - 解决办法:如果是用的
nvm管理的node,可以尝试将其删除,改为直接去node官网下载node进行安装。可以先用命令where webpack寻找webpack的位置,找到后将其手动删除。
- 原因:
九 补充
- 如果当前项目中没有安装
webpack、webpack-cli,执行webpack命令时,则会执行全局安装的webpack、webpack-cli。可以这样验证:先使用npm uninstall webpack webpack-cli将当前项目中的webpack、webpack-cli卸载掉(此时node_modules下的.bin目录下已经没有webpack相关的命令了),然后执行npm run build或npx webpack,可以发现还是能成功打包的; Windows命令下面的可执行文件,后面会多一个.cmd(也是批处理文件);npx webpack或npm run build(注:这里的“build”所设置的对应的命令是“webpack”)执行时,如果没有找到局部的webpack的话(比如未安装局部的webpack),就会去找全局的webpack;- 当使用
npm安装一些全局的软件包后,不知道安装到了什么位置时,可以使用命令npm root -g进行查询,如果是Windows系统,通常会默认保存在以下位置:C:\Users\用户名\AppData\Roaming\npm\node_modules;