谈谈你对Webpack的理解?
1.Webpack是什么?
Webpack 是一个用于现代JavaScript应用程序的静态模块打包工具,可以很方面的管理模块的恶依赖。
2.Webpack作用
- 编译代码能力,提高效率,解决浏览器兼容问题
- 模块整合能力,提高性能,可维护性,解决浏览器频繁请求文件的问题
- 万物皆可模块能力,项目维护性增强,支持不同种类的前端模块类型,统一的模块化方案,所有资源文件的加载都可以通过代码控制
Webpack的优点是什么?
1、专注于处理模块化的项目,能做到开箱即用,一步到位
2、通过plugin扩展,完整好用又不失灵活
3、使用场景不局限于web开发
4、社区庞大活跃,经常引入紧跟时代的发展的新特性,能为大多数场景找到已有的开源扩展
5、提供了更好的开发体验
说说Webpack的工作原理?
WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Sass,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。在3.0出现后,Webpack还肩负起了优化项目的责任。
说说Webpack打包原理?
把一切都视为模块:不管是 css、JS、Image 还是 html 都可以互相引用,通过定义 entry.js,对所有依赖的文件进行跟踪,将各个模块通过 loader 和 plugins 处理,然后打包在一起。
按需加载:打包过程中 Webpack 通过 Code Splitting 功能将文件分为多个 chunks,还可以将重复的部分单独提取出来作为 commonChunk,从而实现按需加载。把所有依赖打包成一个 bundle.js 文件,通过代码分割成单元片段并按需加载
Webpack的构建流程?
webpack的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
1、初始化参数:从配置文件和shell语句读取与合并参数,得出最终的参数
2、开始编译:用上一步得到的参数初始化compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译
3、确定入口:根据配置中的entry找到所有入口文件
4、编译模块:从文件入口触发,调用所有配置的loader对模块进行翻译,找出该模块依赖的模块,重复本步骤直到所有入口依赖的文件都经过本步骤的处理
5、完成模块编译:经过上一步使用loader翻译完所有模块后,得到了每一个模块被翻译后的最终内容,以及他们之间的依赖关系
6、输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再把每个chunk转换成一个单独的文件加入到输出列表,这步是修改输出内容的最后机会
7、输出完成:在确定好输出内容后,很具配置确定输出路径和文件名,把文件内容写到文件系统,在以上过程中,Webpack会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用Webpack提供的API改变Webpack的运行结果
Webpack五个核心概念
1、Entry(入口):指示webpack以哪个文件为入口起点打包。分析构建内部依赖图。
2、Output(出口):指示webpack打包后的资源bundles输出到哪里去,以及如何命名。
3、Loader:Loader让webpack能够去处理那些非javaScript文件【图片文件、html文件、样式文件】(webpack自身只理解javascript)
4、Plugins(插件):可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。
5、Mode(模式):指示webpack使用相应模式的配置(development or production)。
Webpack优化
Webpack系列——打包的性能优化 , 尚硅谷-webpack优化
我们一般会从四个方面进行优化:
1.提升开发体验 (SourceMap)
开发时我们运行的代码是经过 webpack 编译后的,代码很乱,所有 css 和 js 合并成了一个文件,并且多了其他代码。此时如果代码运行出错那么提示代码错误位置我们是看不懂的。一旦将来开发代码文件很多,那么很难去发现错误出现在哪里。所以我们需要更加准确的错误提示,来帮助我们更好的开发代码。
SourceMap(源代码映射)
一个用来生成源代码与构建后代码一一映射的文件的方案。它会生成一个 xxx.map 文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过 xxx.map 文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。
展开> SourceMap 为什么/是什么/怎么用?
怎么用?
通过查看Webpack DevTool 文档open in new window可知,SourceMap 的值有很多种情况.
但实际开发时我们只需要关注两种情况即可:
- 开发模式:
cheap-module-source-map
-
- 优点:打包编译速度快,只包含行映射
- 缺点:没有列映射
module.exports = { // 其他省略 mode: "development", devtool: "cheap-module-source-map", };
- 生产模式:
source-map
-
- 优点:包含行/列映射
- 缺点:打包编译速度更慢
module.exports = { // 其他省略 mode: "production", devtool: "source-map", };
2.提升打包构建速度
开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢。所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。
HotModuleReplacement(HMR/热模块替换)
在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。
HotModuleReplacement怎么用?
1.基本配置
module.exports = { // 其他省略 devServer: { host: "localhost", // 启动服务器域名 port: "3000", // 启动服务器端口号 open: true, // 是否自动打开浏览器 hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了) }, };
此时 css 样式经过 style-loader 处理,已经具备 HMR 功能了。 但是 js 还不行。
2.JS配置
// main.js import count from "./js/count"; import sum from "./js/sum"; // 引入资源,Webpack才会对其打包 import "./css/iconfont.css"; import "./css/index.css"; import "./less/index.less"; import "./sass/index.sass"; import "./sass/index.scss"; import "./styl/index.styl"; const result1 = count(2, 1); console.log(result1); const result2 = sum(1, 2, 3, 4); console.log(result2); // 判断是否支持HMR功能 if (module.hot) { module.hot.accept("./js/count.js", function (count) { const result1 = count(2, 1); console.log(result1); }); module.hot.accept("./js/sum.js", function (sum) { const result2 = sum(1, 2, 3, 4); console.log(result2); }); }
上面这样写会很麻烦,所以实际开发我们会使用其他 loader 来解决。
比如:vue-loaderopen in new window, react-hot-loaderopen in new window。
OneOf
打包时每个文件都会经过所有 loader 处理,虽然因为 test 正则原因实际没有处理上,但是都要过一遍。比较慢。OneOf 顾名思义就是只能匹配上一个 loader, 剩下的就不匹配了。
Include/Exclude
开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。
- include ===> 包含,只处理 xxx 文件
- exclude ===> 排除,除了 xxx 文件以外其他文件都处理
// exclude: /node_modules/, // 排除node_modules代码不编译 include: path.resolve(__dirname, "../src"), // 也可以用包含 exclude: "node_modules", // 默认值
Cache
每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了。Caches是对 Eslint 检查 和 Babel 编译结果进行缓存。
options: { cacheDirectory: true, // 开启babel编译缓存 cacheCompression: false, // 缓存文件不要压缩 },
Thead
当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码。这个速度是比较慢的。我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。而对 js 文件处理主要就是 eslint 、babel、Terser 三个工具,所以我们要提升它们的运行速度。我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。
Thead是多进程打包:开启电脑的多个进程同时干一件事,速度更快。
需要注意:请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右开销。
我们目前打包的内容都很少,所以因为启动进程开销原因,使用多进程打包实际上会显著的让我们打包时间变得很长。
use: [ { loader: "thread-loader", // 开启多进程 options: { workers: threads, // 数量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 开启babel编译缓存 }, }, ],
3.减少代码体积
Tree Shaking
开发时我们定义了一些工具函数库,或者引用第三方工具函数库或组件库。如果没有特殊处理的话我们打包时会引入整个库,但是实际上可能我们可能只用上极小部分的功能。这样将整个库都打包进来,体积就太大了。
Tree Shaking
是一个术语,通常用于描述移除 JavaScript 中的没有使用上的代码。
注意:它依赖 ES Module。
Webpack 已经默认开启了这个功能,无需其他配置。
Babel
Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。可以将这些辅助代码作为一个独立模块,来避免重复引入。
npm i @babel/plugin-transform-runtime -D //下包 //配置 plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
Image Minimizer
开发如果项目中引用了较多图片,那么图片体积会比较大,将来请求速度比较慢。
我们可以对图片进行压缩,减少图片体积。
注意:如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。
image-minimizer-webpack-plugin
: 用来压缩图片的插件。(还可以分为有损压缩和无损压缩两种。)
展开> 怎么用/会报错/如何解决?
npm i image-minimizer-webpack-plugin imagemin -D // 下包 还有剩下包需要下载,有两种模式: 无损压缩: npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D 有损压缩: npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); // 压缩图片 new ImageMinimizerPlugin({ minimizer: { implementation: ImageMinimizerPlugin.imageminGenerate, options: { plugins: [ ["gifsicle", { interlaced: true }], ["jpegtran", { progressive: true }], ["optipng", { optimizationLevel: 5 }], [ "svgo", { plugins: [ "preset-default", "prefixIds", { name: "sortAttrs", params: { xmlnsOrder: "alphabetical", }, }, ], }, ], ], }, }, }),
打包时会出现报错
Error: Error with 'src\images\1.jpeg': '"C:\Users\86176\Desktop\webpack\webpack_code\node_modules\jpegtran-bin\vendor\jpegtran.exe"' Error with 'src\images\3.gif': spawn C:\Users\86176\Desktop\webpack\webpack_code\node_modules\optipng-bin\vendor\optipng.exe ENOENT
我们需要安装两个文件到 node_modules 中才能解决
- jpegtran.exe
需要复制到 node_modules\jpegtran-bin\vendor 下面
jpegtran 官网地址open in new window
- optipng.exe
需要复制到 node_modules\optipng-bin\vendor 下面
4.优化代码运行性能
Code Split (代码分割)
打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。
代码分割(Code Split)主要做了两件事:
- 分割文件:将打包生成的文件进行分割,生成多个 js 文件。
- 按需加载:需要哪个文件就加载哪个文件。
① 多入口提取重复代码
如果多入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复,体积更大。我们需要提取多入口的重复代码,只打包生成一个 js 文件,其他文件引用它就好。
optimization: { // 代码分割配置 splitChunks: { chunks: "all", // 对所有模块都进行分割 // 以下是默认值 // minSize: 20000, // 分割代码最小的大小 // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0 // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割 // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量 // maxInitialRequests: 30, // 入口js文件最大并行请求数量 // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests) // cacheGroups: { // 组,哪些模块要打包到一个组 // defaultVendors: { // 组名 // test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块 // priority: -10, // 权重(越大越高) // reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块 // }, // default: { // 其他没有写的配置会使用上面的默认值 // minChunks: 2, // 这里的minChunks权重更大 // priority: -20, // reuseExistingChunk: true, // }, // }, // 修改配置 cacheGroups: { // 组,哪些模块要打包到一个组 // defaultVendors: { // 组名 // test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块 // priority: -10, // 权重(越大越高) // reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块 // }, default: { // 其他没有写的配置会使用上面的默认值 minSize: 0, // 我们定义的文件体积太小了,所以要改打包的最小文件体积 minChunks: 2, priority: -20, reuseExistingChunk: true, }, }, },
- 按需加载:需要哪个文件就加载哪个文件。
Eslint
可组装的 JavaScript 和 JSX 检查工具。我们使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查。规则的指定我们可以在eslint的规则文档内去查看。
一、配置文件
配置文件由很多种写法:
- .eslintrc.*:新建文件,位于项目根目录
-
- .eslintrc
- .eslintrc.js
- .eslintrc.json
- 区别在于配置格式不一样
- package.json 中 eslintConfig:不需要创建文件,在原有文件基础上写
ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可
二、具体配置
我们以 .eslintrc.js 配置文件为例
module.exports = { // 解析选项 parserOptions: {}, // 具体检查规则 rules: {}, // 继承其他规则 extends: [], // ... // 其他规则详见:https://eslint.bootcss.com/docs/user-guide/configuring };
- parserOptions 解析选项
parserOptions: { ecmaVersion: 6, // ES 语法版本 sourceType: "module", // ES 模块化 ecmaFeatures: { // ES 其他特性 jsx: true // 如果是 React 项目,就需要开启 jsx 语法 } }
- rules 具体规则
"off" 或 0 - 关闭规则
"warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
"error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
rules: { semi: "error", // 禁止使用分号 'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告 'default-case': [ 'warn', // 要求 switch 语句中有 default 分支,否则警告 { commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了 ], eqeqeq: [ 'warn', // 强制使用 === 和 !==,否则警告 'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告 ], }
- extends 继承
开发中一点点写 rules 规则太费劲了,所以有更好的办法,继承现有的规则。
现有以下较为有名的规则:
- Eslint 官方的规则open in new window:eslint:recommended
- Vue Cli 官方的规则open in new window:plugin:vue/essential
- React Cli 官方的规则open in new window:react-app
三、在Webpack中使用
下载包 => 定义 Eslint 配置文件 => 修改 js 文件代码 => 配置 => 运行指令
四、VSCode Eslint 插件
打开 VSCode,下载 Eslint 插件,即可不用编译就能看到错误,可以提前解决
但是此时就会对项目所有文件默认进行 Eslint 检查了,我们 dist 目录下的打包后文件就会报错。但是我们只需要检查 src 下面的文件,不需要检查 dist 下面的文件。
使用 Eslint 忽略文件解决。在项目根目录新建文件:.eslintignore
# 忽略dist目录下所有文件 dist
题外话
// [name]是webpack命名规则,使用chunk的name作为输出的文件名。 // 什么是chunk?打包的资源就是chunk,输出出去叫bundle。 // chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。 // 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)