Tiny'Wo | 小窝

网络中的一小块自留地

面试官:说说Vue 3.0中Treeshaking特性?举例说明一下?

一、是什�?

Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术,专业术语�?Dead code elimination

简单来讲,就是在保持代码运行结果不变的前提下,去除无用的代�?
如果把代码打包比作制作蛋糕,传统的方式是把鸡蛋(带壳)全部丢进去搅拌,然后放入烤箱,最后把(没有用的)蛋壳全部挑选并剔除出去

treeshaking则是一开始就把有用的蛋白蛋黄(import)放入搅拌,最后直接作出蛋�?
也就是说 ,tree shaking 其实是找出使用的代码

Vue2中,无论我们使用什么功能,它们最终都会出现在生产代码中。主要原因是Vue实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用�?

1
2
3
import Vue from 'vue'

Vue.nextTick(() => {})

Vue3源码引入tree shaking特性,将全局 API 进行分块。如果您不使用其某些功能,它们将不会包含在您的基础包中

1
2
3
import { nextTick, observable } from 'vue'

nextTick(() => {})

二、如何做

Tree shaking是基于ES6模板语法(importexports),主要是借助ES6模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变�?
Tree shaking无非就是做了两件事:

  • 编译阶段利用ES6 Module判断哪些模块已经加载
  • 判断那些模块和变量未被使用或者引用,进而删除对应代�?
    下面就来举个例子�?
    通过脚手架vue-cli安装Vue2Vue3项目
1
vue create vue-demo

Vue2 项目

组件中使用data属�?

1
2
3
4
5
6
7
<script>
export default {
data: () => ({
count: 1,
}),
};
</script>

对项目进行打包,体积如下�?

为组件设置其他属性(comptedwatch�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
data: () => ({
question:"",
count: 1,
}),
computed: {
double: function () {
return this.count * 2;
},
},
watch: {
question: function (newQuestion, oldQuestion) {
this.answer = 'xxxx'
}
};

再一次打包,发现打包出来的体积并没有变化

Vue3 项目

组件中简单使�?

1
2
3
4
5
6
7
8
9
10
11
import { reactive, defineComponent } from "vue";
export default defineComponent({
setup() {
const state = reactive({
count: 1,
});
return {
state,
};
},
});

将项目进行打�?

在组件中引入computedwatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { reactive, defineComponent, computed, watch } from "vue";
export default defineComponent({
setup() {
const state = reactive({
count: 1,
});
const double = computed(() => {
return state.count * 2;
});

watch(
() => state.count,
(count, preCount) => {
console.log(count);
console.log(preCount);
}
);
return {
state,
double,
};
},
});

再次对项目进行打包,可以看到在引入computerwatch之后,项目整体体积变大了

三、作�?

通过Tree shakingVue3给我们带来的好处是:

  • 减少程序体积(更小)
  • 减少程序执行时间(更快)
  • 便于将来对程序架构进行优化(更友好)

参考文�?

面试官:说说webpack的热更新是如何做到的?原理是什么?

一、是什�?

HMR 全称 Hot Module Replacement,可以理解为模块热替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用

例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢�?
如果使用的是 HMR,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用

webpack中配置开启热模块也非常的简单,如下代码�?

1
2
3
4
5
6
7
8
const webpack = require('webpack')
module.exports = {
// ...
devServer: {
// 开�?HMR 特�? hot: true
// hotOnly: true
}
}

通过上述这种配置,如果我们修改并保存css文件,确实能够以不刷新的形式更新到页面中

但是,当我们修改并保存js文件之后,页面依旧自动刷新了,这里并没有触发热模�?
所以,HMR 并不�?Webpack 的其他特性一样可以开箱即用,需要有一些额外的操作

我们需要去指定哪些模块发生更新时进行HRM,如下代码:

1
2
3
4
5
if(module.hot){
module.hot.accept('./util.js',()=>{
console.log("util.js更新�?)
})
}

二、实现原�?

首先来看看一张图,如下:

  • Webpack Compile:将 JS 源代码编译成 bundle.js

  • HMR Server:用来将热更新的文件输出�?HMR Runtime

  • Bundle Server:静态资源文件服务器,提供文件访问路�?- HMR Runtime:socket服务器,会被注入到浏览器,更新文件的变化

  • bundle.js:构建输出的文件

  • 在HMR Runtime �?HMR Server之间建立 websocket,即图上4号线,用于实时更新文件变�?
    上面图中,可以分成两个阶段:

  • 启动阶段为上�?1 - 2 - A - B

在编写未经过webpack打包的源代码后,Webpack Compile 将源代码�?HMR Runtime 一起编译成 bundle 文件,传输给 Bundle Server 静态资源服务器

  • 更新阶段为上�?1 - 2 - 3 - 4

当某一个文件或者模块发生变化时,webpack 监听到文件变化对文件重新编译打包,编译生成唯一的hash值,这个hash 值用来作为下一次热更新的标�?
根据变化的内容生成两个补丁文件:manifest(包含了 hash �?chundId ,用来说明变化的内容)和 chunk.js 模块

由于socket服务器在HMR Runtime �?HMR Server之间建立 websocket链接,当文件发生改动的时候,服务端会向浏览器推送一条消息,消息包含文件改动后生成的hash值,如下图的h属性,作为下一次热更细的标�?

在浏览器接受到这条消息之前,浏览器已经在上一次 socket 消息中已经记住了此时的 hash 标识,这时候我们会创建一�?ajax 去服务端请求获取到变化内容的 manifest 文件

mainfest文件包含重新build生成的hash值,以及变化的模块,对应上图的c属�?
浏览器根�?manifest 文件获取模块变化的内容,从而触发render流程,实现局部模块更�?

三、总结

关于webpack热模块更新的总结如下�?

  • 通过webpack-dev-server创建两个服务器:提供静态资源的服务(express)和Socket服务
  • express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析�?- socket server 是一�?websocket 的长连接,双方可以通信
  • �?socket server 监听到对应的模块发生变化时,会生成两个文�?json(manifest文件)和.js文件(update chunk�?- 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
  • 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新

参考文�?

面试官:说说webpack中常见的Loader?解决了什么问题?

一、是什�?loader 用于对模块的”源代�?进行转换,在 import �?加载”模块时预处理文件

webpack做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中。如下图所示:

webpack内部中,任何文件都是模块,不仅仅只是js文件

默认情况下,在遇到import或者require加载模块的时候,webpack只支持对js �?json 文件打包

csssasspng等这些类型的文件的时候,webpack则无能为力,这时候就需要配置对应的loader进行文件内容的解�?
在加载模块的时候,执行顺序如下�?

�?webpack 碰到不识别的模块的时候,webpack 会在配置的中查找该文件解析规�?

关于配置loader的方式有三种�?

  • 配置方式(推荐):在 webpack.config.js文件中指�?loader
  • 内联方式:在每个 import 语句中显式指�?loader
  • CLI 方式:在 shell 命令中指定它�?

配置方式

关于loader的配置,我们是写在module.rules属性中,属性介绍如下:

  • rules是一个数组的形式,因此我们可以配置很多个loader

  • 每一个loader对应一个对象的形式,对象属性test 为匹配的规则,一般情况为正则表达�?

  • 属性use针对匹配到文件类型,调用对应�?loader 进行处理

代码编写,如下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
},
{ loader: 'sass-loader' }
]
}
]
}
};

二、特�?

这里继续拿上述代码,来讲讲loader的特�?
从上述代码可以看到,在处理css模块的时候,use属性中配置了三个loader分别处理css文件

因为loader 支持链式调用,链中的每个loader会处理之前已处理过的资源,最终变为js代码。顺序为相反的顺序执行,即上述执行方式为sass-loadercss-loaderstyle-loader

除此之外,loader的特性还有如下:

  • loader 可以是同步的,也可以是异步的
  • loader 运行�?Node.js 中,并且能够执行任何操作
  • 除了常见的通过 package.json �?main 来将一�?npm 模块导出�?loader,还可以�?module.rules 中使�?loader 字段直接引用一个模�?- 插件(plugin)可以�?loader 带来更多特�?- loader 能够产生额外的任意文�?
    可以通过 loader 的预处理函数,为 JavaScript 生态系统提供更多能力。用户现在可以更加灵活地引入细粒度逻辑,例如:压缩、打包、语言翻译和更多其他特�?

三、常见的loader

在页面开发过程中,我们经常性加载除了js文件以外的内容,这时候我们就需要配置响应的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

下面给出一些常见的loader的使用:

css-loader

分析 css 模块之间的关系,并合成⼀�?css

1
npm install --save-dev css-loader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rules: [
...,
{
test: /\.css$/,
use: {
loader: "css-loader",
options: {
// 启用/禁用 url() 处理
url: true,
// 启用/禁用 @import 处理
import: true,
// 启用/禁用 Sourcemap
sourceMap: false
}
}
}
]

如果只通过css-loader加载文件,这时候页面代码设置的样式并没有生�?
原因在于,css-loader只是负责将.css文件进行一个解析,而并不会将解析后的css插入到页面中

如果我们希望再完成插入style的操作,那么我们还需要另外一个loader,就是style-loader

style-loader

�?css-loader 生成的内容,�?style 标签挂载到页面的 head �?

1
npm install --save-dev style-loader
1
2
3
4
5
6
7
rules: [
...,
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]

同一个任务的 loader 可以同时挂载多个,处理顺序为:从右到左,从下往�?

less-loader

开发中,我们也常常会使用lesssassstylus预处理器编写css样式,使开发效率提高,这里需要使用less-loader

1
npm install less-loader -D
1
2
3
4
5
6
7
rules: [
...,
{
test: /\.css$/,
use: ["style-loader", "css-loader","less-loader"]
}
]

raw-loader

�?webpack 中通过 import 方式导入文件内容,该loader 并不是内置的,所以首先要安装

1
npm install --save-dev raw-loader

然后�?webpack.config.js 中进行配�?

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
...,
module: {
rules: [
{
test: /\.(txt|md)$/,
use: 'raw-loader'
}
]
}
}

file-loader

把识别出的资源模块,移动到指定的输出⽬目录,并且返回这个资源在输出目录的地址(字符�?

1
npm install --save-dev file-loader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rules: [
...,
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: "file-loader",
options: {
// placeholder 占位�?[name] 源资源模块的名称
// [ext] 源资源模块的后缀
name: "[name]_[hash].[ext]",
//打包后的存放位置
outputPath: "./images",
// 打包后文件的 url
publicPath: './images',
}
}
}
]

url-loader

可以处理�?file-loader 所有的事情,但是遇到图片格式的模块,可以选择性的把图片转�?base64 格式的字符串,并打包�?js 中,对小体积的图片比较合适,大图片不合适�?

1
npm install --save-dev url-loader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
rules: [
...,
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: "url-loader",
options: {
// placeholder 占位�?[name] 源资源模块的名称
// [ext] 源资源模块的后缀
name: "[name]_[hash].[ext]",
//打包后的存放位置
outputPath: "./images"
// 打包后文件的 url
publicPath: './images',
// 小于 100 字节转成 base64 格式
limit: 100
}
}
}
]

参考文�?

面试官:说说Loader和Plugin的区别?编写Loader,Plugin的思路�?

一、区�?

前面两节我们有提到LoaderPlugin对应的概念,先来回顾�?

  • loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
  • plugin 赋予�?webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事

从整个运行时机上来看,如下图所示:

可以看到,两者在运行时机上的区别�?

  • loader 运行在打包文件之�?- plugins 在整个编译周期都起作�?
    Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过Webpack提供�?API 改变输出结果

对于loader,实质是一个转换器,将A文件进行编译形成B文件,操作的是文件,比如将A.scssA.less转变为B.css,单纯的文件转换过程

二、编写loader

在编�?loader 前,我们首先需要了�?loader 的本�?
其本质为函数,函数中�?this 作为上下文会�?webpack 填充,因此我们不能将 loader设为一个箭头函�?
函数接受一个参数,�?webpack 传递给 loader 的文件源内容

函数�?this 是由 webpack 提供的对象,能够获取当前 loader 所需要的各种信息

函数中有异步操作或同步操作,异步操作通过 this.callback 返回,返回值要求为 string 或�?Buffer

代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 导出一个函数,source为webpack传递给loader的文件源内容
module.exports = function(source) {
const content = doSomeThing2JsString(source);

// 如果 loader 配置�?options 对象,那么this.query将指�?options
const options = this.query;

// 可以用作解析其他模块路径的上下文
console.log('this.context');

/*
* this.callback 参数�? * error:Error | null,当 loader 出错时向外抛出一�?error
* content:String | Buffer,经�?loader 编译后需要导出的内容
* sourceMap:为方便调试生成的编译后内容�?source map
* ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生�?AST 的过�? */
this.callback(null, content); // 异步
return content; // 同步
}

一般在编写loader的过程中,保持功能单一,避免做多种功能

less文件转换�?css 文件也不是一步到位,而是 less-loadercss-loaderstyle-loader几个 loader 的链式调用才能完成转�?

三、编写plugin

由于webpack基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段执行自己的插件任�?
在之前也了解过,webpack编译会创建两个核心对象:

  • compiler:包含了 webpack 环境的所有的配置信息,包�?options,loader �?plugin,和 webpack 整个生命周期相关的钩�?- compilation:作�?plugin 内置事件回调函数的参数,包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当检测到一个文件变化,一次新�?Compilation 将被创建

如果自己要实现plugin,也需要遵循一定的规范�?

  • 插件必须是一个函数或者是一个包�?apply 方法的对象,这样才能访问compiler实例
  • 传给每个插件�?compiler �?compilation 对象都是同一个引用,因此不建议修�?- 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡�?

实现plugin的模板如下:

1
2
3
4
5
6
7
8
9
10
11
12
class MyPlugin {
// Webpack 会调�?MyPlugin 实例�?apply 方法给插件实例传�?compiler 对象
apply (compiler) {
// 找到合适的事件钩子,实现自己的插件功能
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation: 当前打包构建流程的上下文
console.log(compilation);

// do something...
})
}
}

�?emit 事件发生时,代表源文件的转换和组装已经完成,可以读取到最终将输出的资源、代码块、模块及其依赖,并且可以修改输出资源的内�?

参考文�?

面试官:说说webpack中常见的Plugin?解决了什么问题?

一、是什�?Plugin(Plug-in)是一种计算机应用程序,它和主应用程序互相交互,以提供特定的功�?

是一种遵循一定规范的应用程序接口编写出来的程序,只能运行在程序规定的系统下,因为其需要调用原纯净系统提供的函数库或者数�?
webpack中的plugin也是如此,plugin赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,它们会运行�?webpack 的不同阶段(钩子 / 生命周期),贯穿了webpack整个编译周期

目的在于解决loader 无法实现的其他事

配置方式

这里讲述文件的配置方式,一般情况,通过配置文件导出对象中plugins属性传入new实例对象。如下所示:

1
2
3
4
5
6
7
8
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 访问内置的插�?module.exports = {
...
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({ template: './src/index.html' }),
],
};

二、特�?

其本质是一个具有apply方法javascript对象

apply 方法会被 webpack compiler 调用,并且在整个编译生命周期都可以访�?compiler 对象

1
2
3
4
5
6
7
8
9
10
11
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, (compilation) => {
console.log('webpack 构建过程开始!');
});
}
}

module.exports = ConsoleLogOnBuildWebpackPlugin;

compiler hook �?tap 方法的第一个参数,应是驼峰式命名的插件名称

关于整个编译生命周期钩子,有如下�?

  • entry-option :初始化 option
  • run
  • compile�?真正开始的编译,在创建 compilation 对象之前
  • compilation :生成好�?compilation 对象
  • make �?entry 开始递归分析依赖,准备对每个模块进行 build
  • after-compile�?编译 build 过程结束
  • emit :在将内存中 assets 内容写到磁盘文件夹之�?- after-emit :在将内存中 assets 内容写到磁盘文件夹之�?- done�?完成所有的编译过程
  • failed�?编译失败的时�?

三、常见的Plugin

常见的plugin有如图所示:

下面介绍几个常用的插件用法:

HtmlWebpackPlugin

在打包结束后,⾃动生成⼀�?html ⽂文件,并把打包生成的 js 模块引⼊到该 html �?

1
npm install --save-dev html-webpack-plugin
1
2
3
4
5
6
7
8
9
10
11
12
// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
title: "My App",
filename: "app.html",
template: "./src/html/index.html"
})
]
};
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--./src/html/index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%=htmlWebpackPlugin.options.title%></title>
</head>
<body>
<h1>html-webpack-plugin</h1>
</body>
</html>

�?html 模板中,可以通过 <%=htmlWebpackPlugin.options.XXX%> 的方式获取配置的�?
更多的配置可以自寻查�?

clean-webpack-plugin

删除(清理)构建目录

1
npm install --save-dev clean-webpack-plugin
1
2
3
4
5
6
7
8
9
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
module.exports = {
...
plugins: [
...,
new CleanWebpackPlugin(),
...
]
}

mini-css-extract-plugin

提取 CSS 到一个单独的文件�?

1
npm install --save-dev mini-css-extract-plugin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...,
module: {
rules: [
{
test: /\.s[ac]ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
...,
new MiniCssExtractPlugin({
filename: '[name].css'
}),
...
]
}

DefinePlugin

允许在编译时创建配置的全局对象,是一个webpack内置的插件,不需要安�?

1
2
3
4
5
6
7
8
9
10
const { DefinePlugun } = require('webpack')

module.exports = {
...
plugins:[
new DefinePlugin({
BASE_URL:'"./"'
})
]
}

这时候编译template模块的时候,就能通过下述形式获取全局对象

1
<link rel="icon" href="<%= BASE_URL%>favicon.ico>"

copy-webpack-plugin

复制文件或目录到执行区域,如vue的打包过程中,如果我们将一些文件放到public的目录下,那么这个目录会被复制到dist文件夹中

1
npm install copy-webpack-plugin -D
1
2
3
4
5
6
7
8
9
10
11
12
new CopyWebpackPlugin({
parrerns:[
{
from:"public",
globOptions:{
ignore:[
'**/index.html'
]
}
}
]
})

复制的规则在patterns属性中设置�?

  • from:设置从哪一个源中开始复�?- to:复制到的位置,可以省略,会默认复制到打包的目录�?
  • globOptions:设置一些额外的选项,其中可以编写需要忽略的文件

参考文�?- https://webpack.docschina.org/concepts/plugins/

面试官:与webpack类似的工具还有哪些?区别�?

一、模块化工具

模块化是一种处理复杂系统分解为更好的可管理模块的方�?
可以用来分割,组织和打包应用。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整�?bundle)

在前端领域中,并非只有webpack这一款优秀的模块打包工具,还有其他类似的工具,例如RollupParcelsnowpack,以及最近风头无两的Vite

通过这些模块打包工具,能够提高我们的开发效率,减少开发成�?
这里没有提及gulpgrunt是因为它们只是定义为构建工具,不能类�?

Rollup

Rollup 是一�?ES Modules 打包器,从作用上来看,Rollup �?Webpack 非常类似。不过相比于 WebpackRollup 要小巧的�?
现在很多我们熟知的库都都使用它进行打包,比如:VueReactthree.js�?
举个例子�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ./src/messages.js
export default {
hi: 'Hey Guys, I am zce~'
}

// ./src/logger.js
export const log = msg => {
console.log('---------- INFO ----------')
console.log(msg)
console.log('--------------------------')
}

export const error = msg => {
console.error('---------- ERROR ----------')
console.error(msg)
console.error('---------------------------')
}

// ./src/index.js
import { log } from './logger'
import messages from './messages'
log(messages.hi)

然后通过rollup进行打包

1
$ npx rollup ./src/index.js --file ./dist/bundle.js

打包结果如下�?

可以看到,代码非常简洁,完成不像webpack那样存在大量引导代码和模块函�?
并且error方法由于没有被使用,输出的结果中并无error方法,可以看到,rollup默认开始Tree-shaking 优化输出结果

因此,可以看到Rollup的优点:

  • 代码效率更简洁、效率更�?- 默认支持 Tree-shaking

但缺点也十分明显,加载其他类型的资源文件或者支持导�?CommonJS 模块,又或是编译 ES 新特性,这些额外的需�?Rollup 需要使用插件去完成

综合来看,rollup并不适合开发应用使用,因为需要使用第三方模块,而目前第三方模块大多数使用CommonJs方式导出成员,并且rollup不支持HMR,使开发效率降�?
但是在用于打包 JavaScript 库时,rollup�?webpack 更有优势,因为其打包出来的代码更小、更快,其存在的缺点可以忽略

Parcel

Parcel ,是一款完全零配置的前端打包器,它提供�?“傻瓜式�?的使用体验,只需了解简单的命令,就能构建前端应用程�?
Parcel �?Webpack 一样都支持以任意类型文件作为打包入口,但建议使用HTML文件作为入口,该HTML文件像平时一样正常编写代码、引用资源。如下所示:

1
2
3
4
5
6
7
8
9
10
11
<!-- ./src/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Parcel Tutorials</title>
</head>
<body>
<script src="main.js"></script>
</body>
</html>

main.js文件通过ES Moudle方法导入其他模块成员

1
2
3
4
5
6
7
8
// ./src/main.js
import { log } from './logger'
log('hello parcel')
// ./src/logger.js
export const log = msg => {
console.log('---------- INFO ----------')
console.log(msg)
}

运行之后,使用命令打�?

1
npx parcel src/index.html

执行命令后,Parcel不仅打包了应用,同时也启动了一个开发服务器,跟webpack Dev Server一�?
webpack类似,也支持模块热替换,但用法更简�?
同时,Parcel有个十分好用的功能:支持自动安装依赖,像webpack开发阶段突然使用安装某个第三方依赖,必然会终止dev server然后安装再启动。而Parcel则免了这繁琐的工作流�?
同时,Parcel能够零配置加载其他类型的资源文件,无须像webpack那样配置对应的loader

打包命令如下�?

1
npx parcel src/index.html

由于打包过程是多进程同时工作,构建速度会比Webpack 快,输出文件也会被压缩,并且样式代码也会被单独提取到单个文件�?

可以感受到,Parcel 给开发者一种很大的自由度,只管去实现业务代码,其他事情用Parcel解决

Snowpack

Snowpack,是一种闪电般快速的前端构建工具,专为现代Web设计,较复杂的打包工具(如WebpackParcel)的替代方案,利用JavaScript的本机模块系统,避免不必要的工作并保持流畅的开发体�?
开发阶段,每次保存单个文件时,WebpackParcel都需要重新构建和重新打包应用程序的整个bundle。而Snowpack为你的应用程序每个文件构建一次,就可以永久缓存,文件更改时,Snowpack会重新构建该单个文件

下图给出webpacksnowpack打包区别�?

在重新构建每次变更时没有任何的时间浪费,只需要在浏览器中进行HMR更新

Vite

vite ,是一种新型前端构建工具,能够显著提升前端开发体�?
它主要由两部分组成:

  • 一个开发服务器,它基于 原生 ES 模块 提供了丰富的内建功能,如速度快到惊人�?[模块热更新HMR
  • 一套构建指令,它使�?Rollup打包你的代码,并且它是预配置的,可以输出用于生产环境的优化过的静态资�?
    其作用类似webpack + webpack-dev-server,其特点如下�?
  • 快速的冷启�?- 即时的模块热更新
  • 真正的按需编译

vite会直接启动开发服务器,不需要进行打包操作,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常�?
利用现代浏览器支持ES Module的特性,当浏览器请求某个模块的时候,再根据需要对模块的内容进行编译,这种方式大大缩短了编译时�?
原理图如下所示:

在热模块HMR方面,当修改一个模块的时候,仅需让浏览器重新请求该模块即可,无须像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高

webpack

相比上述的模块化工具,webpack大而全,很多常用的功能做到开箱即用。有两大最核心的特点:一切皆模块�?按需加载*

与其他构建工具相比,有如下优势:

  • 智能解析:对 CommonJS �?AMD 、ES6 的语法做了兼�?- 万物模块:对 js、css、图片等资源文件都支持打�?- 开箱即用:HRM、Tree-shaking等功�?- 代码分割:可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时�?- 插件系统,具有强大的 Plugin 接口,具有更好的灵活性和扩展�?- 易于调试:支�?SourceUrls �?SourceMaps
  • 快速运行:webpack 使用异步 IO 并具有多级缓存,这使�?webpack 很快且在增量编译上更加快
  • 生态环境好:社区更丰富,出现的问题更容易解�?

参考文�?

面试官:说说webpack的构建流�?

一、运行流�?

webpack 的运行流程是一个串行的过程,它的工作流程就是将各个插件串联起来

在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条webpack机制中,去改变webpack的运作,使得整个系统扩展性良�?
从启动到结束会依次执行以下三大步骤:

  • 初始化流程:从配置文件和 Shell 语句中读取与合并参数,并初始化需要使用的插件和配置插件等执行环境所需要的参数
  • 编译构建流程:从 Entry 发出,针对每�?Module 串行调用对应�?Loader 去翻译文件内容,再找到该 Module 依赖�?Module,递归地进行编译处�?- 输出流程:对编译后的 Module 组合�?Chunk,把 Chunk 转换成文件,输出到文件系�?

初始化流�?

从配置文件和 Shell 语句中读取与合并参数,得出最终的参数

配置文件默认下为webpack.config.js,也或者通过命令的形式指定配置文件,主要作用是用于激活webpack的加载项和插�?
关于文件配置内容分析,如下注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var path = require('path');
var node_modules = path.resolve(__dirname, 'node_modules');
var pathToReact = path.resolve(node_modules, 'react/dist/react.min.js');

module.exports = {
// 入口文件,是模块构建的起点,同时每一个入口文件对应最后生成的一�?chunk�? entry: './path/to/my/entry/file.js'�? // 文件路径指向(可加快打包过�?�? resolve: {
alias: {
'react': pathToReact
}
},
// 生成文件,是模块构建的终点,包括输出文件与输出路径�? output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js'
},
// 这里配置了处理各模块�?loader ,包�?css 预处�?loader ,es6 编译 loader,图片处�?loader�? module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
query: {
presets: ['es2015', 'react']
}
}
],
noParse: [pathToReact]
},
// webpack 各插件对象,�?webpack 的事件流中执行对应的方法�? plugins: [
new webpack.HotModuleReplacementPlugin()
]
};

webpack �?webpack.config.js 中的各个配置项拷贝到 options 对象中,并加载用户配置的 plugins

完成上述步骤之后,则开始初始化Compiler编译对象,该对象掌控者webpack声明周期,不执行具体的任务,只是进行一些调度工�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
beforeCompile: new AsyncSeriesHook(["params"]),
compile: new SyncHook(["params"]),
afterCompile: new AsyncSeriesHook(["compilation"]),
make: new AsyncParallelHook(["compilation"]),
entryOption: new SyncBailHook(["context", "entry"])
// 定义了很多不同类型的钩子
};
// ...
}
}

function webpack(options) {
var compiler = new Compiler();
...// 检查options,若watch字段为true,则开启watch线程
return compiler;
}
...

Compiler 对象继承�?Tapable,初始化时定义了很多钩子函数

编译构建流程

根据配置中的 entry 找出所有的入口文件

1
2
3
module.exports = {
entry: './src/file.js'
}

初始化完成后会调用Compilerrun来真正启动webpack编译构建流程,主要流程如下:

  • compile 开始编�?- make 从入口点分析模块及其依赖的模块,创建这些模块对象
  • build-module 构建模块
  • seal 封装构建结果
  • emit 把各个chunk输出到结果文�?

compile 编译

执行了run方法后,首先会触发compile,主要是构建一个Compilation对象

该对象是编译阶段的主要执行者,主要会依次下述流程:执行模块创建、依赖收集、分块、打包等主要任务的对�?

make 编译模块

当完成了上述的compilation对象后,就开始从Entry入口文件开始读取,主要执行_addModuleChain()函数,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
_addModuleChain(context, dependency, onModule, callback) {
...
// 根据依赖查找对应的工厂函�? const Dep = /** @type {DepConstructor} */ (dependency.constructor);
const moduleFactory = this.dependencyFactories.get(Dep);

// 调用工厂函数NormalModuleFactory的create来生成一个空的NormalModule对象
moduleFactory.create({
dependencies: [dependency]
...
}, (err, module) => {
...
const afterBuild = () => {
this.processModuleDependencies(module, err => {
if (err) return callback(err);
callback(null, module);
});
};

this.buildModule(module, false, null, null, err => {
...
afterBuild();
})
})
}

过程如下�?
_addModuleChain中接收参数dependency传入的入口依赖,使用对应的工厂函数NormalModuleFactory.create方法生成一个空的module对象

回调中会把此module存入compilation.modules对象和dependencies.module对象中,由于是入口文件,也会存入compilation.entries�?
随后执行buildModule进入真正的构建模块module内容的过�?

build module 完成模块编译

这里主要调用配置的loaders,将我们的模块转成标准的JS模块

在用 Loader 对一个模块转换完后,使用 acorn 解析转换后的内容,输出对应的抽象语法树(AST),以方�?Webpack 后面对代码的分析

从配置的入口模块开始,分析�?AST,当遇到require等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系

输出流程

seal 输出资源

seal方法主要是要生成chunks,对chunks进行一系列的优化操作,并生成要输出的代�?
webpack 中的 chunk ,可以理解为配置�?entry 中的模块,或者是动态引入的模块

根据入口和模块之间的依赖关系,组装成一个个包含多个模块�?Chunk,再把每�?Chunk 转换成一个单独的文件加入到输出列�?

emit 输出完成

在确定好输出内容后,根据配置确定输出的路径和文件�?

1
2
3
4
output: {
path: path.resolve(__dirname, 'build'),
filename: '[name].js'
}

�?Compiler 开始生成文件前,钩�?emit 会被执行,这是我们修改最终文件的最后一个机�?
从而webpack整个打包过程则结束了

小结

参考文�?

面试官:如何提高webpack的构建速度�?

一、背�?

随着我们的项目涉及到页面越来越多,功能和业务代码也会随着越多,相应的 webpack 的构建时间也会越来越�?
构建时间与我们日常开发效率密切相关,当我们本地开发启�?devServer 或�?build 的时候,如果时间过长,会大大降低我们的工作效�?
所以,优化webpack 构建速度是十分重要的环节

二、如何优�?

常见的提升构建速度的手段有如下�?

  • 优化 loader 配置
  • 合理使用 resolve.extensions
  • 优化 resolve.modules
  • 优化 resolve.alias
  • 使用 DLLPlugin 插件
  • 使用 cache-loader
  • terser 启动多线�?- 合理使用 sourceMap

优化loader配置

在使用loader时,可以通过配置includeexcludetest属性来匹配文件,接触includeexclude规定哪些匹配应用loader

如采�?ES6 的项目为例,在配�?babel-loader 时,可以这样�?

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
module: {
rules: [
{
// 如果项目源码中只�?js 文件就不要写�?/\.jsx?$/,提升正则表达式性能
test: /\.js$/,
// babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开�? use: ['babel-loader?cacheDirectory'],
// 只对项目根目录下�?src 目录中的文件采用 babel-loader
include: path.resolve(__dirname, 'src'),
},
]
},
};

合理使用 resolve.extensions

在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库�?resolve可以帮助webpack从每�?require/import 语句中,找到需要引入到合适的模块代码

通过resolve.extensions是解析到文件时自动添加拓展名,默认情况如下:

1
2
3
4
module.exports = {
...
extensions:[".warm",".mjs",".js",".json"]
}

当我们引入文件的时候,若没有文件后缀名,则会根据数组内的值依次查�?
当我们配置的时候,则不要随便把所有后缀都写在里面,这会调用多次文件的查找,这样就会减慢打包速度

优化 resolve.modules

resolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块。默认值为['node_modules'],所以默认会从node_modules中查找文�?当安装的第三方模块都放在项目根目录下�?./node_modules 目录下时,所以可以指明存放第三方模块的绝对路径,以减少寻找,配置如下�?

1
2
3
4
5
6
module.exports = {
resolve: {
// 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
// 其中 __dirname 表示当前工作目录,也就是项目根目�? modules: [path.resolve(__dirname, 'node_modules')]
},
};

优化 resolve.alias

alias给一些常用的路径起一个别名,特别当我们的项目目录结构比较深的时候,一个文件的路径可能是./../../的形�?
通过配置alias以减少查找过�?

1
2
3
4
5
6
7
8
module.exports = {
...
resolve:{
alias:{
"@":path.resolve(__dirname,'./src')
}
}
}

使用 DLLPlugin 插件

DLL全称�?动态链接库,是为软件在winodw种实现共享函数库的一种实现方式,而Webpack也内置了DLL的功能,为的就是可以共享,不经常改变的代码,抽成一个共享的库。这个库在之后的编译过程中,会被引入到其他项目的代码�?
使用步骤分成两部分:

  • 打包一�?DLL �?- 引入 DLL �?

打包一�?DLL �?

webpack内置了一个DllPlugin可以帮助我们打包一个DLL的库文件

1
2
3
4
5
6
7
8
9
module.exports = {
...
plugins:[
new webpack.DllPlugin({
name:'dll_[name]',
path:path.resolve(__dirname,"./dll/[name].mainfest.json")
})
]
}

引入 DLL �?

使用 webpack 自带�?DllReferencePlugin 插件�?mainfest.json 映射文件进行分析,获取要使用的DLL�?
然后再通过AddAssetHtmlPlugin插件,将我们打包的DLL库引入到Html模块�?

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
...
new webpack.DllReferencePlugin({
context:path.resolve(__dirname,"./dll/dll_react.js"),
mainfest:path.resolve(__dirname,"./dll/react.mainfest.json")
}),
new AddAssetHtmlPlugin({
outputPath:"./auto",
filepath:path.resolve(__dirname,"./dll/dll_react.js")
})
}

使用 cache-loader

在一些性能开销较大�?loader 之前添加 cache-loader,以将结果缓存到磁盘里,显著提升二次构建速度

保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大�?loader 使用此 loader

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
module: {
rules: [
{
test: /\.ext$/,
use: ['cache-loader', ...loaders],
include: path.resolve('src'),
},
],
},
};

terser 启动多线�?

使用多进程并行运行来提高构建速度

1
2
3
4
5
6
7
8
9
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
};

合理使用 sourceMap

打包生成 sourceMap 的时候,如果信息越详细,打包速度就会越慢。对应属性取值如下所示:

三、总结

可以看到,优化webpack构建的方式有很多,主要可以从优化搜索时间、缩小文件搜索范围、减少不必要的编译等方面入手

参考文�?

面试官:说说如何借助webpack来优化前端性能�?

一、背�?

随着前端的项目逐渐扩大,必然会带来的一个问题就是性能

尤其在大型复杂的项目中,前端业务可能因为一个小小的数据依赖,导致整个页面卡顿甚至奔�?
一般项目在完成后,会通过webpack进行打包,利用webpack对前端项目性能优化是一个十分重要的环节

二、如何优�?

通过webpack优化前端的手段有�?

  • JS代码压缩
  • CSS代码压缩
  • Html文件代码压缩
  • 文件大小压缩
  • 图片压缩
  • Tree Shaking
  • 代码分离
  • 内联 chunk

JS代码压缩

terser是一个JavaScript的解释、绞肉机、压缩机的工具集,可以帮助我们压缩、丑化我们的代码,让bundle更小

production模式下,webpack 默认就是使用 TerserPlugin 来处理我们的代码的。如果想要自定义配置它,配置方法如下�?

1
2
3
4
5
6
7
8
9
10
11
12
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
...
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true // 电脑cpu核数-1
})
]
}
}

属性介绍如下:

  • extractComments:默认值为true,表示会将注释抽取到一个单独的文件中,开发阶段,我们可设置为 false ,不保留注释
  • parallel:使用多进程并发运行提高构建的速度,默认值是true,并发运行的默认数量�?os.cpus().length - 1
  • terserOptions:设置我们的terser相关的配置:
  • compress:设置压缩相关的选项,mangle:设置丑化相关的选项,可以直接设置为true
  • mangle:设置丑化相关的选项,可以直接设置为true
  • toplevel:底层变量是否进行转�? - keep_classnames:保留类的名�? - keep_fnames:保留函数的名称

CSS代码压缩

CSS压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等

CSS的压缩我们可以使用另外一个插件:css-minimizer-webpack-plugin

1
npm install css-minimizer-webpack-plugin -D

配置方法如下�?

1
2
3
4
5
6
7
8
9
10
11
12
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
// ...
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin({
parallel: true
})
]
}
}

Html文件代码压缩

使用HtmlWebpackPlugin插件来生成HTML的模板时候,通过配置属性minify进行html优化

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
...
plugin:[
new HtmlwebpackPlugin({
...
minify:{
minifyCSS:false, // 是否压缩css
collapseWhitespace:false, // 是否折叠空格
removeComments:true // 是否移除注释
}
})
]
}

设置了minify,实际会使用另一个插件html-minifier-terser

文件大小压缩

对文件的大小进行压缩,减少http传输过程中宽带的损�?

1
npm install compression-webpack-plugin -D
1
2
new ComepressionPlugin({
test:/\.(css|js)$/, // 哪些文件需要压�? threshold:500, // 设置文件多大开始压�? minRatio:0.7, // 至少压缩的比�? algorithm:"gzip", // 采用的压缩算�?})

图片压缩

一般来说在打包之后,一些图片文件的大小是远远要�?js 或�?css 文件要来的大,所以图片压缩较为重�?
配置方法如下�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
}
},
{
loader: 'image-webpack-loader',
options: {
// 压缩 jpeg 的配�? mozjpeg: {
progressive: true,
quality: 65
},
// 使用 imagemin**-optipng 压缩 png,enable: false 为关�? optipng: {
enabled: false,
},
// 使用 imagemin-pngquant 压缩 png
pngquant: {
quality: '65-90',
speed: 4
},
// 压缩 gif 的配�? gifsicle: {
interlaced: false,
},
// 开�?webp,会�?jpg �?png 图片压缩�?webp 格式
webp: {
quality: 75
}
}
}
]
},
]
}

Tree Shaking

Tree Shaking 是一个术语,在计算机中表示消除死代码,依赖于ES Module的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系�?
webpack实现Trss shaking有两种不同的方案�?

  • usedExports:通过标记某些函数是否被使用,之后通过Terser来进行优化的
  • sideEffects:跳过整个模�?文件,直接查看该文件是否有副作用

两种不同的配置方案, 有不同的效果

usedExports

配置方法也很简单,只需要将usedExports设为true

1
2
3
4
5
6
module.exports = {
...
optimization:{
usedExports
}
}

使用之后,没被用上的代码在webpack打包中会加入unused harmony export mul注释,用来告�?Terser 在优化时,可以删除掉这段代码

如下面sum函数没被用到,webpack打包会添加注释,terser在优化时,则将该函数去掉

sideEffects

sideEffects用于告知webpack compiler哪些模块时有副作用,配置方法是在package.json中设置sideEffects属�?
如果sideEffects设置为false,就是告知webpack可以安全的删除未用到的exports

如果有些文件需要保留,可以设置为数组的形式

1
2
3
4
"sideEffecis":[
"./src/util/format.js",
"*.css" // 所有的css文件
]

上述都是关于javascripttree shakingcss同样也能够实现tree shaking

css tree shaking

css进行tree shaking优化可以安装PurgeCss插件

1
npm install purgecss-plugin-webpack -D
1
2
3
4
5
6
7
8
9
10
11
12
13
const PurgeCssPlugin = require('purgecss-webpack-plugin')
module.exports = {
...
plugins:[
new PurgeCssPlugin({
path:glob.sync(`${path.resolve('./src')}/**/*`), {nodir:true}// src里面的所有文�? satelist:function(){
return {
standard:["html"]
}
}
})
]
}
  • paths:表示要检测哪些目录下的内容需要被分析,配合使用glob
  • 默认情况下,Purgecss会将我们的html标签的样式移除掉,如果我们希望保留,可以添加一个safelist的属�?

代码分离

将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文�?
默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度

代码分离可以分出出更小的bundle,以及控制资源加载优先级,提供代码的加载性能

这里通过splitChunksPlugin来实现,该插件webpack已经默认安装和集成,只需要配置即�?
默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all

1
2
3
4
5
6
7
8
module.exports = {
...
optimization:{
splitChunks:{
chunks:"all"
}
}
}

splitChunks主要属性有如下�?

  • Chunks,对同步代码还是异步代码进行处理
  • minSize�?拆分包的大小, 至少为minSize,如何包的大小不超过minSize,这个包不会拆分
  • maxSize�?将大于maxSize的包,拆分为不小于minSize的包
  • minChunks:被引入的次数,默认�?

内联chunk

可以通过InlineChunkHtmlPlugin插件将一些chunk的模块内联到html,如runtime的代码(对模块进行解析、加载、模块信息相关的代码),代码量并不大,但是必须加载的

1
2
3
4
5
6
7
const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
...
plugin:[
new InlineChunkHtmlPlugin(HtmlWebpackPlugin,[/runtime.+\.js/]
}

三、总结

关于webpack对前端性能的优化,可以通过文件体积大小入手,其次还可通过分包的形式、减少http请求次数等方式,实现对前端性能的优�?

参考文�?

面试官:说说webpack proxy工作原理?为什么能解决跨域?

一、是什�?

webpack proxy,即webpack提供的代理服�?
基本行为就是接收客户端发送的请求后转发给其他服务�?
其目的是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限制�?
想要实现代理首先需要一个中间服务器,webpack中提供服务器的工具为webpack-dev-server

webpack-dev-server

webpack-dev-server�?webpack 官方推出的一款开发工具,将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一�?
目的是为了提高开发者日常的开发效率,*只适用在开发阶�?

关于配置方面,在webpack配置对象属性中通过devServer属性提供,如下�?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ./webpack.config.js
const path = require('path')

module.exports = {
// ...
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
proxy: {
'/api': {
target: 'https://api.github.com'
}
}
// ...
}
}

devServetr里面proxy则是关于代理的配置,该属性为对象的形式,对象中每一个属性就是一个代理的规则匹配

属性的名称是需要被代理的请求路径前缀,一般为了辨别都会设置前缀为 /api,值为对应的代理匹配规则,对应如下�?

  • target:表示的是代理到的目标地址
  • pathRewrite:默认情况下,我们的 /api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite
  • secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false
  • changeOrigin:它表示是否更新代理后请求的 headers 中host地址

二、工作原�?

proxy工作原理实质上是利用http-proxy-middleware 这个http代理中间件,实现请求转发给其他服务器

举个例子�?
在开发阶段,本地地址为http://localhost:3000,该浏览器发送一个前缀带有/api标识的请求到服务端获取数据,但响应这个请求的服务器只是将请求转发到另一台服务器�?

1
2
3
4
5
6
7
8
9
const express = require('express');
const proxy = require('http-proxy-middleware');

const app = express();

app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));
app.listen(3000);

// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar

三、跨�?

在开发阶段, webpack-dev-server 会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行�?localhost 的一个端口上,而后端服务又是运行在另外一个地址�?
所以在开发阶段中,由于浏览器同源策略的原因,当本地访问后端就会出现跨域请求的问题

通过设置webpack proxy实现代理请求后,相当于浏览器与服务端中添加一个代理�?
当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地

在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能正常接收数据

注意�?服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限�?

参考文�?

0%