| 工具 | 功能 |
|---|---|
| webpack | 前端资源模块化管理和打包工具,1、可将很多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源; 2、还可以将按需加载的模块进行代码分割,等到实际需要的时候再异步加载; 3、让它自动实现这些功能,得提前编辑好配置文件 |
| vue-cli | 脚手架工具,用配置好的模板迅速搭建起一个项目工程来,基于webpack构建,带有合理的默认配置,省去了自己配置webpack配置文件的基本内容调整 webpack 配置最简单的方式就是在 vue.config.js 中的 configureWebpack 选项提供一个对象。 |
webpack:
1、webpack打包前后的变化
| 未使用打包 | -因为文件里涉及到了lodash,所以要在html文件中先引入lodash: |
| 使用打包后 | -dist文件夹中的index.html是构建过程中经过最小化和优化后产生的输出结果,之前的src/index.html是用于书写和编辑的源代码 |
2、webpack的相关配置信息
【插播一条📢📢📢】:安装一个package时,此package要打包到生产环境bundle中的话,安装的命令为 npm install --save。如果用于开发环境的bundle,则使用 npm install --save-dev
| 配置 | 含义 | 解释 |
|---|---|---|
| 入口entry | 指示webpack从哪个模块开始,构建内部的依赖图,进入起点后,会找到入口起点依赖的模块和库默认值./src/index.js,可通过配置config.js中的entry属性,来指定一个或多个不同的入口起点 | 1、单个入口,拓展性不高; 2、对象语法,最可拓展的方式(推荐)在其中传入不同类型的文件,可以实现将css和js或者其他文件分离在不同的bundle |
| 输出output | 指示webpack从哪里输出所创建的bundle,如何向硬盘写入编译文件主要输出默认./dist/main.js,其余生成的文件默认在./dist文件夹中。也可通过配置output字段,只能指定一个output配置项。 | 设置为一个对象,输出文件名可配置在output.filename中如果有多个入口起点,可使用占位符确保每个文件具有唯一的名称filename: '[name].[fullhash].js' |
| loader | webpack只能处理js和JSON文件,这个能让webpack处理其他类型的文件的源代码,将它们转换为有效的模块,供应用程序使用,以及添加到依赖图中loader的两个属性:test:识别出哪些文件会被转换,use:定义在进行转换时,用哪个loader注:1、定义在module.rules中;2、使用正则表达式匹配文件时,单引号和双引号意义不一样,单引号匹配任何满足这个表达式的文件,后者匹配具有绝对路径的满足这个表达式的单个文件 | 1、config.js文件中(推荐)可在module.rules中指定多个loader,loader取值、执行的顺序:从下往上,或者是从右到左;2、在import语句显式指定!将资源中的loader分开!前缀表示禁用所有已配置的normal loader;!!前缀表示禁用所有已配置的loader; -!前缀表示禁用所有已配置的preloader和loader;3、链式调用,第一个loader将转换结果传递给下一个loader,直到最后一个loader返回webpack所要的JS |
| 插件plugin | 支柱功能,用于执行范围更广的任务,比如打包优化、资源管理、注入环境变量 | 是一个具有apply方法的JS对象,apply会被webpack compiler调用,在整个编译生命周期都可访问compiler对象可以在这里暴露一个动态的Public Path场景:在my-host.com/app/* 上有一个host应用,并且在foo-app.com 上有一个子应用。子应用程序挂载在host域上,可以通过my-host.com/app/foo-app 访问到子应用,且my-host.com/app/foo-app…* 可以通过代理重定向到foo-app.com/* 【HtmlWebpackPlugin插件】默认生成自己的index.html文件,替换原有的index.html文件,所有的bundle会自动添加到html中 |
| target | webpack 能够为多种环境或 target 构建编译,target设置为node时,表示webpack将在Node.js环境编译代码 | |
| 模式mode | 告知 webpack 使用相应模式的内置优化 www.webpackjs.com/configurati… | |
| devServer | webpack-dev-server是一个封装好的webpack开发服务器,默认集成一些第三方插件并可供配置放在devServer对象中 | |
| devtool | devtool: 'inline-source-map' (可用于开发环境、测试环境,但不要用于生产环境,否则会增加bundle体积大小与降低整体性能)devtool: 'source-map' (可用于生产环境) | 当webpack打包源代码的时候,将好几个js文件打包到一个bundle文件中输入,出错的时候无法定位到某个js文件上。所以提供了一inline-source-map选项(不要用于生产环境),帮助定位错误在哪个js文件中的哪一行 |
3、webpack的相关分析
- 【chunk】
不同于以下配置,在webpack中指的是一个代码块。在webpack打包过程中,一堆module的集合,从入口模块开始,入口模块引用其他模块,其他模块再引用模块,这条打包路径就形成一个chunk。如果有多个入口文件,可能会产生多条打包路径,就会形成多个chunk。这是main chunk。还有一种chunk是可以延迟加载的块,可能会出现在使用动态导入或SplitChunksPlugin 时。
- 【配置configuration】
mode、entry、output等选项属于基本配置,把 webpack 配置以 npm 包的形式来管理,是对今后项目构建升级的维护考虑
- 【依赖图】
从入口开始,webpack会递归的构建一个依赖关系图,这个图包含了应用程序中所需的每个模块,然后将所有的模块打包为少量的bundle由浏览器加载
<对于 HTTP/1.1 的应用程序来说,由 webpack 构建的 bundle 非常强大。当浏览器发起请求时,它能最大程度的减少应用的等待时间。而对于 HTTP/2 来说,你还可以使用代码分割进行进一步优化>
- 【runtime】
在浏览器运行过程中,webpack用来连接模块化应用程序所需的所有代码,包括:在模块交互时,连接模块所需的加载和解析逻辑。以及尚未加载模块的延迟加载逻辑。
- 【manifest】
出现的原因:在经过打包、压缩、延迟加载而拆分为细小的chunk,原先/src目录的文件结构不复存在,一旦应用在浏览器中以index.html打开时,webpack要如何管理一些bundle和应用所需要的各种资源模块之间的交互呢。
解决:当compiler开始执行、解析和映射应用程序时,manifest作为数据集合,保留了所有模块的详细要点。当完成打包并发送到浏览器时,runtime会通过manifest来解析和加载模块
- 【webpack模块】
能以各种方式表达它们的依赖关系
- 【模块解析module resolution】
resolver是一个帮助寻找模块绝对路径的库,从每个require、import语句中找到需要引入到bundle中的模块代码。使用enhanced-resolve能解析出绝对路径、相对路径、模块路径
- 【webpack5模块联合】
wepack构建产生的模块都存储在本地,直接被当前应用所使用,webpack5中,运行时把当前构建的应用作为容器应用,异步加载远程模块(不属于当前构建)。
如下图,容器应用之间可以相互的依赖和相互加载、使用。
区别:1、模块共享:微前端需要把该模块独立出去,并以合理调用方式被其他微应用远程加载;模块联合中每个应用允许暴露多个接口,其他应用可以动态远程加载该应用后,直接使用其接口。2、模块加载:模块联合中可像使用普通npm包一样引用一个远程模块。3、模块切换:微前端中微应用的切换通常由路由状态改变,模块联合中远程模块与路由没有关联,加载的契机由host应用自己决定。
Webpack 5 通过ModuleFederationPlugin来实现模块接口暴露和远程模块声明的工作。ModuleFederationPlugin插件组合了ContainerPlugin和ContainerReferencePlugin这两个插件的功能,ContainerPlugin使用指定的公开模块来创建一个额外的容器入口,ContainerReferencePlugin允许使用import方式使用远程模块,需提前声明远程模块。ModuleFederationPlugin构建一个运行时独立模块
name表示的是容器的名称,filename为容器入口文件,构建后会在dist目录里产生ui.js的额外容器入口文件;exposes暴露任何想要分享出去的模块;shared用来指定公共模块异步模块加载使用
- 【缓存】
如图展示的是webpack的工作流程图。浏览器的缓存可以使网站加载速度更快,但同时由于缓存的存在,获取文件更新的代码会有些许棘手
解决:通过必要的配置,确保webpack编译生成的文件能够被客户端缓存,在文件内容变化后,能够请求到新的文件。
通过将output.filename设置为'[name].[contenthash].js'这种可替换模板字符串的方式,其中[contenthash]将根据资源内容创建出唯一hash,资源内容变,[contenthash]也会变
- 【模块热替换HMR】
在应用程序运行过程中,替换、添加或删除模块时,无需重新加载整个页面。(1、保留在完全重新加载页面期间丢失的应用程序状态;2、只更新变更的内容;3、在源代码中修改CSS/JS时,会立刻在浏览器中进行更新)
必须使用module.hot API处理传入的HMR请求,告诉webpack支持HMR
// 检查是否支持HMR接口
if(module.hot) {
// 支持热更新
module.hot.accept('xxx',function(){});
}
在xxx中写上对应依赖的文件,function中添加模块更新后的处理函数。
用于处理新的模块代码替换老的模块后,业务代码仍绑定在旧函数的事件处理程序中。所以面对较为复杂的应用程序中实现HMR较为棘手。但可以通过借助一些loader来实现HMR。
例子:
index.js文件中:
import printMe from "./print.js";
async function getComponent() {
const element = document.createElement("button");
// 使用default的原因是因为webpack4在导入CommonJS模块时,将不再解析为module.exports的值
// 而是为CommonJS模块创建一个artificial namespace对象
const { default: _ } = await import("lodash");
element.innerHTML = _.join(["Hello", "webpack!"], "");
element.onclick = printMe;
return element;
}
getComponent().then((component) => {
let element = component; // 存储element,在print.js修改时重新渲染
document.body.appendChild(element);
});
if (module.hot) {
module.hot.accept("./print.js", function () {
console.log("Accepting the updated printMe module!");
printMe();
// printMe()可替换为以下
// document.body.removeChild(element);
// element = component();
// 重新渲染“component”,以便更新click事件处理函数
// document.body.appendChild(element);
});
}
print.js文件中:
虽然打印出来的是更新后的模块代码,但是点击按钮是打印的还是之前的代码,所以需要在accept函数中进行更新业务代码
- 【tree shaking】
用于移除js上下文中的未引用代码,依赖于静态的ES6模块化语法,所有导入文件都会受到tree shaking的影响
满足条件:
- 使用ES module规范编写代码
- 在webpack.config.js中配置optimization.usedExports:true,启动标记功能 ;
- 在生产环境中启动代码压缩:mode:"production" optimization.minimize="true"
<所有的import标记为 /* harmony import*/; 所有被使用过的export标记为 /* harmony export([type]) /; 没有被使用过的export标记为 / unused harmony export [FuncName] */>
问题:当有些副作用的文件不应该被移除掉,可以通过sideEffect属性来处理副作用。
解决:在package.json文件中指定“sideEffects":false提示指定包以及依赖项没有副作用(副作用指:一个函数修改了它自己范围之外的东西),也可以sideEffects:["./xx./xx"]指定哪些文件/模块是有副作用的
4、对比webpack提供代码发生变化后自动编译代码的几种方式
| 方式 | 含义 | 使用 |
|---|---|---|
| watch mode | 通过watch,可以监听到依赖图中所有文件的更改,一旦有一个文件被更新,代码将被重新编译,从而不必手动运行构建运行命令:npm run watch | 1、在package.json文件的npm scripts中增加 |
| webpack-dev-server | 提供了一个基本的Web server,具有实时重新加载的功能。在内部使用了webpack-dev-middleware这个封装器。编译后不会写入到任何输出文件,而是将bundle文件保留在内存中运行命令:npm start | 1、安装依赖npm install --save-dev webpack-dev-server 2、在webpack.config文件中增加:(第一部分是将dist目录下的文件作为localhost:8080的可访问文件,文件的访问路径为:http://[devServer.host]:[devServer.port]/[output.publicPath]/[output.filename];第二部分是因为若单个HTML页面中具有多个入口,将要添加该配置,否则要遇到这个问题) |
| webpack-dev-middleware | 是一个封装器,可以把webpack处理过的文件发送到一个server,可作为一个单独的package来使用运行命令:npm run server | 1、安装依赖npm install --save-dev express webpack-dev-middleware;2、在webpack.config.js中的output属性中添加:publicPath:'/',添加server.js文件;3、在package.json的script中添加:"server":"node server.js",方便运行server |
5、对比webpack提供的代码分离特性的几种方式
代码分离:将代码分离到不同的bundle中,按需加载或并行加载这些文件。可控制资源加载的优先级,以及减小加载时间
| 含义 | 使用 | 备注 | |
|---|---|---|---|
| 入口起点 | 使用entry配置手动分离 | 优点:最简单直观;缺点: 手动配置较多,存在一些隐患。如果入口chunk之间包含一些重复的模块,重复的模块都会被引入到各个bundle中,bundle文件体积将会增大 | |
| 防止重复 | 使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk | 1、第一种:配置dependOn选项,可以在多个chunk之间共享模块 | |
| 动态导入 | 通过模块的内联函数调用来分离代码 1、ES提案的import()语法实现动态导入;2、webpack特定的require.ensure;意义:延迟或“按需”加载是优化站点或应用程序的好方法。这种做法实质上涉及在逻辑断点处拆分代码,然后在用户完成需要或将需要新代码块的操作后加载它。这加快了应用程序的初始加载速度并减轻了它的整体重量,因为有些块甚至可能永远不会加载。) | 1、import()调用会在内部用到Promise | 举例: |
6、webpack版本区别(可持续补充中......)
1、vector.js文件的作用是存入一些文件或library,打包成为单独的chunk,浏览器可以独立的缓存它们,减少加载时间
在webpack<4的版本中,通常将vector作为一个单独的入口起点添加在entry中,之后可编译为一个单独的文件
但是在webpack4版本中,不推荐这么做,而是使用optimization.splitChunks选项,将vector和app模块分开,并为其创建一个单独的文件
2、在webpack4版本中可以通过cli使用loader,但是在webpack5版本中已弃用
3、webpack5具有资源模块,允许使用资源文件而无需配置额外的loader
| webpack5之前 | webpack5 |
|---|---|
| raw-loader:将文件导入为字符串 | asset/source:导出资源的源代码 |
| url-loader:将文件作为data URL内联到bundle中 | asset/inline:导出一个资源的data URL asset:在导出一个data URL和发送一个单独的文件之前自动选择 |
| file-loader: 将文件发送到输出目录 | asset/resource:发送一个单独的文件并导出URL |
4、从webpack5开始,可以是使用Web Worker代替 worker-loader (只适用于ES模块,不可用于CommonJS)
new Worker(new URL('./worker.js', import.meta.url)); 这种语法为了实现不使用bundler就可以运行代码,可以在浏览器中的原生ES模块中使用