Webpack5基础

您所在的位置:网站首页 loader特点 Webpack5基础

Webpack5基础

2024-03-11 22:39| 来源: 网络整理| 查看: 265

前言

前端开发时一般都会使用框架(React、Vue)、ES6模块化语法、Less或者Sass等 css预处现器等语法。这样的代码要想在浏览器运行,必须要编译成浏览器能够识别的 JS、Css等语法。而打包工具的作用就是压缩代码、兼容性处理、提升代码性能、代码编译等。

常见的打包工具包括Grunt、Gulp、Parcel、Webpack、Rollup、Vite等等,目前市面上最常用的是webpack。

第一章 Webpack的基本配置 1. 基本使用

Webpack 是一个静态资源打包工具。它会以一个或多个文件作为打包的入口,将我们整个项目所有文件编译组合成一个或多个文件输出。输出的文件就是编译好的文件bundle,可以在浏览器端运行。

Webpack本身的功能是有限的:

开发模式:只能编译JS中的ES Module语法 生产模式:不仅可以编译JS中的ES Module语法,还可以压缩JS代码 直接使用JS代码

index.html主文件 image.png

未打包的main.js文件 image.png

控制台报错 image.png

使用打包后的JS代码

第一步:初始化一个package.json文件npm init -y

image.png

第二步:下载webpack npm i webpack webpack-cli -D

第三步:打包指定目录文件npx webpack ./src/main.js --mode=development

development模式 image.png

production模式 image.png

控制台正常输出 image.png

2. 核心概念 Entry:入口文件,webpack编译的起点,即从哪个文件开始打包 output:输出,webpack打包完的文件从哪里输出。其中output.filename对应initial chunk文件名称,output.chunkFilename对应non-initial chunk文件名称 Loader:加载器,webpack本身只能处理JS、json资源文件,其他资源需要借助loader处理 Plugin:插件,扩展webpack功能 mode:模式,分为development开发模式和production生产模式 3. 其他概念 Compiler:编译管理器,webpack启动后会创建compiler对象,该对象一直存活到编译结束 Compilation:单次编译过程的管理器,每次触发重新编译时,都会创建一个新的compilation对象 Dependence:依赖对象,webpack基于该类型记录模块间依赖关系 Module:webpack内部所有资源都会以“module”对象形式存在,所有关于资源的操作、转译、合并都是以 “module” 为基本单位进行的 Chunk:编译完成准备输出时,webpack会将module按特定的规则组织成一个一个的chunk,这些chunk某种程度上跟最终输出一一对应 4. Webpack基本配置

在根目录下创建一个Webpack.config.js文件并完成基础配置

const path = require("path"); module.exports = { // 入口,相对路径 entry: "./src/main.js", // 输出,绝对路径 output: { path: path.resolve(__dirname,"dist"), // 路径 filename: "main.js",// 文件名 }, // 加载器 module: {rules: []}, // 插件 plugins:[], // 模式 mode: "development" }

可以在output中添加clean配置,自动清除上一次的打包资源。

output: { path: path.resolve(__dirname,"dist"), // 路径 filename: "main.js",// 文件名 clean: true, // 在生成文件之前清空output目录 }, 5. create-react-app中webpack的默认配置 const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); module.exports = { optimization:{ splitChunks:{ // 拆分模块 chunks: 'all' }, minimizer:[ new TerserPlugin({ terserOptions:{ compress:{ pure_funcs:['console.log'], // 移除console.log代码 } } }) ] }, mode: 'development', // 开发模式 entry: { // 多入口打包 case:{ import:'./src/case.tsx', dependOn:'vendor' // 共享模块的chunk名称 }, list:{ import:'./src/list.tsx', dependOn:'vendor' } }, devServer:{ hot:true // HMR }, output: { clean: true, // 自动清除上次打包内容 path: path.join(__dirname, 'dist'), filename: '[name]-[contenthash].js', }, resolve: { extensions: ['.tsx', '.ts'] }, plugins:[ new HtmlWebpackPlugin({ title:'管理输出', // html文件标题 filename: 'index.html', // html文件名字 }), ], module: { rules: [ { test: /\.(js|ts|jsx|tsx)$/, // 解析.tsx文件 include: path.appSrc, // 只包含app/src/文件夹下面的文件 use: [{ loader: "esbuild-loader", options: { loader: "tsx", target: "es2015" }, }] }, { test: /\.css$/, use: ['style-loader',{ loader:'css-loader', options:{ sourceMap:true } }] }, { test: /\.html$/, use: 'html-loader' } ], }, }; 第二章 资源文件的处理 1. 处理样式资源 1.1 style-loader

作用:把CSS插入到DOM中,推荐将style-loader与css-loader一起使用

// 下载 npm install --save-dev style-loader // 使用加载器 module: { rules: [ { test: /\.css$/i, // 正则表达式匹配文件 use: ["style-loader", "css-loader"], // 从右到左依次使用loader处理 } ] }, 1.2 css-loader

css-loader会对@import和url()进行处理,就像js解析import/require()一样。

// 下载 npm install --save-dev css-loader // 使用加载器 module: { rules: [ { test: /\.css$/i, // 正则表达式匹配文件 use: ["style-loader", "css-loader"], // 从右到左依次使用loader处理 } ] },

image.png

1.3 less-loader

作用:将Less编译为CSS的loader

// 下载 npm install less less-loader --save-dev // 使用加载器 module: { rules: [ { test: /\.less$/i, use: ['style-loader','css-loader','less-loader'], }, ] },

image.png

1.4 sass-loader

作用:加载Sass/SCSS文件并将他们编译为CSS。

// 下载 npm install sass-loader sass --save-dev // 使用加载器 module: { rules: [ { test: /\.s[ac]ss$/i, use: [ // 将JS字符串生成为style节点 'style-loader', // 将CSS转化成CommonJS模块 'css-loader', // 将Sass编译成CSS 'sass-loader', ], }, ] },

image.png

1.5 stylus-loader

作用:将Stylus文件编译为CSS

// 下载 npm install stylus stylus-loader --save-dev // 使用加载器 module: { rules: [ { test: /.styl$/, loader: "stylus-loader", }, ] }, 2. 处理图片资源 2.1 简单配置

Webpack4处理图片资源时需要使用file-loader和url-loader两个加载器,而Webpack5已经内置了两个Loader,使用时不需要单独下载安装,只需要简单配置即可。

module: { rules: [ { test: /\.(png|jpe?g|gif|webp)$/i, type: "asset", } ] },

图片作为背景图片,通过url引入 image.png

image.png

2.2 资源模块

资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外loader。包括以下内容:

raw-loader:将文件导入为字符串 url-loader:将文件作为data URI内联到bundle中 file-loader:将文件发送到输出目录

一般情况下webpack将按照默认条件,自动地在resource和inline之间进行选择:小于8kb的文件,将会视为inline模块类型,否则会被视为resource模块类型。可以通过在webpack配置的module rule层级中,设置Rule.parser.dataUrlCondition.maxSize选项来修改此条件。

resource资源:直接发送到输出目录,其路径会被被注入到bundle中 inline资源:文件会作为data URI注入到bundle中,格式为base64,可以减少网络请求 module: { rules: [ { test: /\.(png|jpe?g|gif|webp)$/i, type: "asset", parser: { dataUrlCondition: { maxSize: 100 * 1024 // 小于100kb转化为inline资源 } } } ] },

image.png

🤔:url-loader和file-loader的区别是什么?

🙋:大致总结如下

首先概念不同:file-loader可以指定要复制和放置资源文件的位置,以及如何使用版本哈希命名以获得更好的缓存。url-loader允许你有条件地将文件转换为内联的base-64 URL (当文件小于给定的阈值),这会减少小文件的HTTP请求数。如果文件大于该阈值,会自动的交给file-loader处理。

处理图片资源方式不同:file-loader将文件上的import和require解析为url,并将该文件发射到输出目录中。url-loader可以识别图片的大小,并把图片转换成base64,从而减少代码的体积,如果图片超过设定的限制,就会继续用file-loader处理。

2.3 自定义输出文件

默认情况下,asset/resource模块以[hash][ext][query]文件名发送到输出目录中,可以通过在webpack配置中设置output.assetModuleFilename来修改此模板字符串。

特点:不能对asset/resource模块下的内容进行区分

output: { // 所有输出文件的路径 path: path.resolve(__dirname,"dist"), // 入口文件对应的输出文件名称 filename: "main.js", // asset/resource模块的输出路径和名称配置 assetModuleFilename: 'images/[hash][ext][query]' },

也可以通过generator.filename单独设置某个resource模块,输出到指定目录下。

{ test: /\.(png|jpe?g|gif|webp)$/i, type: "asset", parser: { dataUrlCondition: { maxSize: 100 * 1024 // 100kb } }, // 将图片资源输出到dist/static/images目录中 // 文件名为前8位hash值 + 文件扩展名 + 其他扩展字段 generator: { filename: 'static/images/[hash:8][ext][query]' } }

image.png

3. 处理字体图标资源

字体图标资源也属于资源模块,但是不需要转化为base-64格式,所以需要使用Resource。

{ test: /\.(ttf|Woff2?)$/i, type:"asset/resource", generator: { filename: 'static/media/[hash:8][ext][query]' } } 4. 处理其他资源

例如音频、视频等标资源也属于资源模块,同样也不需要转化为base-64格式,所以需要使用Resource。

{ test: /\.(map3|map4|avi)$/i, type:"asset/resource", generator: { filename: 'static/media/[hash:8][ext][query]' } } 5. 处理JS资源

Webpack对JS的处理是有限的,只能编译JS中ES模块化语法,不能编译其他语法,导致JS不能在IE等浏览器中运行,所以需要做一些兼容性处理。

Babel:JS兼容性处理 Eslint:代码格式校验

需要先完成Eslint检测代码格式无误后,再由Babel做代码兼容性处理。

5.1 Eslint

作用:用来检测js和jsx语法的工具,可以扩展各种功能

配置文件 .eslintrc.*:新建位于项目根目录的文件,可以是.js或者.json格式 package.json中eslintConfig:直接在package文件中添加配置,Eslint会自动查找和读取对应配置规则 使用 // 下载 npm install eslint-webpack-plugin eslint --save-dev // 添加配置文件.eslintrc.js module.exports = { // 继承eslint规则 extends:["eslint:recommended"], env:{ node:true, // 启用node中的全局变量 browser:true, // 启用浏览器中的全局变量 }, parserOptions:{ ecmaVersion: 6, sourceType: "module" }, rules:{ "no-var": 2, } } // 添加eslint忽略文件.eslintignore,忽略打包后的dist文件夹 dist

image.png

5.2 babel

作用:将ES6语法转换为向后兼容的JS代码,以便能够运行在当前和旧版本的浏览器中

配置文件 babel.config.*:新建位于根目录的文件,格式为.js或者.json .babelrc.*:新建位于根目录的文件,格式为.js或者.json package.json的babel:直接在package文件中添加配置 使用 // 下载 npm install -D babel-loader @babel/core @babel/preset-env // 使用加载器 { test: /\.m?js$/, exclude: /(node_modules)/, // 忽略node包 loader: 'babel-loader', } // 添加外部的babel.config.js文件,配置预设规则 module.exports = { presets: ['@babel/preset-env'] // 智能预设 } 6. 处理html资源

作用:自动生成一个HTML5文件, 在body中使用script标签引入所有webpack生成的bundle

// 下载 npm install --save-dev html-webpack-plugin // 使用插件 plugins:[ new ESLintPlugin({ context: path.resolve(__dirname,"src") }), new HtmlWebpackPlugin({ // 配置模版,生成的html文件中会自动保留模板格式 template: path.resolve(__dirname,"public/index.html") }) ],

image.png image.png

第三章 搭建开发服务器 1. 自动化打包

作用:自动化编译代码,取消手动输入npx webpack指令操作

// 下载 npm install --save-dev webpack-dev-server // 添加配置项 module.esports = { // 开发服务器配置 devServer:{ host: "localhost", // 启动服务器域名 port: "3000", // 启动服务器端口号 open: true // 是否自动打开浏览器 }, } // 使用 npx webpack server

浏览器自动弹出3000窗口 image.png

2. 生产模式

生产模式即开发完部署上线,生产模式需要对代码进行优化,让其运行性能更好。优化主要从两个角度出发:

优化代码运行性能 优化代码打包迪度

一般在项目中会拆分生产模式和开发模式的配置文件,并在package.json中通过不同的指令分别启动。

image.png

3. 生产模式优化配置 3.1 提取输出CSS文件

插件:MiniCssExtractPlugin

作用:将CSS提取到单独的文件中,为每个包含CSS的JS文件创建一个CSS文件,并且支持CSS和SourceMaps的按需加载。

MiniCssExtractPlugin.loader:将css文件以link方式引入 style-loader:将css样式放到style内联样式中 // 下载 npm install --save-dev mini-css-extract-plugin // 使用 MiniCssExtractPlugin.loader代替style-loader,并在plugin中引入 module: { rules: [ { test: /\.css$/i, // 正则表达式匹配文件 use: [MiniCssExtractPlugin.loader, "css-loader"], }, ] }, plugins:[ new MiniCssExtractPlugin({ filename: "css/main.css" }) ],

MiniCssExtractPlugin-loader image.png style-loader

image.png

3.2 压缩CSS文件

生产模式默认开启了js和html压缩,针对css,需要使用插件对其进行压缩。

插件:ss-minimizer-webpack-plugin

// 下载 npm install css-minimizer-webpack-plugin --save-dev // 使用 plugins:[ new CssMinimizerPlugin() ] 第四章 Webpack优化设置 1. 提升开发体验 1.1 SourceMap

SourceMap是一个源代码映射的系统,可以生成源代码与构建后代码一一映射的文件。SourceMap会生成一个xxx.map文件,里面包含源代码和构建后代码在每一行、每一列的映射关系,当构建后代码出错了,会通过xxx.map文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。

开发模式:cheap-module-source-map

优点: 打包编译速度快,只包含行映射

缺点: 没有列映射

mode: "production", devtool: "cheap-module-source-map"

生产模式:source-map

优点:既包含行映射,又包含列映射

缺点:打包编译速度慢

mode: "production", devtool: "source-map" 1.2 模块热替换

模块热替换(HMR)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:

保留在完全重新加载页面期间丢失的应用程序状态 只更新变更内容,从而节省开发时间 在源代码中CSS/JS产生修改时,会立刻在浏览器中进行更新,相当于在浏览器devtools中直接更改样式 devServer:{ host: "localhost", // 启动服务器域名 port: "3000", // 启动服务器端口号 open: true, // 是否自动打开浏览器 hot: true, // 是否开启HMR热模块替换功能,Webpack5默认开启 },

HRM的原理实际上是 webpack-dev-server(WDS)和浏览器之间维护了一个websocket服务。当本地资源发生变化后,webpack会先将打包生成新的模块代码放入内存中,然后WDS向浏览器推送更新,并附带上构建时的hash,让客户端和上一次资源进行对比。客户端对比出差异后会向WDS发起Ajax请求获取到更改后的内容(文件列表、hash),通过这些信息再向WDS发起jsonp请求获取到最新的模块代码。

2. 提升打包构建速度 2.1 OneOf

作用:规定一个文件只能被一个loader处理,提升打包速度

module: { rules: [{ oneOf: [ { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, "css-loader"], }, { test: /\.less$/i, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'], }, ] }] } 2.2 Include/Exclude

针对JS文件做处理,提升打包编译速度。

include:包含,只处理xx文件 exclude:排除,除了xxx文件以外其他文件都需要处理 { test: /\.m?js$/, exclude: /(node_modules)/, // 忽略node包 loader: 'babel-loader', } { test: /\.m?js$/, include: path.resolve(__dirname,"./src"), loader: 'babel-loader', } new ESLintPlugin({ context: path.resolve(__dirname, "src"), exclude: "node_modules", // exclude的默认值 }), 3. 减少代码体积 3.1 Cache

每次打包时js文件都要经过Eslint检查和Babel编译,速度比较慢。我们可以缓存之前的Eslint检查和Babel编译的结果,这样第二次打包时速度就会更快了。

// 缓存babel编译 { test: /\.m?js$/, include: path.resolve(__dirname,"./src"), loader: 'babel-loader', options:{ cacheDirectory: true, // 开启babel缓存 cacheCompression: false // 关闭缓存文件压缩 } } // 缓存eslint检查 plugins: [ new ESLintPlugin({ context: path.resolve(__dirname, "src"), exclude: "node_modules", // exclude的默认值 cache: true, // 开启缓存 cacheLocation: path.resolve(__dirname, "./node_modules/.cache/eslintcache") // 缓存路径 }), ] 3.2 Thead

当项目越来越庞大时,打包速度就会越来越慢,影响最严重的就是JS的打包速度。而对js文件处理主要就是eslint、babel、Terser三个工具,所以要想提升js文件的运行速度,可以开启多进程同时处理js文件,从而提升打包速度。

多进程打包指的是开启电脑的多个进程同时干一件事,速度更快。

⚠️:请在特别耗时的操作中使用,因为每个进程启动就有大约600ms左右开销。而启动进程的数量不得大于CPU的核数

1)获取CPU核数

const os = require("os"); const threads = os.cpus().length;

2)babel解析:开启多进程,设置进程数量

{ test: /\.m?js$/, include: path.resolve(__dirname, "./src"), use: [ { loader: 'thread-loader', // 开启多进程 options: { works: threads // 进程数量 } }, { loader: 'babel-loader', options: { cacheDirectory: true, // 开启babel缓存 cacheCompression: false // 关闭缓存文件压缩 } }, ], }

3)eslint校验:开启多进程,设置进程数量

optimization: { minimizer: [ new CssMinimizerPlugin(), // 压缩css new TerserWebpackPlugin({ // 开启多进程、设置进程数量 parallel: threads }) ] } 3.3 Tree Shaking

用于描述移除JavaScript中的没有使用的代码,前提是必须依赖ES Hodule模块化。

Webpack5目前已经内置了Tree Shaking,无需过多的配置。

Tree Shaking的工作原理如下:

ES6模块系统:Tree Shaking的基础是ES6模块系统,它具有静态特性,意味着模块的导入和导出关系在编译时就已经确定,不会受到程序运行时的影响

静态分析:在Webpack的构建过程中,会通过静态分析依赖图,从入口文件开始,逐级追踪每个模块的依赖关系,以及模块之间的导入和导出关系

标记未使用代码:在分析模块依赖时,Webpack会标记每个变量、函数、类和导入,以确定它们是否被实际使用。如果一个导入的模块只是被导入而没有被使用,或者某个模块的部分代码没有被使用,Webpack会将这些未使用的部分标记为"unused"

删除未使用代码:在代码标记为未使用后,Webpack会在最终的代码生成阶段,通过工具(如UglifyJS等)删除这些未使用的代码,包括未使用的模块、函数、变量和导入等

3.4 Babel文件体积优化

Babel为编译的每个文件都插入了辅助代码,如公共方法的辅助代码_extend。但是有一些辅助代码会被重复添加到每一个需要它的文件中,造成文件体积过大。通过将捕助代码作为一个独立模块,从而避免重复引入问题。

@babel/plugin-transform-runtime:禁用了Babel自动对每个文件的 runtime注入,改为引入@babel/plugin-transform-runtin内的所有辅助代码。通过减少定义和重复引入,从而减少文件体积。

// 下载 npm i @babel/plugin-transform-runtime -D // 使用 { loader: 'babel-loader', options: { cacheDirectory: true, // 开启babel缓存 cacheCompression: false, // 关闭缓存文件压缩 plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积 } } 3.5 图片压缩

image-minimizer-webpack-plugin插件,可以对本地静态图片进行压缩处理,从而减少代码体积。

压缩图片的模式分为有损压缩和无损压缩两种:

无损压缩:imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo 有损压缩:imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo // 下载 npm i image-minimizer-webpack-plugin imagemin -D // 使用-无损压缩 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'} } ] } ] ] } } }) 4. 优化代码运行性能 4.1 Code Split

Code Split,通过将代码分割打包,从而可以按需加载,优化加载速度。

代码分调的作用:

分剩文件:将打包生成的文件进行分割,生成多个js文件 按需加载:需要哪个文件就加载哪个文件

1)多入口打包

entry:{ app: "./src/app.js", main: "./src/main.js" }

2)多入口提取公共模块

optimization: { splitChunks: { chunks: "all" } }

3)多入口按需加载

// import()动态加载语法,返回值为promise对象 import(./count.js).then((res)=>{ ... }).catch((err)=>{ ... })

4)模块命名

// 对某个引入模块命名 import(/* webpackChunkName:"math" */'./math.js') // 打包输出文件名称使用 module.exorts = { output:{ chunkFilename:"static/js/[name].js" } } 4.2 Preload/Prefetch

1)含义

Preload:立即加载资源 Prefetch:在浏览器空闲时才开始加载资源

2)共同点

只加载资源,不会执行资源 可以缓存资源

3)区别

Preload加载优先级高,Prefetch加载优先级低 Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面使用到的资源,也可以加载下一个页面使用到的资源

4)适用场景

当前页面优先级高的资源用Preload加载 下一个页面需要使用的资源用Prefetch加载 4.3 Core-js

core-js是用来做ES6以及以上API的polyfill的工具。polyfill翻译过来叫做垫片/补丁,就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上使用该新特性。

core-js一般会和babel一起使用,为ES6及以上语法生成对应的兼容性实现方案,并且会在dist文件夹下面生成一个新的打包文件。

// 下载 npm i core-js // 配合babel.config.js使用 module.exports = { presets: [ [ '@babel/preset-env', { useBuiltIns: "usage", // 按需加载自动引入 coreJs: 3 } ] ] } 4.4 PWA

浙进式网络应用程序(progressive web application PWA)是一种可以提供类似于native app(原生应用程序)体验的Web App的技术。其中最重要的是在离线(offline) 时,应用程序能够继续运行功能。

其内部是通过Service Workers技术实现的。

// 下载 npm i workbox-webpack-plugin --save-dev // 添加配置 plugins: [ new WorkboxPlugin({ clientsClaim: true, skipWaiting: true }) ] // 使用-main.js if("servicelorker" in navigator{ window.addEventListener("load", ()=>{ navigator.serviceWorker .register("/service-worker.js") .then((registration)=>{console.lor("sW registered: ",registration)}) .catch((registrationError)=>{console.log("sW registration failed: ", reristrationError)}) 5. 性能优化概述

1)提升开发体验

source Map:开发或上线时代码报错能有更加准确的错误提示

2)提升打包构建速度

HMR:开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快 oneOf:资源文件一旦被某个loader处理了,就不会继续遍历其他loader,打包速度更快 Include/Exclude:排除或只检测某些文件,处理的文件更少,速度更快 Cache:对eslint和babel处理的结果进行缓存,让第二次打包速度更快 Thead:多进程处理esint和babel任务,速度更快(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效)

3)减少代码体积

Tree shaking:移除没有使用的多余代码,让代码体积更小 babel/plugin-transform-runtime:对babel进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小 Image hininizer:对项目中图片进行压缩,体积更小,请求速度更快。(本地项目静态图片才需要进行压缩)

4)优化代码运行性能

code Split:对代码进行分割成多个js文件,从而使单个文件体积更小,并行加载速度更快 import():动态导入语法,按需加载 Preload/Prefetch:对代码进行提前加载,提升用户体验 Network cache:对输出资源文件进行更好的命名,利于缓存处理,提升用户体验 core-js:对js进行兼容性处理,使代码能运行在低版本浏览器中 PWA:实现代码离线访问功能,提升用户体验 第五章 webpack核心模块 1. Loader 1.1 loader工作原理 作用

Loader是帮助webpack将不同类型的文件转换为webpack可识别的模块。

原理

loader在底层就是一个函数,当webpack解祈资源时,会调用相应的loader方法处理。loader()接收三个参数:

content:文件内容 map:与SourceMap相关数据 meta:其他loader传递的数据 module.exports = function(content, map, meta){ ... .... return content; } 1.2 分类 优先级分类 pre:前置loader normal:普通loader inline:内联loader post:后置loader 不同优先级的执行顺序 不同优先级loader:pre > normal >inline > post 相同优先级loader:从右到左,从下到上 配置优先级 配置方式:在webpack.config.js文件中指定loader为pre、normal、post loader中一种,不添加任何指定时,默认为normal loader 内联方式:在每个import语句中显式指定loader为inline loader // pre loader { enforce: "pre" test: /\.js$/, loader: "loader1" }, // normal loader { test: /\.js$/, loader: "loader2" } // post loader { enforce: "post" test: /\.js$/, loader: "loader3" } // inline loader // 使用style-loader和css-loader处理styles.css文件 import Styles from 'style-loader!css-loader?modules!./styles.css"; // 前面添加一个“!”,跳过normal loader import Styles from '!style-loader!css-loader?modules!./styles.css"; // 前面添加一个“-!”,跳过 pre、normal loader import Styles from '-!style-loader!css-loader?modules!./styles.css"; // 前面添加一个“!!”,跳过pre、normal、postloader import Styles from '!!style-loader!css-loader?modules!./styles.css"; Loader分类 同步loader // 只有一个loader时 module.exports = function(content, map, meta){ // 不需要向下传递参数和source-map return content; } // 有多个loader连用时 module.exports = function(content, map,meta){ // err:代表是否有错误 // content:处理后的内容 // source-map:继续向下传递source-map // meta:给下一个loader传递的参数 this.callback(null,content,map,meta); } 异步loader module.exports = function(content , map, meta){ // 获取异步的回调函数 const callback = this.async(); setTimeout(() => { // 参数和同步回调函数一致 callback(null, content, map, meta); }, 1000); } Raw loader raw loader表示具有raw属性的loader,属性值为布尔值。可以是同步也可以是异步的loader,区别是其接收到的content是Buffer格式的流数据 function mayLoader(content){ // Buffer流,一般用于操作图片、图标等资源文件 return content; } mayLoader.raw = true; module.exports = mayLoader; pitch loader

pitch loader表示具有pitch属性的loader,属性值为函数。可以是同步也可以是异步的loader,特点是执行顺序会早于其他loader。

如连用三个loader处理文件资源时,会从左到右依次执行每个loader的pitch方法,然后在从右到左依次执行每个loader方法。

正常的pitch函数无返回值,如果在执行过程中某个pitch有返回值,则会中断执行顺序,转而执行前一个pitch所在的loader方法。

module.exports = function(content){ console.log('normal loader'); return content; } module.exports.pitch = function(){ consel.log("pitch loader"); }

pitch无返回值时的执行顺序 image.png

pitch2有返回值时的执行顺序 image.png

1.3 Loader常用API 方法名含义使用this.async异步回调loader,返回this.callbackconst callback = this.async()this.callback同步或异步调用的、可以返回多个结果的函数this.callback(err,content,sourceMap?,meta?)this.getOptions(schema)获取loader的options配置,schema为验证规则this.getOptions(schema)this.emitFile生成一个文件thisemitFile(name,content,sourceMap)this.utils.contextify返回一个相对路径this.utils.contextify(context,request)this.utils.absolutify返回一个绝对路径this.utils.absolutify(context,request) 1.4 自定义Loader 清除console.log代码的loader module.exports = function (content){ return content.replace(/consolel.log\(.* );?/g,""); } 低版本浏览器适配loader const babel = require( @babel/core"); const schema = require("./schema.json"); module.exports = function (content) { // 异步loader const callback = this.async(); const options = this.getOptions(schema); // 使用babe1对代码进行编译 babel.transform(content, options, function(err, result){ if (err){ callback(err); }else{ callback(null, result.code); } }); } 2. Plugin 2.1 Plugin原理分析 作用

Plugin 可以扩展 webpack,加入一些自定义的构建行为,使 webpack 可以执行更广泛的任务,拥有更强的构建能力。

工作原理

webpack 在编译的过程中,会触发一系列 Tapable 钩子事件,插件的作用就是找到对应的钩子,往钩子中挂载任务。当 webpack 构建的时候,插件注册的事件就会随着钩子的触发而执行。

第一步:获取Webpack编译器中的数据;

第二步:在合适的时机,监听Webpack的事件,如compile、emit、done等;

第三步:响应Webpack事件,并对编译期间的资源进行处理,例如对JavaScript、CSS等进行压缩、合并、优化等;

第四步:处理完成后,向Webpack编译器返回相应的处理结果,以便Webpack最终生成相应的文件;

常用的插件工作原理:

html-webpack-plugin:通过在Compilation对象中的html-webpack-plugin-before-html-processing钩子函数中添加生成的html文件内容来实现自动输出html文件 clean-webpack-plugin:在编译之前清除目标目录中的文件,就是通过在Compiler对象中监听webpack工作流程的钩子函数来实现的 工作流程

1️⃣:webpack 中的 plugin 是一个类(构造函数),通过在 plugins 配置中实例化进行调用

plugins:[new HTMLWebpackPlugin()],

2️⃣:在 plugin 的原型对象上指定了一个 apply 方法,入参是 compiler 对象

apply(compiler){}

3️⃣:指定一个事件钩子,并调用内部提供的API

4️⃣:完成内部操作后,调用 webpack 提供的 callback 方法

// 自定义plugin class TestPlugin{ constructor(){} // webpack将compiler对象传递给apply方法 apply(compiler){ // 挂载钩子 compiler.hooks.environmenttap("TestPlugin",()=>{}) compiler.hooks.emit.tap("TestPlugin",(compilation)=>{} compiler.hooks.emit.tapAsync("Testplugin",(compilation, callback) => { setTimeout(()=>{ // 调用webpack的回调函数 callback(); }, 2000); }) } } module.exports = TestPlugin; 2.2 webpack钩子

钩子的本质就是事件,为了方便开发者直接介入和控制编译过程,webpack把编译过程中触发的各类关键事件封装成事件接口暴露了出来,这些接口被称为 hooks(钩子)。

Tapable为webpack提供了统一的插件接口(钩子)类型定义,它是webpack的核心功能库。webpack中目前有十种hooks:

image.png

Tapable提供了三个方法给插件,用于注入不同类型的自定义构建行为:

top:即可以注册同步钩子和异步钩子 topAsync:回调函数方式注册异步钩子 tapPromise:Promise方式注册异步钩子

plugin通过Tapable注册事件。对于监听事件的触发,同步钩子通过call方法,异步钩子通过callAsync方法和promise方法。

2.3 编译管理器

plugin功能的实现主要依赖于compiler和complation对象,两者都继承自Tapable对象

Compiler对象

compiler对象中保存着完整的Webpack环境配置,每次启动webpack构建时,都会创建一个独一无二的compiler,它有以下主要属性:

compiler.options:访问本次启动webpack时所有的配置文件,包括但不限于 loaders、entry、output、plugin等等完整配置信息。 compiler.inputFileSystem、compiler.outputFileSysten:进行文件操作,相当于Nodejs中fs compiler.hooks:注册Tapable的不同种类Hook,从而可以在compiler生命周期中植入不同的逻辑 Compilation对象

compilation对象代表一次资源的构建,compilation实例能够访问所有的模块和它们的依赖。一个compilation对象会对构建依赖图中所有横块进行编译,在编译阶段横块会被加载(load)、封存(seal)、优化(optimize)、分块(chunk)、哈希(hash)和重新创建(restore)。

它有以下主要属性:

compilation.modules:访问所有横块,打包的每一个文件都是一个横块 compilation.chunks:chunk即是多个modules组成而来的一个代码块,入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk compilation.assets:访问本次打包生成所有文件的结果 compilation.hooks:注册tapable的不同种类Hook,用于在compilation编译模块阶段进行逻辑添加以及修改

compiler对象结构 image.png

compilation对象结构 image.png

2.4 自定义plugin

创建一个自定义的打包zip插件

const JsZip = require('jszip'); class ZipPlugin { constructor(options) { this.options = options; } apply(compiler) { compiler.hooks.emit.tapPromise('1', (compilation) => { const assets = compilation.assets; const zip = new JsZip(); for(let filename in assets) { zip.file(filename, assets[filename].source()) } // nodebuffer是node环境中的二进制形式;blob是浏览器环境 return zip.generateAsync({type: 'nodebuffer'}).then((content) =>{ assets[this.options.name] = { source(){return content}, } return new Promise((resolve, reject) => { resolve(compilation); }) }) }) } } module.exports = ZipPlugin;

webpack.config.js中使用插件:

module.exports = { plugins: [ new ZipPlugin({ name: 'my.zip' }) ] } 3. Loader和Plugin的区别

1)功能不同

Loader本质是一个函数,它是一个转换器。webpack只能解析原生js文件,对于其他类型文件就需要用loader进行转换。 Plugin是一个插件,用于增强webpack功能。webpack在运行的生命周期中会广播出许多事件,Plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。

2)用法不同

Loader的配置是在module.rules中,类型为数组,每⼀项都是⼀个 Object,⾥⾯描述了对于什么类型的⽂件(test),使⽤什么加载(loader)和使⽤的参数(options) Plugin的配置在plugins中,类型为数组,每一项是一个Plugin的实例,参数都通过构造函数传入的。 第六章 Webpack工作原理 1. 核心功能

webpack最核心的功能:内容转化+资源合并

初始化 初始化参数:参数 = 配置文件、配置对象、Shell 参数 + 默认配置 创建编译器对象:通过参数创建Compiler对象 初始化编译环境:注入内置插件、注册模块工厂、初始化RuleSet集合、加载配置的插件 开始编译:执行compiler对象的run方法 确定入口:根据entry找出所有入口文件 转换入口:调用compilition.addEntry将入口文件转换为dependence对象 构建 编译模块(make):根据dependence对象创建module对象,调用loader将模块转译为标准JS内容,调用JS解释器将内容转换为AST对象,从中找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理 完成模块编译:上一步递归处理所有能触达到的模块后,得到module 集合以及 module之间的依赖关系图 生成 输出资源(seal):根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

image.png

2. 初始化阶段 整合参数:process.args + webpack.config.js 校验参数:validateSchema 合并参数:getNormalizedWebpackOptions + applyWebpackOptionsBaseDefaults 基于参数创建compiler对象:new Compiler 插入外部plugin插件:遍历plugins集合,执行插件的apply方法 加载内置plugin插件:new WebpackOptionsApply().process

3. 构建阶段 构建module子类:根据文件类型调用handleModuleCreate 构建 module 子类 转义module内容:调用runLoaders将各类资源转译为 JavaScript 文本 解析JS文本:调用 acorn 将 JS 文本解析为AST 遍历AST,处理依赖

module => AST => dependences => module

🤔:webpack与babel区别?

🙋:相同点:webpack构建阶段会读取源码,解析为AST集合,babel解析阶段会读取源码解析为AST集合。不同点:Webpack只遍历AST集合,babel会对AST做等价转换

🤔:webpack如何识别资源依赖?

🙋:遍历AST集合,通过识别require/import之类的导入语句,确定依赖关系

4. 生成阶段 构建chunkGroup对象 将module分配给chunk:遍历compilation.modules集合,将module按entry/动态引入的规则分配给不同的Chunk对象 记录assets输出规则:遍历module/chunk ,调用compilation.emitAssets方法将assets信息记录到 compilation.assets对象中 将assets写入文件系统 控制流回转到compiler对象

entry及entry触达到的模块,组合成一个chunk;

使用动态引入语句引入的模块,各自组合成一个chunk;

5. 资源形态流转

1)compiler.make

entry文件以dependence对象形式加入compilation的依赖列表,dependence对象记录entry的类型、路径等信息 根据dependence调用对应的工厂函数创建module对象,之后读入 module 对应的文件内容,调用 loader-runner 对内容做转化,转化结果若有其它依赖则继续读入依赖资源,重复此过程直到所有依赖均被转化为module

2)compilation.seal

遍历module集合,根据entry配置及引入资源的方式,将module分配到不同的chunk 遍历 chunk 集合,调用compilation.emitAsset方法标记chunk的输出规则,即转化为assets集合

3)compiler.emitAssets:将assets写入文件系统

补充 1. SplitChunks原理 optimization: { splitChunks: { chunks: "all" } }

splitChunks的默认配置如下所示:

splitChunks: { chunks: "async", //initial表示直接引入的模块,async表示按需引入的模块,all表示都包括 minSize: 30000, //最小包体积,这里的单位是byte,超过这个大小的包会被splitChunks优化 minChunks: 1, //模块的最小引用次数,如果引用次数低于这个值,将不会被优化 maxAsyncRequests: 5, //设置async chunks的最大并行请求数 maxInitialRequests: 3, //设置initial chunks的最大并行请求数 automaticNameDelimiter: '~', //产出chunks的文件名分割符 name: true, //true:根据提取chunk的名字自动生成,false:根据缓存组IdHint生成,string:生成文件名即为这个string cacheGroups: { //缓存组,自定义拆包规则在此定义 vendors: { //默认配置,node_modules的chunk test: /[\/]node_modules[\/]/, priority: -10 }, default: { //业务代码的chunk minChunks: 2, priority: -20, reuseExistingChunk: true //复用已存在的chunks } } }

SplitChunks工作流程:

分析模块间的依赖关系

SplitChunksPlugin 会分析模块之间的依赖关系,并根据这些关系确定哪些模块可以组成一个共享块。这样可以确保代码被正确地分离,而不会出现意外的行为。

生成共享块

SplitChunksPlugin 根据配置项生成共享块。配置项包括 minSize(指定共享块大小的最小值)、maxSize(指定共享块大小的最大值)、minChunks(指定一个模块至少被使用的次数才会被拆分成共享块)等。

提取共享块

在分析和生成共享块后,SplitChunksPlugin 会将共享块提取出来,并创建新的 chunk(即打包后的文件),将这些共享块放入新的 chunk 中。这样,每个共享块只需被下载一次,而不必重复下载多次,从而提高了应用程序的加载速度。

缓存共享块

为了进一步提高性能,SplitChunksPlugin 会将共享块缓存起来,并在后续的构建中重复使用它们。这样,如果某个共享块已经存在于缓存中,就不必再重新生成它,从而节省了构建时间。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3