07-webapck八股文

6/28/2022

# webapck串讲(八股文)

# 一,webpack介绍与基本配置

# 1,webpack基本介绍

​ 是前端的打包工具。安卓代码打完后,需要打包成apk。iOS代码写完后,也是需要打包。java代码写完后也是需要打包的,java代码打包成jar或war包。N年前,前端开发的网页一般是不打包。现在web前端项目都是需要打包的。webpack已是前端打包构建的不二选择。

1679534034475

学习webpack要学习的内容:

  • 基本配置
    • dev-server
    • 打包js高级语法
    • 打包样式
    • 打包图上文件
    • 常用loader和plugin
  • 高级配置
    • 多入口
    • 抽离和压缩css
    • 抽离公共的代码
    • 打包vue文件和react文件
    • ....
  • 优化打包效率
    • 优化babel-loader
    • IgnorePlugin
    • noParse
    • happyPack
    • 自动刷新
    • 热更新
    • DllPlugin
    • ...
  • 优化产出的代码
    • 小图片使用base64编译
    • bundle加hash
    • 使用cdn
    • 抽离公共的代码
    • 懒加载
    • scope hosting
  • babel

# 2,安装相应依赖

创建一个空的文件夹,如下:

1679534486619

初始化配置文件,如下:

1679534527082

使用vscode打包配置文件,如下:

1679534553397

本次课程用到的依赖,如下:

"devDependencies": {
    "@babel/core": "^7.7.4",
    "@babel/preset-env": "^7.7.4",
    "autoprefixer": "^9.7.3",
    "babel-loader": "^8.0.6",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.2.1",
    "file-loader": "^5.0.2",
    "happypack": "^5.0.1",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.10.3",
    "less-loader": "^5.0.0",
    "mini-css-extract-plugin": "^0.8.0",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "style-loader": "^1.0.1",
    "terser-webpack-plugin": "^2.2.2",
    "url-loader": "^3.0.0",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.9.0",
    "webpack-merge": "^4.2.2",
    "webpack-parallel-uglify-plugin": "^1.1.2"
},
"dependencies": {
    "lodash": "^4.17.15",
    "moment": "^2.24.0"
}
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

copy上面的依赖,如下:

1679534611861

安装上面的依赖,如下 :

1679535076865

# 3,两种打包方式

打包分两种:

  • 开发打包
    • 在写项目的过程中进行打包,也叫内存打包,把生成的静态资源放到的内存中了,在内存中有一个服务器。通过localhost:8080就可以访问项目。
  • 生产打包
    • 项目要上线了,在硬盘上打包,打包出来的就是静态资源。只需要把这些静态资源上线就OK了。

先在硬盘上打包,创建目录结构如下:

1679535125482

现在就需要打包了,配置一个命令dev(最好叫build),如下:

1679535226313

开始演示生产模式打包,如下:

1679535375889

# 4,配置入口和出口

上面的的打包是零配置,不需要进行任何的配置,但是我们学习webpack,就是学习一堆的配置,先配置入口和出口,封装两个路径,如下:

1679535899715

回到webpack的配置文件,配置入口和出口,如下:

1679536158626

现在打包时,需要告诉webpack,要按我们上面的配置进行打包,如下:

1679536371945

删除之前的dist,如下:

1679536288434

开始打包,如下:

1679536392181

看打包后资源,如下:

1679536415586

# 5,配置页面自动插入bundle.js

创建一个public目录,目录下面放index.html页面。这个页面就是一个模板,打包时,根据这个模板插入上面打包的bundle.js,如下:

1679537655217

这个插件已经安装好,如下:

1679537678439

开始配置,如下:

1679537898611

开始打包,如下:

1679537915156

打包后的dist,如下:

1679537965264

运行之如下:

1679538034303

# 6,区分是生产打包还是开发打包

不同的打包模式,配置文件是不一样的,但是也有相同的配置,区分一下,如下:

1679538294577

开发模式打包,先不管,生说生产模式打包,合并的放,需要通过webpack-marge这个包,这个包已经安装过了。配置如下:

1679538821942

1679538838195

打包测试,如下 :

1679538868989

浏览器中运行页面,如下:

1679538901878

同理,开发模式打包,也是一样的,如下:

1679538986705

# 7,本地服务和代理

所谓的本地服务器(前端服务器),就是在内存中打包。如果在硬盘上打包,写一点代码,想看效果,就需要打包,再写一点代码,想看效果,还需要打包,浏览器中运行。期望的是,边写代码边自动打包,边自动刷新浏览器,就可以看效果。此时就需要配置一个本地服务了,它会在内存中打包,速度非常快,并且会托管pubcli目录。安装一个依赖,如下:

1679539990329

配置一个命令,如下:

1679540059998

简单做一下配置,如下:

1679540324113

开始打包,如下:

1679540342402

不光打包,还开一个本地服务器,把内存中打好包都托管到服务器,并且这个服务器也会托管publick目录。

在开发过程,可以在开发服务器中配置代码,用于解决跨域问题,如下:

1679540657423

后面再打包,通过npm run dev就可以在内存中打包。开发项目过程中都是在内存中打包。通过npm run build就可以在硬盘上的打包,生成dist文件夹,我们只需要上线dist文件夹就OK了。

# 8,处理 ES6

webpack默认是可以打包js的,但是不能转化es6+的代码。下面的箭头函数,打包完后,还是箭头函数,如下:

1679541113884

1679541130592

我们希望把es6+的代码转化成es5的代码,因为所有的浏览器都认识es5的代码。使用babel进行转化,安装如下:

1679541517316

开始配置之,如下:

1679541929553

还需要创建一个babel的配置文件,配置预设,如下:

1679541955436

打包测试是否可以把箭头函数转化,如下:

1679541982137

1679542007493

1679542027847

# 9,处理样式

书写样式,如下:

1679551686375

在入口文件中,引入样式,如下:

1679551709238

尝试打包,如下:

1679551730987

报错了,如下:

1679551870296

上面的两个loader都安装好了,如下:

1679551905474

进行配置,如下:

1679552038166

尝试再次打包,如下:

1679552089457

样式再考虑一个less,创建一个less文件,如下:

1679552529527

在入口文件中,引入less,如下:

1679552253925

尝试打包,如下 :

1679552277020

报错了,因为webpack默认不能处理less文件,如下:

1679552312549

需要安装一个less-loader,less-loader可以把less转化成css,如下:

1679552360789

配置,如下 :

1679552433133

再尝试打包,如下:

1679552552194

看如下样式:

1679552693681

要使用postcss-loader,就需要安装之,如下:

1679552722467

配置之,如下:

1679552772821

只有这一个loader并不能给css3属性添加前缀,要做不同的事件,需要安装不同的postcss插件,要给css3属性添加前缀,就安装加前缀的插件,如下:

1679552852892

还需要配置postcss,如下:

1679552903255

测试之,如下:

1679552927186

1679553990604

发现,并没有添加前缀,原因是少了一个配置文件,如下:

1679554183502

再次打包,如下:

1679554201226

还有你想把px转化成rem,之前可以借助vscode中的插件来实现,其实也可以使用postcss来处理。

# 10,处理图片

准备一张图片,把图片插入到页面中,如下:

1679554514860

尝试打包时,就报错了,如下:

1679554537640

默认情况下,webpack也是不能打包图片的,需要使用一个loader。使用file-lodder来处理,如下:

1679554603352

配置如下:

1679554669013

再次尝试打包,如下 :

1679554696822

在硬盘上打包,如下:

1679554722802

看一下打包后的资源,如下:

1679554747728

使用file-loader,你本身是一张图片,打完包后,还是一张图片。还有一个loder,叫url-loader,可以把一张图片,打包成一个字符串,准备如下的图片:

1679554896408

使用之,如下:

1679554932433

配置如下 :

1679555520107

注释掉了file-loader的配置,如下:

1679555545141

开始打包,如下:

1679555560724

这张图片就被打包成了base64编码,如下:

1679555627572

在浏览器中运行网页,如下 :

1679555660683

如果大小超过了10kb,效果和file-loader是一样的。

# 11,两个打包小优化

给bundle.js添加hash,分析如下 :

1679555858845

添加之,如下:

1679556098609

现在每一次打包,希望把上一次打包的残留文件清除掉,使用一个插件,叫CleanWebPackPlugin,如下:

1679556168150

在生产环境中使用,如下:

1679556418032

打包测试之,如下:

1679556504328

改变一个内容,如下:

1679556568184

再次去打包,如下 :

1679556609453

补充一点,如果是开发环境,对于图片,使用file-loader,就OK,如下:

1679558042078

内存打包测试,如下:

1679558070841

1679558097596

# 二,高级配置(面试)

# 1,配置多入口

前面的配置只有一个入口,叫index.js。有的项目中需要配置多个入口,多个入口,对应了多个html文件,创建一个html文件,如下:

1679558283496

再准备一个入口,如下:

1679558377678

开始配置,如下:

1679558872988

1679558894356

1679558921004

打包测试如下:

1679558946280

到此配置多入口就OK了。

# 2,抽离和压缩css到css文件中

  • 问:哪一种模式下,需要把css单独抽离并压缩?

  • 答:生产模式下,才去抽离压缩。

在开发模式下,配置如下:

1679560397824

1679560432424

在生产模式下,就需要抽离压缩了,要抽离使用mini-css-extract-plugin,要压缩使用optimize-css-assets-webpack-plugin,这两个已经安装过了,如下:

1679560536026

配置如下:

1679561188480

1679561231287

# 3,抽离公共的代码

项目中安装了一个依赖,如下:

1679620084759

在入口文件中使用之,如下:

1679620279074

1679620291884

由于我们是多入口,不同的页面,需要引入不同的chunk,如下:

1679620587119

在内存中打包,如下:

1679620452849

浏览器中效果如下:

1679620787074

1679620872298

多的100多kb就是loadsh这个第三方库的代码。现在思考能不能抽离第三方库的代码,如果不抽离,就意味着每次打包都打包到了index.js中,loadsh第三方库有必须打包吗?答:没有。

  • 什么时候需要抽离第三方库的代码?

  • 答:生产环境,配置需要配置到生产环境中。

配置如下:

1679622144852

参考代码:

// 优化的
optimization: {
    // 压缩
    minimizer: [
        // 压缩js
        new TerserJSPlugin({}),
        // 压缩css
        new OptimizeCSSAssetsPlugin({})
    ],

    // 分割代码块
    splitChunks: {
        // 不同的分法:
        //    initial   入口chunk   对于异步导入的文件不处理
        //    async  异步chunk   只对异步导入的文件处理
        //    all   全部chunk 
        chunks: "all",

        // 缓存分组
        cacheGroups: {
            // 第三方模块
            vendor: {
                name: "vendor", // chunk名称
                priority: 1, // 权限更高,优先抽离,重要!!!
                test: /node_modules/,
                minSize: 0,  // 大小限制
                minChunks: 1  // 最少复用过几次
            },
            // 公共的模块
            common: {
                name: 'common', // chunk 名称
                priority: 0, // 优先级
                minSize: 0,  // 公共模块的大小限制
                minChunks: 2  // 公共模块最少复用过几次
            }
        }
    }
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

不同的入口引入不同的chunk,如下:

1679622195243

生产模式打包,如下:

1679622214058

看打包后的代码如下:

1679622239980

1679622254546

在浏览器中也测试一把,如下:

1679622283603

1679622318395

# 4,懒加载

准备一个动态的数据,类似于vue中准备一个组件,实现组件的懒加载,如下:

1679623835077

在入口文件中,5秒后,再去用到上面的数据,如下:

1679623990212

打包测试之,如下:

1679624021364

运行打包后的资源,如下:

1679624143441

# 5,module chunk bundle的区别

  • module
    • 各个的源码文件,在webpack眼中,一切都是模模块
  • chunk
    • 多个模块合并成chunk,如entry,import(), splitChunk
  • bundle
    • 最终输出的静态资源

看如下的几个chunk:

1679624352532

1679624388847

1679624418920

# 6,提升构建速度

构建速度就是所谓打包速度,在公司中项目是比较大的,项目越大,打包的速度越慢,越慢越不好,我们可尽可能提升打包(构建)速度。打包分两类:

  • 开发过程中打包,内存中打包
  • 产生过程打包,硬盘打包

要尽可能提升开发过程中的打包速度,也是面试比较热门的八股文,所谓的提升构建速度就是性能优化方案,方案有很多。

# 1)优化babel-loader

要优化开发过程中的打包速度,把babel-loader的配置放到dev.js中,如下:

1679624884210

1679624915895

1679624943610

优化一下babel-loader,如下:

1679625224009

# 2)IgnorePlugin

  • 作用:避免引入一些无用的模块

项目中用的模块可能非常多,有些模块是无用的,如项目中用到了moment模块,如下:

1679625398676

看一下moment官网(http://momentjs.cn/),如下:

1679625493186

在项目中,通过import moment from "moment",默认会引入所有的语言,导致代码过大。如果项目中只用到的中文和英文,其它的语言都是无用的模块。那么我们就需要避免引入一些无用的模块,需要通过IgnorePlugin实现。

在入口文件中,直接引入moment,如下:

1679625798860

开始打包,如下:

1679625737217

浏览器中测试是否可以打印日期,如下 :

1679625810191

查看打包的大小,如下:

1679625979522

我们有两种策略,要么把moment这个分割出去,要么避免引入一些无用的模块。在说避免引入一些无用的模块之前,通过一个命令,也可以分析webpack资源大小,是打包文件分析工具webpack-bundle-analyzer。大家下去尝试使用一下这个工具,通过这个工具,可以查看打包的资源的大小,如下:

1679626293619

使用IgnorePlugin去忽略一些无用的模块,配置如下:

1679626383982

1679626493552

用到什么语言,就需要手动去加载什么语言,如下:

1679626554985

在开发环境中也配置一下,如下:

1679626731075

打包一下,看一下,打包后资源体积是否变小了,如下:

1679626582714

1679626699205

# 3)noParse

  • IgnorePlugin: 直接不引入,代码中压根没有那个包
  • noParse: 代码中引入了,但不打包,生产环境中可以配置CDN

配置如下:

module.exports = {
        module: {
            // 忽略对"react.min.js"文件的递归解析
            noParse: [/react\.min\.js$]
            }
        }
1
2
3
4
5
6

# 4)happyPack多进程打包

JS单线程的,开启多进行打包,提高构建速度(特别是在多核CPU上)。需要注意的是:

  • 项目较小,打包本身就很快,开启多进行反而会降低速度(进程开销)
  • 项目较大,打包本身就很慢,开启多进行打包就会提升速度

配置如下:

1679627873104

需要去New这个插件,如下:

1679627960512

如果js模块,把对.js文件的处理转次给id为babel的HappyPack实例,如下:

1679628112229

配置完后,基本上看到什么效果。甚至打包速度会变慢。如下:

1679628171509

# 5)使用ParallelUglifyPlugin多进程压缩JS

所谓的压缩就是让代码体积变的更小,传输速度就更快。也是性能优化的一种方案,配置如下:

1679628293519

1679628461392

在入口文件中,添加测试代码如下:

1679628524684

开始打包,如下:

1679628564576

1679628606457

# 6)自动全屏刷新

用于开发过程中打包,边写代码,边自动打包,边自动刷新浏览器。整个网页都会刷新,速度较慢,状态会丢失。

指定自动刷新,如下:

1679639653371

指定开发过程中打包,如下:

1679639350817

浏览器中测试:

1679639725233

1679639765734

1679639806418

由于我们配置了开发服务器,watch监听是不需要配置,注释掉,如下:

1679639871579

# 7)热更新

热更新:代码变了,网页不会整体刷新,状态也不会丢失。要实现热更新,需要使用一个模块,叫HotModuleReplacementPlugin。此模块就可以实现热更新。配置如下:

1679640352171

入口文件如下:

1679640373592

打包测试之,如下:

1679640397052

改变样式代码,浏览器不会全屏刷新,并且状态不会丢失,如下:

1679640512797

然后,看一下JS的热更新,书写代码如下:

1679640632384

1679640638706

JS代码变化了,需要热更新,处理如下:

1679640945105

# 8)dllplugin

dllplugin是webpack内置的插件,可以一些经常不变的模块,打包成一个dll文件,同一个版本的只会构建一次,不用每一次都重新构建。如Vue框架和react框架,体积比较在,版本比较稳定,构建慢。此时就可以把vue或react打包成一个dll文件。在项目中直接引入dll文件就OK。有兴趣的自学一下。

# 7,总结提升打包速度(npm run build)

  • 优化babel-loader(缓存)
  • IgnorePlugin(代码中压根就不引入)
  • noParse(代码中引入,但是不参与打包。线上环境可以使用CDN)
  • happyPack(多进程打包)
  • ParallelUglifyPlugin(多进程JS代码压缩)
  • ....

# 8,总结提升打包速度(npm run dev)

  • 优化babel-loader(缓存)

  • IgnorePlugin(代码中压根就不引入)

  • noParse(代码中引入,但是不参与打包。线上环境可以使用CDN)

  • happyPack(多进程打包)

  • ParallelUglifyPlugin(多进程JS代码压缩)

  • 自动刷新(webpack-dev-server)

  • 热更新

  • DllPlugin

# 9,总结产出代码性能优化

  • 体积更新(压缩合并)
  • 合理分包,提取公共的代码
  • 小图片base64编码(url-loader)
  • bundle加上hash
  • 懒加载
  • IngorePlugin
  • 使用CDN(一些第三方库,直接使用CDN加,不要打包)
  • 产生打包配置mode:production
    • 配置production,自动开启代码压缩
    • Vue,React等会自动删除调整
    • 启动Tree-Shaking(树摇),前提是使用ES6的模块化

# 三,babel

# 1,babel是什么与搭建babel环境

babel就是用于把js高级语法,转化成JS低级语法。前端开发环境中必备的工具。有些语法是可以转化的,有些语法是不能转化的,不能转化,需要使用原生JS进行模拟实现,如Promise,使用原生js模拟Promise...。但是还有一些语法是不能模拟,如Proxy,Proxy也是ES6中的语法,使用原生JS是不能模拟。

配置一个babel的环境,创建一个项目,如下:

1679643147582

生成一个配置文件,如下:

1679643176045

使用vscode打包babel-demo项目,如下 :

1679643225513

安装如下依赖:

 "devDependencies": {
    "@babel/cli": "^7.7.5",
    "@babel/core": "^7.7.5",
    "@babel/plugin-transform-runtime": "^7.7.5",
    "@babel/preset-env": "^7.7.5"
  },
  "dependencies": {
    "@babel/polyfill": "^7.7.0",
    "@babel/runtime": "^7.7.5"
  }
1
2
3
4
5
6
7
8
9
10

1679643508703

1679643683691

安装依赖,如下:

1679643713244

# 2,利用babel转化语法

创建一个src目录,书写index.js,并转化之,如下:

1679643921475

配置预设,如下:

1679644001428

再次的转化,如下:

1679644030453

有些语法是不能转化的,如下 :

1679644659973

引用babel-polyfill,如下:

1679644687246

还需要配置按需引入,如下:

1679644706070

转化如下:

1679644736799

babel-polyfill是有问题的

  • 会污染全局环境,如下:

1679645037759

如果自己做的是一个独立的web项目,使用babel-polyfill是没有问题。如果把项目打包成一个第三方库,让别人使用,那么babel-polyfill就会污染全局环境。原因,它会把模拟出来的很多的API都挂载到window。如:window. Promise, Array.prototype.includes.... 如何解决?答:通过@babel/runtime。

配置如下:

1679645104890

再去打包,如下:

1679645146808

# 四,总结webpack相关面试题

# 1,webpack中你都做了哪些配置

  • 基本的配置
    • 本地服务器
    • 处理ES6
    • 处理样式
    • 处理图片
    • 处理字体图标
    • 处理vue文件
    • 处理jsx文件
    • 拆分配置和merge
  • 高级配置
    • 多入口
    • 抽离css文件
    • 懒加载
    • 抽离公共代码
  • 提升打包速度(性能优化)
    • 开发打包性能优化
      • 自动刷新
      • 热更新
      • DllPlugin
      • 优化babel-loader
      • IgnorePlugin
      • noParse
      • happyPack
      • ParalleUglifyPlugin
      • ...
    • 生产打包性能优化
      • 优化babel-loader
      • IgnorePlugin
      • noParse
      • happyPack
      • ParalleUglifyPlugin
      • .....
    • 产出代码的性能优化
      • 小图片转成base64
      • bundle加hash
      • 懒加载
      • 提取公共代码
      • 使用CDN加速
      • IgorePlugin
      • 使用mode:production

# 2,前端为什么要进行打包构建

  • 代码体积更小(Tree-Shaking, 压缩, 合并),传输就更快,加载更快
  • 编译JS高级语法(TS, ES6+, 模块化,scss, less)
  • 兼容性(polyfill, postcss, eslint)
  • 搭建高效的开发环境
  • 统一构建流程和产出标准
  • 集成公司的规范

# 3,module chunk bundle的区别

  • module
    • 各个的源码文件,在webpack眼中,一切都是模模块
  • chunk
    • 多个模块合并成chunk,如entry,import(), splitChunk
  • bundle
    • 最终输出的静态资源

# 4,loader和plugin的区别

  • loader是加载转换器, 如:less => css
  • plugin是插件,是用来增强webpack

# 5,说一下,常见的loader和plugin

只需要把课堂上讲的loader和plugin说一下就ok了。参考:

  • https://www.webpackjs.com/loaders/
  • https://www.webpackjs.com/plugins/

# 6,babel和webpack有什么区别

  • babel是JS新语法的编译工具,不关心模块化
  • webpack是打包构建工具,是多个loader和plugin的集合

# 7,babel-polyfill和babel-runtime的区别

  • babel-polyfill是使用原生JS模拟实现JS高级语法的,babel-runtime也是一样的
  • babel-polyfill会污染全局环境,babel-runtime不会污染全局环境
  • 当代码需要打包成一个第三方的lib时,使用babel-runtime

# 8,webpack如何实现懒加载

  • import函数
  • 结合Vue或React的异步组件来说(vue-router或React-router-dom)-

# 9,为什么proxy不能被polyfill

有些语法是可以模拟,有些语法是不能模块的,如:

  • 如class可以使用funcion模拟
  • 如Promise可以使用callback来模拟
  • 但是Proxy的功能用Object.defineProperty是无法模拟的
Last Updated: 5/17/2023, 11:37:25 AM