Webpack原理知识总结

webpack知识总结

学习网站:https://geekdaxue.co/read/nardo@goi5e0/jo5a3NxuKNlLvHoV

问题1. Webpack 是如何满足模块化打包需求的

回答:首先webpack可以识别各种类型的模块,包括CommonJS、ES6模块、AMD等。会从入口文件开始,递归地分析模块之间的依赖关系。对于不同类型的模块,webpack初始只能处理JS模块,对于不能原生处理的模块使用loader(解释器)进行处理。模块被找到并处理后会转化为函数的形式,作为入参合并到同一文件中,减少模块请求次数。

问题2:通过 Webpack 打包后的结果是如何运行起来的?

回答:在对bundle.js文件进行分析后,可以发现:整体生成的代码其实就是一个立即执行函数,这个函数是 Webpack 工作入口(webpackBootstrap),它接收一个 modules 参数,调用时传入了一个数组。展开这个数组,里面的元素均是参数列表相同的函数。这里的函数对应的就是我们源代码中的模块,也就是说每个模块最终被包裹到了这样一个函数中,从而实现模块私有作用域。这个立即执行函数接收一个包含所有模块的数组作为参数。__webpack_require__ 函数用于加载模块,它会检查模块是否已经在缓存中,如果不在则执行模块代码并将结果缓存起来。最后,通过调用 __webpack_require__ 函数启动入口模块,从而开始整个应用的执行。

问题3:loader的工作原理

回答:loader解释器的入参对应类型的文件的内容,收到入参后对内容进行处理,最后输出处理后的结果。Webpack 加载资源文件的过程类似于一个工作管道,你可以在这个过程中依次使用多个 Loader,但是最终这个管道结束过后的结果必须是一段标准的 JS 代码字符串。

问题4:为什么webpack中的loader数组是从右到左/从下到上?

回答:Webpack 的 loader 机制是基于链式调用实现的。每个 loader 都会接收上一个 loader 的输出作为输入,并进行相应的转换,然后将转换后的结果传递给下一个 loader。在 Webpack 的内部实现中,当处理一个模块时,会根据配置的 loader 数组创建一个 loader 链。Webpack 会从数组的最后一个 loader 开始调用,依次将每个 loader 串联起来。

问题5:了解过哪些plugin,在什么场景使用?

回答:

问题6:plugin的工作原理

回答:简单来说,Webpack 的插件机制就是常见的钩子函数。Webpack 要求我们的插件必须是一个函数或者是一个包含 apply 方法的对象,一般我们都会定义一个类型,在这个类型中定义 apply 方法。然后在使用时,再通过这个类型来创建一个实例对象去使用这个插件。该apply接收一个 compiler 对象参数,是 Webpack 工作过程中最核心的对象,里面包含了我们此次构建的所有配置信息,我们就是通过这个对象去注册钩子函数。Webpack再使用钩子函数对对应阶段的打包结果进行处理。

如:

通过 compiler 对象的 hooks 属性访问到 emit 钩子,再通过 tap 方法注册一个钩子函数,接收插件名以及挂载的函数
class RemoveCommentsPlugin {
  apply (compiler) {
    compiler.hooks.emit.tap('RemoveCommentsPlugin', compilation => { // compilation为此次运行打包的上下文,所有打包过程中产生的结果
        for (const name in compilation.assets) {
            if (name.endsWith('.js')) {
                const contents = compilation.assets[name].source()
                const noComments = contents.replace(/\/\*{2,}\/\s?/g, '')
                compilation.assets[name] = {
                    source: () => noComments,
                    size: () => noComments.length
                }
            }
        }
    })
  }
}

问题7:Webpack的工作机制

回答:

  1. Webpack CLI 启动打包流程;
  2. 载入 Webpack 核心模块,创建 Compiler 对象;
  3. 使用 Compiler 对象开始编译整个项目;
  4. 从入口文件开始,解析模块依赖,形成依赖关系树;
  5. 递归依赖树,将每个模块交给对应的 Loader 处理;
  6. 合并 Loader 处理完的结果,将打包结果输出到 dist 目录。

逐步流程如下:

  1. 解析命令行参数(CLI参数)中的配置,加载配置文件,将配置文件中的配置和CLI参数中的配置合并(优先使用CLI参数)
  2. 传入配置选项到Webpack核心模块,生成Compiler对象,
  3. 注册配置中的每一个插件(外部插件和内置插件),为了后续生命周期开始钩子函数的调用
  4. 根据配置选项中是否启用了监视模式来启动构建方法(监视模式为watch方法,非监视模式为run方法)
  5. 以run为例,这个方法内部就是先触发了 beforeRun 和 run 两个钩子,然后最关键的是调用了当前对象的 compile 方法,真正开始编译整个项目
  6. compile 方法内部主要就是创建了一个 Compilation 对象,这个对象我们在 前面有提到,Compilation 字面意思是 “合集”,实际上,你就可以理解为一次构建过程中的上下文对象,里面包含了这次构建中全部的资源和信息。
  7. 建完 Compilation 对象过后,紧接着触发了一个叫作 make 的钩子,进入整个构建过程最核心的 make 阶段。
  8. make 阶段主体的目标就是:根据 entry 配置找到入口模块,开始依次递归出所有依赖,形成依赖关系树,然后将递归到的每个模块交给不同的 Loader 处理。这个阶段比较特殊,采用事件触发机制,即为触发注册在make阶段的钩子函数。
  9. make阶段最终会执行到SingleEntryPlugin 插件(假设为单文件打包模式),SingleEntryPlugin 中调用了 Compilation 对象的 addEntry 方法,开始解析入口;
  10. addEntry 方法中又调用了 _addModuleChain 方法,将入口模块添加到模块依赖列表中;
  11. 紧接着通过 Compilation 对象的 buildModule 方法进行模块构建;
  12. buildModule 方法中执行具体的 Loader,处理特殊资源加载;
  13. build 完成过后,通过 acorn 库生成模块代码的 AST 语法树;
  14. 根据语法树分析这个模块是否还有依赖的模块,如果有则继续循环 build 每个依赖;
  15. 所有依赖解析完成,build 阶段结束;
  16. 最后合并生成需要输出的 bundle.js 写入 dist 目录。

问题8:如何优化Webpack的构建速度和打包结果?

回答:

  1. 开启缓存避免重复编译,提升构建速度
  2. 利用多核 CPU 的优势,将打包任务分配到多个进程中并行处理,加快打包速度
  3. 使用 HappyPack:可以将 loader 解析任务分发给多个子进程。
  4. 减少 Webpack 搜索模块的路径,避免不必要的文件查找,提高构建效率
  5. 将不经常变化的第三方库提前打包成动态链接库(DLL),在主项目构建时直接引用,避免重复打包。
  6. Tree Shaking : 去除代码中未使用的部分,减小打包体积。Tree Shaking 主要针对 ES6 模块静态分析。
  7. 代码分割:将代码拆分成多个小的文件,按需加载,减少首屏加载时间
  8. 配置splitChunks,对chunk进行分割
  9. 使用图片压缩插件对图片进行压缩,减小图片文件大小

其他:

  1. 不同环境下的配置:提供判断当前环境切换不同的配置
  2. 不同环境的配置文件:一般在这种方式下,项目中最少会有三个 webpack 的配置文件。其中两个用来分别适配开发环境和生产环境,另外一个则是公共配置。因为开发环境和生产环境的配置并不是完全不同的,所以需要一个公共文件来抽象两者相同的配置。
  3. 使用生产环境的优化插件:
    • DefinePlugin :是用来为我们代码中注入全局成员
    • mini-css-extract-plugin:是一个可以将 CSS 代码从打包结果中提取出来的插件
    • optimize-css-assets-webpack-plugin:提取出的CSS代码不能被正常压缩,使用该插件可以压缩CSS文件
posted @   shaodao  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示