山东中佛龙建设有限公司网站,百度通用网址,什么网站做优化最好,seo电商1 优化 webpack 打包体积的思路
优化 webpack 打包体积的思路包括#xff1a;
提取第三方库或通过引用外部文件的方式引入第三方库#xff1a;将第三方库单独打包#xff0c;并通过 CDN 引入#xff0c;减少打包体积。使用代码压缩插件#xff1a;例如 UglifyJsPlugin
提取第三方库或通过引用外部文件的方式引入第三方库将第三方库单独打包并通过 CDN 引入减少打包体积。使用代码压缩插件例如 UglifyJsPlugin可以压缩 JavaScript 代码减小文件体积。启用服务器端的 Gzip 压缩通过服务器端配置 Gzip 压缩减少传输体积。按需加载资源文件使用 require.ensure 或动态导入import()的方式按需加载资源文件避免一次性加载所有资源优化加载速度和体积。优化 devtool 中的 source-map选择合适的 devtool 配置确保在开发阶段能够提供足够的错误追踪信息但不会增加过多的打包体积。剥离 CSS 文件将 CSS 文件单独打包通过 link 标签引入利用浏览器的并行加载能力。去除不必要的插件检查 webpack 配置中的插件移除不必要的插件或根据环境区分开发环境和生产环境的配置避免将开发环境的调试工具打包到生产环境中。
除了上述优化思路还可以考虑以下几点
使用 Tree Shaking通过配置 webpack将未使用的代码在打包过程中消除减少打包体积。使用模块化引入合理使用 ES6 模块化语法或其他模块化方案按需引入模块避免不必要的全局引入。按需加载第三方库对于较大的第三方库可以考虑按需加载而不是一次性全部引入。优化图片资源压缩图片使用适当的图片格式尽量减小图片体积。优化字体文件如果使用了大量的字体文件可以考虑只引入需要的字体文件避免全部引入。使用缓存通过配置合适的缓存策略利用浏览器缓存机制减少重复加载资源。
综合以上优化思路可以有效减小 webpack 打包生成的文件体积提升应用性能和加载速度。需要根据具体项目情况和需求选择合适的优化策略和配置。
2 优化 webpack 打包效率的方法
使用增量构建和热更新在开发环境下使用增量构建和热更新功能只重新构建修改过的模块减少整体构建时间。避免无意义的工作在开发环境中避免执行无意义的工作如提取 CSS、计算文件 hash 等以减少构建时间。配置合适的 devtool选择适当的 devtool 配置提供足够的调试信息但不会对构建性能产生太大影响。选择合适的 loader根据需要加载的资源类型选择高效的 loader避免不必要的解析和处理过程。启用 loader 缓存对于耗时较长的 loader如 babel-loader可以启用缓存功能避免重复处理同一文件。采用引入方式引入第三方库对于第三方库可以通过直接引入的方式如 CDN 引入来减少打包时间。提取公共代码通过配置 webpack 的 SplitChunks 插件提取公共代码避免重复打包相同的代码提高打包效率。优化构建时的搜索路径指定需要构建的目录和不需要构建的目录减少搜索范围加快构建速度。模块化引入需要的部分使用按需引入的方式只引入需要的模块或组件避免加载不必要的代码提高构建效率。
通过以上优化措施可以有效提升 webpack 的打包效率减少开发和构建时间提升开发效率和用户体验。根据具体项目需求和场景选择适合的优化方法进行配置和调整。
3 编写Loader
编写一个名为 reverse-txt-loader 的 Loader实现对文本内容进行反转处理的功能。
// reverse-txt-loader.jsmodule.exports function (source) {// 对源代码进行处理这里是将字符串反转const reversedSource source.split().reverse().join();// 返回处理后的 JavaScript 代码作为模块输出return module.exports ${reversedSource};;
}; 上述代码定义了一个函数该函数接收一个参数 source即原始的文本内容。在函数内部我们将源代码进行反转处理并将处理后的结果拼接成一个字符串再通过 module.exports 输出为一个 JavaScript 模块。
要使用这个 Loader需要在 webpack 配置中指定该 Loader 的路径
// webpack.config.jsmodule.exports {// ...module: {rules: [{test: /.txt$/,use: [{loader: ./path/reverse-txt-loader}]}]}// ...
}; 上述配置将该 Loader 应用于所有以 .txt 结尾的文件。在构建过程中当遇到需要加载的 .txt 文件时会调用 reverse-txt-loader 对文件内容进行反转处理并将处理后的结果作为模块的输出。
请注意在实际使用中需要根据实际路径修改 loader 配置的路径并将该 Loader 安装在项目中。
4 编写plugin
编写一个自定义的 Webpack 插件需要创建一个 JavaScript 类并在类中实现指定的生命周期方法。下面是一个简单的示例展示如何编写一个自定义的 Webpack 插件
class MyPlugin {constructor(options) {// 在构造函数中可以接收插件的配置参数this.options options;}// Webpack 在安装插件时会自动调用 apply 方法并将 compiler 对象传递进来apply(compiler) {// 在适当的生命周期钩子中挂载插件的功能// 示例在 emit 生命周期钩子中添加自定义的功能compiler.hooks.emit.tap(MyPlugin, (compilation) {// compilation 对象包含了当前构建过程的各种信息// 可以在这里执行一些自定义的操作// 示例向输出的文件中添加自定义的注释const comment this.options.comment || Custom comment;for (const asset in compilation.assets) {if (compilation.assets.hasOwnProperty(asset)) {compilation.assets[asset].source () {return /* ${comment} */\n compilation.assets[asset].source();};}}});}
} 以上是一个简单的插件示例它在构建过程中的 emit 生命周期钩子中向输出的文件添加了自定义的注释。你可以根据实际需求在其他生命周期钩子中实现不同的功能。
要使用该插件在 webpack 的配置文件中进行如下配置
const MyPlugin require(./path/to/MyPlugin);module.exports {// ...plugins: [new MyPlugin({comment: Custom comment,}),],
};这样当你运行 webpack 构建时该插件就会被应用并执行指定的功能。
需要注意的是Webpack 的插件机制非常灵活可以根据实际需求编写各种各样的插件。插件可以监听多个生命周期钩子并在每个生命周期钩子中实现自定义的功能。详细的插件开发文档可以参考 Webpack 官方文档。
5 说一下webpack的一些plugin怎么使用webpack对项目进行优化
Webpack 提供了许多插件Plugins来帮助优化项目构建和性能。下面列举一些常用的插件以及它们的作用
构建优化插件
ContextReplacementPlugin用于限制某些模块的上下文可以减少编译体积。IgnorePlugin用于忽略特定的模块减少打包体积。babel-plugin-import用于按需加载和使用模块减少打包体积。babel-plugin-transform-runtime将代码中的公共部分提取到一个单独的模块中减少打包体积。happypack、thread-loader实现并行编译加快构建速度。uglifyjs-webpack-plugin通过并行压缩和缓存来加快代码压缩的速度。
性能优化插件
Tree-shaking通过静态分析代码去除未使用的代码减少打包体积。Scope Hoisting将模块之间的关系进行静态分析减少打包后的模块数量提升代码执行速度。webpack-md5-plugin根据文件内容生成 hash实现缓存的更新机制。splitChunksPlugin根据配置将代码拆分成多个块实现按需加载和并行加载的效果。import()、require.ensure动态导入模块实现按需加载提升页面加载速度。
除了使用这些插件还可以通过配置 webpack 的其他参数来进一步优化项目例如
配置 devtool选择合适的 Source Map 类型既满足调试需求又不影响构建速度。配置 output使用 chunkhash 或 contenthash 生成文件名实现长期缓存。使用 cache-loader、hard-source-webpack-plugin、uglifyjs-webpack-plugin 等插件开启缓存加速再次构建。使用 DllWebpackPlugin 和 DllReferencePlugin 预编译公共模块减少重复构建时间。
综合使用这些插件和优化策略可以显著提升 webpack 项目的构建效率和性能。但是需要根据具体的项目需求和场景选择合适的插件和优化方法。
6 webpack Plugin 和 Loader 的区别
Loader 用于对模块源码进行转换将非 JavaScript 模块转换为 JavaScript 模块或对模块进行预处理。它描述了 webpack 如何处理不同类型的文件比如将 Sass 文件转换为 CSS 文件或将 ES6 代码转换为 ES5 代码。Loader 是针对单个文件的转换操作通过配置 rules 来匹配文件并指定相应的 LoaderPlugin 用于扩展 webpack 的功能解决 Loader 无法解决的问题。Plugin 可以监听 webpack 构建过程中的事件并在特定的时机执行相应的操作。它可以在打包优化、资源管理、环境变量注入等方面提供额外的功能。Plugin 的功能范围更广泛可以修改 webpack 的内部行为从而实现更复杂的构建需求。
总的来说Loader 是用于处理模块源码的转换工具而 Plugin 则是用于扩展 webpack 的功能通过监听 webpack 构建过程中的事件来执行相应的操作。它们各自的作用和功能不同但都可以用于优化和定制 webpack 的构建过程。在配置 webpack 时我们可以通过配置 Loader 和 Plugin 来满足不同的需求并实现对模块的转换和构建过程的定制化
7 tree shaking 的原理是什么
Tree shaking 的原理主要是基于静态分析的方式来实现无用代码的消除从而减小最终打包生成的文件体积。它的工作原理可以简要概括如下
采用 ES6 Module 语法Tree shaking 只对 ES6 Module 语法进行静态分析和优化。ES6 Module 的特点是可以进行静态分析这意味着在编译阶段就能够确定模块之间的依赖关系。静态分析模块依赖在编译过程中通过静态分析可以确定每个模块的依赖关系以及模块中导出的函数、变量等信息。标记未被引用的代码在静态分析的过程中会标记出那些未被其他模块引用的函数、变量和代码块。消除未被引用的代码在构建过程中根据静态分析得到的标记信息可以对未被引用的代码进行消除。这样在最终生成的打包文件中未被引用的代码将不会包含在内。 总结来说Tree shaking 的核心思想是通过静态分析模块依赖关系并标记和消除未被引用的代码。这样可以大大减小打包后的文件体积提升应用的性能和加载速度。需要注意的是Tree shaking 只对 ES6 Module 语法起作用而对于 CommonJS 等其他模块系统则无法进行静态分析和优化。 8 common.js 和 es6 中模块引入的区别 CommonJS 是一种模块规范最初被应用于 Nodejs成为 Nodejs 的模块规范。运行在浏览器端的 JavaScript 由于也缺少类似的规范在 ES6 出来之前前端也实现了一套相同的模块规范 (例如: AMD)用来对前端模块进行管理。自 ES6 起引入了一套新的 ES6 Module规范在语言标准的层面上实现了模块功能而且实现得相当简单有望成为浏览器和服务器通用的模块解决方案。但目前浏览器对 ES6 Module 兼容还不太好我们平时在 Webpack 中使用的 export和 import会经过 Babel 转换为 CommonJS 规范 CommonJS 和 ES6 Module 在模块引入的方式和特性上有一些区别主要包括以下几个方面
输出方式CommonJS 输出的是一个值的拷贝而 ES6 Module 输出的是值的引用。在 CommonJS 中模块导出的值是被复制的即使导出模块后修改了模块内部的值也不会影响导入模块的值。而在 ES6 Module 中模块导出的值是引用关系如果导出模块后修改了模块内部的值会影响到导入模块的值加载时机CommonJS 模块是运行时加载也就是在代码执行到导入模块的位置时才会加载模块并执行。而 ES6 Module 是编译时输出接口也就是在代码编译阶段就会确定模块的依赖关系并在运行前静态地解析模块的导入和导出导出方式CommonJS 采用的是 module.exports 导出可以导出任意类型的值。ES6 Module 采用的是 export 导出只能导出具名的变量、函数、类等而不能直接导出任意值导入方式CommonJS 使用 require() 来导入模块可以使用动态语法允许在条件语句中使用。ES6 Module 使用 import 来导入模块它是静态语法只能写在模块的顶层不能写在条件语句中this 指向CommonJS 模块中的 this 指向当前模块的 exports 对象而不是全局对象。ES6 Module 中的 this 默认是 undefined在模块中直接使用 this 会报错 总的来说CommonJS 主要用于服务器端的模块化开发运行时加载更适合动态加载模块而 ES6 Module 是在语言层面上实现的模块化方案静态编译更适合在构建时进行模块依赖的静态分析和优化。在前端开发中通常使用打包工具如 webpack将 ES6 Module 转换为 CommonJS 或其他模块规范以实现在浏览器环境中的兼容性。 9 babel原理
Babel 是一个 JavaScript 编译器。他把最新版的 javascript 编译成当下可以执行的版本简言之利用 babel 就可以让我们在当前的项目中随意的使用这些新最新的 es6甚至 es7 的语法 ES6、7代码输入 - babylon进行解析 - 得到AST抽象语法树- plugin用babel-traverse对AST树进行遍历转译 -得到新的AST树-用babel-generator通过AST树生成ES5代码 它的工作流程包括解析parse、转换transform和生成generate三个主要步骤
解析parse Babel 使用解析器如 Babylon将输入的 JavaScript 代码解析成抽象语法树AST。解析器将代码分析成语法结构并生成对应的 AST表示代码的抽象语法结构。这个阶段包括词法分析和语法分析。词法分析将源代码转换为一个个标记tokens的流而语法分析则将这个标记流转换为 AST 的形式。转换transform 在转换阶段Babel 使用插件plugins对 AST 进行遍历和转换。插件可以对 AST 进行增删改查的操作可以根据需求对语法进行转换、代码优化等。Babel 的插件系统非常灵活可以根据需要自定义插件或使用现有插件来进行代码转换。生成generate 在生成阶段Babel 使用生成器如 babel-generator将经过转换的 AST 转换回字符串形式的 JavaScript 代码。生成器会深度优先遍历 AST并根据 AST 的节点类型生成对应的代码字符串最终将代码字符串输出。
通过以上三个步骤Babel 实现了将最新版本的 JavaScript 代码转换为向后兼容的代码使得开发者可以在当前环境中使用较新的 JavaScript 特性和语法。同时Babel 还提供了一些常用的插件和预设presets以便开发者快速配置和使用常见的转换规则如转换 ES6、ES7 语法、处理模块化、转换 JSX 等。
总的来说Babel 的原理是通过解析、转换和生成的过程将新版本的 JavaScript 代码转换为兼容旧环境的代码使开发者能够在当前环境中使用较新的 JavaScript 特性和语法。
1 介绍一下 webpack 的构建流程
核心概念
entry入口。webpack是基于模块的使用webpack首先需要指定模块解析入口(entry)webpack从入口开始根据模块间依赖关系递归解析和处理所有资源文件。output输出。源代码经过webpack处理之后的最终产物。loader模块转换器。本质就是一个函数在该函数中对接收到的内容进行转换返回转换后的结果。因为 Webpack 只认识 JavaScript所以 Loader 就成了翻译官对其他类型的资源进行转译的预处理工作。plugin扩展插件。基于事件流框架 Tapable插件可以扩展 Webpack 的功能在 Webpack 运行的生命周期中会广播出许多事件Plugin 可以监听这些事件在合适的时机通过 Webpack 提供的 API 改变输出结果。module模块。除了js范畴内的es module、commonJs、AMD等css import、url(...)、图片、字体等在webpack中都被视为模块。
解释几个 webpack 中的术语
module指在模块化编程中我们把应用程序分割成的独立功能的代码模块chunk指模块间按照引用关系组合成的代码块一个 chunk 中可以包含多个 modulechunk group指通过配置入口点entry point区分的块组一个 chunk group 中可包含一到多个 chunkbundlingwebpack 打包的过程asset/bundle打包产物
webpack 的打包思想可以简化为 3 点
一切源代码文件均可通过各种 Loader 转换为 JS 模块 module模块之间可以互相引用。webpack 通过入口点entry point递归处理各模块引用关系最后输出为一个或多个产物包 js(bundle) 文件。每一个入口点都是一个块组chunk group在不考虑分包的情况下一个 chunk group 中只有一个 chunk该 chunk 包含递归分析后的所有模块。每一个 chunk 都有对应的一个打包后的输出文件asset/bundle 打包流程
初始化参数从配置文件和 Shell 语句中读取并合并参数得出最终的配置参数。开始编译从上一步得到的参数初始化 Compiler 对象加载所有配置的插件执行对象的 run 方法开始执行编译。确定入口根据配置中的 entry 找出所有的入口文件。编译模块从入口文件出发调用所有配置的 loader 对模块进行翻译再找出该模块依赖的模块这个步骤是递归执行的直至所有入口依赖的模块文件都经过本步骤的处理。完成模块编译经过第 4 步使用 loader 翻译完所有模块后得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。输出资源根据入口和模块之间的依赖关系组装成一个个包含多个模块的 chunk再把每个 chunk 转换成一个单独的文件加入到输出列表这一步是可以修改输出内容的最后机会。输出完成在确定好输出内容后根据配置确定输出的路径和文件名把文件内容写入到文件系统。 简版
Webpack CLI 启动打包流程载入 Webpack 核心模块创建 Compiler 对象使用 Compiler 对象开始编译整个项目从入口文件开始解析模块依赖形成依赖关系树递归依赖树将每个模块交给对应的 Loader 处理合并 Loader 处理完的结果将打包结果输出到 dist 目录。 在以上过程中Webpack 会在特定的时间点广播出特定的事件插件在监听到相关事件后会执行特定的逻辑并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果 构建流程核心概念
Tapable一个基于发布订阅的事件流工具类Compiler 和 Compilation 对象都继承于 TapableCompilercompiler对象是一个全局单例他负责把控整个webpack打包的构建流程。在编译初始化阶段被创建的全局单例包含完整配置信息、loaders、plugins以及各种工具方法Compilation代表一次 webpack 构建和生成编译资源的的过程在watch模式下每一次文件变更触发的重新编译都会生成新的 Compilation 对象包含了当前编译的模块 module, 编译生成的资源变化的文件, 依赖的状态等而每个模块间的依赖关系则依赖于AST语法树。每个模块文件在通过Loader解析完成之后会通过acorn库生成模块代码的AST语法树通过语法树就可以分析这个模块是否还有依赖的模块进而继续循环执行下一个模块的编译解析。
最终Webpack打包出来的bundle文件是一个IIFE的执行函数。
// webpack 5 打包的bundle文件内容(() { // webpackBootstrapvar __webpack_modules__ ({file-A-path: ((modules) { // ... })index-file-path: ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { // ... })})// The module cachevar __webpack_module_cache__ {};// The require functionfunction __webpack_require__(moduleId) {// Check if module is in cachevar cachedModule __webpack_module_cache__[moduleId];if (cachedModule ! undefined) {return cachedModule.exports;}// Create a new module (and put it into the cache)var module __webpack_module_cache__[moduleId] {// no module.id needed// no module.loaded neededexports: {}};// Execute the module function__webpack_modules__[moduleId](module, module.exports, __webpack_require__);// Return the exports of the modulereturn module.exports;}// startup// Load entry module and return exports// This entry module cant be inlined because the eval devtool is used.var __webpack_exports__ __webpack_require__(./src/index.js);
}) webpack详细工作流程 2 介绍 Loader
常用 Loader:
file-loader: 加载文件资源如 字体 / 图片 等具有移动/复制/命名等功能url-loader: 通常用于加载图片可以将小图片直接转换为 Date Url减少请求babel-loader: 加载 js / jsx 文件 将 ES6 / ES7 代码转换成 ES5抹平兼容性问题ts-loader: 加载 ts / tsx 文件编译 TypeScriptstyle-loader: 将 css 代码以style标签的形式插入到 html 中css-loader: 分析import和url()引用 css 文件与对应的资源postcss-loader: 用于 css 的兼容性处理具有众多功能例如 添加前缀单位转换 等less-loader / sass-loader: css预处理器在 css 中新增了许多语法提高了开发效率
编写原则:
单一原则: 每个 Loader 只做一件事链式调用: Webpack 会按顺序链式调用每个 Loader统一原则: 遵循 Webpack制定的设计规则和结构输入与输出均为字符串各个 Loader 完全独立即插即用
3 介绍 plugin 插件系统是 Webpack 成功的一个关键性因素。在编译的整个生命周期中Webpack 会触发许多事件钩子Plugin 可以监听这些事件根据需求在相应的时间点对打包内容进行定向的修改。 一个最简单的 plugin 是这样的:
class Plugin{// 注册插件时会调用 apply 方法// apply 方法接收 compiler 对象// 通过 compiler 上提供的 Api可以对事件进行监听执行相应的操作apply(compiler){// compilation 是监听每次编译循环// 每次文件变化都会生成新的 compilation 对象并触发该事件compiler.plugin(compilation,function(compilation) {})}
}
注册插件:
// webpack.config.js
module.export {plugins:[new Plugin(options),]
}事件流机制: Webpack 就像工厂中的一条产品流水线。原材料经过 Loader 与 Plugin 的一道道处理最后输出结果。 通过链式调用按顺序串起一个个 Loader通过事件流机制让 Plugin 可以插入到整个生产过程中的每个步骤中 Webpack 事件流编程范式的核心是基础类 Tapable是一种 观察者模式 的实现事件的订阅与广播 const { SyncHook } require(tapable)const hook new SyncHook([arg])// 订阅
hook.tap(event, (arg) {// event-hookconsole.log(arg)
})// 广播
hook.call(event-hook)Webpack 中两个最重要的类 Compiler 与 Compilation 便是继承于 Tapable也拥有这样的事件流机制。 Compiler: 可以简单的理解为 Webpack 实例它包含了当前 Webpack 中的所有配置信息如 options loaders, plugins 等信息全局唯一只在启动时完成初始化创建随着生命周期逐一传递 Compilation: 可以称为 编译实例。当监听到文件发生改变时Webpack 会创建一个新的 Compilation 对象开始一次新的编译。它包含了当前的输入资源输出资源变化的文件等同时通过它提供的 api可以监听每次编译过程中触发的事件钩子 区别: Compiler 全局唯一且从启动生存到结束Compilation对应每次编译每轮编译循环均会重新创建 常用 Plugin: UglifyJsPlugin: 压缩、混淆代码CommonsChunkPlugin: 代码分割ProvidePlugin: 自动加载模块html-webpack-plugin: 加载 html 文件并引入 css / js 文件extract-text-webpack-plugin / mini-css-extract-plugin: 抽离样式生成 css 文件 DefinePlugin: 定义全局变量optimize-css-assets-webpack-plugin: CSS 代码去重webpack-bundle-analyzer: 代码分析compression-webpack-plugin: 使用 gzip 压缩 js 和 csshappypack: 使用多进程加速代码构建EnvironmentPlugin: 定义环境变量 调用插件 apply 函数传入 compiler 对象 通过 compiler 对象监听事件
loader和plugin有什么区别 webapck默认只能打包JS和JOSN模块要打包其它模块需要借助loaderloader就可以让模块中的内容转化成webpack或其它laoder可以识别的内容。 loader就是模块转换化或叫加载器。不同的文件需要不同的loader来处理。plugin是插件可以参与到整个webpack打包的流程中不同的插件在合适的时机可以做不同的事件。
webpack中都有哪些插件这些插件有什么作用
html-webpack-plugin 自动创建一个HTML文件并把打包好的JS插入到HTML文件中clean-webpack-plugin 在每一次打包之前删除整个输出文件夹下所有的内容mini-css-extrcat-plugin 抽离CSS代码放到一个单独的文件中optimize-css-assets-plugin 压缩css
4 webpack 热更新实现原理 HMR 的基本流程图 当修改了一个或多个文件文件系统接收更改并通知 webpackwebpack 重新编译构建一个或多个模块并通知 HMR 服务器进行更新HMR Server 使用 webSocket 通知 HMR runtime 需要更新HMR 运行时通过 HTTP 请求更新 jsonpHMR 运行时替换更新中的模块如果确定这些模块无法更新则触发整个页面刷新
5 webpack 层面如何做性能优化
优化前的准备工作
准备基于时间的分析工具我们需要一类插件来帮助我们统计项目构建过程中在编译阶段的耗时情况。speed-measure-webpack-plugin 分析插件加载的时间使用 webpack-bundle-analyzer 分析产物内容
代码优化: 无用代码消除是许多编程语言都具有的优化手段这个过程称为 DCE (dead code elimination)即 删除不可能执行的代码 例如我们的 UglifyJs它就会帮我们在生产环境中删除不可能被执行的代码例如:
var fn function() {return 1;// 下面代码便属于 不可能执行的代码// 通过 UglifyJs (Webpack4 已内置) 便会进行 DCEvar a 1;return a;
}摇树优化 (Tree-shaking)这是一种形象比喻。我们把打包后的代码比喻成一棵树这里其实表示的就是通过工具 “摇” 我们打包后的 js 代码将没有使用到的无用代码 “摇” 下来 (删除)。即 消除那些被 引用了但未被使用 的模块代码。 原理: 由于是在编译时优化因此最基本的前提就是语法的静态分析ES6的模块机制 提供了这种可能性。不需要运行时便可进行代码字面上的静态分析确定相应的依赖关系。 问题: 具有 副作用 的函数无法被 tree-shaking 在引用一些第三方库需要去观察其引入的代码量是不是符合预期尽量写纯函数减少函数的副作用可使用 webpack-deep-scope-plugin可以进行作用域分析减少此类情况的发生但仍需要注意
code-spliting: 代码分割技术将代码分割成多份进行 懒加载 或 异步加载避免打包成一份后导致体积过大影响页面的首屏加载 Webpack 中使用 SplitChunksPlugin 进行拆分 按 页面 拆分: 不同页面打包成不同的文件 按 功能 拆分: 将类似于播放器计算库等大模块进行拆分后再懒加载引入提取复用的业务代码减少冗余代码 按 文件修改频率 拆分: 将第三方库等不常修改的代码单独打包而且不改变其文件 hash 值能最大化运用浏览器的缓存
scope hoisting: 作用域提升将分散的模块划分到同一个作用域中避免了代码的重复引入有效减少打包后的代码体积和运行时的内存损耗
编译性能优化: 升级至 最新 版本的 webpack能有效提升编译性能 使用 dev-server / 模块热替换 (HMR) 提升开发体验 监听文件变动 忽略 node_modules 目录能有效提高监听时的编译效率 缩小编译范围 modules: 指定模块路径减少递归搜索mainFields: 指定入口文件描述字段减少搜索noParse: 避免对非模块化文件的加载includes/exclude: 指定搜索范围/排除不必要的搜索范围alias: 缓存目录避免重复寻址 babel-loader 忽略node_moudles避免编译第三方库中已经被编译过的代码使用cacheDirectory可以缓存编译结果避免多次重复编译 多进程并发 webpack-parallel-uglify-plugin: 可多进程并发压缩 js 文件提高压缩速度HappyPack: 多进程并发文件的 Loader 解析 第三方库模块缓存: DLLPlugin 和 DLLReferencePlugin 可以提前进行打包并缓存避免每次都重新编译 使用分析 Webpack Analyse / webpack-bundle-analyzer 对打包后的文件进行分析寻找可优化的地方配置profiletrue对各个编译阶段耗时进行监控寻找耗时最多的地方 source-map: 开发: cheap-module-eval-source-map生产: hidden-source-map
优化webpack打包速度 减少文件搜索范围 比如通过别名loader 的 testinclude exclude Webpack4 默认压缩并行 Happypack 并发调用 babel 也可以缓存编译 Resolve 在构建时指定查找模块文件的规则 使用DllPlugin不用每次都重新构建 externals 和 DllPlugin 解决的是同一类问题将依赖的框架等模块从构建过程中移除。它们的区别在于 在 Webpack 的配置方面externals 更简单而 DllPlugin 需要独立的配置文件。DllPlugin 包含了依赖包的独立构建流程而 externals 配置中不包含依赖框架的生成方式通常使用已传入 CDN 的依赖包externals 配置的依赖包需要单独指定依赖模块的加载方式全局对象、CommonJS、AMD 等在引用依赖包的子模块时DllPlugin 无须更改而 externals 则会将子模块打入项目包中
优化打包体积 提取第三方库或通过引用外部文件的方式引入第三方库 代码压缩插件UglifyJsPlugin 服务器启用gzip压缩 按需加载资源文件 require.ensure 优化devtool中的source-map 剥离css文件单独打包 去除不必要插件通常就是开发环境与生产环境用同一套配置文件导致 Tree Shaking 在构建打包过程中移除那些引入但未被使用的无效代码 开启 scope hosting 体积更小创建函数作用域更小代码可读性更好 6 介绍一下 Tree Shaking
对tree-shaking的了解
作用
它表示在打包的时候会去除一些无用的代码
原理
ES6的模块引入是静态分析的所以在编译时能正确判断到底加载了哪些模块分析程序流判断哪些变量未被使用、引用进而删除此代码
特点
在生产模式下它是默认开启的但是由于经过babel编译全部模块被封装成IIFE它存在副作用无法被tree-shaking掉可以在package.json中配置sideEffects来指定哪些文件是有副作用的。它有两种值一个是布尔类型如果是false则表示所有文件都没有副作用如果是一个数组的话数组里的文件路径表示改文件有副作用rollup和webpack中对tree-shaking的层度不同例如对babel转译后的class如果babel的转译是宽松模式下的话(也就是loose为true)webpack依旧会认为它有副作用不会tree-shaking掉而rollup会。这是因为rollup有程序流分析的功能可以更好的判断代码是否真正会产生副作用。
原理
ES6 Module 引入进行静态分析故而编译的时候正确判断到底加载了那些模块静态分析程序流判断那些模块和变量未被使用或者引用进而删除对应代码 依赖于import/export 通过导入所有的包后再进行条件获取。如下
import foo from foo;
import bar from bar;if(condition) {// foo.xxxx
} else {// bar.xxx
} ES6的import语法完美可以使用tree shaking因为可以在代码不运行的情况下就能分析出不需要的代码 CommonJS的动态特性模块意味着tree shaking不适用。因为它是不可能确定哪些模块实际运行之前是需要的或者是不需要的。在ES6中进入了完全静态的导入语法import。这也意味着下面的导入是不可行的
// 不可行ES6 的import是完全静态的
if(condition) {myDynamicModule require(foo);
} else {myDynamicModule require(bar);
}
7 介绍一下 webpack scope hosting 作用域提升将分散的模块划分到同一个作用域中避免了代码的重复引入有效减少打包后的代码体积和运行时的内存损耗 8 Webpack Proxy工作原理为什么能解决跨域
1. 是什么
webpack proxy即webpack提供的代理服务
基本行为就是接收客户端发送的请求后转发给其他服务器
其目的是为了便于开发者在开发模式下解决跨域问题浏览器安全策略限制
想要实现代理首先需要一个中间服务器webpack中提供服务器的工具为webpack-dev-server
2. webpack-dev-server
webpack-dev-server是 webpack 官方推出的一款开发工具将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一起
目的是为了提高开发者日常的开发效率「只适用在开发阶段」
关于配置方面在webpack配置对象属性中通过devServer属性提供如下
// ./webpack.config.js
const path require(path)module.exports {// ...devServer: {contentBase: path.join(__dirname, dist),compress: true,port: 9000,proxy: {/api: {target: https://api.github.com}}// ...}
}devServetr里面proxy则是关于代理的配置该属性为对象的形式对象中每一个属性就是一个代理的规则匹配
属性的名称是需要被代理的请求路径前缀一般为了辨别都会设置前缀为/api值为对应的代理匹配规则对应如下
target表示的是代理到的目标地址pathRewrite默认情况下我们的 /api-hy 也会被写入到URL中如果希望删除可以使用pathRewritesecure默认情况下不接收转发到https的服务器上如果希望支持可以设置为falsechangeOrigin它表示是否更新代理后请求的 headers 中host地址
2. 工作原理 proxy工作原理实质上是利用http-proxy-middleware 这个http代理中间件实现请求转发给其他服务器 举个例子
在开发阶段本地地址为http://localhost:3000该浏览器发送一个前缀带有/api标识的请求到服务端获取数据但响应这个请求的服务器只是将请求转发到另一台服务器中
const express require(express);
const proxy require(http-proxy-middleware);const app express();app.use(/api, proxy({target: http://www.example.org, changeOrigin: true}));
app.listen(3000);// http://localhost:3000/api/foo/bar - http://www.example.org/api/foo/bar
3. 跨域 在开发阶段 webpack-dev-server 会启动一个本地开发服务器所以我们的应用在开发阶段是独立运行在 localhost的一个端口上而后端服务又是运行在另外一个地址上 所以在开发阶段中由于浏览器同源策略的原因当本地访问后端就会出现跨域请求的问题
通过设置webpack proxy实现代理请求后相当于浏览器与服务端中添加一个代理者
当本地发送请求的时候代理服务器响应该请求并将请求转发到目标服务器目标服务器响应数据后再将数据返回给代理服务器最终再由代理服务器将数据响应给本地 在代理服务器传递数据给本地浏览器的过程中两者同源并不存在跨域行为这时候浏览器就能正常接收数据 注意「服务器与服务器之间请求数据并不会存在跨域行为跨域行为是浏览器安全策略限制」 9 介绍一下 babel原理 babel 的编译过程分为三个阶段parsing、transforming、generating以 ES6 编译为 ES5 作为例子 ES6 代码输入babylon 进行解析得到 ASTplugin 用 babel-traverse 对 AST树进行遍历编译得到新的 AST树用 babel-generator 通过 AST树生成 ES5 代码。
Babel原理及其使用(opens new window)
10 介绍一下Rollup Rollup 是一款 ES Modules 打包器。它也可以将项目中散落的细小模块打包为整块代码从而使得这些划分的模块可以更好地运行在浏览器环境或者 Node.js 环境。 Rollup优势
输出结果更加扁平执行效率更高自动移除未引用代码打包结果依然完全可读。
缺点
加载非 ESM 的第三方模块比较复杂因为模块最终都被打包到全局中所以无法实现 HMR浏览器环境中代码拆分功能必须使用 Require.js 这样的 AMD 库 我们发现如果我们开发的是一个应用程序需要大量引用第三方模块同时还需要 HMR 提升开发体验而且应用过大就必须要分包。那这些需求 Rollup 都无法满足。如果我们是开发一个 JavaScript 框架或者库那这些优点就特别有必要而缺点呢几乎也都可以忽略所以在很多像 React 或者 Vue 之类的框架中都是使用的 Rollup 作为模块打包器而并非 Webpack 总结一下Webpack 大而全Rollup 小而美。
在对它们的选择上我的基本原则是应用开发使用 Webpack类库或者框架开发使用 Rollup。
不过这并不是绝对的标准只是经验法则。因为 Rollup 也可用于构建绝大多数应用程序而 Webpack 同样也可以构建类库或者框架。 hash、chunkhash、contenthash区别
如果是hash的话是和整个项目有关的有一处文件发生更改则所有文件的hash值都会发生改变且它们共用一个hash值如果是chunkhash的话只和entry的每个入口文件有关也就是同一个chunk下的文件有所改动该chunk下的文件的hash值就会发生改变如果是contenthash的话和每个生成的文件有关只有当要构建的文件内容发生改变时才会给该文件生成新的hash值并不会影响其它文件。
11 webpack常用插件总结
1. 功能类
1.1 html-webpack-plugin 自动生成html基本用法 new HtmlWebpackPlugin({filename: index.html, // 生成文件名template: path.join(process.cwd(), ./index.html) // 模班文件
})1.2 copy-webpack-plugin 拷贝资源插件 new CopyWebpackPlugin([{from: path.join(process.cwd(), ./vendor/),to: path.join(process.cwd(), ./dist/),ignore: [*.json]}
])1.3 webpack-manifest-plugin assets-webpack-plugin 俩个插件效果一致都是生成编译结果的资源单只是资源单的数据结构不一致而已 webpack-manifest-plugin 基本用法
module.exports {plugins: [new ManifestPlugin()]
}assets-webpack-plugin 基本用法
module.exports {plugins: [new AssetsPlugin()]
}1.4 clean-webpack-plugin 在编译之前清理指定目录指定内容 // 清理目录
const pathsToClean [dist,build
]// 清理参数
const cleanOptions {exclude: [shared.js], // 跳过文件
}
module.exports {// ...plugins: [new CleanWebpackPlugin(pathsToClean, cleanOptions)]
}1.5 compression-webpack-plugin 提供带 Content-Encoding 编码的压缩版的资源 module.exports {plugins: [new CompressionPlugin()]
}1.6 progress-bar-webpack-plugin 编译进度条插件 module.exports {//...plugins: [new ProgressBarPlugin()]
}2. 代码相关类
2.1 webpack.ProvidePlugin 自动加载模块如 $ 出现就会自动加载模块$ 默认为jquery的exports new webpack.ProvidePlugin({$: jquery,
})2.2 webpack.DefinePlugin 定义全局常量 new webpack.DefinePlugin({process.env: {NODE_ENV: JSON.stringify(process.env.NODE_ENV)}
})2.3 mini-css-extract-plugin extract-text-webpack-plugin 提取css样式对比 mini-css-extract-plugin 为webpack4及以上提供的plugin支持css chunkextract-text-webpack-plugin 只能在webpack3 及一下的版本使用不支持css chunk
基本用法 extract-text-webpack-plugin
const ExtractTextPlugin require(extract-text-webpack-plugin);module.exports {module: {rules: [{test: /.css$/,use: ExtractTextPlugin.extract({fallback: style-loader,use: css-loader})}]},plugins: [new ExtractTextPlugin(styles.css),]
}基本用法 mini-css-extract-plugin
const MiniCssExtractPlugin require(mini-css-extract-plugin);
module.exports {module: {rules: [{test: /.css$/,use: [{loader: MiniCssExtractPlugin.loader,options: {publicPath: / // chunk publicPath}},css-loader]}]},plugins: [new MiniCssExtractPlugin({filename: [name].css, // 主文件名chunkFilename: [id].css // chunk文件名})]
}3. 编译结果优化类
3.1 wbepack.IgnorePlugin 忽略regExp匹配的模块 new webpack.IgnorePlugin(/^./locale$/, /moment$/)3.2 uglifyjs-webpack-plugin 代码丑化用于js压缩 module.exports {//...optimization: {minimizer: [new UglifyJsPlugin({cache: true, // 开启缓存parallel: true, // 开启多线程编译sourceMap: true, // 是否sourceMapuglifyOptions: { // 丑化参数comments: false,warnings: false,compress: {unused: true,dead_code: true,collapse_vars: true,reduce_vars: true},output: {comments: false}}}]}
};3.3 optimize-css-assets-webpack-plugin css压缩主要使用 cssnano 压缩器 https://github.com/cssnano/cssnano module.exports {//...optimization: {minimizer: [new OptimizeCssAssetsPlugin({cssProcessor: require(cssnano), // css 压缩优化器cssProcessorOptions: { discardComments: { removeAll: true } } // 去除所有注释})]}
};3.4 webpack-md5-hash 使你的chunk根据内容生成md5用这个md5取代 webpack chunkhash。 var WebpackMd5Hash require(webpack-md5-hash);module.exports {// ...output: {//...chunkFilename: [chunkhash].[id].chunk.js},plugins: [new WebpackMd5Hash()]
};3.5 SplitChunksPlugin
CommonChunkPlugin 的后世用于chunk切割。 webpack 把 chunk 分为两种类型一种是初始加载initial chunk另外一种是异步加载 async chunk如果不配置SplitChunksPluginwebpack会在production的模式下自动开启默认情况下webpack会将 node_modules 下的所有模块定义为异步加载模块并分析你的 entry、动态加载import()、require.ensure模块找出这些模块之间共用的node_modules下的模块并将这些模块提取到单独的chunk中在需要的时候异步加载到页面当中其中默认配置如下 module.exports {//...optimization: {splitChunks: {chunks: async, // 异步加载chunkminSize: 30000,maxSize: 0,minChunks: 1,maxAsyncRequests: 5,maxInitialRequests: 3,automaticNameDelimiter: ~, // 文件名中chunk分隔符name: true,cacheGroups: {vendors: {test: /[\/]node_modules[\/]/, // priority: -10},default: {minChunks: 2, // 最小的共享chunk数priority: -20,reuseExistingChunk: true}}}}
};4. 编译优化类
4.1 DllPlugin DllReferencePlugin autodll-webpack-plugin
dllPlugin将模块预先编译DllReferencePlugin 将预先编译好的模块关联到当前编译中当 webpack 解析到这些模块时会直接使用预先编译好的模块。autodll-webpack-plugin 相当于 dllPlugin 和 DllReferencePlugin 的简化版其实本质也是使用 dllPlugin DllReferencePlugin它会在第一次编译的时候将配置好的需要预先编译的模块编译在缓存中第二次编译的时候解析到这些模块就直接使用缓存而不是去编译这些模块
dllPlugin 基本用法
const output {filename: [name].js,library: [name]_library,path: ./vendor/
}module.exports {entry: {vendor: [react, react-dom] // 我们需要事先编译的模块用entry表示},output: output,plugins: [new webpack.DllPlugin({ // 使用dllPluginpath: path.join(output.path, ${output.filename}.json),name: output.library // 全局变量名 也就是 window 下 的 [output.library]})]
}DllReferencePlugin 基本用法
const manifest path.resolve(process.cwd(), vendor, vendor.js.json)module.exports {plugins: [new webpack.DllReferencePlugin({manifest: require(manifest), // 引进dllPlugin编译的json文件name: vendor_library // 全局变量名与dllPlugin声明的一致}]
}autodll-webpack-plugin 基本用法
module.exports {plugins: [new AutoDllPlugin({inject: true, // 与 html-webpack-plugin 结合使用注入html中filename: [name].js,entry: {vendor: [react,react-dom]}})]
}4.2 happypack thread-loader 多线程编译加快编译速度thread-loader不可以和 mini-css-extract-plugin 结合使用 happypack 基本用法
const HappyPack require(happypack);
const os require(os);
const happyThreadPool HappyPack.ThreadPool({ size: os.cpus().length });
const happyLoaderId happypack-for-react-babel-loader;module.exports {module: {rules: [{test: /.jsx?$/,loader: happypack/loader,query: {id: happyLoaderId},include: [path.resolve(process.cwd(), src)]}]},plugins: [new HappyPack({id: happyLoaderId,threadPool: happyThreadPool,loaders: [babel-loader]})]
}thread-loader 基本用法
module.exports {module: {rules: [{test: /.js$/,include: path.resolve(src),use: [thread-loader,// your expensive loader (e.g babel-loader)babel-loader]}]}
}4.3 hard-source-webpack-plugin cache-loader 使用模块编译缓存加快编译速度 hard-source-webpack-plugin 基本用法
module.exports {plugins: [new HardSourceWebpackPlugin()]
}cache-loader 基本用法
module.exports {module: {rules: [{test: /.ext$/,use: [cache-loader,...loaders],include: path.resolve(src)}]}
}5. 编译分析类
5.1 webpack-bundle-analyzer 编译模块分析插件 new BundleAnalyzerPlugin({analyzerMode: server,analyzerHost: 127.0.0.1,analyzerPort: 8889,reportFilename: report.html,defaultSizes: parsed,generateStatsFile: false,statsFilename: stats.json,statsOptions: null,logLevel: info
}),5.2 stats-webpack-plugin PrefetchPlugin stats-webpack-plugin 将构建的统计信息写入文件该文件可在 http://webpack.github.io/analyse中上传进行编译分析并根据分析结果可使用 PrefetchPlugin 对部分模块进行预解析编译 stats-webpack-plugin 基本用法
module.exports {plugins: [new StatsPlugin(stats.json, {chunkModules: true,exclude: [/node_modules[\/]react/]})]
};PrefetchPlugin 基本用法
module.exports {plugins: [new webpack.PrefetchPlugin(/web/, app/modules/HeaderNav.jsx),new webpack.PrefetchPlugin(/web/, app/pages/FrontPage.jsx)
];
}5.3 speed-measure-webpack-plugin 统计编译过程中各loader和plugin使用的时间 const SpeedMeasurePlugin require(speed-measure-webpack-plugin);const smp new SpeedMeasurePlugin();const webpackConfig {plugins: [new MyPlugin(),new MyOtherPlugin()]
}
module.exports smp.wrap(webpackConfig);12 webpack热更新原理 当修改了一个或多个文件文件系统接收更改并通知 webpackwebpack 重新编译构建一个或多个模块并通知 HMR 服务器进行更新HMR Server 使用 webSocket 通知 HMR runtime 需要更新HMR 运行时通过 HTTP 请求更新 jsonpHMR 运行时替换更新中的模块如果确定这些模块无法更新则触发整个页面刷新
13 webpack原理简述
1.1 核心概念 JavaScript 的 模块打包工具 (module bundler)。通过分析模块之间的依赖最终将所有模块打包成一份或者多份代码包 (bundler)供 HTML 直接引用。实质上Webpack 仅仅提供了 打包功能 和一套 文件处理机制然后通过生态中的各种 Loader 和 Plugin 对代码进行预编译和打包。因此 Webpack 具有高度的可拓展性能更好的发挥社区生态的力量。 Entry: 入口文件Webpack会从该文件开始进行分析与编译Output: 出口路径打包后创建 bundler的文件路径以及文件名Module: 模块在 Webpack 中任何文件都可以作为一个模块会根据配置的不同的 Loader 进行加载和打包Chunk: 代码块可以根据配置将所有模块代码合并成一个或多个代码块以便按需加载提高性能Loader: 模块加载器进行各种文件类型的加载与转换Plugin: 拓展插件可以通过 Webpack 相应的事件钩子介入到打包过程中的任意环节从而对代码按需修改
1.2 工作流程 (加载 - 编译 - 输出)
读取配置文件按命令 初始化 配置参数创建 Compiler 对象调用插件的 apply 方法 挂载插件 监听然后从入口文件开始执行编译按文件类型调用相应的 Loader 对模块进行 编译并在合适的时机点触发对应的事件调用 Plugin 执行最后再根据模块 依赖查找 到所依赖的模块递归执行第三步将编译后的所有代码包装成一个个代码块 (Chunk) 并按依赖和配置确定 输出内容。这个步骤仍然可以通过 Plugin 进行文件的修改;最后根据 Output 把文件内容一一写入到指定的文件夹中完成整个过程
1.3 模块包装
(function(modules) {// 模拟 require 函数从内存中加载模块function __webpack_require__(moduleId) {// 缓存模块if (installedModules[moduleId]) {return installedModules[moduleId].exports;}var module installedModules[moduleId] {i: moduleId,l: false,exports: {}};// 执行代码modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);// Flag: 标记是否加载完成module.l true;return module.exports;}// ...// 开始执行加载入口文件return __webpack_require__(__webpack_require__.s ./src/index.js);})({./src/index.js: function (module, __webpack_exports__, __webpack_require__) {// 使用 eval 执行编译后的代码// 继续递归引用模块内部依赖// 实际情况并不是使用模板字符串这里是为了代码的可读性eval(__webpack_require__.r(__webpack_exports__);//var _test__WEBPACK_IMPORTED_MODULE_0__ __webpack_require__(test, ./src/test.js););},./src/test.js: function (module, __webpack_exports__, __webpack_require__) {// ...},})总结:
模块机制: webpack自己实现了一套模拟模块的机制将其包裹于业务代码的外部从而提供了一套模块机制文件编译: webpack规定了一套编译规则通过 Loader 和 Plugin以管道的形式对文件字符串进行处理
1.4 webpack的打包原理
初始化参数从配置文件和 Shell 语句中读取与合并参数得出最终的参数开始编译用上一步得到的参数初始化 Compiler 对象加载所有配置的插件执行对象的 run 方法开始执行编译确定入口根据配置中的 entry 找出所有的入口文件编译模块从入口文件出发调用所有配置的 Loader 对模块进行翻译再找出该模块依赖的模块再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理完成模块编译在经过第4步使用 Loader 翻译完所有模块后得到了每个模块被翻译后的最终内容以及它们之间的依赖关系输出资源根据入口和模块之间的依赖关系组装成一个个包含多个模块的 Chunk再把每个 Chunk 转换成一个单独的文件加入到输出列表这步是可以修改输出内容的最后机会输出完成在确定好输出内容后根据配置确定输出的路径和文件名把文件内容写入到文件系统
1.5 webpack的打包原理详细
相关问题
webpack 工作流程是怎样的webpack 在不同阶段做了什么事情
webpack 是一种模块打包工具可以将各类型的资源例如图片、CSS、JS 等转译组合为 JS 格式的 bundle 文件
webpack 构建的核心任务是完成内容转化和资源合并。主要包含以下 3 个阶段
初始化阶段
初始化参数从配置文件、配置对象和 Shell 参数中读取并与默认参数进行合并组合成最终使用的参数创建编译对象用上一步得到的参数创建 Compiler 对象。初始化编译环境包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等
构建阶段
开始编译执行 Compiler 对象的 run 方法创建 Compilation 对象。确认编译入口进入 entryOption 阶段读取配置的 Entries递归遍历所有的入口文件调用 Compilation.addEntry 将入口文件转换为 Dependency 对象。编译模块make 调用 normalModule 中的 build 开启构建从 entry 文件开始调用 loader 对模块进行转译处理然后调用 JS 解释器acorn将内容转化为 AST 对象然后递归分析依赖依次处理全部文件。完成模块编译在上一步处理好所有模块后得到模块编译产物和依赖关系图
生成阶段
输出资源seal 根据入口和模块之间的依赖关系组装成多个包含多个模块的 Chunk再把每个 Chunk 转换成一个 Asset 加入到输出列表这步是可以修改输出内容的最后机会。写入文件系统emitAssets 确定好输出内容后根据配置的 output 将内容写入文件系统
知识点深入
1. webpack 初始化过程
从 webpack 项目 webpack.config.js 文件 webpack 方法出发可以看到初始化过程如下 将命令行参数和用户的配置文件进行合并 调用 getValidateSchema 对配置进行校验 调用 createCompiler 创建 Compiler 对象 将用户配置和默认配置进行合并处理实例化 Compiler实例化 NodeEnvironmentPlugin处理用户配置的 plugins执行 plugin 的 apply 方法。触发 environment 和 afterEnvironment 上注册的事件。注册 webpack 内部插件。触发 initialize 事件
// lib/webpack.js 122 行 部分代码省略处理
const create () {if (!webpackOptionsSchemaCheck(options)) {// 校验参数getValidateSchema()(webpackOptionsSchema, options);}// 创建 compiler 对象compiler createCompiler(webpackOptions);
};// lib/webpack.js 57 行
const createCompiler (rawOptions) {// 统一合并处理参数const options getNormalizedWebpackOptions(rawOptions);applyWebpackOptionsBaseDefaults(options);// 实例化 compilerconst compiler new Compiler(options.context);// 把 options 挂载到对象上compiler.options options;// NodeEnvironmentPlugin 是对 fs 模块的封装用来处理文件输入输出等new NodeEnvironmentPlugin({infrastructureLogging: options.infrastructureLogging,}).apply(compiler);// 注册用户配置插件if (Array.isArray(options.plugins)) {for (const plugin of options.plugins) {if (typeof plugin function) {plugin.call(compiler, compiler);} else {plugin.apply(compiler);}}}applyWebpackOptionsDefaults(options);// 触发 environment 和 afterEnvironment 上注册的事件compiler.hooks.environment.call();compiler.hooks.afterEnvironment.call();// 注册 webpack 内置插件new WebpackOptionsApply().process(options, compiler);compiler.hooks.initialize.call();return compiler;
};2. webpack 构建阶段做了什么
在 webpack 函数执行完之后就到主要的构建阶段首先执行 compiler.run()然后触发一系列钩子函数执行 compiler.compile() 在实例化 compiler 之后执行 compiler.run() 执行 newCompilation 函数调用 createCompilation 初始化 Compilation 对象 执行 _addEntryItem 将入口文件存入 this.entriesmap 对象遍历 this.entries 对象构建 chunk。 执行 handleModuleCreation开始创建模块实例。 执行 moduleFactory.create 创建模块 执行 factory.hooks.factorize.call 钩子然后会调用 ExternalModuleFactoryPlugin 中注册的钩子用于配置外部文件的模块加载方式使用 enhanced-resolve 解析模块和 loader 的真实绝对路径执行 new NormalModule() 创建 module 实例 执行 addModule存储 module 执行 buildModule添加模块到模块队列 buildQueue开始构建模块, 这里会调用 normalModule 中的 build 开启构建 创建 loader 上下文。执行 runLoaders通过 enhanced-resolve 解析得到的模块和 loader 的路径获取函数执行 loader。生成模块的 hash 所有依赖都解析完毕后构建阶段结束 // 构建过程涉及流程比较复杂代码会做省略// lib/webpack.js 1284行// 开启编译流程compiler.run((err, stats) {compiler.close(err2 {callback(err || err2, stats);});});// lib/compiler.js 1081行// 开启编译流程compile(callback) {const params this.newCompilationParams();// 创建 Compilation 对象const Compilation this.newCompilation(params);}// lib/Compilation.js 1865行// 确认入口文件addEntry() {this._addEntryItem();}// lib/Compilation.js 1834行// 开始创建模块流程创建模块实例addModuleTree() {this.handleModuleCreation()}// lib/Compilation.js 1548行// 开始创建模块流程创建模块实例handleModuleCreation() {this.factorizeModule()}// lib/Compilation.js 1712行// 添加到创建模块队列执行创建模块factorizeModule(options, callback) {this.factorizeQueue.add(options, callback);}// lib/Compilation.js 1834行// 保存需要构建模块_addModule(module, callback) {this.modules.add(module);}// lib/Compilation.js 1284行// 添加模块进模块编译队列开始编译buildModule(module, callback) {this.buildQueue.add(module, callback);}3. webpack 生成阶段做了什么 构建阶段围绕 module 展开生成阶段则围绕 chunks 展开。经过构建阶段之后webpack 得到足够的模块内容与模块关系信息之后通过 Compilation.seal 函数生成最终资源 3.1 生成产物
执行 Compilation.seal 进行产物的封装 构建本次编译的 ChunkGraph 对象执行 buildChunkGraph这里会将 import()、require.ensure 等方法生成的动态模块添加到 chunks 中 遍历 Compilation.modules 集合将 module 按 entry/动态引入 的规则分配给不同的 Chunk 对象。 调用 Compilation.emitAssets 方法将 assets 信息记录到 Compilation.assets 对象中。 执行 hooks.optimizeChunkModules 的钩子这里开始进行代码生成和封装。 执行一系列钩子函数reviveModules, moduleId, optimizeChunkIds 等执行 createModuleHashes 更新模块 hash执行 JavascriptGenerator 生成模块代码这里会遍历 modules创建构建任务循环使用 JavascriptGenerator 构建代码这时会将 import 等模块引入方式替换为 webpack_require 等并将生成结果存入缓存执行 processRuntimeRequirements根据生成的内容所使用到的 webpack_require 的函数添加对应的代码执行 createHash 创建 chunk 的 hash执行 clearAssets 清除 chunk 的 files 和 auxiliary这里缓存的是生成的 chunk 的文件名主要是清除上次构建产生的废弃内容
3.2 文件输出
回到 Compiler 的流程中执行 onCompiled 回调。
触发 shouldEmit 钩子函数这里是最后能优化产物的钩子。遍历 module 集合根据 entry 配置及引入资源的方式将 module 分配到不同的 chunk。遍历 chunk 集合调用 Compilation.emitAsset 方法标记 chunk 的输出规则即转化为 assets 集合。写入本地文件用的是 webpack 函数执行时初始化的文件流工具。执行 done 钩子函数这里会执行 compiler.run() 的回调再执行 compiler.close()然后执行持久化存储前提是使用的 filesystem 缓存模式
1.6 总结
初始化参数从配置文件和 Shell 语句中读取并合并参数得出最终的配置参数。开始编译从上一步得到的参数初始化 Compiler 对象加载所有配置的插件执行对象的 run 方法开始执行编译。确定入口根scope据配置中的 entry 找出所有的入口文件。编译模块从入口文件出发调用所有配置的 loader 对模块进行翻译再找出该模块依赖的模块这个步骤是递归执行的直至所有入口依赖的模块文件都经过本步骤的处理。完成模块编译经过第 4 步使用 loader 翻译完所有模块后得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。输出资源根据入口和模块之间的依赖关系组装成一个个包含多个模块的 chunk再把每个 chunk 转换成一个单独的文件加入到输出列表这一步是可以修改输出内容的最后机会。输出完成在确定好输出内容后根据配置确定输出的路径和文件名把文件内容写入到文件系统。
14 webpack性能优化-构建速度 先分析遇到哪些问题在配合下面的方法优化不要上来就回答让人觉得背面试题 优化babel-loader缓存 IgnorePlugin 忽略某些包避免引入无用模块直接不引入需要在代码中引入 import moment from moment默认会引入所有语言JS代码代码过大 import moment from moment
moment.locale(zh-cn) // 设置语言为中文// 手动引入中文语言包
import moment/locale/zh-cn// webpack.prod.js
pluins: [// 忽略 moment 下的 /locale 目录new webpack.IgnorePlugin(/./locale/, /moment/),
]noParse 避免重复打包引入但不打包 happyPack多线程打包 JS单线程的开启多进程打包提高构建速度(特别是多核CPU) // webpack.prod.jsconst HappyPack require(happypack){module: {rules: [// js{test: /.js$/,// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例use: [happypack/loader?idbabel],include: srcPath,// exclude: /node_modules/},]},plugins: [// happyPack 开启多进程打包new HappyPack({// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件id: babel,// 如何处理 .js 文件用法和 Loader 配置中一样loaders: [babel-loader?cacheDirectory]}),]}parallelUglifyPlugin多进程压缩JS 关于多进程 项目较大打包较慢开启多进程能提高速度项目较小打包很快开启多进程反而会降低速度进程开销按需使用 // webpack.prod.js
const ParallelUglifyPlugin require(webpack-parallel-uglify-plugin){plugins: [// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码new ParallelUglifyPlugin({// 传递给 UglifyJS 的参数// 还是使用 UglifyJS 压缩只不过帮助开启了多进程uglifyJS: {output: {beautify: false, // 最紧凑的输出comments: false, // 删除所有的注释},compress: {// 删除所有的 console 语句可以兼容ie浏览器drop_console: true,// 内嵌定义了但是只用到一次的变量collapse_vars: true,// 提取出出现多次但是没有定义成变量去引用的静态值reduce_vars: true,}}})]
} 自动刷新开发环境使用dev-server即可 热更新开发环境 自动刷新整个网页全部刷新速度较慢状态会丢失 热更新新代码生效网页不刷新状态不丢失 // webpack.dev.js
const HotModuleReplacementPlugin require(webpack/lib/HotModuleReplacementPlugin);entry: {// index: path.join(srcPath, index.js),index: [webpack-dev-server/client?http://localhost:8080/,webpack/hot/dev-server,path.join(srcPath, index.js)],other: path.join(srcPath, other.js)
},
devServer: {hot: true
},
plugins: [new HotModuleReplacementPlugin()
], // 代码中index.js// 增加开启热更新之后的代码逻辑
if (module.hot) {// 注册哪些模块需要热更新module.hot.accept([./math], () {const sumRes sum(10, 30)console.log(sumRes in hot, sumRes)})
} DllPlugin 动态链接库dllPlugin只适用于开发环境,因为生产环境下打包一次就完了,没有必要用于生产环境 前端框架如react、vue体积大构建慢 较稳定不常升级版本同一个版本只构建一次不用每次都重新构建 webpack已内置DllPlugin不需要安装 DllPlugin打包出dll文件 DllReferencePlugin引用dll文件 // webpack.common.js
const path require(path)
const HtmlWebpackPlugin require(html-webpack-plugin)
const { srcPath, distPath } require(./paths)module.exports {entry: path.join(srcPath, index),module: {rules: [{test: /.js$/,use: [babel-loader],include: srcPath,exclude: /node_modules/},]},plugins: [new HtmlWebpackPlugin({template: path.join(srcPath, index.html),filename: index.html})]
} // webpack.dev.js
const path require(path)
const webpack require(webpack)
const { merge } require(webpack-merge)
const webpackCommonConf require(./webpack.common.js)
const { srcPath, distPath } require(./paths)// 第一引入 DllReferencePlugin
const DllReferencePlugin require(webpack/lib/DllReferencePlugin);module.exports merge(webpackCommonConf, {mode: development,module: {rules: [{test: /.js$/,use: [babel-loader],include: srcPath,exclude: /node_modules/ // 第二不要再转换 node_modules 的代码},]},plugins: [new webpack.DefinePlugin({// window.ENV productionENV: JSON.stringify(development)}),// 第三告诉 Webpack 使用了哪些动态链接库new DllReferencePlugin({// 描述 react 动态链接库的文件内容manifest: require(path.join(distPath, react.manifest.json)),}),],devServer: {port: 8080,progress: true, // 显示打包的进度条contentBase: distPath, // 根目录open: true, // 自动打开浏览器compress: true, // 启动 gzip 压缩// 设置代理proxy: {// 将本地 /api/xxx 代理到 localhost:3000/api/xxx/api: http://localhost:3000,// 将本地 /api2/xxx 代理到 localhost:3000/xxx/api2: {target: http://localhost:3000,pathRewrite: {/api2: }}}}
}) // webpack.prod.js
const path require(path)
const webpack require(webpack)
const webpackCommonConf require(./webpack.common.js)
const { merge } require(webpack-merge)
const { srcPath, distPath } require(./paths)module.exports merge(webpackCommonConf, {mode: production,output: {filename: bundle.[contenthash:8].js, // 打包代码时加上 hash 戳path: distPath,// publicPath: http://cdn.abc.com // 修改所有静态文件 url 的前缀如 cdn 域名这里暂时用不到},plugins: [new webpack.DefinePlugin({// window.ENV productionENV: JSON.stringify(production)})]
}) // webpack.dll.jsconst path require(path)
const DllPlugin require(webpack/lib/DllPlugin)
const { srcPath, distPath } require(./paths)module.exports {
mode: development,
// JS 执行入口文件
entry: {// 把 React 相关模块的放到一个单独的动态链接库react: [react, react-dom]
},
output: {// 输出的动态链接库的文件名称[name] 代表当前动态链接库的名称// 也就是 entry 中配置的 react 和 polyfillfilename: [name].dll.js,// 输出的文件都放到 dist 目录下path: distPath,// 存放动态链接库的全局变量名称例如对应 react 来说就是 _dll_react// 之所以在前面加上 _dll_ 是为了防止全局变量冲突library: _dll_[name],
},
plugins: [// 接入 DllPluginnew DllPlugin({// 动态链接库的全局变量名称需要和 output.library 中保持一致// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值// 例如 react.manifest.json 中就有 name: _dll_reactname: _dll_[name],// 描述动态链接库的 manifest.json 文件输出时的文件名称path: path.join(distPath, [name].manifest.json),}),
],
} scripts: {dev: webpack serve --config build/webpack.dev.js,dll: webpack --config build/webpack.dll.js
},
优化打包速度完整代码
// webpack.common.jsconst path require(path)
const HtmlWebpackPlugin require(html-webpack-plugin)
const { srcPath, distPath } require(./paths)module.exports {entry: {index: path.join(srcPath, index.js),other: path.join(srcPath, other.js)},module: {rules: [// babel-loader]},plugins: [// new HtmlWebpackPlugin({// template: path.join(srcPath, index.html),// filename: index.html// })// 多入口 - 生成 index.htmlnew HtmlWebpackPlugin({template: path.join(srcPath, index.html),filename: index.html,// chunks 表示该页面要引用哪些 chunk 即上面的 index 和 other默认全部引用chunks: [index, vendor, common] // 要考虑代码分割}),// 多入口 - 生成 other.htmlnew HtmlWebpackPlugin({template: path.join(srcPath, other.html),filename: other.html,chunks: [other, vendor, common] // 考虑代码分割})]
}// webpack.dev.js
const path require(path)
const webpack require(webpack)
const webpackCommonConf require(./webpack.common.js)
const { smart } require(webpack-merge)
const { srcPath, distPath } require(./paths)
const HotModuleReplacementPlugin require(webpack/lib/HotModuleReplacementPlugin);module.exports smart(webpackCommonConf, {mode: development,entry: {// index: path.join(srcPath, index.js),index: [webpack-dev-server/client?http://localhost:8080/,webpack/hot/dev-server,path.join(srcPath, index.js)],other: path.join(srcPath, other.js)},module: {rules: [{test: /.js$/,loader: [babel-loader?cacheDirectory],include: srcPath,// exclude: /node_modules/},// 直接引入图片 url{test: /.(png|jpg|jpeg|gif)$/,use: file-loader},// {// test: /.css$/,// // loader 的执行顺序是从后往前// loader: [style-loader, css-loader]// },{test: /.css$/,// loader 的执行顺序是从后往前loader: [style-loader, css-loader, postcss-loader] // 加了 postcss},{test: /.less$/,// 增加 less-loader 注意顺序loader: [style-loader, css-loader, less-loader]}]},plugins: [new webpack.DefinePlugin({// window.ENV productionENV: JSON.stringify(development)}),new HotModuleReplacementPlugin()],devServer: {port: 8080,progress: true, // 显示打包的进度条contentBase: distPath, // 根目录open: true, // 自动打开浏览器compress: true, // 启动 gzip 压缩hot: true,// 设置代理proxy: {// 将本地 /api/xxx 代理到 localhost:3000/api/xxx/api: http://localhost:3000,// 将本地 /api2/xxx 代理到 localhost:3000/xxx/api2: {target: http://localhost:3000,pathRewrite: {/api2: }}}},// watch: true, // 开启监听默认为 false// watchOptions: {// ignored: /node_modules/, // 忽略哪些// // 监听到变化发生后会等300ms再去执行动作防止文件更新太快导致重新编译频率太高// // 默认为 300ms// aggregateTimeout: 300,// // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的// // 默认每隔1000毫秒询问一次// poll: 1000// }
})// webpack.prod.js
const path require(path)
const webpack require(webpack)
const { smart } require(webpack-merge)
const { CleanWebpackPlugin } require(clean-webpack-plugin)
const MiniCssExtractPlugin require(mini-css-extract-plugin)
const TerserJSPlugin require(terser-webpack-plugin)
const OptimizeCSSAssetsPlugin require(optimize-css-assets-webpack-plugin)
const HappyPack require(happypack)
const ParallelUglifyPlugin require(webpack-parallel-uglify-plugin)
const webpackCommonConf require(./webpack.common.js)
const { srcPath, distPath } require(./paths)module.exports smart(webpackCommonConf, {mode: production,output: {// filename: bundle.[contentHash:8].js, // 打包代码时加上 hash 戳filename: [name].[contentHash:8].js, // name 即多入口时 entry 的 keypath: distPath,// publicPath: http://cdn.abc.com // 修改所有静态文件 url 的前缀如 cdn 域名这里暂时用不到},module: {rules: [// js{test: /.js$/,// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例use: [happypack/loader?idbabel],include: srcPath,// exclude: /node_modules/},// 图片 - 考虑 base64 编码的情况{test: /.(png|jpg|jpeg|gif)$/,use: {loader: url-loader,options: {// 小于 5kb 的图片用 base64 格式产出// 否则依然延用 file-loader 的形式产出 url 格式limit: 5 * 1024,// 打包到 img 目录下outputPath: /img1/,// 设置图片的 cdn 地址也可以统一在外面的 output 中设置那将作用于所有静态资源// publicPath: http://cdn.abc.com}}},// 抽离 css{test: /.css$/,loader: [MiniCssExtractPlugin.loader, // 注意这里不再用 style-loadercss-loader,postcss-loader]},// 抽离 less{test: /.less$/,loader: [MiniCssExtractPlugin.loader, // 注意这里不再用 style-loadercss-loader,less-loader,postcss-loader]}]},plugins: [new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹new webpack.DefinePlugin({// window.ENV productionENV: JSON.stringify(production)}),// 抽离 css 文件new MiniCssExtractPlugin({filename: css/main.[contentHash:8].css}),// 忽略 moment 下的 /locale 目录new webpack.IgnorePlugin(/./locale/, /moment/),// happyPack 开启多进程打包new HappyPack({// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件id: babel,// 如何处理 .js 文件用法和 Loader 配置中一样loaders: [babel-loader?cacheDirectory]}),// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码new ParallelUglifyPlugin({// 传递给 UglifyJS 的参数// 还是使用 UglifyJS 压缩只不过帮助开启了多进程uglifyJS: {output: {beautify: false, // 最紧凑的输出comments: false, // 删除所有的注释},compress: {// 删除所有的 console 语句可以兼容ie浏览器drop_console: true,// 内嵌定义了但是只用到一次的变量collapse_vars: true,// 提取出出现多次但是没有定义成变量去引用的静态值reduce_vars: true,}}})],optimization: {// 压缩 cssminimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],// 分割代码块splitChunks: {chunks: all,/*** initial 入口chunk对于异步导入的文件不处理async 异步chunk只对异步导入的文件处理all 全部chunk*/// 缓存分组cacheGroups: {// 第三方模块vendor: {name: vendor, // chunk 名称priority: 1, // 权限更高优先抽离重要test: /node_modules/,minSize: 0, // 大小限制minChunks: 1 // 最少复用过几次},// 公共的模块common: {name: common, // chunk 名称priority: 0, // 优先级minSize: 0, // 公共模块的大小限制minChunks: 2 // 公共模块最少复用过几次}}}}
})webpack性能优化-产出代码线上运行
前言
体积更小合理分包不重复加载速度更快、内存使用更少
产出代码优化
小图片base64编码减少http请求
// 图片 - 考虑 base64 编码的情况
module: {rules: [{test: /.(png|jpg|jpeg|gif)$/,use: {loader: url-loader,options: {// 小于 5kb 的图片用 base64 格式产出// 否则依然延用 file-loader 的形式产出 url 格式limit: 5 * 1024,// 打包到 img 目录下outputPath: /img1/,// 设置图片的 cdn 地址也可以统一在外面的 output 中设置那将作用于所有静态资源// publicPath: http://cdn.abc.com}}},]
}bundle加contenthash有利于浏览器缓存 懒加载import()语法减少首屏加载时间 提取公共代码第三方代码Vue、React、loadash等没有必要多次打包可以提取到vendor中 IgnorePlugin忽略不需要的包如moment多语言减少打包的代码 使用CDN加速减少资源加载时间 output: {filename: [name].[contentHash:8].js, // name 即多入口时 entry 的 keypath: path.join(__dirname, .., dist),// 修改所有静态文件 url 的前缀如 cdn 域名// 这样index.html中引入的js、css、图片等资源都会加上这个前缀publicPath: http://cdn.abc.com
},webpack使用production模式mode: production 自动压缩代码 启动Tree Shaking ES6模块化import和exportwebpack会自动识别才会生效 Commonjs模块化require和module.exportswebpack无法识别不会生效 ES6模块和Commonjs模块区别 ES6模块是静态引入编译时引入Commonjs是动态引入执行时引入只有ES6 Module才能静态分析实现Tree Shaking Scope Hoisting是webpack3引入的一个新特性它会分析出模块之间的依赖关系尽可能地把打散的模块合并到一个函数中去减少代码间的引用从而减少代码体积 减少代码体积创建函数作用域更少代码可读性更好