面试官灵魂三连问

  1. 你用过webpack么,它的原理是什么?
  2. 你还知道些构建工具,各有什么优缺点?
  3. 你在项目中有配置过么,在哪些场景配置过?

Webpack介绍

什么是webpack

webpack 是一个模块打包器。webpack 的主要目标是将 JavaScript 文件打包在一起,打包后的文件用于在浏览器中使用,但它也能够胜任转换(transform)、打包(bundle)。

webpack原理是什么

工作原理

webpack可以看做是模块打包机:

它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Sass,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。

在3.0出现后,Webpack还肩负起了优化项目的责任。

打包原理

把一切都视为模块:不管是 css、JS、Image 还是 html 都可以互相引用,通过定义 entry.js,对所有依赖的文件进行跟踪,将各个模块通过 loader 和 plugins 处理,然后打包在一起。

按需加载:打包过程中 Webpack 通过 Code Splitting 功能将文件分为多个 chunks,还可以将重复的部分单独提取出来作为 commonChunk,从而实现按需加载。把所有依赖打包成一个 bundle.js 文件,通过代码分割成单元片段并按需加载

核心概念

  • Entry:入口,webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。告诉webpack要使用哪个模块作为构建项目的起点,默认为./src/index.js
  • output :出口,告诉webpack在哪里输出它打包好的代码以及如何命名,默认为./dist
  • Module:模块,在 webpack 里一切皆模块,一个模块对应着一个文件。webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
  • Plugin:扩展插件,在 webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。

构建流程

Webpack 的运⾏流程是⼀个串⾏的过程,从启动到结束会依次执⾏以下流程:

  1. 初始化参数:解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果。
  2. 开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点,做出相应的反应,执行对象的 run 方法开始执行编译。
  3. 确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去。
  4. 编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
  5. 完成模块编译:在经过第4步使⽤ Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  6. 输出资源:根据⼊⼝和模块之间的依赖关系,组装成⼀个个包含多个模块的 Chunk,再把每个 Chunk 转换成⼀个单独的⽂件加⼊到输出列表,这步是可以修改输出内容的最后机会;
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和⽂件名,把⽂件内容写⼊到⽂件系统。

注意

在以上过程中,Webpack 会在特定的时间点,⼴播出特定的事件,插件在监听到感兴趣的事件后,会执⾏特定的逻辑。并且插件可以调⽤ Webpack 提供的 API ,改变 Webpack 的运⾏结果。比如UglifyPlugin,会在loader转换递归完,对结果使用UglifyJs压缩,覆盖之前的结果。

基本功能

  • 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等等
  • 文件优化:压缩 JavaScript、CSS、html 代码,压缩合并图片等
  • 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
  • 模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
  • 自动刷新:监听本地源代码的变化,自动构建,刷新浏览器
  • 代码校验:在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过
  • 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。

webpack基本配置

安装

全局安装

1
2
npm install -g webpack
npm install -g webpack-cli

项目依赖

1
2
npm install --save-dev webpack
npm install --save-dev webpack-cli

基本配置-入口:

入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。

基本配置-出口

output 属性告诉 webpack 在哪里输出它所创建的 bundles

基本配置-loaders

loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

基本配置-plugins

webpack插件(自动打开浏览器、热更新等)

  1. loader是使webpack拥有加载和解析非js文件的能力
  2. plugin 可以扩展webpack的功能,使得webpack更加灵活。可以在构建的过程中通过webpack的api改变输出的结果

基本配置-模式

package.json中设置:

1
2
3
4
”scripts": {
"dev": "webpack --mode development", // 开发环境
"build": "webpack --mode production", // 生产环境
}

webpack.config.js中设置:

1
2
3
4
5
6
7
8
module.exports = {
entry: './src/app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
mode: 'development' // 设置mode
}

development:有内置的调试功能;打包后的代码可阅读,没被压缩

production:内置生产阶段的很多优化功能;代码被压缩

基本配置-启动本地服务

方式一:

在package.json里的script属性里配置,完了执行npm run xxx

例如vue项目中

package.json

1
2
3
4
5
6
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
}
复制代码

命令行:

1
npm run dev

方式二:

通过npx

例如vue项目中

命令行:

1
npx cli-service serve

npx:www.npmjs.com/package/npx

模块化

webpack 默认使用common.js规范

如果只是使用import 导入模后,浏览器是不能直接识别的,必须导入webpack才行。

image-20220908051238818.png

其他基本常用配置

webpack跨域解决方案

proxy

ES6转ES5

babel

less转css

less-loader

image-20220908035219133.png

注意:配置时执行顺序默认是从右到左,从下到上

处理图片

方式一:url-loader

小于limit限制的转base64

image-20220509013551329.png 说明:url-loader处理的图片一般比较小,会把图片转换成base64代码,直接添加页面。提供了一个方便的选择 limit,该选项用于控制当文件大小小于 limit 时才使用 url-loader。通常会设置5-8kb

扩展:哈希的作用是命中缓存,使返回更快些。

方式二:file-loader

把图片转换成路径。

image-20220908035830131.png

拆分配置和merge

背景:‘‘基本配置-模式’’,里面介绍过模式之间的区别,为了解决手动更改mode带来的不便,所以,要将这些打包的配置,拆分到不同的文件里面来。

  • webpack.dev.config.js 开发环境的配置;
  • webpack.prod.config.js生产环境的配置;
  • webpack.base.config.js是开发环境和生产环境都会使用的配置;
  • 通过webpack-merge模块将不同配置的代码合并;

webpack.dev.config.js

1
2
3
4
5
6
7
8
const commonConfig = require('./webpack.base.config')
// webpack-merge 将公共的配置 和 开发的配置合并结合
const { smart: merge } = require('webpack-merge')
const devConfig = {
mode: 'development'
}
module.exports = merge(commonConfig, devConfig)
复制代码

webpack.prod.config.js

1
2
3
4
5
6
7
const commonConfig = require('./webpack.base.config')
const { smart: merge } = require('webpack-merge')
const prodConfig = {
mode: 'production'
}
module.exports = merge(commonConfig, prodConfig)
复制代码

业务场景

  1. 单页应用

  2. 一个项目管理多个单页面

  3. 代码分隔优化

    一个好的代码分割对浏览器首屏效果提升很大。

  4. 构建服务端渲染

  5. 热更新

  6. 优化开发编译、生产环境打包速度

  7. 优化首屏速度

扩展:还知道哪些构建工具?有什么优缺点?

大致了解下就好 ,现在主流的是webpack、rollup、vite

构建工具有哪些?

webpackgruntgulpFIS3Vite

他们有什么不同?

  • 都是前端构建工具,grunt和gulp在早期比较流行,FIS3停止维护了,现在webpack相对来说比较主流,不过一些轻量化的任务,还是会用gulp来处理,比如单独打包CSS文件等。
  • grunt和gulp是基于任务**(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程
  • webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。
  • webpack优点是专注于处理模块化的项目,尤其适合单页应用。Webpack的缺点是只能用于采用模块化开发的项目。
  • FIS3并没有入口(entry)的概念,在FIS3中,构建流程不单单根据js来,而是分析每一个文件的依赖关系,生成一个资源表‘sourceMap’,你可以根据资源表定义任何的产出规则
  • Fis3的优点是集成了各种Web开发所需的构建功能,配置简单、开箱即用。其缺点是目前官方已经不再更新和维护不支持最新版本的Node.js。Fis3是一种专注于Web开发的完整解决方案,如果将Grunt、Gulp比作汽车的发动机,则可以将Fis3比作一辆完整的汽车

总结:

从构建思路来说:gulp和grunt需要开发者将整个前端构建过程拆分成多个Task,并合理控制所有Task的调用关系; webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工;FIS3任何文件都可以作为入口

对于知识背景:gulp更像后端开发者的思路,需要对于整个流程了如指掌; webpack更倾向于前端开发者的思路。

参考文章:前端工具简介 fis3,webpack

补充:

  1. Webpack是一个模块打包器,他可以递归的打包项目中的所有模块,最终生成几个打包后的文件。
  2. 他和其他的工具最大的不同在于他支持code-splitting、模块化(AMD,ESM,CommonJs)、全局分析。

有类似webpack的工具么?

同样是基于入口的打包工具还有以下几个:webpackrollupparcelvitesnowpack

从应用场景上来看:

  • webpack:适合大型复杂的前端站点构建,尤其是模块化的,单页应用。
  • rollup:专门针对类库进行打包,它的优点是小巧而专注。因此现在很多我们熟知的库都都使用它进行打包,比如:Vue、React和three.js等。
  • parcel:零配置,傻瓜式。适用于简单的实验室项目,打包出错很难调试。不支持Tree Shaking。更多优点:传送门
  • vite:灵活、复杂度适中,未来趋势。开发期间无需打包,越大型体验感越好。
  • snowpackvite类似。

rollup、vite、snowpack,都是基于ESM,开发期间无需构建,浏览器直用。

参考文章:

从Npm Script到Webpack,6种常见的前端构建工具对比

webpack VS parcel

深入对比Webpack、Parcel、Rollup打包工具

作为官方推荐的vite,和现在应用最广的webpack的区别?

需要理解,当下最火,被问到概率大

技巧提示

加粗字体连起来,就是浓缩重点~

Webpack 是一个打包模块化 JavaScript 的工具,在 Webpack 里一切文件皆模块,通过 Loader 转换文件,通过 Plugin 注入钩子,最后输出由多个模块组合成的文件。Webpack 专注于构建模块化项目。

一切文件:JavaScript、CSS、SCSS、图片、模板,在 Webpack 眼中都是一个个模块,这样的好处是能清晰的描述出各个模块之间的依赖关系,以方便 Webpack 对模块进行组合和打包。 经过 Webpack 的处理,最终会输出浏览器能使用的静态资源。因此每次编译都需要重新构建

vite主要遵循的是使用ESM(Es modules模块)的规范来执行代码,由于现代浏览器基本上都支持了ESM规范,所以在开发阶段并不需要将代码打包编译成es5模块,即可直接在浏览器上运行。我们只需要从入口文件出发, 在遇到对应的 import 语句时,将对应的模块加载到浏览器中就可以了。当项目越大,文件越多时,vite的开发时优势越明显。vite热更新比webpack,vite在HRM方面,当某个模块内容改变时,让浏览器直接重新请求该模块即可,而不是像webpack重新将该模块的所有依赖重新编译

Vite的使用简单,只需执行初始化命令,就可以得到一个预设好的开发环境,开箱即获得一堆功能,包括:CSS预处理、html预处理、异步加载、分包、压缩、HMR等。使用复杂度介于Parcel和Webpack的中间,只是暴露了极少数的配置项和plugin接口,既不会像Parcel一样配置不灵活,又不会像Webpack一样需要了解庞大的loader、plugin生态,灵活适中、复杂度适中。

总体来说,Vite在前端构建工具领域上开辟了一条和webpack完全不同的道路,很好地解决了前端开发阶段构建速度慢的问题。

参考文章:Vite介绍和原理解析

扩展:常见Webpack面试真题

前端为何要进行打包和构建?

代码层面

  • 体积更小(Tree-Shaking、压缩、合并),加载更快
  • 编译高级语言或语法(TS、ES6+,模块化,SCSS)
  • 兼容性和错误检查(Polyfill、postcss、eslint)

研发流程层面

  • 统一、高效的开发环境
  • 统一的构建流程和产出标准
  • 集成公司构建规范(提测、上线等)

module\chunk\bundle区别

  • module:各个源码文件,webpack中一切皆模块。
  • chunk:在内存中还没有产出的代码块。
  • bundle:每个chunk打包后,都可以产生个bundle文件。

loader和plugin区别

  • loader模块转换器,如less->css,举2-3个例子
  • plugin扩展插件,如HtmlWebpackPlugin,举2-3个例子

Webpack的loader的加载顺序为什么是从右向左:

  1. Webpack选择了compose方式(即 函数式编程),而不是pipe的方式。

  2. 对于loader来说呢,有一个最大的需求——任意组合。而使用compose函数就很好的能完成这个需求,我们只需要在loader中的顺序就可以完成这个函数嵌套的需求,让编程更精练、算法更清晰。

常见的loader和plugin

挑几个记一下。

全面完整版:

  1. loaderwww.webpackjs.com/loaders/

  2. pluginwww.webpackjs.com/plugins/

babel和webpack的区别

Babel.js是新语法编译工具,不关心模块化

webpack是打包构建工具,是多个loader、plugin的集合。你配置什么loader plugin,它就有什么能力。

bable-polyfill和able-runtime的区别

  • babel-polyfill 会污染全局
  • babel-runtime不会污染全局
  • 产出第三方lib要用babel-runtime

webpack如何实现懒加载

  • import()
  • 结合Vue React异步组件
  • 结合Vue-router React-router异步加载路由

为何Proxy不能被Polyfill?

因为没有任何东西,可以模拟它。

  • 如Class可以用funciton模拟
  • 如Promse可以用callback来模拟
  • 但Proxy的功能用Object.defineProperty模拟

webpack如何优化构建速度?

可用于生产环境

  • 优化babel-loader

  • IgnorePlugin

  • noParse

    多进程:

  • happyPack

  • ParallelUglifyPlugin

不可用于生产环境

  • 自动刷新
  • 热更新
  • DIIPlugin

如何利用webpack来优化前端性能

  1. 提取公共代码。

  2. 压缩代码。(development和production)

  3. 使用loader的时候,使用exclude排除node_modules中的文件

  4. 配置extractTextWebpackPlugin插件

  5. 使用TreeShaking插件:Tree-shaking 概念最早由Rollup.js 提出,后来在webpack2中被引入进来,但是这个这一特性能够被支持得益于ES6 modules的静态特性。ES6的模块声明相比于传统CommonJS的同步require有着本质区别。这种modules设计保证了依赖关系是提前确定的,使得静态分析成为了可能,与运行时无关。(除那些引用的但却没有使用的代码)

webpack-dev-server和http服务器如nginx有什么区别?

  1. webpack-dev-server使用内存来存储webpack开发环境下的打包文件
  2. 并且可以使用模块热更新
  3. 他比传统的http服务对开发更加简单高效。

什么是长缓存?在webpack中如何做到长缓存优化?

  1. 浏览器在用户访问页面的时候,为了加快加载速度,会对用户访问的静态资源进行存储,但是每一次代码升级或是更新,都需要浏览器去下载新的代码,最方便和简单的更新方式就是引入新的文件名称。

  2. 在webpack中可以在output纵输出的文件指定chunkhash,并且分离经常更新的代码和框架代码。

  3. 通过NameModulesPlugin或是HashedModuleIdsPlugin使再次打包文件名不变。

什么是Tree-shaking?CSS可以Tree-shaking吗?

  1. Tree-shaking是指在打包中去除那些引入了,但是在代码中没有被用到的那些死代码。
  2. 在webpack中Tree-shaking是通过uglifySPlugin来Tree-shaking JS
  3. css需要使用Purify-CSS。

Webpack5和Webpack4的区别有哪些?

1. Tree Shaking(强大)

如果我们的项目中引入了loader 包,但是我只用了其中的一个方法。其他没有用到的方法是不是冗余的?此时 tree-shaking 就可以把没有用的那些东西剔除掉,来大大减少最终的bundle体积。

2. 压缩代码

内部本身就自带 js 压缩功能,他内置了 terser-webpack-plugin 插件,我们不用再下载安装。

而且在 mode=“production” 的时候会自动开启 js 压缩功能。

3. 缓存配置

webpack5 内部内置了 cache 缓存机制。直接配置即可。

cache 会在开发模式下被设置成 type: memory ,而且会在生产模式把cache 给禁用掉。

4. 启动服务的差别

  1. webpack4 启动服务: 通过 webpack-dev-server 启动服务
  2. webpack5 启动服务: 内置使用 webpack serve 启动,但是他的日志不是很好,所以一般都加都喜欢用 webpack-dev-server 优化。

5. 打包

  • webpack4打包: 即使后续没有使用到num1的函数,依然会将代码打包进去
  • webpack5打包: 后续没有使用到num1的函数,不会将代码打包进去

6. 输出代码

  • webpack4只能输出es5的代码
  • webpack5新增属性output.ecmaVersion,可以生成ES5和ES6的代码

7. splitChunk

webpack4 将超过30kb的文件单独提为一个chunk

webpack5可以区分是js还是css,可以精确划分


本篇笔记参考稀土掘金何逸轩一支鱼,仅供博主学习使用。