跳到主要内容

webpack

原理​

  • 1、解析入口文件,获取AST抽象语法树
  • 2、找出所有依赖模块
  • 3、AST转化编码code
  • 4、递归解析所有依赖项、生成依赖关系图
  • 5、输出bundle

webpack热更新如何做到动态替换模块?​

  • Webpack的热更新功能主要依靠HMR(Hot Module Replacement)插件实现
  • HMR插件会监听应用程序中模块的变化,并在模块更新时动态替换它们

具体来说,HMR插件会在应用程序运行时与Webpack服务器进行通信。当某个模块发生变化时,Webpack服务器会通知客户端进行更新,客户端接收到更新通知后,会替换相应的模块并重新渲染页面

HMR插件【热更新】原理​

  • HMR(Hot Module Replacement)插件

HMR插件的工作原理如下:

  1. 在应用程序运行时,HMR插件会与Webpack服务器进行通信。
  2. 当某个模块发生变化时,Webpack服务器会通知客户端更新该模块。
  3. 客户端接收到更新通知后,会替换相应的模块并重新渲染页面。

这里的webpack服务器是基于node的开发服务器

webpack-dev-server插件来启动Webpack服务器

webpack-dev-server 【webpack服务器】 原理​

Webpack服务器是一个基于Node.js的开发服务器,可以帮助您在本地运行和调试Webpack项目。它提供了许多开发工具,例如: - 编译代码并实时重新加载 - 提供静态文件服务 - 提供代理服务 - 提供热更新功能 Webpack服务器的工作原理如下:

  1. 启动Webpack服务器时,它会读取Webpack配置文件(即webpack.config.js),并根据配置文件中的信息进行初始化。
    1. Webpack服务器会监听源文件的变化,并在源文件发生变化时自动重新编译代码。 3. Webpack服务器会将编译后的代码保存在内存中,并提供静态文件服务。 4. Webpack服务器会自动打开浏览器,并跳转到默认的URL(通常是 http://localhost:8080)。用户可以在浏览器中访问该URL来查看应用程序的效果。 5. 当用户修改源文件时,Webpack服务器会自动重新编译代码,并在浏览器中实时刷新页面,以便用户查看代码更新后的实时效果

webpack文件监听的原理?​

原理:采用轮询的方式,每隔一段时间去判断每个文件的最后修改时间是否发生变化,如果发生变化,就先缓存起来,等待延时时间结束,期间如果遇到别的文件更改都统一缓存,然后一起重新打包 实现方式:

  • 利用了nodejs文件系统模块,fs.watch方法

如何开启监听模式呢? 有两种方式:

  • 1、启动 webpack 命令时,带上 --watch 参数
  • 2、在配置 webpack.config.js 中设置 watch:true

文件监听功能只能用于开发环境,在生产环境中不应启用它,因为它会增加额外的开销,降低构建的效率

js
module.export = {
// 默认false,也就是不开启 【webpack-dev-server 和 webpack-dev-middleware 里 Watch 模式默认开启的】
watch: true,
// 只有开启监听模式时,watchOptions才有意义
watchOptions: {
// 默认为空,不监听的文件或者文件夹,支持正则匹配
ignored: /node_modules/,
// 监听到变化发生后会等300ms再去执行,默认300ms
aggregateTimeout:300,
// 判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,poll指的是每次轮询间隔的时间
poll:1000
}
}

架构问题​

如何优化 Webpack 的构建速度​

  • 优化解析时间 【开启多进程】

    • webpack5内置terser-webpack-plugin, 可以多进程执行代码压缩、提高构建速度
  • 利用缓存 【优化二次打包时间】

    • 在webpack5之前,一般会使用cache-loader将编译结构写入磁盘缓存,或者使用babel-loader?cacheDirectory=true,设置babel编译的结果写进磁盘缓存。

    • webpack5新增的cache属性,会默认开启磁盘缓存【memory】,默认将编译结果缓存在 node_modules/.cache/webpack目录下。

    • 当设置 cache.type: "filesystem" 时,webpack 会在内部以分层方式启用文件系统缓存和内存缓存。 从缓存读取时,会先查看内存缓存,如果内存缓存未找到,则降级到文件系统缓存。 写入缓存将同时写入内存缓存和文件系统缓存。

  • tree Shaking 【减少包体积】

    • 可以去除未使用的模块
  • 合理使用 sourceMap

    • 打包生成 sourceMap 的时候,如果信息越详细,打包速度就会越慢
  • 优化搜索时间 【缩小文件搜索范围,减少不必要的编译工作】

    • 因为webpack在打包的时候,会解析入口文件,递归分析模块
    • 优化 loader 配置、优化 resolve.modules 配置、优化 resolve.alias 配置、优化 resolve.extensions 配置

多进程打包有用过吗?​

  • webpack5内置terser-webpack-plugin,默认是启动多进程并行的,可以多进程执行代码压缩、提高构建速度,
  • thread-loader : 多进程方式运行资源加载

知道sourceMap是什么吗?​

  • sourceMap是一项将编译、打包、压缩后的代码映射回源代码的技术,由于打包压缩后的代码并没有阅读性可言,一旦在开发中报错或者遇到问题,直接在混淆代码中debug问题会带来非常糟糕的体验,sourceMap可以帮助我们快速定位到源代码的位置,提高我们的开发效率

如何使用 Webpack 持久化缓存大幅提升构建性能?​

  • 文件系统缓存
js
module.exports = {
...
// 持久化缓存配置
cache: {
// 将缓存类型设置为文件系统,默认是memory
type: 'filesystem',
buildDependencies: {
// 更改配置文件时,重新缓存
config: [__filename]
}
},
optimization: {
// 值为"single"会创建一个在所有生成chunk之间共享的运行时文件
runtimeChunk: 'single',
moduleIds: 'deterministic',
},
}

Webpack 都有哪些实现并行构建的方法?​

  • HappyPack:多进程方式运行资源加载(Loader)逻辑; 【作者不维护了】
  • Thread-loader:Webpack 官方出品,同样以多进程方式运行资源加载逻辑;【Webpack 官方提供】
  • Parallel-Webpack:多进程方式运行多个 Webpack 构建实例;
  • TerserWebpackPlugin:支持多进程方式执行代码压缩、uglify 功能。

插件开发进阶:如何提升插件健壮性?​

插件架构:Hook 体系是如何影响 Webpack 架构的?​

HMR:如何动态替换页面代码​

Dependency Graph:如何管理模块间依赖?​

谈谈Tree-Shaking 实现原理​

  • webpack2.x开始⽀支持 tree shaking概念,顾名思义,"摇树"
    • 通常用于移除 JavaScript 上下文中的未引用代码(dead-code 无生命的代码)
    • 摇树 可以理解为 把没有用到的代码给移除下去,消除无用代码,不进行打包,
  • 只支持ES module的引入方式!!!!

原理

  • treeShaking依赖ES6的静态语法 【ES module】

  • ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了那些模块

    • import命令会被js引擎静态分析
  • 基于 ESM 的模块导入导出规则,可以实现静态编译时处理模块间的依赖

  • 静态分析程序流,判断那些模块和变量未被使用或者引用,进而删除对应代码

如何使用tree-shaking?

1).确保代码是es6格式,即 export,import

2).package.json中,设置sideEffects

3).确保tree-shaking的函数没有副作用

4).babelrc中设置presets [["env", { "modules": false }]] 禁止转换模块,交由webpack进行模块化处理

webpack如何实现持久化缓存?​

  • 保证 hash 值的唯一性,即为每个打包后的资源生成一个独一无二的 hash 值,只要打包内容不一致,那么 hash 值就不一致。
  • 保证 hash 值的稳定性,我们需要做到修改某个模块的时候,只有受影响的打包后文件 hash 值改变,与该模块无关的打包文件 hash 值不变。

为什么需要做持久化缓存​

  1. 用户使用浏览器第一次访问我们的站点时,该页面引入了各式各样的静态资源,如果我们能做到持久化缓存的话,可以在 http 响应头加上 Cache-control 或 Expires 字段来设置缓存,浏览器可以将这些资源一一缓存到本地。
  2. 用户在后续访问的时候,如果需要再次请求同样的静态资源,且静态资源没有过期,那么浏览器可以直接走本地缓存而不用再通过网络请求资源。

遇到问题总结​

1、如何解决webpack重复打包同名依赖包?​

  • 可用webpack配置的spiltchunk、SplitChunksPlugin、进行代码分离、代码拆分、分包,避免他们之间的重复依赖
  • external 配置
external配置​
  • 防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)
    • 比如可以从CDN去引入包,而不用打包
SplitChunks​
  • SplitChunksPlugin插件作用:简单的来说就是Webpack中一个提取或分离代码的插件,主要作用是提取公共代码,防止代码被重复打包、拆分过大的js文件,合并零散的js文件

2、不建议线上发布使用 DllPlugin 插件:​

DllPlugin 本身有几个缺点:

  1. 首先你需要额外多配置一份 webpack 配置,增加额外工作量。
  2. 其中一个页面用到了一个体积很大的第三方依赖库而其它页面根本不需要用到,但若直接将它打包在 dll.js 里很不值得,每次页面打开都要去加载这段无用的代码,无法使用到 webpack2 的 Code Splitting 功能。
  3. 第一次打开的时候需要下载 dll 文件,因为你把很多库全部打在一起了,导致 dll 文件很大,首次进入页面加载速度很慢。

虽然你可以打包成 dll 文件,然后让浏览器去读取缓存,这样下次就不用再去请求,比如你用 lodash 其中一个函数,而你用dll会将整个 lodash 文件打进去,这就会导致你加载无用代码过多,不利于首屏渲染时间。

我认为的正确的姿势是:

  1. 像 React、Vue 这样整体性偏强的库,可以生成 vendor 第三方库来去做缓存,因为你一般技术体系是固定的,一个站点里面基本上都会用到统一技术体系,所以生成 vendor 库用于缓存。
  2. 像 antd、lodash 这种功能性组件库,可以通过 tree shaking 来进行消除,只保留有用的代码,千万不要直接打到 vendor 第三方库里,不然你将大量执行无用的代码。

vendor库的值是依赖的第三方库,不会经常变更的,如react、vue

3、external问题​

  • 指出应将哪些模块视为外部模块,外部模块不会被打包
    • 防止将某些 import 的包(package)打包到 bundle 中
  • external 接受一个模块名称的数组或一个接受模块名称的函数,如果它被视为外部引用(externals)则返回 true

用法:

  • 在index.html引入的脚本,如从CDN引入,或者已经打包好的js,这不需要重新打包
  • 比如在inxex.html引入了lodash,external配置了lodash外部模块
    • 则lodash不会被打包,可以在组件通过import引入lodash模块