说说你对webpack的理解?解决了什么问题?
webpack最初的目标是实现前端项目模块化,目的在于更高效的管理和维护项目中的每一个资源
模块化:
最早的时候,我们通过文件划分的形式实现模块化,也就是将每个功能及其相关状态数据各自单独放在不同的js文件中
约定每个文件就是一个独立的模块,然后把js文件引入到页面,一个script标签对应一个模块,然后调用模块化的成员,这种的弊端非常的明显,模块都在全局中工作,大量的模块成员污染了环境,模块和模块之间没有依赖关系,维护起来困难,没有私有空间
随着命名空间的出现,规定每个模块只能暴露一个全局对象,然后模块的内容都挂载在这个对象中,但是这种方式也没有解决第一种方式的依赖等问题
再后来,我们使用立即执行函数为模块提供私有空间,通过参数的形式作为依赖声明,但是仍然存在一定的问题,通过script标签引入页面,这些模块的加载不受控制,时间久了维护起来也很困难
理想的解决方案就是在页面引入一个js入口文件,其余用到的模块可以通过代码控制,按需加载起来
问题:
现代前端开发变得越来越复杂,我们在开发过程中可能会遇到的问题:
- 需要通过模块化的方式来开发
- 使用一些高级的特性来加快我们的开发效率或者安全性,比如通过ES6+、TypeScript开发脚本逻辑,通过sass、less等方式来编写css样式代码
- 监听文件的变化来并且反映到浏览器上,提高开发的效率
- JavaScript 代码需要模块化,HTML 和 CSS 这些资源文件也会面临需要被模块化的问题
- 开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化
是什么:
webpack是一个用于现代的javascript应用程序的静态模块的打包工具
静态模块:
静态模块指的是开发阶段,可以被webpack直接引用的资源,可以直接被打包进bundle.js的资源
webpack的能力:
编译代码能力,提高效率,解决浏览器兼容的问题
模块整合能力,提高性能,可维护性,解决浏览器频繁请求文件的问题
万物皆可模块,项目维护性增强,支持不同种类的前端模块类型,统一的模块化方案,所有资源文件都可以通过代码控制
说说webpack的构建流程?
webpack的运行流程是一个串行的过程,他的工作流程就是将各个插件串联起来
在运行过程中会广播时间,插件只需要监听它所关心的事件,就能加入到这条webpack机制中,去改变webpack的运作,使整个系统扩展性良好
从启动到结束会依次执行以下三大步骤:
- 初始化流程:从配置文件和
Shell
语句中读取与合并参数,并初始化需要使用的插件和配置插件等执行环境所需要的参数 - 编译构建流程:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理
- 输出流程:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统
说说webpack中常见的Loader?解决了什么问题?
loader用于对模块的源代码进行转换,在import或加载模块是预处理文件
webpack做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成指定的文件中
在webpack中,任何文件都是模块,不仅仅是js文件,默认情况下,在遇到import或者require加载模块的时候,webpack只支持js和json文件打包
像css
、sass
、png
等这些类型的文件的时候,webpack
则无能为力,这时候就需要配置对应的loader
进行文件内容的解析、
关于配置loader
的方式有三种:
- 配置方式(推荐):在 webpack.config.js文件中指定 loader
- 内联方式:在每个 import 语句中显式指定 loader
- CLI 方式:在 shell 命令中指定它们
特性:
loader支持链式调用,链中的每个loader会处理之前处理果的资源,最终变为js代码,顺序为相反的顺序执行
除此之外,loader
的特性还有如下:
- loader 可以是同步的,也可以是异步的
- loader 运行在 Node.js 中,并且能够执行任何操作
- 除了常见的通过
package.json
的main
来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用loader
字段直接引用一个模块 - 插件(plugin)可以为 loader 带来更多特性
- loader 能够产生额外的任意文件
常见的loader:
- style-loader: 将css添加到DOM的内联样式标签style里
- css-loader :允许将css文件通过require的方式引入,并返回css代码
- less-loader: 处理less
- sass-loader: 处理sass
- postcss-loader: 用postcss来处理CSS
- autoprefixer-loader: 处理CSS3属性前缀,已被弃用,建议直接使用postcss
- file-loader: 分发文件到output目录并返回相对路径
- url-loader: 和file-loader类似,但是当文件小于设定的limit时可以返回一个Data Url
- html-minify-loader: 压缩HTML
- babel-loader :用babel来转换ES6文件到ES
说说webpack中常见的Plugin?解决了什么问题?
Plugin是一种计算机应用程序,他和主应用程序互相交互,提供特定的功能
是一种遵循一定规范的应用程序接口编写出来的程序,只能运行在程序规定的系统下,因为其需要调用原纯净系统提供的函数库和数据
webpack中的plugin也是如此,plugin赋予其各种灵活的功能,例如打包优化,资源管理,环境变量注入等,它们运行在webpack的不同阶段,贯穿了webpack整个编译周期
特性:
本质是一个具有apply方法的javascript对象
关于整个编译生命周期钩子,有如下:
- entry-option :初始化 option
- run
- compile: 真正开始的编译,在创建 compilation 对象之前
- compilation :生成好了 compilation 对象
- make 从 entry 开始递归分析依赖,准备对每个模块进行 build
- after-compile: 编译 build 过程结束
- emit :在将内存中 assets 内容写到磁盘文件夹之前
- after-emit :在将内存中 assets 内容写到磁盘文件夹之后
- done: 完成所有的编译过程
- failed: 编译失败的时候
常见的Plugin
HtmlWebpackPlugin:在打包结束后,⾃动生成⼀个 html
⽂文件,并把打包生成的js
模块引⼊到该 html
中
clean-webpack-plugin:删除(清理)构建目录
mini-css-extract-plugin:提取 CSS
到一个单独的文件中
DefinePlugin:允许在编译时创建配置的全局对象,是一个webpack
内置的插件,不需要安装
copy-webpack-plugin:复制文件或目录到执行区域,如vue
的打包过程中,如果我们将一些文件放到public
的目录下,那么这个目录会被复制到dist
文件夹中
复制的规则在patterns
属性中设置:
-
from:设置从哪一个源中开始复制
-
to:复制到的位置,可以省略,会默认复制到打包的目录下
-
globOptions:设置一些额外的选项,其中可以编写需要忽略的文件
说说Loader和Plugin的区别?编写Loader,Plugin的思路?
区别:
- loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
- plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事
两者在运行时机上的区别:
- loader 运行在打包文件之前
- plugins 在整个编译周期都起作用
编写loader
在编写 loader
前,我们首先需要了解 loader
的本质
其本质为函数,函数中的 this
作为上下文会被 webpack
填充,因此我们不能将 loader
设为一个箭头函数
函数接受一个参数,为 webpack
传递给 loader
的文件源内容
函数中 this
是由 webpack
提供的对象,能够获取当前 loader
所需要的各种信息
函数中有异步操作或同步操作,异步操作通过 this.callback
返回,返回值要求为 string
或者 Buffer
一般在编写loader
的过程中,保持功能单一,避免做多种功能
如less
文件转换成 css
文件也不是一步到位,而是 less-loader
、css-loader
、style-loader
几个 loader
的链式调用才能完成转换
编写plugin
如果自己要实现plugin
,也需要遵循一定的规范:
- 插件必须是一个函数或者是一个包含
apply
方法的对象,这样才能访问compiler
实例 - 传给每个插件的
compiler
和compilation
对象都是同一个引用,因此不建议修改 - 异步的事件需要在插件处理完任务时调用回调函数通知
Webpack
进入下一个流程,不然会卡住
在 emit
事件发生时,代表源文件的转换和组装已经完成,可以读取到最终将输出的资源、代码块、模块及其依赖,并且可以修改输出资源的内容
说说webpack的热更新是如何做到的?原理是什么?
HMR
全称 Hot Module Replacement
,可以理解为模块热替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用
我们在应用运行过程中修改了某个模块,通过自动刷新就会导致整个应用的整体刷新,那页面中的状态信息都会丢失
如果使用的HMR,就可以实现只将修改的模块试试替换至应用中,不必完全刷新整个应用
配置热更新非常简单
const webpack = require('webpack')
module.exports = {
// ...
devServer: {
// 开启 HMR 特性
hot: true
// hotOnly: true
}
}
通过上述这种配置,如果我们修改并保存css文件,确实能够以不刷新的形式更新到页面中
我们修改并保存js文件之后,页面依旧自动刷新了,这里并没有触发热更新
所以,HMR并不像webpack的其他特性一样可以开箱即用,还需要一些额外的配置
if(module.hot){
module.hot.accept('./util.js',()=>{
console.log("util.js更新了")
})
}
总结:
关于webpack
热模块更新的总结如下:
- 通过
webpack-dev-server
创建两个服务器:提供静态资源的服务(express)和Socket服务 - express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
- socket server 是一个 websocket 的长连接,双方可以通信
- 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)
- 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
- 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新
说说webpack proxy工作原理?为什么能解决跨域?
webpack proxy,是webpack提供的代理服务
基本的行为就是接收客户端发送的请求后转发给其他服务器,目的就是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限制)
想要实现代理首先需要一个中间服务器,webpack中提供服务器的工具为webpack-dev-server
目的是为了提高开发者日常的开发效率,只适用在开发阶段
devServetr里面的proxy是关于代理的配置,该属性为对象的形式,对象中的每一个属性就是一个代理的规则匹配
属性的名称是需要被代理的请求路径前缀,一般是为了辨别都会设置前缀为api,值为对应的代理匹配规则
- target:表示的是代理到的目标地址
- pathRewrite:默认情况下,我们的 /api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite
- secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false
- changeOrigin:它表示是否更新代理后请求的 headers 中host地址
工作原理:
proxy工作原理实质上是利用http-proxy-middleware这个http代理中间件,实现请求转发给其他服务器
跨域:
在开发阶段,webpack-dev-server会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在location的一个端口上,而后端服务又是运行在另一个地址上
所以在开发阶段中,由于浏览器同样策略的原因,当本地访问后端就会出现跨域请求的问题
通过设置webpack proxy实现代理请求后,相当于浏览器与服务端中添加一个代理者
当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地
再代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能正常的接收数据
注意:服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制
说说如何借助webpack来优化前端性能?
随着前端的项目逐渐扩大,必然会带来一个问题就是性能
尤其在大型复杂的项目中,前端业务可能因为一个小小的依赖,导致整个页面卡顿甚至崩溃
一般在项目完成后,会通过webpack进行打包,利用webpack对前端项目性能优化是一个十分重要的环节
优化方法:
- JS代码压缩:压缩可以丑化我们的代码,使打包内存更小
- CSS代码压缩:css压缩通常是去除无用的空格,因为很难去修改选择器,属性的名称值
- Html文件代码压缩:
- 文件大小压缩
- 图片压缩:有些图片文件比js或css文件更大
- Tree Shaking
- 代码分离:实现按需加载
- 内联 chunk:chunk的模块关联到html
总结
关于webpack
对前端性能的优化,可以通过文件体积大小入手,其次还可通过分包的形式、减少http请求次数等方式,实现对前端性能的优化
如何提高webpack的构建速度?
随着我们的项目涉及到的页面越来越多,功能和业务代码也会随着越多,相应的webpack的构建时间也会越来越久
构建时间与我们日常开发效率密切相关,当我们本地启动devserver或者build的时候,如果时间过长,会大大的降低我们的工作效率
如何优化
- 优化 loader 配置
- 合理使用 resolve.extensions
- 优化 resolve.modules
- 优化 resolve.alias
- 使用 DLLPlugin 插件
- 使用 cache-loader
- terser 启动多线程
- 合理使用 sourceMap
总结:
优化webpack
构建的方式有很多,主要可以从优化搜索时间、缩小文件搜索范围、减少不必要的编译等方面入手
与webpack类似的工具还有哪些?区别?
模块化工具:
模块化是一种处理复杂系统分解为更好的可管理模块的方式
可以用来分割,组织和打包应用。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体
通过这些模块打包工具,能够提高我们的开发效率,减少成本
rollup
Rollup
是一款 ES Modules
打包器,从作用上来看,Rollup
与 Webpack
非常类似。不过相比于 Webpack
,Rollup
要小巧的多
Parcel
Parcel ,是一款完全零配置的前端打包器,它提供了 “傻瓜式” 的使用体验,只需了解简单的命令,就能构建前端应用程序
Parcel
跟 Webpack
一样都支持以任意类型文件作为打包入口,但建议使用HTML
文件作为入口,该HTML
文件像平时一样正常编写代码、引用资源
Snowpack
Snowpack,是一种闪电般快速的前端构建工具,专为现代Web
设计,较复杂的打包工具(如Webpack
或Parcel
)的替代方案,利用JavaScript
的本机模块系统,避免不必要的工作并保持流畅的开发体验
Vite
vite ,是一种新型前端构建工具,能够显著提升前端开发体验
它主要由两部分组成:
- 一个开发服务器,它基于 原生 ES 模块 提供了丰富的内建功能,如速度快到惊人的 [模块热更新HMR
- 一套构建指令,它使用 Rollup打包你的代码,并且它是预配置的,可以输出用于生产环境的优化过的静态资源
其作用类似webpack
+ webpack-dev-server
,其特点如下:
- 快速的冷启动
- 即时的模块热更新
- 真正的按需编译
vite
会直接启动开发服务器,不需要进行打包操作,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快
webpack
相比上述的模块化工具,webpack
大而全,很多常用的功能做到开箱即用。有两大最核心的特点:一切皆模块和按需加载
与其他构建工具相比,有如下优势:
- 智能解析:对 CommonJS 、 AMD 、ES6 的语法做了兼容
- 万物模块:对 js、css、图片等资源文件都支持打包
- 开箱即用:HRM、Tree-shaking等功能
- 代码分割:可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时间
- 插件系统,具有强大的 Plugin 接口,具有更好的灵活性和扩展性
- 易于调试:支持 SourceUrls 和 SourceMaps
- 快速运行:webpack 使用异步 IO 并具有多级缓存,这使得 webpack 很快且在增量编译上更加快
- 生态环境好:社区更丰富,出现的问题更容易解决