这是我参与「第四届青训营 」笔记创作活动的的第12天
前言
webpack现在可以说是理解前端工程化概念的入门第一关,因此理解和掌握基础的webpack对于工程化时代可谓是必经之路。
一、webpack是什么?
概念:webpack 是前端项目工程化的具体解决方案。
简单来解释就是我们在用前端开发时,开发的项目是由很多不同类型的文件构成的,无论如何最后都是要压缩成一坨代码来发布,为了解决不同文件类型引起的代码压缩混淆、还有一些浏览器的兼容性(比如:JavaScript的新特性,TS代码等),还有提高性能优化等等。
二、如何使用webpack?
webpack使用起来其实就是一个工具,几乎所有的操作都是在webpack.config.js文件中操作的。
1.示例
安装webpack npm install webpack webpack-cli -D
执行命令 npx webpack
在webpack.config.js中配置文件
- webpack.config.js 是 webpack 的配置文件。webpack 在真正开始打包构建之前,会先读取这个配置文件, 从而基于给定的配置,对项目进行打包。 也就是说,我们所有打包操作都依赖于这个配置文件。
我们观察上面的例子,我们先不考虑里面一些文件的内容,可以把整个export出去的东西看成一个对象。这里面有三个属性分别是,
entry,output和module,我们先一一来解释一下这三个属性。 - entry这个属性填的值是整个配置文件的入口文件,也就是我们首先从哪个文件开始打包编译。
- output这个属性值是最后我们打包压缩完的文件名,也就是说我们填main.js最后就会打包成一个叫main.js的文件
- module这个属性决定了如何处理项目中的不同类型的模块,其中最重要的个属性是rules,这个属性用来存放我们设置的处理规则。比如:在例子中,test意义是让我们过滤出.less后缀的文件以use中的loader来处理这些文件。
2.webpack打包的过程
这个过程是webpack的打包的核心流程。 大概是这样:首先会取得entry的文件来处理,然后扫描里面的import和require了哪些模块和代码,对应去寻找所在的文件,并用module模块定义的规则来处理找寻到的文件,如果处理过程在又发现import或者require的文件,则接着递归处理下去,直到完全处理完打包生成output的文件。
webpack属性的区分
关于Webpack的使用方法,基本都围绕配置"展开,而这些配置大致可划分为两类:
- 流程类:作用于流程中某个or若干个环节,直接影响打包效果的配置项
- 工具类:主流程之外,提供更多工程化能力的配置项
流程类
流程类的属性主要有:entry,output,module。
1.如何打包css文件?
首先我们需要安装loader npm add -D css-loader style-loader 在这里我们只需要重点关注module中的操作即可。
module: {
rules: [
{ test: /.css$/, use: ['style-loader', 'css-loader'] },
]
}
其中,test 表示匹配的文件类型,用正则表达式过滤出为所以后缀为.css的文件。 use 表示对应要调用的 loader,要注意的是
- use 数组中指定的 loader 顺序是固定的
- 多个 loader 的调用顺序是:从后往前调用
一些疑问
1.Loader有什么作用?为什么这里需要用到css-loader、style-loader?
答:loader这里是用来转译过滤出来的文件的,在转译过程是需要调用对应的加载资源库, css-loader、style-loader就是在转译时所需要的库。
2.与旧时代一在HTML文件中维护css相比,这种方式会有什么优劣处?
答:显然在此时我们已经不需要再手动去维护HTML文件中的css代码了,这些多出来的冗余操作可以丢掉了。
3.有没有接触过Less、Sass、 Stylus 这一类CSS预编译框架?如何在Webpack接 入这些工具?
2.如何打包js文件?
webpack 只能打包处理一部分高级的 JavaScript 语法。对于那些 webpack 无法处理的高级 js 语法,需要借助于 babel-loader 进行打包处理。例如 webpack 无法处理下面的 JavaScript 代码 :
// 1.定义了名为info的装饰器
function info(target) {
// 2.为目标添加静态属性info
target.info = 'Person info
}
// 3.为Person 类应用info 装饰器
@info
class Person {}
// 4.打印Person 的静态属性info
console. log(Person. info)
安装bable
npm i babel-loader @babel/core @babel/preset-env -D
module: {
rules: [
// 使用 babel-loader 处理高级的 JS 语法
// 在配置 babel-loader 的时候,程序员只需要把自己的代码进行转换即可;一定要排除 node_modules 目录中的 JS 文件,因为第三方包中的 JS 兼容性,不需要程序员关心
{ test: /.js$/, use: 'babel-loader', options: { presets: [ '@babel/preset-env' ] } }
]
},
一些疑问
1.Babel具体有什么功能?
答:Babel 是一个 JavaScript 编译器,我们很多类型的代码文件单独拎出来其实是不能跑的。比如react中的JSX,react也是在脚手架中直接引入babel编译器,把JSX代码解析成JS代码。所以Babel就是把一些其他类型的代码编译成JS。
2.Babel与Webpack分别解决了什么问题?为何两者能协作到一起了?
答:Babel是来解析代码变成JS代码,webpack是用来打包压缩整合代码的工具。在webpack中有个东西叫loader,loader需要解析到很多代码文件,两者有同样的应用场景,因此用到出现babel-loader其实并不奇怪。
3.webpack生成HTML
webpack 中的 HTML 插件(类似于一个模板引擎插件), 可以通过此插件自定制 index.html 页面的内容 。html-webpack-plugin 是 webpack 中的 HTML 插件,可以通过此插件自定制 index.html 页面的内容。 例如:通过 html-webpack-plugin 插件,将 src 目录下的 index.html 首页,复制到项目根目录中一份!
安装包 npm install html-webpack-plugin -D
module.exports = {
// 插件的数组,将来 webpack 在运行时,会加载并调用这些插件
plugins: [htmlPlugin, new CleanWebpackPlugin()],
}
plugins这个属性是用来挂载插件的。
通过 HTML 插件复制到项目根目录中的 index.html 页面,也被放到了内存中。 HTML 插件在生成的 index.html 页面,自动注入了打包的 bundle.js 文件
一些问题
1.相比于手工维护HTML内容,这种自动生成的方式有什么优缺点?
答:在传统项目中,每次对项目打包后我们都需要手动在index.html添加js的链接,当打包后的js入口文件名更改时,我们又需要再一次修改index.html中的js。 但是用自动生成的方式即可避免这种问题。
工具类
工具类一般有这些,如下图:
1.HMR(模块热替换)
HMR 全称 Hot Module Replacement,中文语境通常翻译为模块热更新,它能够在保持页面状态的情况下动态替换资源模块,提供丝滑顺畅的 Web 页面开发体验。 使用HRM Webpack 生态下,只需要经过简单的配置即可启动 HMR 功能,大致上分两步:
// webpack.config.js
module.exports = {
// ...
devServer: {
// 必须设置 devServer.hot = true,启动 HMR 功能
hot: true
}
};
之后,还需要调用 module.hot.accept 接口,声明如何将模块安全地替换为最新代码,如:
import component from "./component";
let demoComponent = component();
document.body.appendChild(demoComponent);
// HMR interface
if (module.hot) {
// Capture hot update
module.hot.accept("./component", () => {
const nextComponent = component();
// Replace old content with the hot loaded one
document.body.replaceChild(nextComponent, demoComponent);
demoComponent = nextComponent;
});
}
模块代码的替换逻辑可能非常复杂,幸运的是我们通常不太需要对此过多关注,因为业界许多 Webpack Loader 已经提供了针对不同资源的 HMR 功能,例如:
style-loader内置 Css 模块热更vue-loader内置 Vue 模块热更react-hot-reload内置 React 模块热更接口
因此,站在使用的角度,只需要针对不同资源配置对应支持 HMR 的 Loader 即可,很容易上手。
2.Tree-Shaking
Tree-shaking树摇,用来删除Dead Code(没有被用到的代码) 使用Tree-shaking
这里解释一下mode结点内容: mode属性:mode 节点的可选值有两个,分别是: development 和 production.
development为开发环境 不会对打包生成的文件进行代码压缩和性能优化 ,打包速度快,适合在开发阶段使用 。production为 生产环境, 会对打包生成的文件进行代码压缩和性能优化, 打包速度很慢,仅适合在项目发布阶段使用 。
三、理解Loader
loader的意义?
因为webpack有个问题,就是他只能处理js代码,他无法解析类似JXS,TS等代码。所以为了处理非标准JS资源,设计出资源翻译模块Loader用于将资源翻译为标准JS。
loader的链式调用?
什么是链式调用?
使用上,可以为某种资源文件配置多个 Loader,Loader 之间按照配置的顺序从前到后(pitch),再从后到前依次执行,从而形成一套内容转译工作流。
例如对于下面的配置:
module.exports = {
module: {
rules: [
{
test: /.less$/i,
use: [
"style-loader",
"css-loader",
"less-loader",
],
},
],
},
};
这是一个典型的 less 处理场景,针对 .less 后缀的文件设定了:less、css、style 三个 loader 协作处理资源文件,按照定义的顺序,Webpack 解析 less 文件内容后先传入 less-loader;less-loader 返回的结果再传入 css-loader 处理;css-loader 的结果再传入 style-loader;最终以 style-loader 的处理结果为准,流程简化后如:
三个 Loader 分别完成内容转化工作的一部分,形成从右到左的调用链条。链式调用这种设计有两个好处。
- 一是保持单个 Loader 的单一职责,一定程度上降低代码的复杂度。
- 二是细粒度的功能能够被组装成复杂而灵活的处理链条,提升单个 Loader 的可复用性。
不过,这只是链式调用的一部分,这里面有两个问题:
- Loader 链条一旦启动之后,需要所有 Loader 都执行完毕才会结束,没有中断的机会 —— 除非显式抛出异常
- 某些场景下并不需要关心资源的具体内容,但 Loader 需要在 source 内容被读取出来之后才会执行。
一些问题?
1.Loader输入是什么?要求的输出是什么?
答:Loader函数的输入是通过source参数输入,为执行的 loader 资源文件的内容。 Loader的输出是通过函数的返回值进行的,一般Loader 要做的事情就是将 source 转译为另一种形式的 output,也就是返回可模块化的内容。
2.Loader 的链式调用是什么意思?如何串联多个Loader?
答:1.Loader 之间按照配置的顺序从前到后(pitch),再从后到前依次执行,从而形成一套内容转译工作流。 2.通过module属性中的use来按顺序配置loader。(注意这里是逆序)
3.Loader 中如何处理异步场景?
答:具体操作分三步:
- 1.调用
this.async获取异步回调函数,此时 Webpack 会将该 Loader 标记为异步加载器,会挂起当前执行队列直到 callback 被触发 - 2.调用对应库将所需的资源转译为标准目标文件(比如例子中调用less库,转译成css)
- 3.调用异步回调 callback 返回处理结果 例子:
import less from "less";
async function lessLoader(source) {
// 1. 获取异步回调函数
const callback = this.async();
// ...
let result;
try {
// 2. 调用less 将模块内容转译为 css
result = await (options.implementation || less).render(data, lessOptions);
} catch (error) {
// ...
}
const { css, imports } = result;
// ...
// 3. 转译结束,返回结果
callback(null, css, map);
}
export default lessLoader;
参考文献: