【webpack快速入门】如何利用webpack代码分割,做前端性能优化?

1,810 阅读8分钟

前言

大家好,我是东东吖,一名前端工程师。上一篇文章我们讲了摇树优化,今天我们来讲如何通过webpack代码分割(Code Splitting),做前端性能优化。在日常开发中,想必大家都遇到过这种场景,那就是首次打开某个网页的速度很慢很慢,慢到白屏,需要等待很长的时间,但是当你打开过一次之后,速度就很快了,这是为什么呢?这是因为当你进入网站的时候,需要加载大量的资源,当带宽有限,就会很慢。那为什么加载第一次很慢,而从第二次起,就很快了呢?那个因为你加载过一次之后,浏览器就会有缓存,不会再次去请求资源,所以就会很快。缓存解决了再次访问的加载速度,那怎么解决首屏的加载速度呢?

初识 Code Splitling

要解决那怎么解决首屏的加载速度,核心问题还是我们的资源打包后太大了,那怎么办呢?我们可以加大带宽,这样我们的资源就会加载很快,但是如果公司没有预算加大带宽,我们该怎么办呢?在打包优化方面,我们可以对代码进行分包。我们都知道webpack是目前最为流行的项目打包工具之一,当然其他的打包工具也很优秀。当前较为流行的前端框架也推出了基于webpack的脚手架。比如Vue框架使用Vue-cli作脚手架,react框架使用create-react-app脚手架来搭建项目的开发环境。它们都为我们项目的开发带来便利。webpack作为代码打包工具,代码分割便显得尤为重要了,因为代码被分割了,包的体积就小了,加载的速度自然就提升了,今天我们来聊一聊webpack Code Splitling(代码分割/代码分包)。

webpack代码分割有以下两种方式:

  • 动态导入

  • 按需加载,需要用到某个模块时,再加载这个模块,动态导入的模块会被webpack自动分包,我们在使用vue或者react进行懒加载路由就是用的这种方法。

  • 多入口打包

  • 适用于传统的多页应用程序,一个页面对应一个打包入口,公共模块单独提取。

动态导入

动态导入,或许我们很多同学都有用到,就是路由懒加载。但是很多同学并不清楚它为啥会提升加载速度,只是从网上了解到他是按需加载,但是其中的细节并不清楚,今天我们就来一探究竟。

平时大家在做项目的时候,都是只有首屏直接引入,其他路由进行的懒加载,那如果我们全部路由都直接引入,会发生什么呢?

image.png

image.png

我们对上面这种全部直接引入的路由进行打包,我们发现打包后的app.js的体积有3232KB。

image.png

这么大的体积,当我们带宽有限的时候,加载速度就会很慢,长时间白屏,让人很难受。据统计,一个网站打开的时间超过3秒,很多人就不想再等待它了,会直接“啪”关掉它,如果不是非用不可,那可能就再也不会打开它了,所有前端性能优化尤为重要,它决定了用户粘度和用户数量。

那如果我们只有首屏直接引入,其他路由利用懒加载的形式呢?

image.png

当我们使用懒加载路由之后,无需对webpack做任何配置,webpack会自动进行分包。`

image.png

在打包的时候,把各个模块分开进行打包,生产不同的chunk,当进入某个页面的时候,才会加载对应的包,不会全部打包到app.js,app.js的体积就变小了,加载速度自然就变快了。

多入口打包

上诉属于单页应用打包,如果是多页应用我们可以采取多入口打包,多入口打包其实和单入口打包其实是差不多,多入口打包只是要配置不同的入口和出口。

接下来我们来为多页应用打包做准备工作:

我们新先建一个home.html、home.js和about.html、about.js,这两个home和about将作为我们页面,它们都会引用一部分公共的模块,公共的方法common.js,公共的样式common.css。

image.png

//home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>多入口打包-home页面</title>
  
</head>
<body>
    <h1>多入口打包-home页面</h1>
    
</body>


</html>

//home.js

import "./common.css"
import { common } from "./common.js"
 const home = ()=>{
  console.log("home....");

}
home()
common()

image.png

//about.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>多入口打包-about页面</title>
</head>
<body>
    <h1>多入口打包-about页面</h1>
    
</body>

</html>
//about.js

import "./common.css"
import { common } from "./common.js"
 const about = ()=>{
    console.log("home....");
}

about()
common()






我们再来建home和abpot共同引用的公共模块,公共的方法common.js和公共的样式common.css。

//common.js

export const common = ()=>{
    console.log("common方法.......")
}


//common.css

h1{
    color: red;
}

/* http://meyerweb.com/eric/tools/css/reset/ 
   v2.0 | 20110126
   License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
	margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
	display: block;
}
body {
	line-height: 1;
}
ol, ul {
	list-style: none;
}
blockquote, q {
	quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}
table {
	border-collapse: collapse;
	border-spacing: 0;
}

现在多页应用打包的前置条件已经准备好了,接下来我们将配置webpack多入口打包。

首先,配置多个入口和出口,出口是利用的变量[name],对应的就是入门前面的入口文件名,注释标注的部门是单入口打包的配置,同学们可以对比一下。

image.png

 entry:{
    about:"./src/about.js",
    home:"./src/home.js",
   
  },
  // publicPath:'dist/'  ,        //注意dist后面的/不能省略
  output: {
    //打包后出口
    path: path.join(__dirname, "dist"), //打包后路径
    filename: "[name]_bundle.js", //打包后文件名
  
  },

其次,为home和about页面配置htmlWebpackPlugin

image.png

// 用于生成about.html
    new htmlWebpackPlugin({
      template:'./src/about.html',
      filename:"about.html",
    }),

    

  // 用于生成home.html
  new htmlWebpackPlugin({
    template:'./src/home.html',
    filename:"home.html",
  }),

做好以上配置之后,我们就可以执行打包命令了,当打包后会生成home.html和about.html文件,还有他们对应的js文件home_bundle.js和about_bundle.js。

image.png

但是当我们打开home.html和about.html文件,他们两个文件都引用了home_bundle.js和about_bundle.js,

image.png

image.png

这样可以不行,我们想要的效果是他们各自引用自己对应的文件,那么我们需要为它们各自设置一个chunks,这样它们就能引用自己的js文件了

image.png

     // 用于生成about.html
        new htmlWebpackPlugin({
          template:'./src/about.html',
          filename:"about.html",
          chunks:['about'],
        }),

        

      // 用于生成home.html
      new htmlWebpackPlugin({
        template:'./src/home.html',
        filename:"home.html",
        chunks:['home'],
      }),
    

再次执行打包命令,观察打包结果

image.png

image.png

可以发现我们的home和about页面都现在都各自引用了自己的js文件,没有多引用其他的,这样就达到了我们想要的效果。

提取公共模块

你会发现目前我们的多入口打包已经完成了,但是目前还是会存在一些问题,比如我们的公共模块的公共方法common.js和common.css作为公共模块,在home和about中都引用了,他们会被一起打包到他们对应的js文件中,那么会有什么问题呢?

没错,现在的问题就是home_bundle.js和about_bundle.js里面都分别包含一个公共模块的方法和样式,但是其实他们是一摸一样的,如果按现在的状况,应用在启用后,就会加载公共模块加载两次,性能损耗,浪费带宽,那我们该怎么办呢?

其实很简单,我们需要把这公共的方法和样式提取出来,单独引入这些公共模块。

image.png

  optimization: {
    splitChunks:{
      chunks:"all",
      minSize: 1, 
    }
    
  },

由于我们的公共模块的文件都比较小,只有1-2Kb,所以我们设置minSize为1KB。

image.png 我们再次执行打包命令。

image.png

我们会发现,就会多出两个文件,他们就是我们的公共模块被提取出来了,公共的方法和样式会被提取到src_common_js-src_common_css_bundle.js中,并起由于涉及到了css会多出一个css-loader的js文件。

我们再打开home和about页面,他们会引入三个文件,引用各种的模块和单独引入公共的模块。

image.png

image.png

以上就是如何利用webpack代码分割,做前端性能优化的全部内容,由于我们案例中,共同模块体积比较小,看不出明显的区别,但在实际项目中,当公共模块和第三方包的体积非常大时,提取公共模块在性能上就会体现出具体大差异。

结束

对于本文章,你有任何疑问,可在评论区留言交流。另外,我自己建了一个前端技术交流群,群成员工作年限0-10年都有,如果想进前端技术交流群,可以加我微信fangdongdong_25,请备注掘金哦。