深入浅出Webpack - 5 - webpack原理

webpack原理

常⽤配置项

  • Entry:⼊⼝,Webpack执⾏构建的第⼀步将从Entry开始,可抽象成输⼊。
  • Output:输出配置
  • Module:模块,在 Webpack ⾥⼀切皆模块,⼀个模块对应⼀个⽂件。Webpack 会从配置的Entry开始,递归找出所有依赖的模块。
  • Loader:模块转换器,⽤于将模块的原内容按照需求转换成新内容。
  • Plugin:扩展插件,在Webpack构建流程中的特定时机会⼴播对应的事件,插件可以监听这些事件的发⽣,在特定的时机做对应的事情。
  • DevServer:开发服务器配置,用于配置 webpack-dev-server,提供热更新、代理等功能。
  • Resolve:模块解析配置,用于配置模块如何被解析,如别名、扩展名等。
  • Externals:外部扩展,声明某些依赖不打包进 bundle,而是通过外部引入(如 CDN)。
  • Devtool:Source Map 配置,控制如何生成 Source Map,便于调试。
  • Watch & WatchOptions:文件监听,用于监听文件变化并重新编译。

webpack运⾏流程

  • 初始化参数:从配置⽂件和Shell语句中读取与合并参数,得出最终的参数。

  • 开始编译:⽤上⼀步得到的参数初始化Compiler对象,加载所有配置的插件,通过执⾏对象的run⽅法开始执⾏编译。

  • 确定⼊⼝:根据配置中的entry找出所有⼊⼝⽂件。

  • 编译模块:从⼊⼝⽂件出发,调⽤所有配置的 Loader 对模块进⾏翻译,再找出该模块依赖的模块,再递归本步骤直到所有⼊⼝依赖的⽂件都经过了本步骤的处理。

  • 完成模块编译:经过上一步使⽤ Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容及它们之间的依赖关系。

  • 输出资源:根据⼊⼝和模块之间的依赖关系,组装成⼀个个包含多个模块的Chunk,再将每个 Chunk 转换成⼀个单独的⽂件加⼊输出列表中,这是可以修改输出内容的最后机会。

  • 输出完成:在确定好输出内容后,根据配置确定输出的路径和⽂件名,将⽂件的内容写⼊⽂件系统中。

  • 在webpack运⾏时,Webpack会在特定的时间点⼴播特定的事件,插件在监听到感兴趣的事件后会执⾏特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack的运行结果。

webpack构建流程三阶段

  1. 初始化:启动构建,读取与合并配置参数,加载Plugin,实例化Compiler。

    • (1)初始化参数,从配置⽂件和Shell语句中读取与合并参数,得出最终参数,这个过程中还会执⾏配置中的插件实例化语句new Plugin()。
    • (2)实例化Compiler,⽤上⼀步得到的参数初始化Compiler实例,Compiler负责⽂件监听和⾃动编译,在Compiler实例中包含了完整的Webpack配置,全局只有⼀个Compiler实例。
    • (3)加载插件,依次调⽤插件的apply⽅法,让插件可以监听后续的所有事件节点。同时向插件传⼊compiler实例的引⽤,以⽅便插件通过compiler调⽤webpack提供的API。
    • (4)environment,开始应⽤Node.js⻛格的⽂件系统到compiler对象,⽅便后续的⽂件查找和读取。
    • (5)entry-option,读取配置的entrys,为每个entry实例化⼀个对应的entryPlugin,为后⾯该entry的递归解析⼯作做准备。
    • (6)fter-plugins,调⽤完所有内置的和配置的插件的apply⽅法。
    • (7)after-resolvers,根据配置初始化resolver,resolver负责在⽂件系统中寻找指定路径的⽂件。
  2. 编译:从Entry发出,针对每个Module串⾏调⽤对应的Loader去翻译⽂件的内容,再找到该Module依赖的Module,递归地进⾏编译处理。

    • 编译阶段会发⽣的事件:run、watch-run、compile、compilation(build-module、normal-module-loader、program、seal)、make、after-compile、invalid
    • run,启动⼀次新的编译
    • watch-run,在监听模式下启动编译,该事件中可以获取哪些⽂件发⽣了变化从⽽导致重新启动⼀次新的编译
    • compile,告诉插件⼀次新的编译将要启动,同时会给插件带上compiler对象
    • compilation,当webpack以开发模式运⾏时,每当检测到⽂件的变化,便有⼀次新的Compilation被创建。⼀个Compilation对象包含了当前的模块资源、编译⽣成资源、变化的⽂件等。Compilation对象也提供了很多事件回调给插件进⾏扩展
      • 在编译阶段中,最重要的事件是compilation,因为在compilation阶段调⽤了Loader,完成了每个模块的转换操作。在compilation阶段⼜会发⽣很多⼩事件
      • build-module,使⽤对应的Loader去转换⼀个模块
      • normal-module-loader,在⽤Loader转换完⼀个模块后,使⽤acorn解析转换后的内容,输出对应的抽象语法树,⽅便Webpack对代码进⾏分析
      • program,从配置的⼊⼝模块开始,分析其AST,当遇到require等导⼊其他模块等语句时,便将其加⼊依赖的模块列表中,同时对新找出的依赖模块递归分析,最终弄清楚所有模块的依赖关系
      • seal,所有模块及其依赖的模块都通过Loader转换完成,根据依赖关系开始⽣成chunk
    • make,⼀个新的Compilation创建完毕,即将从Entry开始读取⽂件,根据⽂件的类型和配置的Loader对⽂件进⾏编译,编译完后再找出该⽂件依赖的⽂件,递归地编译和解析
    • after-compile,⼀次Compilation执⾏完成
    • invalid,当遇到⽂件不存在、⽂件编译错误等异常时会触发该事件,该事件不会导致Webpack退出
  3. 输出:将编译后的Module组合成Chunk,将Chunk转换成⽂件,输出到⽂件系统中。

    • should-emit,所有需要输出的⽂件已经⽣成,询问插件有哪些⽂件需要输出,有哪些不需要输出
    • emit,确定好要输出哪些⽂件后,执⾏⽂件输出,可以在这⾥获取和修改输出的内容
    • after-emit,⽂件输出完毕
    • done,成功完成⼀次完整的编译和输出流程
    • failed,如果在编译和输出的流程中遇到异常,导致webpack退出,就会直接跳过本步骤,插件可以在事件中获取具体的错误原因
    • 在输出阶段已经得到了各个模块经过转换后的结果和其依赖关系,并且将相关模块组合在⼀起形成⼀个个Chunk。在输出阶段会根据 Chunk 的类型,使⽤对应的模板⽣成最终要要输出的⽂件内容。
    • bundle.js在输出的⽂件中定义了⼀个可以在浏览器中执⾏的加载函数,来模拟Node.js中的require语句。
    • 多个独⽴的模块⽂件合并输出到⼀个bundle.js中的原因:浏览器不能像Node.js那样快速地在本地加载⼀个个模块,必须通过⽹络请求去加载还未得到的⽂件,若模块的数量很多,则加载时间会很⻓,因此将所有模块都存放在来数监听模式下,⽂件发⽣变化,再编译,输出,循环执⾏。

Loader

  • Loader 就像⼀个翻译员,能将源⽂件经过转化后输出新的结果,并且⼀个⽂件还可以链式地经过多个翻译员翻译。

  • ⼀个 Loader 的职责是单⼀的,只需要完成⼀种转换。如果⼀个源⽂件需要经历多步转换才能正常使⽤,就通过多个Loader去转换。在调⽤多个Loader去转换⼀个⽂件时,每个Loader都会链式地顺序执⾏。第1个Loader将会拿到需处理的原内容,上⼀个Loader处理后的结果会被传给下⼀个Loader接着处理,最后的Loader将处理后的最终结果返回给Webpack。如处理样式文件多个loader协作完成

    • 先将SCSS源代码提交给sass-loader,将SCSS转换成CSS;
    • 将 sass-loader输出的 CSS提交给 css-loader处理,找出 CSS中依赖的资源、压缩CSS等;
    • 将css-loader输出的CSS提交给style-loader处理,转换成通过脚本加载的JavaScript代码。
    • 可以看出,以上处理过程需要有顺序地链式执⾏,先sass-loader,再css-loader,再style-loader。
  • Webpack是运⾏在Node.js上的,⼀个Loader其实就是⼀个Node.js模块,这个模块需要导出⼀个函数。这个导出的函数的⼯作就是获得处理前的原内容,对原内容执⾏处理后,返回处理后的内容。

  • 调试本地开发的Loader⽅法:

    • 1.npm link
    • 2.ResolverLoader
  • 完成Npm link的步骤如下:

    • 确保正在开发的本地Npm模块(也就是正在开发的Loader)的package.json已经正确配置好;
    • 在本地的Npm模块根⽬录下执⾏npm link,将本地模块注册到全局;
    • 在项⽬根⽬录下执⾏npm link loader-name,将第2步注册到全局的本地Npm模块链接到项⽬的 node_moduels 下,其中的 loader-name 是指在第 1 步的package.json⽂件中配置的模块名称。
    • 链接好Loader到项目后就可以像使用一个真正的Npm模块一样使用本地的Loader了。
  • ResolveLoader⽤于配置Webpack如何寻找Loader,它在默认情况下只会去node_modules⽬录下寻找。为了让Webpack加载放在本地项⽬中的Loader,需要修改resolveLoader.modules。

 resolveLoader: {
    modules: ['node_modules', './loaders'] 
 }

常⽤Loader

  • 加载⽂件:
    • raw-loader,将⽂本⽂件的内容加载到代码中。
    • file-loader,将⽂件输出到⼀个⽂件夹中,在代码中通过相对URL去引⽤输出的⽂件。
    • url-loader,和file-loader类似,但是能在⽂件很⼩的情况下以base64⽅式将⽂件的内容注⼊代码中。
    • source-map-loader,加载额外的SourceMap⽂件,以⽅便断点调试。
    • svg-inline-loader,将压缩后的SVG内容注⼊代码中。
    • node-loader,加载 Node.js 原⽣模块的.node⽂件。
    • image-loader,:加载并且压缩图⽚⽂件。
    • json-loader,加载JSON⽂件。
  • 编译模版:
    • pug-loader,将Pug模版转换成JavaScript函数并返回。
    • handlebars-loader,将Handlebars模版编译成函数并返回。
    • ejs-loader,将EJS模版编译成函数并返回。
    • haml-loader,将 HAML代码转换成HTML。
  • 转换脚本语⾔:
    • babel-loader,将ES6转换成ES5。
    • ts-loader,将TypeScript转换成JavaScript。
    • awesome-typescript-loader,将TypeScript转换成JavaScript,性能要⽐ts-loader好。
  • 转换样式⽂件:
    • css-loader,加载CSS,⽀持模块化、压缩、⽂件导⼊等特性。
    • style-loader ,将 CSS 代码注⼊JavaScript中,通过DOM操作去加载CSS。
    • sass-loader,将SCSS/SASS代码转换成CSS。
    • postcss-loader,扩展 CSS 语法,使⽤下⼀代CSS。
    • less-loader,将Less代码转换成CSS代码。
  • 检查代码:
    • eslint-loader,通过 ESLint 检查 JavaScript代码。
    • tslint-loader,通过TSLint检查TypeScript代码。
    • mocha-loader,加载 Mocha测试⽤例的代码。
  • 其他loader:
    • vue-loader,加载 Vue.js 单⽂件组件。
    • i18n-loader,加载多语⾔版本,⽀持国际化。
    • ignore-loader,忽略部分⽂件。
    • ui-component-loader,按需加载UI组件库,例如在使⽤antdUI组件库时,不会因为只⽤到了Button组件⽽打包进所

Plugin

  • 在Webpack运⾏的⽣命周期中会⼴播许多事件,Plugin可以监听这些事件,在合适的时机通过Webpack提供的API改变输出结果。
  • Webpack启动后,在读取配置的过程中会先执⾏new BasicPlugin(options),初始化⼀个 BasicPlugin 并获得其实例。在初始化 compiler 对象后,再调⽤ basicPlugin.apply(compiler)为插件实例传⼊ compiler 对象。插件实例在获取到compiler 对象后,就可以通过 compiler.plugin(事件名称,回调函数)监听到 Webpack ⼴播的事件,并且可以通过compiler 对象去操作Webpack。
class BasicPlugin {
    constructor(options) {}
    apply(compiler) {
        compiler.plugin('compilation', (compilation) => {
        
        })
    }
}
module.exports = BasicPlugin;

// 使⽤⾃定义Plugin
const BasicPlugin = require('./plugins/basic-plugin');
module.export = {
 plugins: [new BasicPlugin(options)]
}
  • 在开发Plugin时最常⽤的两个对象就是Compiler和Compilation,它们是Plugin和Webpack之间的桥梁。Compiler和Compilation的含义如下。

    • Compiler对象包含了Webpack环境的所有配置信息,包含options、loaders、plugins等信息。这个对象在 Webpack 启动时被实例化,它是全局唯⼀的,可理解为Webpack实例。
    • Compilation对象包含了当前的模块资源、编译⽣成资源、变化的⽂件等。当Webpack以开发模式运⾏时,每当检测到⼀个⽂件发⽣变化,便有⼀次新的Compilation被创建。Compilation对象也提供了很多事件回调供插件进⾏扩展。通过Compilation也能读取到Compiler对象。
    • Compiler、Compilation的区别:Compiler代表了整个Webpack从启动到关闭的生命周期,而Compilation只代表一次新的编译。
    • 只要能拿到 Compiler 或 Compilation 对象,就能⼴播新的事件,所以在新开发的插件中也能⼴播事件,为其他插件监听使⽤。传给每个插件的Compiler和Compilation对象都是同⼀个引⽤。也就是说,若在⼀个插件中修改了Compiler或Compilation对象上的属性,就会影响到后⾯的插件。有些事件是异步的,这些异步的事件会附带两个参数,第2个参数为回调函数,在插件处理完任务时需要调⽤回调函数通知Webpack,才会进⼊下⼀个处理流程。
  • 监听⽂件的变化: Webpack会从配置的⼊⼝模块出发,依次找出所有依赖模块,当⼊⼝模块或者其依赖的模块发⽣变化时,就会触发⼀次新的Compilation。

常⽤Plugin

  • ⽤于修改⾏为
    • define-plugin,定义环境变量。
    • context-replacement-plugin,修改require语句在寻找⽂件时的默认⾏为。
  • ⽤于优化
    • commons-chunk-plugin,提取公共代码。
    • extract-text-webpack-plugin,提取JavaScript中的CSS代码到单独的⽂件中。
    • prepack-webpack-plugin ,通过Facebook的Prepack优化输出的JavaScript代码的性能。
    • uglifyjs-webpack-plugin,通过UglifyES压缩ES6代码。
    • webpack-parallel-uglify-plugin ,多进程执⾏UglifyJS代码压缩,提升构建的速度。
    • imagemin-webpack-plugin,压缩图⽚⽂件。
    • webpack-spritesmith,⽤插件制作雪碧图。
    • ModuleConcatenationPlugin,开启WebpackScopeHoisting功能。
    • dll-plugin,借鉴DDL的思想⼤幅度提升构建速度。
  • 其他Plugin
    • serviceworker-webpack-plugin,为⽹⻚应⽤增加离线缓存功能。
    • stylelint-webpack-plugin,将stylelint集成到项⽬中。
    • i18n-webpack-plugin,使⽹⻚⽀持国际化。
    • provide-plugin,从环境中提供的全局变量中加载模块,⽽不⽤导⼊对应的⽂件。



参考&感谢各路大神

  • [深入浅出Webpack-吴浩麟]
posted @ 2024-09-03 07:46  安静的嘶吼  阅读(17)  评论(0)    收藏  举报