1. webpack是干什么的
- 是什么
- 核心功能
- 具体的核心功能拆分 模块化管理、编译和转换、代码分割和懒加载、资源优化、热更新与开发环境支持、自定义扩展
Webpack 前端使用最流行的前端静态资源模块打包工具,它的核心功能是将项目中的各种静态资源(如JavaScript、CSS、图片、字体等)当作模块处理,通过一系列的加载器(loaders)和插件(plugins)对这些模块进行转义、压缩、 Tree Shaking等处理方式后,利用模块化管理将它们有效地打包成一个或多个bundle文件,以供Web应用在浏览器中运行。
具体来说,Webpack的主要作用包括:
- 模块化管理:(4 大)支持CommonJS、AMD、ES6模块规范以及其他自定义模块格式,使得前端开发可以采用模块化的方式来组织和管理代码。
- 编译和转换:通过加载器,Webpack可以将各种类型的文件转化为浏览器可识别的格式,例如将ES6+的语法转换为ES5,将SCSS或Less编译成CSS,将图片和字体文件进行Base64编码或Url-loader处理等。
- 代码分割和懒加载:Webpack能够将代码分割成多个chunk,实现按需加载和动态导入,以提高应用的初始加载速度和运行时性能。
- 资源优化:通过minimizer插件(如UglifyJS或TerserWebpackPlugin)进行代码压缩,去除注释和空白字符,通过Tree Shaking移除未使用的代码,还可以对CSS进行提取和压缩,图片进行压缩优化等。
- 热更新与开发环境支持:Webpack Dev Server提供快速的开发环境支持,包括自动重新编译、热模块替换等功能,极大地提高了开发效率。
- 自定义扩展:Webpack具有高度的可扩展性,开发者可以通过编写自定义插件来实现更复杂的构建流程和任务处理。
2. Webpack构建流程
- 初始化参数:
-
- Webpack首先会从配置文件(如webpack.config.js)和Shell语句中读取与合并参数,得出最终的构建参数。这些参数指导Webpack在后续步骤中的行为。
- entry:源码的入口文件,webpack打包的第一步
- output:输入目录,即源码经过webpack处理之后,最终生成的文件
- loader:处理非javascript文件(webpack自身只理解javascipt)
- plugin:在webpack的构建过程中处理其他任务,优化输出结果
- 开始编译:
-
- 使用上一步得到的参数,Webpack会初始化一个Compiler对象。这个对象负责整个Webpack的构建过程。
- 注册所有的插件plugins,插件开始监听webpack构建过程的生命周期事件,不同环节会有相应的处理,然后开始执行编译。
- 在此阶段,Webpack还会加载所有配置的插件,并准备执行编译。插件是Webpack功能扩展的重要机制,它们可以在构建过程中的特定时机介入,改变或增强Webpack的默认行为。
- 从入口文件开始遍历构建:
-
- 根据配置中的entry字段,Webpack会找出所有的入口文件。入口文件是Webpack构建过程的起点,它指示了Webpack应该从哪些文件开始构建依赖图。
- 编译模块:
-
- 从入口文件出发,Webpack会逐层识别模块的依赖关系。这包括CommonJS、AMD、ES6的import等语句,都会被Webpack识别和分析。
- 对于每个依赖的模块,Webpack会根据文件类型和配置的Loader进行转换。Loader负责将特定类型的文件转换成Webpack能够处理的模块格式。例如,对于ES6语法,可以使用babel-loader进行转换。
- 在此过程中,Webpack会递归地处理每个模块的依赖,直到所有入口依赖的文件都经过了处理。
- 树摇通常是在 loader 执行后进行的,具体来说,它是在 webpack 打包过程的优化阶段进行的。在 loader 执行后,webpack 会对生成的抽象语法树(AST)进行遍历和分析,识别出未被引用的代码,并将其删除,从而实现树摇。
- 完成模块编译:
-
- 在经过Loader转换所有模块后,Webpack会得到每个模块被转换后的最终内容以及它们之间的依赖关系。这些信息被存储在内存中,供后续步骤使用。
- 输出资源:
-
- 根据入口和模块之间的依赖关系,Webpack会组装成一个个包含多个模块的Chunk。每个Chunk对应一个输出文件,它包含了该Chunk中所有模块的代码。
- 在此阶段,Webpack还会根据配置对输出内容进行最后的修改,如代码压缩、公共模块提取等。这些操作可以通过插件来实现。
- 输出完成:
-
- 最后,Webpack会根据配置确定输出的路径和文件名,将文件内容写入到文件系统中。至此,Webpack的构建流程就完成了。
3. Loader和Plugin的区别
在Webpack中,Loader和Plugin是两个不同的概念,它们的作用和使用方式也有所不同。
- Loader:意为“加载器”,由于Webpack本身只能打包JS文件,对于非JS文件(如CSS、图片等)无法直接打包,因此需要引入第三方的模块进行打包。Loader扩展了Webpack,专注于转化文件这一个领域,主要作用是进行文件转化,完成压缩/打包/语言翻译等,以实现对非JS文件的打包。Loader在module.rules中配置,也就是说它作为模块的解析规则而存在。例如,处理CSS文件需要使用css-loader,处理图片需要使用file-loader等。使用Loader可以实现代码转换、文件处理、代码压缩等功能。
- Plugin:意为“插件”,主要用于扩展Webpack的功能,可以访问整个编译生命周期,包括加载器已经执行完毕之后的时期。Plugin的目标在于解决Loader无法实现的其他事。它针对打包过程中的某些事件节点执行自定义操作,如生成HTML文件、压缩代码、提取公共代码等。使用Plugin可以实现Webpack无法处理的复杂任务。Plugin在plugins中单独配置,类型可以是函数或带有apply方法的类。
总的来说,Loader主要用于文件的加载和转化,将非JS文件转化为Webpack可处理的模块;而Plugin则用于扩展Webpack的功能,对打包过程进行增强和优化。
Loader为专门解决某个子问题(eslint、scss)的加载器
Plugin 服务于整个webpack的生命周期
- define-plugin :定义环境变量 (Webpack4 之后指定 mode 会自动配置)
- ignore-plugin:忽略部分文件
- Loader:Webpack将一切文件视为模块,但是Webpack原生只能解析JS/JSON文件。Loader的作用是让Webpack拥有了加载和解析非JavaScript文件的能力。Loader可以将文件从不同的语言或格式转换为Webpack可以处理的模块,例如将TypeScript转换为JavaScript,或将内联图像转换为data URL。Loader是一个转换器,专注于文件的编译和转换。
-
- 核心是将 webpack 不能识别的模块转换成能识别的模块
- 附加功能 :添加版权注释、图片太大转 base64
- Plugin:Plugin可以扩展Webpack的功能,让Webpack具有更多的灵活性。Plugin在Webpack的编译和打包过程中起着关键作用,可以访问整个编译生命周期,包括加载器已经执行完毕之后的时期。Plugin可以执行范围更广的任务,如打包优化、资源管理和环境变量注入等。Plugin是一个扩展器,它丰富了Webpack本身,针对的是加载器结束后,Webpack打包的整个过程。
- Loader:在Webpack的配置文件(通常是webpack.config.js)中,Loader在module.rules中配置。它们作为模块的解析规则而存在,以数组的形式定义,每个数组项是一个对象,描述了对于什么类型的文件(test属性)使用什么加载器(loader属性)以及使用的参数(options属性)。
- Plugin:Plugin也在Webpack的配置文件中配置,但在plugins字段中单独配置。它们以数组的形式存在,每个数组项是一个Plugin的实例,参数都通过构造函数传入。Plugin并不直接操作文件,而是基于事件机制工作,会监听Webpack打包过程中的某些节点,然后执行特定的任务。
Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。
将接收内容转译预处理为 webpack能够识别的js
从右向左开始编译:出于设计上的考虑,这一决策遵循了函数式编程中的思想
Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
- Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。
- Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。
- loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
- plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事
- loader 运行在打包文件之前
- plugins 在整个编译周期都起作用
- 在Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过Webpack提供的 API改变输出结果
- 对于loader,实质是一个转换器,将A文件进行编译形成B文件,操作的是文件,比如将A.scss或A.less转变为B.css,单纯的文件转换过程
4. Plugin 可以在loader 执行前执行吗
loader的执行发生在构建过程的开始阶段,当Webpack解析到需要加载的模块时,会根据配置文件中定义的loader来处理这些模块。加载器的执行顺序是从右到左,或者从下到上,这取决于配置文件中的定义。
Plugin的执行时机则更加灵活,它们可以监听Webpack编译器发出的事件,并在这些事件发生时执行自定义逻辑。这意味着Plugin可以在加载器执行之前、之后或甚至与loader并行执行 ,具体取决于插件所监听的事件和Webpack的生命周期阶段。
通常情况下,loader会在Plugin之前执行,因为loader负责将源文件转换为Webpack能够处理的模块,而插件则在这些模块已经处理完毕后的某个阶段执行自定义任务。尽管如此,有些插件可能会在加载器执行之前的某些阶段进行工作,比如修改Webpack的配置或处理环境变量等。
以下的 Plugin 都是在 loader 之前就开始执行 :
- IgnorePlugin:
在打包过程中排除某些特定的模块,防止它们被打包进去。这个插件在解析模块之前就运行了,因此可以在加载器处理文件之前阻止某些模块的加载。 - ProvidePlugin:
自动加载模块,而不必到处import或require它们。这在加载器处理文件之前就已经设置好了,使得在源代码中可以直接使用某些模块而无需显式导入。 - EnvironmentPlugin:
这个插件可以从环境变量中创建全局变量。它在加载器处理文件之前就已经执行,并将环境变量暴露给Webpack配置和源代码。
在 loader 中执行工作
DefinePlugin:
允许在编译时创建配置的全局常量。这可以对开发模式和发布模式下的构建进行区分,并允许在编译时对环境进行更细致的控制。这个插件在加载器转换文件之前就已经运行了。
在 loader 结束后执行的 plugin
- CleanWebpackPlugin
- MiniCssExtractPlugin 合并 css 文件
- HtmlWebpackPlugin
5. ts-loader 做了哪些事
- 识别与加载: 当Webpack配置中设置了对.ts或.tsx文件的处理规则,并指定了使用ts-loader时,Webpack在编译过程中遇到这些文件时,会调用ts-loader。
- 类型检查: ts-loader首先会对TypeScript文件进行类型检查(type checking)。它会读取项目的tsconfig.json配置文件,按照其中的规则对TypeScript源代码进行编译前的类型检查。如果有类型错误,编译过程会终止,并报告错误信息。
- 编译转换: 通过调用TypeScript编译器(tsc),ts-loader将TypeScript源代码转换为JavaScript ES5或ES6(取决于tsconfig.json中的编译目标和模块选项)。这个过程中,高级的TypeScript特性(如泛型、装饰器、枚举、接口等)会被编译成JavaScript基础结构。
- 生成.d.ts声明文件 *(可选): 如果配置允许, ts-loader 还会生成相应的 .d.ts 声明文件,为编译后的JavaScript提供类型信息。
- 输出JavaScript文件: 编译后的JavaScript代码会被ts-loader传递回Webpack的编译流程中,Webpack接着会根据配置将这些代码和其他资源一同打包到bundle文件中。
- 与其他Webpack插件协同工作: ts-loader与Webpack生态中的其他插件可以很好地协同工作,例如与 source-map-loader 一起生成Source Map,以便于调试;或与 eslint-loader 结合,对TypeScript代码进行ESLint的风格检查。
6. 常用的plugins
- CleanWebpackPlugin是在项目打包时先将上一次所打包的文件夹进行一次清除后再去打包的一种插件
- HtmlWebpackPlugin插件能够在打包后帮助我们生成html文件,因为webpack打包是没有帮我们生成html文件的,所以我们需要这个插件帮助我们生成html文件,同时也可以对html文件进行一些另外的配置
- define-plugin:定义环境变量 (Webpack4 之后指定 mode 会自动配置)
- ignore-plugin:忽略部分文件
- html-webpack-plugin:简化 HTML 文件创建 (依赖于 html-loader)
- web-webpack-plugin:可方便地为单页应用输出 HTML,比 html-webpack-plugin 好用
- uglifyjs-webpack-plugin:不支持 ES6 压缩 (Webpack4 以前)
- terser-webpack-plugin: 支持压缩 ES6 (Webpack4)
- webpack-parallel-uglify-plugin: 多进程执行代码压缩,提升构建速度
- mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载 (替代extract-text-webpack-plugin)
- serviceworker-webpack-plugin:为网页应用增加离线缓存功能
- clean-webpack-plugin: 目录清理
- ModuleConcatenationPlugin: 开启 Scope Hoisting
- speed-measure-webpack-plugin: 可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时)
- webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)
有没有手写过
7. 常用的Loader
- 源代码转换 Loader:
-
- TypeScript Loader (ts-loader 或 awesome-typescript-loader):将 TypeScript 文件转译为 JavaScript。
- Babel Loader (babel-loader):将 ES6+ 代码转换为向下兼容的 ES5 代码,或者其他目标环境的兼容代码。
- JSX Loader (babel-loader with React preset):将 JSX(React 语法)转换为标准 JavaScript。
- CoffeeScript Loader (coffee-loader):将 CoffeeScript 文件转换为 JavaScript。
- 样式文件处理 Loader:
-
- CSS Loader (css-loader):处理 CSS 文件,解决 CSS 中的 @import 和 url() 引用。
- PostCSS Loader (postcss-loader):利用 PostCSS 插件处理 CSS,如自动添加浏览器前缀、变量替换等功能。
- Sass/SCSS Loader (sass-loader 或 node-sass-loader):将 Sass 或 SCSS 样式表编译为 CSS。
- Less Loader (less-loader):将 Less 样式表编译为 CSS。
- 图像和媒体资源处理 Loader:
-
- File Loader (file-loader):将二进制文件(如图片、字体等)复制到输出目录,并返回相对于输出路径的 URL。
- URL Loader (url-loader):类似于 File Loader,但可以设置一个阈值,当文件小于指定大小时,将其转换为 Base64 编码内联到 JavaScript 或 CSS 中。
- 模板语言 Loader:
-
- HTML Loader (html-loader):处理 HTML 文件,提取其中的外部资源(如图片)并转换为模块引用。
- Pug/Jade Loader (pug-loader):将 Pug 或 Jade 模板文件编译为 HTML。
- 静态资源管理 Loader:
-
- JSON Loader (json-loader):加载 JSON 文件并将其转换为 JavaScript 对象。
- CSV Loader (csv-loader)、XML Loader (xml-loader) 等:加载并解析相应的数据格式文件。
- 高级功能 Loader:
-
- Webpack Banner Plugin (banner-loader):向文件顶部添加版权信息或其他注释。
- Vue Loader (vue-loader):处理单文件 Vue 组件(.vue 文件)
- coverjs-loader:计算测试的覆盖率
- i18n-loader: 国际化
- cache-loader: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里
8. 编写loader、plugin的思路
编写 loader
- 确定需求:确定你要解决的问题或改进现有的功能。Loader 主要用于转换某种类型的文件,例如将 TypeScript 转换为 JavaScript、将 SASS 转换为 CSS 等。
- 创建 Loader 模块:创建一个新的 Node.js 模块文件,例如 my-custom-loader.js。
- 导出一个函数:在 Loader 文件中,你需要导出一个函数,该函数接收源代码(source code)和 Loader Context 作为参数:
export default function(source, map, meta) {
// ...
};
- 处理源代码:在函数内部,对源代码进行必要的转换操作。这可能涉及字符串操作、正则表达式匹配替换、第三方库的调用等。
-
- 通常情况下,Loader 会返回一个新的转换后的源代码字符串:
- 处理 Source Map(可选):如果你的 Loader 需要处理 Source Map(用于调试时映射原始代码到转换后代码的位置),则需要对传入的 map 参数进行相应处理,并在返回结果时一并提供新的 Source Map 数据。
- 访问 Loader Context:Loader context 提供了一些有用的方法和属性,如 this.callback 用于返回处理后的结果,this.query 获取 Loader 配置参数等。利用这些上下文信息可以让你的 Loader 更加灵活。
- 错误处理:如果在转换过程中出现错误,Loader 应通过 this.emitError(error) 报告错误。
- 测试 Loader 是否适用:在 Loader 的 package.json 文件中声明 test 字段,或者在 webpack 配置文件中通过 test 属性明确指出此 Loader 应用于哪种类型的文件。
- 发布和使用:编写完 Loader 后,将其发布到 npm 或私有仓库,然后在目标项目中通过 npm install 安装并在 Webpack 配置中引用。
编写 plugin
- 定义插件类: Node.js 环境中创建一个新的 JavaScript 文件,例如 my-custom-plugin.js。
-
- 创建一个继承自 webpack.Plugin 类的自定义插件类:
- 构造函数:在插件类中定义构造函数,用于初始化插件实例所需的任何参数或配置:
- 挂载钩子方法:Webpack 插件的工作原理是通过监听和响应 Webpack 生命周期中的钩子函数。根据你的需求,在插件类中定义适当的钩子方法:
-
- 例如,如果你想在编译开始前执行一些操作,可以监听 compiler.hooks.compile;若要在生成资源文件后进行操作,可以监听 compiler.hooks.emit 等。
- 编写处理逻辑:
-
- 在挂载的钩子方法内部,编写具体的业务逻辑。可以操作 webpack compiler 或 compilation 对象,以实现你的功能需求,如修改输出文件、注入额外代码、删除无效资源等
- 错误处理:如果在插件运行过程中发生错误,可以使用 this.emitError(error) 报告错误。
- 导出插件类:
module.exports = MyCustomPlugin; - 配置和使用:在 Webpack 配置文件中,引入你自定义的插件,并将其添加到 plugins 数组中
-
- 可以发布到公共 npm 仓库或私有仓库
9. Tree Shaking原理
在JavaScript编译器和打包工具(如Webpack)的上下文中,“树摇”(Tree Shaking)是一种优化技术,主要用于消除代码中未被引用的无用模块,从而减少最终生成的bundle文件大小。它的底层原理基于静态分析和模块系统的特性。
基本原理:
- 静态分析:
-
- 树摇依赖于ES6模块系统,因为ES6模块具有静态结构,即在编译时就可以确定模块的导入和导出内容。Webpack和其他工具通过分析模块的import/export语法来判断哪些模块成员被真正使用到了。
- 模块依赖图:
-
- 构建工具首先构建出整个项目的模块依赖关系图,其中包括每个模块导出和导入的内容。
- 死代码剔除:
-
- 在编译阶段,工具会对模块进行分析,寻找那些未被任何模块导入的导出成员。由于这些成员没有被引用,因此它们被认为是死代码(Dead Code)。
- 优化输出:
-
- 在生成最终bundle的过程中,工具会剔除这些未使用的导出成员,确保最终输出的bundle中不包含未使用的代码。
简单来说,"树摇"这个名字形象地比喻了如同摇晃一棵大树,将枯叶(未引用的代码)摇落的过程。在Webpack中,Tree Shaking通常配合诸如UglifyJS或Terser等压缩工具一起工作,后者也具备类似的能力来移除未引用的变量和函数。而在现代Webpack配置中,通过开启mode为production以及使用合适的优化插件,Webpack本身就能够较好地实现Tree Shaking。
10. webpack提高效率的插件
- webpack-dashboard:可以更友好的展示相关打包信息。
- webpack-merge:提取公共配置,减少重复配置代码
- speed-measure-webpack-plugin:简称 SMP,分析出 Webpack 打包过程中 Loader 和 Plugin 的耗时,有助于找到构建过程中的性能瓶颈。
- size-plugin:监控资源体积变化,尽早发现问题
- HotModuleReplacementPlugin:模块热替换
11. source map是什么?生产环境怎么用
source map 是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map。
map文件只要不打开开发者工具,浏览器是不会加载的。
线上环境一般有三种处理方案:
- hidden-source-map:借助第三方错误监控平台 Sentry 使用
- nosources-source-map:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高
- sourcemap:通过 nginx 设置将 .map 文件只对白名单开放(公司内网)
注意:避免在生产中使用 inline- 和 eval-,因为它们会增加 bundle 体积大小,并降低整体性能。
12. 文件监听原理
原理:轮询判断文件的最后编辑时间是否变化,如果某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout 后再执行。
发现源码发生变化时,自动重新构建出新的输出文件。
Webpack开启监听模式,有两种方式:
- 启动 webpack 命令时,带上 --watch 参数
- 在配置 webpack.config.js 中设置 watch:true
缺点:每次需要手动刷新浏览器
13. P0热更新原理
webpack的热更新又称热替换(Hot Module Replacement),缩写为HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。
- 第一步,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。
- 第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。
- 第三步是 webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。
- 第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。
- webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。
- HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。
- 而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。
- 最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。
14. 如何对bundle体积进行监控和分析?
前端代码编辑器 ide 插件 Import Cost 可以帮助我们对引入模块的大小进行实时监测,
还可以使用 webpack-bundle-analyzer 生成 bundle 的模块组成图,显示所占体积。
bundlesize 工具包可以进行自动化资源体积监控。
15. webpack 打包是hash码是如何生成的
.webpack生态中存在多种计算hash的方式
- hash: 这是根据每次编译会话(compilation)来生成的 hash,每次编译都会生成一个新的 hash,即使文件内容没有发生任何变化。因此,这种 hash 不能用于长期缓存。
- chunkhash: 这是根据每个 chunk 的内容生成的 hash,当 chunk 的内容发生变化时,chunkhash 才会发生变化。这种 hash 可以用于长期缓存,但是需要注意的是,如果入口文件发生了变化,其依赖的所有 chunk 的 chunkhash 都会发生变化。
- contenthash: 这是根据文件内容生成的 hash,只有当文件内容发生变化时,contenthash 才会发生变化。这种 hash 是最精确的,也是最适合用于长期缓存的。在 Webpack 中,可以通过配置 output.filename 中的占位符来使用 contenthash,例如:[name].[contenthash].js。
2.避免相同随机值
- webpack在计算hash后分割chunk。产生相同随机值可能是因为这些文件属于同一个chunk,可以将某个文件提到独立的chunk(如放入entry)
16. 文件指纹是什么?怎么用?
文件指纹是打包后输出的文件名的后缀。
- Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改
- Chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash
- Contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变
JS的文件指纹设置
设置 output 的 filename,用 chunkhash。
CSS的文件指纹设置
设置 MiniCssExtractPlugin 的 filename,使用 contenthash。
module.exports = {
entry: {
app: './scr/app.js',
search: './src/search.js'
},
output: {
filename: '[name][chunkhash:8].js',
path:__dirname + '/dist'
},
plugins:[
new MiniCssExtractPlugin({
filename: `[name][contenthash:8].css`
})
]
}
图片的文件指纹设置
设置file-loader的name,使用hash。
占位符名称及含义
- ext 资源后缀名
- name 文件名称
- path 文件的相对路径
- folder 文件所在的文件夹
- contenthash 文件的内容hash,默认是md5生成
- hash 文件内容的hash,默认是md5生成
- emoji 一个随机的指代文件内容的emoj
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename:'bundle.js',
path:path.resolve(__dirname, 'dist')
},
module:{
rules:[{
test:/.(png|svg|jpg|gif)$/,
use:[{
loader:'file-loader',
options:{
name:'img/[name][hash:8].[ext]'
}
}]
}]
}
}
17. 如何保证各个loader按照预想方式工作?
可以使用 enforce 强制执行 loader 的作用顺序,pre 代表在所有正常 loader 之前执行,post 是所有 loader 之后执行。(inline 官方不推荐使用)
Webpack 中的 loader 在执行时是串行关系,也就是说,当一个文件需要经过多个 loader 处理时,这些 loader 会按照配置的顺序依次执行。这种顺序通常是从右到左,即从配置数组的最后一项开始,向前执行,直至第一项。每个 loader 会接收前一个 loader 的处理结果作为输入,然后输出自己的处理结果给下一个 loader,直到所有 loader 执行完毕,最后将最左侧 loader 的输出作为最终结果
18. 代码分割的本质?意义
代码分割的本质其实就是在源代码直接上线和打包成唯一脚本main.bundle.js这两种极端方案之间的一种更适合实际场景的中间状态。
阿卡丽:荣耀剑下取,均衡乱中求
「 用可接受的服务器性能压力增加来换取更好的用户体验。 」
源代码直接上线:虽然过程可控,但是http请求多,性能开销大。
打包成唯一脚本:一把梭完自己爽,服务器压力小,但是页面空白期长,用户体验不好。
(Easy peezy right)
19. 钩子函数
Webpack 中的钩子函数是由其内部使用的 Tapable 库提供的,用于插件系统中扩展和定制构建过程。Tapable 提供了一系列同步和异步的钩子,使得插件可以在合适的时机介入编译过程,执行自定义逻辑。虽然具体的钩子函数数量随着 Webpack 版本的升级可能会有所增减和变化,但下面是一些常见的 Webpack 钩子函数示例:
Compiler Hooks (编译器级别的钩子) :
compile: 在新的编译开始时调用。make:在开始编译所有模块之前调用。watchRun:在 watch 模式下每次重新编译之前调用。normalModuleFactory:创建 NormalModuleFactory 时调用,可用于修改模块工厂的行为。afterCompile:在完成编译之后调用。done:整个编译完成后调用,包括所有额外的处理步骤(如生成资产等)。invalid:当依赖项发生变化导致重新编译时调用。
Compilation Hooks (编译阶段的钩子) :
optimizeAssets/optimizeChunkAssets:在压缩或处理输出的资产(如 JavaScript 和 CSS 文件)之前调用。optimizeModules/optimizeChunks:在模块和chunk优化阶段调用。seal:在编译准备封存并输出资源之前调用。additionalAssets:在所有其他资产生成完毕后,如果插件还需要生成额外的资产,则可以在此钩子中添加。emit:在所有资源准备就绪即将写出到硬盘时调用。
Module Hooks (模块级别的钩子) :
moduleFactory:在模块工厂创建模块时调用。parser:在模块解析阶段可以用来修改模块的 AST(抽象语法树)。buildModule:在模块开始构建时调用。
实际使用中会有更多的细节钩子。每个钩子都允许插件在特定时刻改变或增强 Webpack 的行为,例如添加额外的转换步骤、修改输出内容、甚至完全接管某个编译阶段。对于具体的使用场景,需要查阅 Webpack 官方文档或查看相关插件的源码来获取更准确和详尽的信息。
20. 针对构建速度的优化
- 升级 Webpack 版本: 确保使用最新版本的 Webpack,新版本通常包含了性能优化和更快的构建算法。
- 使用缓存机制:
-
- 开启 Webpack 内置的持久化缓存(在 Webpack 5 中默认开启部分缓存功能)。
- 使用 cache-loader 在 loaders 中增加缓存层。
- 应用 hard-source-webpack-plugin 这样的第三方插件,它提供了更强大的缓存机制。
- 多进程/多实例构建:
-
- 利用 Webpack 5 的并行构建特性(通过设置 parallel 选项)。
- 使用 thread-loader 或已过时的 happypack 将工作负载分配到多个核心,加快编译速度。
- DLLPlugin 和 DLLReferencePlugin: 预先打包第三方库,使主构建流程避开这部分代码,只重新编译项目自身的变动代码。
- 优化 Loader 和 Plugin 配置:
-
- 减少不必要的 Loader 应用范围,精简 Loader 链。
- 选择高效的 Loader 和 Plugin,如使用 tree-shaking 功能的 UglifyJS 或 TerserPlugin 进行压缩。
- 优化 Resolve 配置:
-
- 设置 resolve.modules 和 resolve.extensions 来减少寻找模块路径所需的时间。
- 使用 resolve.alias 创建别名,简化模块引用路径。
- Tree Shaking 和 Scope Hoisting:
-
- 启用 Tree Shaking(摇树优化),移除未使用的代码。
- 使用 Webpack 的 module concatenation 或 optimization.concatenateModules 选项来减少模块间的开销。
- 懒加载和代码分割:
-
- 按需加载代码块,避免一次性加载所有资源。
- 开发环境热更新(HMR)优化:
-
- 调整 HMR 设置,使得只有更改过的模块重新编译和刷新浏览器。
- 缩小构建范围:
-
- 通过 watch 模式下设置 watchOptions 来监视指定的文件变化,而非整个项目。
- Scope hoisting
-
- Scope hoisting(作用域提升)技术能够间接地提高构建速度,尤其是在客户端加载和执行代码时。
- 在 Webpack 中,Scope hoisting 是一种用于优化输出代码的编译策略。在不启用 Scope hoisting 的情况下,每个模块会被包裹在一个闭包函数里,这会导致在运行时有大量的函数调用和作用域上下文切换。启用 Scope hoisting 后,Webpack 会尝试分析模块间的依赖关系,尽可能地将模块内的变量和函数提升到同一作用域内,从而减少函数调用和闭包的数量,使得最终生成的代码体积更小,减少了代码的执行开销。
- 虽然 Scope hoisting 主要是针对代码体积和运行性能进行优化,但理论上较小的代码体积也会带来更快的网络传输速度,从而间接改善用户体验和加载速度。此外,由于 Scope hoisting 降低了运行时的函数调用开销,页面的初始加载速度和执行速度有可能得以提高,特别是对于依赖众多的小模块而言。
- 然而,在构建过程中,Scope hoisting 的实现和分析过程可能会增加编译时的计算负担,因此在构建速度方面,它并不一定总是显著提升编译速度,反而在某些情况下可能略微增加构建时间。但从总体效果来看,考虑到它带来的运行时性能提升和代码体积减小的优势,Scope hoisting 仍然是一个值得开启的优化选项。
- 动态Polyfill
-
- 建议采用 polyfill-service 只给用户返回需要的polyfill,社区维护。 (部分国内奇葩浏览器UA可能无法识别,但可以降级返回所需全部polyfill)
更多优化请参考官网-构建性能
21. webpack 怎么配置前端的优化
- 代码分割与动态加载(Code Splitting) :
Webpack可以通过import()动态导入模块或者配置SplitChunksPlugin来实现代码分割,将大型应用拆分成多个较小的块,这样在初始加载时只需加载必需的模块,其余模块按需加载。这可以显著减少首次加载时间,提高页面的加载速度。 - Tree Shaking(死代码消除) :
Webpack配合ES6模块语法和摇树优化插件(如TerserWebpackPlugin),可以分析出哪些模块成员未被引用,并在打包过程中将其去除,从而减小最终输出文件的大小。 - 资源压缩与混淆(Minification) :
使用诸如UglifyJSPlugin(已被TerserWebpackPlugin替代)、cssnano等工具对JavaScript和CSS进行压缩和混淆,去掉空白字符、注释、缩短变量名等,减少文件大小。 - 模块热更新(Hot Module Replacement, HMR) :
在开发环境下,Webpack的HMR功能可以实现模块的热替换,避免因代码修改而全页刷新,从而提升开发体验和效率。 - 懒加载(Lazy Loading) :
Webpack配合路由懒加载技术,可以延迟非首屏所需的组件和资源的加载,提升页面首屏显示速度。 - 资源指纹与缓存策略:
Webpack通过output.filename和output.chunkFilename的哈希命名策略,确保每次构建后资源文件的名称包含哈希值,利于客户端缓存,同时确保资源更新时浏览器能正确请求最新版本。 - 公共模块抽离(CommonsChunkPlugin / optimization.splitChunks) :
抽取多个入口chunk中重复使用的公共模块,将其单独打包到一个或多个文件中,避免重复加载。 - 资源预加载(Prefetching/Prefetch) :
预测用户下一步可能需要的资源,并在空闲时间提前下载,减少后续页面跳转或交互时的等待时间。 - CDN托管与缓存策略:
结合Webpack的publicPath配置,将静态资源上传至CDN,利用CDN加速资源加载,同时通过HTTP缓存头设置,合理利用浏览器缓存。 - 图片优化:
使用url-loader、file-loader等加载器对图片进行压缩,并根据需要转换为更紧凑的格式(如将PNG转为WebP),减小图片资源大小。 - Gzip压缩:
配合服务器开启Gzip压缩,Webpack在打包时可以生成Gzip压缩后的资源,传输时减小带宽消耗。
通过以上优化措施,Webpack帮助前端项目在开发和部署阶段都能实现更好的性能表现。
22. gulp和webpack的异同
Webpack 和 Gulp 之间本质区别在于它们的设计目标和核心功能的不同:
- Webpack:
-
- 是一个模块打包器,其主要关注点在于解决前端模块化的方案,以及资源的管理和打包。Webpack 可以理解项目的内部依赖结构,并通过 loader 转换不同的资源类型(如 JavaScript、CSS、图片等),再通过 plugin 进一步处理这些资源,如捆绑、压缩、分割代码块等。
- Webpack 支持“一切皆模块”的概念,允许开发者以模块化的方式组织项目,并能处理模块间的动态引用和按需加载。
- Webpack 在构建过程中直接提供了模块打包、转换、优化的能力,具备高度集成的特性。
- Gulp:
-
- 是一个流式构建工具,它更加强调的是前端开发流程的自动化,通过定义任务(task)来执行一系列构建步骤,比如文件压缩、合并、预处理、测试、部署等。
- Gulp 的核心优势在于其基于流(stream)的高效文件处理机制,能够有效减少内存占用并提高构建速度。
- Gulp 并不直接处理模块打包,而是通过集成各种插件来实现各种构建任务,它更适合于解决构建过程中的工作流程问题,而不涉及模块间的依赖分析和管理。
总结来说,Webpack 更专注于前端资源的模块化打包和优化,是一种底层构建解决方案;而 Gulp 则是一个灵活的自动化构建工具,用于创建和自动化前端工作流程,通常用来配合其他插件或工具共同完成构建任务。在现代前端工程实践中,由于 Webpack 的强大功能和模块化能力,很多原本由 Gulp 完成的任务现在可以直接通过 Webpack 的配置来实现,使得单一工具即可满足大部分构建需求。然而,两者并不互斥,可以根据项目的实际情况结合使用。
23. webpck 和 gulp 场景Gulp
和 Webpack 都是前端开发工具,但它们在使用场景上的侧重点不同:
Gulp:
- 任务自动化:
Gulp 专注于构建任务的自动化,它可以用于多种任务,如文件拷贝、压缩、合并、预处理、图片优化、代码质量检查、单元测试等。Gulp 的强项在于其基于流(Stream)的文件处理机制,可以高效地处理和转换文件内容。 - 工作流构建:
Gulp 适合用于构建复杂的前端工作流程,比如先对 SCSS 文件进行编译,然后压缩 CSS,接着压缩 JS 文件,最后将它们合并并部署到服务器。你可以通过编写自定义的任务和管道(pipeline)来构建适合自己项目的完整构建流程。 - 模块化程度较低:
Gulp 不直接处理模块化问题,它更适合于处理已有模块化结构(如 CommonJS、AMD 等)的前端项目,或者不那么注重模块化的项目。
Webpack:
- 模块打包:
Webpack 的核心功能是模块打包,它能够处理 JavaScript 模块间的依赖关系,并将其转换、打包为可在浏览器中运行的代码。Webpack 支持多种模块规范(如 ES6 modules),并可以通过 loader 处理各种类型的资源(如 CSS、图片、字体等)。 - 代码分割与懒加载:
Webpack 提供了强大的代码分割和动态加载功能,可以根据模块间的依赖关系生成多个分块(chunk),利于网页性能优化和按需加载。 - 资源处理和优化:
Webpack 内置了资源处理功能,可以压缩 JavaScript、CSS,甚至可以将图片转换为 base64 数据内联到代码中。通过配置 plugins,Webpack 可以实现更深度的优化,比如 Tree Shaking(去除未使用的代码)和 Scope Hoisting(提升代码执行效率)。
总结来说,Gulp 更像是一个通用的构建工具,擅长整合多种构建任务,而 Webpack 则是一个高度集成的模块打包工具,尤其适合现代化的前端模块化项目,它可以更好地处理模块间的依赖关系并进行优化。在实际开发中,两者经常结合使用,Gulp 可以负责项目中 Webpack 之外的其他构建任务,而 Webpack 负责核心的模块打包和资源处理工作。随着 Webpack 功能的不断完善和生态的壮大,许多原本由 Gulp 处理的任务现在也可以直接通过 Webpack 配置来完成。
24. webpack 的项目改用 gulp 会遇到哪些问题
如果一个原本使用 webpack 的前端项目改为使用 Gulp,可能会遇到以下几类问题:
- 任务自动化差异:
-
- Webpack 是一个强大的模块打包工具,它可以处理模块导入、代码分割、转译、压缩、热更新等功能,而 Gulp 更侧重于构建流程自动化,不直接提供模块打包的功能。
- 如果项目原先依赖了 webpack 的模块打包机制,那么在迁移到 Gulp 时,你需要找到相应的 Gulp 插件或者自行编写脚本来实现这些功能,比如 Babel 编译、LESS/SASS 转 CSS、图片压缩、文件合并等。
- 配置复杂性:
-
- Webpack 配置集中在一个或多个配置文件中,它能精细地控制模块加载规则、输出路径、公共路径、插件应用等。
- 而 Gulp 是基于任务的构建工具,它的配置相对分散在各个 task 中,因此可能需要更多手动配置去处理相似的构建细节。
- 静态资源处理:
-
- Webpack 通过 loader 可以方便地处理各种类型的静态资源,并且能够自动添加 hash 或者版本号到文件名以实现缓存 busting。
- 在 Gulp 中,虽然也有类似的插件可以完成类似的任务,但可能需要更细致的配置来整合不同的流处理操作。
- 动态导入与按需加载:
-
- Webpack 支持动态导入(
import())及代码分割特性,这有助于优化首屏加载速度。 - 要在 Gulp 中实现同样的效果,可能需要借助 Rollup、Browserify 等其它打包工具,或者配合 Gulp 通过更复杂的逻辑实现按需加载。
- Webpack 支持动态导入(
- 热更新与开发者体验:
-
- Webpack Dev Server 提供了实时编译、热模块替换等提升开发效率的功能。
- 使用 Gulp 进行开发时,虽然也可以通过监听文件变化重新构建,但实现同等程度的热更新体验通常需要集成 BrowserSync 等额外工具,并且可能没有 Webpack 的热模块替换那么高效。
- 项目结构适应:
-
- 如果项目的组织结构和构建流程是围绕着 Webpack 的特点设计的,那么转换到 Gulp 可能需要重构部分项目结构以适应 Gulp 的工作流。
25. webpack 和 vite 的优缺点对比
Webpack 和 Vite 的优缺点对比:
Webpack 优点:
- 成熟度与生态:Webpack 已经是非常成熟且广泛应用的构建工具,拥有极其丰富的插件和 loader 生态系统,几乎可以处理任何类型的前端资源,适用于各种复杂的项目需求。
- 高度自定义:Webpack 提供高度灵活的配置选项,允许开发者精细控制打包流程,包括代码分割、模块加载、资源优化等方面。
- 跨平台支持:Webpack 支持多种模块格式(CommonJS、AMD、ES modules 等),并且可以很好地处理旧版浏览器的兼容问题。
- 资源管理和优化:Webpack 可以自动处理静态资源的引用,并通过 Tree-shaking、Scope Hoisting 等技术进行代码优化。
Webpack 缺点:
- 启动速度:相比于 Vite,Webpack 的启动速度较慢,尤其在大型项目中,全量编译和热更新等待时间较长。
- 开发体验:开发环境下的热更新可能不够即时,因为它往往涉及到全量或部分重新编译。
- 配置复杂度:由于功能强大,Webpack 的配置可能较为复杂,对新手不太友好。
Vite 优点:
- 快速启动与热更新:Vite 在开发环境中利用 ES module 的原生支持,无需预先编译全部代码,按需编译,大大提升了启动和热更新的速度。
- 轻量级开发体验:Vite 基于 esbuild 预构建依赖,极大地加快了开发阶段的构建速度。
- 内置 Vue.js 支持:由 Vue.js 团队开发,对 Vue.js 应用提供了很好的开箱即用支持。
- 现代化架构:Vite 更加契合现代浏览器对 ES modules 的支持,提供了更好的开发体验。
Vite 缺点:
- 生态相对较小:相较于 Webpack,Vite 的生态系统还在发展中,插件和 loader 的种类和成熟度有待提升。
- 生产环境构建:虽然开发阶段快速,但在生产环境构建时,Vite 依赖 Rollup 进行打包,可能在某些场景下(如 CSS 代码分割、CommonJS 模块处理等)不如 Webpack 灵活和全面。
- 浏览器兼容性:Vite 需要浏览器支持 ES modules,对于老旧浏览器可能需要额外的polyfill或服务端支持。
- 稳定性和成熟度:作为一个相对较新的工具,Vite 在一些边缘情况下的表现和稳定性可能不如 Webpack 成熟。
Webpack 是目前最主流和全能的前端构建工具,特别适合大型、复杂的项目和需要深度定制的情况。而 Vite 更关注提升开发者的生产力和现代前端项目的快速迭代,尤其在 Vue.js 项目中表现出色。选择哪一个取决于项目的具体情况、团队对工具的熟悉程度以及对构建速度、灵活性和生态完整性的权衡。
26. 什么是Code Splitting
Code Splitting代码分割,是一种优化技术。它允许将一个大的chunk拆分成多个小的chunk,从而实现按需加载,减少初始加载时间,并提高应用程序的性能 。
在Webpack中通过optimization.splitChunks配置项来开启代码分割
27. webpack配置有哪些
- entry:入口文件
- output:输出文件配置
- resolve:用来配置模块的解析方式
- module:用来配置模块如何被解析
- plugins:插件
- devServer:开发服务器配置
- devtool:调试工具
- optimization:优化相关配置
- externals:外部扩展的配置
- performance:性能相关配置
- target:构建的目标环境