第二节:webpack性能优化

一.概述

1.webpack性能优化

  • 开发环境性能优化
  • 生产环境性能优化

2.开发环境性能优化

  • 优化webpack的打包构建速度
    • HRM
  • 优化代码调试
    • source-map

3.生产环境性能优化

  • 优化打包构建速度
    • oneOf
    • babel缓存
    • 多进程打包
  • 优化代码运行的性能
    • 缓存(hash-chunkhash-contenthash
    • tree shacking
    • code split
    • 懒加载/预加载
    • PWA
    • externals
    • dll

4.存在的问题

当我们修改某一个css或者模块的时候,所有的文件都会被重新打包;如果模块很多的情况下,这样是很耗费时间的。我们希望只修改发生变化的模块;这就要使用webpack中的HMR功能;

二.HMR

hot module replacement热模块替换/模块热替换

作用:一个模块发生变化,只会重新打包这一个模块,而不是打包所有;这样会极大地提升构建速度。

使用方法:只需要在devServer中设置hot属性为true即可:

  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    //开启HRM
    hot: true
  }

注意:修改了webpack配置之后,一定要重启devServer

重启后,再次开启devServer

npx webpack-dev-server

可以看到打开的html文件的输出中显示,已开启HRM

image-20200422133055910

随后,修改入口文件index.js依赖的index.less,可以看到这一次只重新打包了index.less模块:

image-20200422133344188

所以验证了css文件可以使用HMR功能,其实:

  • 样式文件:可以使用HMR功能:因为style-loader内部实现;

  • js文件:默认不使用HMR功能 ;

    • 解决方法:需要修改js代码,添加支持HMR功能的代码:

      if (module.hot){
        //一旦 module.hot 为 true ,说明开启了HMR功能。此时才让HMR功能生效
        module.hot.accept('./print.js', function(){
          //方法会监听print.js文件的变化,一旦发生变化,其他模块不会重新打包构建,会执行后面的回调函数
        })
      }
      

    上面的print.js为发生变化的模块,如果有其他发生变化需要开启HMR功能的模块,只需要添加相应路径就可以了;

    注意:HMR功能对js文件处理,只能处理非入口js文件。因为入口文件引入了很多依赖,它一变就要不可避免的重新引入依赖;

  • html文件:默认不使用HMR功能,同时会导致问题:html文件不能热更新了(devServer的功能,自动更新);

    • 解决方法:修改entry入口,将html文件引入。但是还是是用不了HMR功能;

        entry: ['./src/js/index.js', './src/index.html']
      
    • 其实HTML文件不需要HMR功能,因为html文件通常只有一个,它变了就说明有重新打包的必要;

三.source-map

source-map是一种技术,提供源代码到构建后代码的映射技术。(如果构建后代码出错了,通过映射可以追踪源代码错误)

使用方法为,只需要在五大属性的同级下添加一个devtool属性即可:

devtool: 'source-map'

执行webpack打包后,会生成一个.map文件:

image-20200422172159137

里面存储着映射关系:

image-20200422172211659

写法分为:

[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map

其中:

  • source-map:外部;

  • 作用:显示错误代码的准确信息和源代码的错误位置;

  • inline-source-map:内联:所有文件只生成一个source-map文件,并且拼接在js文件中的最后;

    • 作用:显示错误代码的准确信息和源代码的错误位置;
  • hidden-source-map:外部

    • 作用:显示错误代码和错误原因,但是没有错误位置。不能追踪到源代码错误,只能提示到构建后代码的位置;
  • eval-source-map:内联:每一个文件都生成对应的source-map,并且都在eval函数中;

    • 作用:显示错误代码的准确信息和源代码的错误位置;只不过提示为每个文件通过eval添加的哈希值:

      image-20200422182218171

  • nosources-source-map:外部;

    • 作用:显示错误代码和错误原因,但是没有任何源代码信息;与hidden-source-map一起提供隐藏代码的服务:

      image-20200422182434853

      但是点击提示,不会显示源代码信息:

      image-20200422182508258

  • cheap-source-map:外部;

    • 作用:显示错误代码的准确信息和源代码的错误位置;但是只能精确到行:

      123

      如图,只有console发生了错误,但是只能精确到行;

  • cheap-module-source-map:外部;

    • 作用:显示错误代码的准确信息和源代码的错误位置;同样只能将错误精确到源码中的行;不同在于module会将loadersource-map也加进来;

内联与外部的区别:

  • 外部方式会生成.map文件而内联是没有的,而是将.map文件的内容内嵌到打包后的js文件的最后;
  • 内联构建速度更快

下面我们来一个个测试它们的作用:

1.作用演示

source-map

首先在入口文件index.js中增添一处错误代码:

image-20200422173824361

随后通过下列指令开启devServer

npx webpack-dev-server

在显示的网页中可以看到出现了错误提示:

image-20200422173957584

并且这个提示包含了错误出现在哪个文件的哪一行中,点击这个提示可以跳转到源代码出错的地方:

image-20200422174139860

所以source-map的作用为:显示错误代码的准确信息和源代码的错误位置;

inline-source-map

注意:修改了webpack.config.js的配置后,一定要重新启动devServer;在开启的网页中,一样显示了错误代码的准确信息和源代码的错误位置;

image-20200422181514667

点击提示即可跳转到错误源代码的位置:

image-20200422181601425

2.选择

source-map有这么多种写法,那么我们怎么选择呢?

开发环境

要求:速度快,调试友好;

  • 速度方面:eval > inline > cheap ...

    通过组合,速度从快到慢的写法如下:

    eval-cheap-source-map

    eval-source-map

  • 调试友好方面:定位更精确,就越友好。所以友好程度排名为:

    source-map:精确到具体位置;

    cheap-module-source-map:精确到行,但是包含了loader等依赖文件;

    cheap-source-map:最后是精确到行,但是不包含依赖文件;

折中方案为采用eval-source-map 或者 eval-cheap-module-source-map,框架中的脚手架默认使用前者;

生产环境

源代码要不要隐藏?调试要不要更友好?

内联会让代码体积变大,所以在生产环境中一般不使用内联;

  • 隐藏代码:

    hidden-source-map:只隐藏源代码,会提示构建后的代码错误;

    nosources-source-map:全部隐藏;

最后结论

需要调试友好时使用source-map,需要速度快时使用cheap-module-source-map

四.oneOf

通常一个loader处理一类文件,也就是说并不是每一个文件都需要使用全部的loader去处理,这样会降低性能;可以使用oneOf设置文件只匹配oneOf数组中的一个loader

    rules: [
      {
        //oneOf的意思为:只会匹配以下中的一个loader
        oneOf: [
      //1.处理css文件
      {
          //...		
      },
      //2.处理less文件
      {
          //...	
      },
      //3.js语法检查
      {
          //...	
      },
      //4.js兼容性处理
      {
          //...	
      },
      //5.处理图片
      {
          //...	
      },
      //6.处理html中的图片
      {
          //...	
      },
      //8.处理其他文件
      {
          //...	
      }
        ]
      }
    ]

需要注意的是只能选择oneOf数组中的一个loader执行;像eslintbabel这样的loader都会处理js文件,这就需要将其中一个loader抽离出oneOf数组了:

    rules: [
      //js语法检查eslint
      {
        test: /\.js$/,
        exclude: /node_modules/,
        //优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        oneOf: [
      //js兼容性处理babel
      {
          //...	
      },
        ]
      }
    ]

并且eslint-loaderenforce属性为pre表示会优先执行该loader;这样两个loader就不会互相影响了;

这样配合着使用oneOf可以提升打包构建的速度;

使用webpack进行文件打包时,并不是每次打包所有的文件都不一样,如果可以将每次打包都不变的文件缓存起来,下次直接复用,就可以大幅度提升打包的速度。这就涉及到了webpack中的缓存机制;

五.缓存

1.babel缓存

比如处理了100份文件,我们希望在第一次处理的时候可以将这100份文件的处理结果缓存起来,以后只重新处理其中发生变化的文件,其余文件直接使用缓存。可通过cacheDirectory开启缓存,配置如下:

      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          presets: [
            [
              '@babel/preset-env',
              {
                useBuiltIns: 'usage',
                corejs: {version: 3},
                targets: {
                  chrome: '60',
                  firefox: '50'
                }
              }
            ]
          ],
          //开启babel缓存
          //第二次构建时,会读取之前的缓存
          cacheDirectory: true
        }
      },

2.文件基本缓存

src的文件结构如下:

image-20200422202438244

首先在根目录下创建一个服务器文件server.js

/**
 * 服务器
 * 启动服务器指令;
 * 方式一:
 * 首先需要全局安装:npm i nodemon -g
 * nodemon server.js
 * 
 * 方式二:
 * node server.js
 */
const express = require('express')

const app = express();

app.use(express.static('build', {maxAge: 1000 * 3600}));

app.listen(3000)

执行该文件:

node server.js

由于该文件监听的是3000端口,所以访问地址为:http://localhost:3000

打开调试工具的network选项,可以看到第一次打开网页,资源都需要请求:

image-20200422203705066

刷新一次后,再次查看:

image-20200422203749200

可以看到,浏览器直接从缓存读取了这两个文件。

点开其中一个文件,可以看到在HTTP响应头中的max-age字段正是server.js中设置的3600s

image-20200422204007559

也就是这两个资源需要被强制缓存两个小时

读取缓存的速度非常之快,所以缓存能够大大提高加载速度。

存在的问题:

当被缓存的文件发生变化时,就不能使用这个过期的缓存了,需要重新请求该最新版的该文件。

解决方案:

hash

每次打包都给文件拼接上一个hash值当做是版本号,这样缓存的文件与发生更新后的文件由于版本号不一样,名字也就不同,所以会重新加载更新后的文件;具体配置为在每个文件的output对象中为filename属性拼接上hash值:

  output: {
    filename: 'js/built.[hash:10].js',
    path: resolve(__dirname, 'build')
  },
      
  //...
    plugins: [
    new MiniCssExtractPlugin({
      //设置输出路径
      filename: 'css/built.[hash:10].css',
    })
    ]

再次执行webpack打包,此时打包出来的文件就会拼接上10位的hash值了:

image-20200422205159752

这个hash值取的是每次执行webpack时生成hash值的前10位:

image-20200422205610042

这样就保证了,每次打包都会生成不同的打包文件。

随后,执行服务器代码:

node server.js

打开地址http://localhost:3000,这次请求的资源都带上了hash值:

image-20200422205337408

当我们修改了源代码之后,再进行打包,打包出来的文件就拼接上了另外10hash值了:

image-20200422205745490

此时刷新网页,浏览器就会重新请求,经过再次打包的更新后的文件了:

image-20200422210237054

文件资源缓存存在的问题

由于所有文件都共享同一次webpack打包时生成的hash值,所以只要有一个文件发生了变化,重新打包后,所有文件的缓存都会失效。

chunkhash

webpack引入了另外一个hash值:chunkhash。只需要将webpack.config.js配置中的hash替换为chunkhash即可。根据chunk生成哈希值,如果打包来源于同一个chunk哈希值一样。

此时,再进行打包,发现打包后的cssjs文件仍然共有一个hash值:

image-20200422213852887

这是因为:css文件是在js文件中引入的,所以同属于一个chunk

chunk:入口文件index.js会引入很多文件,比如cssless等,这样一个整体被称为chunk,即代码块;

contenthash

使用contenthash才是最优解,这是根据文件的内容生成的hash值,不同文件的hash值一定不一样;将webpack.config.js中的chunkhash改为contenthash,再次打包:

image-20200422214434376

这次打包出来的css文件与js文件的hash值就不一样了;这样的话如果只改变了css文件,那么浏览器第一次缓存的js文件仍然有效,只需要重新请求css文件即可:

第一次打开页面,两文件都需要请求:

image-20200422214731287

修改css文件并重新打包后,再一次刷新浏览器。只需要重新请求css文件即可:

image-20200422214910419

所以一共有两种缓存策略:

  • babel缓存:直接在webpack.config.js中配置即可;
    • 优点:让第二次打包构建速度更快;
  • 文件资源缓存:给文件添加hash值,有三种选择,最优解为添加contenthash值。
    • 优点:让上线运行的代码更好地使用缓存;

六.Tree shaking

1.简介

在应用程序中引入的源代码和一些库可以理解为树上的一个绿色的活的树叶,哪些应用程序中没有引用的代码就是灰色的,枯萎的树叶;为了去掉这些灰色的树叶,我们需要摇晃这棵树,这就是树摇(tree shaking)的概念;树摇是为了去除应用程序中没有使用的代码,这样能让代码的体积变得更小。

tree shaking的作用为去除无用的代码;但是有前提:

  • 必须使用ES6模块化;
  • mode设置为production环境;

2.作用

减少代码体积。例如:

// app.js
export function A(a, b) {
    return a + b
}

export function B(a, b) {
    return a + b
}

// index.js
import {A} from '/app.js'

A(1, 2)

index.js引用了app.jsA函数,如果tree-shaking起了作用,B函数是不会被打包进最后的bundle的。因为,index.js中并没有用到B函数;

3.配置

webpack.josn中配置sideEffects

"sideEffects": false

它表示所有的代码都是没有副作用的,都可进行tree shaking

问题:可能会把css@babel/polyfill等文件删除(副作用);

解决方法:将sideEffects的值设置为["*.css"]

"sideEffects": ["*.css"]

这样写在该数组内的元素,就不会执行tree shaking

默认情况下会将入口文件index.js与其引入的js文件一起打包,最后输出一个js文件。如图所示,源码文件夹src中的test.jsindex.js最后都被打包成了一个js文件:

image-20200422231043429

如果我们需要将代码分门别类地打包呢?这时候就需要用到代码分割了;

七.代码分割

1.直接使用多入口

要进行抽离,可以先从entry指定的入口文件路径下手,配置如下:

  entry: {
    index: './src/js/index.js',
    test: './src/js/test.js'
  },

entry这样的配置称为多入口;对于多入口:有一个入口,最终输出就有一个bundle。配置后,执行webpack进行打包:

由于配置里有两个入口,所以打包后输出的js文件也有两个:

image-20200422231805371

为了方便区分打包后输出的内容,修改output配置:

  output: {
    //[name]:取文件名
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },

再次打包:此时打包后的文件与打包前的文件的对应关系就一目了然了:

image-20200422232210416

一般,单页面应用对应的是单入口,多页面的对应的是多入口;

2.使用optimization

单入口时

webpack.config.js中添加:

entry: './src/js/index.js',
//...
optimization: {
    splitChunks: {
      chunks: 'all',
    } 
  },

然后在入口文件index.js中引入jquery

import $ from 'jquery'

执行webpack进行打包,可以看到输出了两个文件,另外一个文件用于存储jquery

image-20200422234145068

这种方式的好处是:可以将node_modules中代码单独打包一个chunk最终输出;

多入口时

配置如下:

 entry: {
    index: './src/js/index.js',
    test: './src/js/test.js'
 },
 //...
 optimization: {
  splitChunks: {
    chunks: 'all',
  } 
},

再次打包过后生成了三个chunk

image-20200422234535342

如果没有在配置中添加optimization属性,那么在多入口的情况下,如果两个js文件都引入了jquery,那么打包出来的两个js文件,大小是相近的:

image-20200422234929761

这是因为两个js文件各自打包一份jquery,这种重复打包比较不好;

解决方法为添加optimization属性,它的好处为:它就会分析多入口chunk中有没有公共的文件,如果有就会打包成单独的chunk

总结:optimization的作用:

  • 单入口时:可以将node_modules中代码单独打包一个chunk最终输出;

  • 多入口时:会分析多入口chunk中有没有公共的文件,如果有就会打包成单独的chunk

也就是说多入口时一般配合着optimization使用;

3.单入口动态引入(常用)

当若想要只使用单入口就能实现多入口配合optimization使用时达到的效果的话,可以采用这种方法:

通过js代码,让某个文件被单独打包成一个chunkimport动态导入语法,能将某个文件单独打包。比如在入口文件index.js中动态引入test.js

test.js代码:

export function mul(x, y) {
  return x * y;
}

export function count(x, y) {
  return x - y;
}

随后在index.js中动态引入:

import('./test')
    .then(({mul, count}) => {
      //文件加载成功
      // eslint-disable-next-line
      console.log(mul(2, 4))
    })
    .catch(() => {
     // eslint-disable-next-line
      console.log('文件加载失败')	
    })

import返回的是一个Promise对象,then方法代表加载成功,catch方法代表加载失败;

随后打包,输出了两个js文件,其中的1.js代表test.js

image-20200423001718614

打包出来的js文件名字是webpack打包过程生成的随机id,不太美观,我们通过给import增加参数设置打包后输出的js文件的名字:

//设置输出文件名为test
import(/* webpackChunkName: 'test' */'./test')
    .then(({mul, count}) => {
      // eslint-disable-next-line
      console.log(mul(2, 4))
    })
    .catch(() => {
     // eslint-disable-next-line
      console.log('文件加载失败')	
    })

再次打包:

image-20200423002236707

可见,自定义了test.js打包后输出文件的名字;

八.懒加载和预加载

1.懒加载

指的是触发了某些条件才加载,而不是一开始就加载;这里讲的是js文件的懒加载;

可以通过import().then的方式实现js文件的懒加载,比如在入口文件index.js中这样写:

//懒加载
document.getElementById('btn').onclick = function(){
  import((/* webpackChunkName: 'test' */ './test').then((mul) => {
    console.log(mul(3, 5));
  })
}

一般将import语句,放在一些判断语句中,当满足一定的条件时才执行import动态引入;如上面的代码,将引入代码放在了按钮btn的点击事件中,只有点击按钮btn才会引入test.js文件;

首先,源文件目录src结构如下:

image-20200423104612306

test.js代码如下:

console.log('test.js文件被加载了');

export function mul(x, y) {
  return x * y;
}

index.js的代码如下:

console.log('index.js文件被加载了');

document.getElementById('btn').onclick = function(){
  //懒加载
  import(/* webpackChunkName: 'test'*/ './test').then(() => {
    console.log('test.js被执行了');
  })
}

index.html代码如下,设置了一个按钮:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>hello lazy-loading</h1>
  <button id="btn">按钮</button>
</body>
</html>

打包后,打开经过打包的index.html文件:

image-20200423104830673

image-20200423110520427

可以看到,此时只加载了index.js,当点击了按钮后,才会加载test.js文件:

image-20200423105350339

当我们多次点击按钮时,发现只会再次执行test.js而不会再次加载。因为第一次加载之后该文件就被缓存起来了,之后使用的时候就可以直接从缓存中读取了:

image-20200423105531704

2.预加载

为入口文件index.js添加webpackPrefetch

console.log('index.js文件被加载了');

document.getElementById('btn').onclick = function(){
  //懒加载
  //预加载prefetch
  import(/* webpackChunkName: 'test', webpackPrefetch: true*/ './test').then(() => {
    console.log('test.js被执行了');
  })
}

打包后,打开经过打包的index.html文件:可以看到test.js文件已经提前加载好了:

image-20200423110024772

但是还没有输出:

image-20200423110042574

当我们点击按钮后,会从缓存中直接读取,所以会大大提升test.js的加载速度:

image-20200423110742476

3.区别

  • 懒加载:当文件需要使用时才加载;

  • 预加载:在使用之前,提前加载js文件;

    正常加载可以认为是并行加载:同一时间加载多个文件;这些文件没有优先级,只是按代码顺序加载,这样就会导致提前加载还不需要用到的文件;

    而预加载prefetch:是等其他资源加载完毕,浏览器空闲了,才会偷偷地加载资源;不会阻碍重要资源的优先加载;

总的来说:懒加载用的比较多,但是预加载存在兼容性问题,需要慎用;

九.PWA

1.简介

PWA:渐进式网络开发应用程序,简而言之就是离线访问技术;

实现它需要用到插件:workbox-webpack-plugin

首先全局下载该插件:

npm i workbox-webpack-plugin -D

然后在webpack.config.js中引入该插件:

const WorkboxWebpackPlugin = require('workbox-webpack-plugin');

然后直接在plugins属性中使用就可以了:

  plugins: [
    new WorkboxWebpackPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true
    })
  ],

GenerateSW中的两个参数作用为:

  • 帮助Service Worker快速启动;
  • 删除旧的Service Worker

最终会生成一个Service Worker的配置文件;

2.配置

需要在入口文件index.js中注册Service Worker

// 注册Service Worker
// 处理兼容性问题
if('serviceworker' in navigator){//如果有这个属性,才在页面全局资源加载完后进行注册
  window.addEventListener('load', () => {
    navigator.serviceW
      
      orker.register('/service-worker.js')
    //成功
    .then(() => {
      console.log('sw注册成功了');
    })
    //失败
    .catch(() => {
      console.log('sw注册失败了');
    })
  })
}

3.打包

此时执行打包会报eslint的错误:

image-20200423121153980

原因:eslint不认识windownavigator这些全局变量;

解决方法:需要修改webpack.jsoneslint的配置。

  "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
      "browser": true
    }
  },

其中的"browser": true表示:支持浏览器端全局变量;

再次执行webpack构建就不会报错了,成功输出了service-worker.js等文件:

image-20200423121756981

4.验证Server Worker

sw代码必须运行在服务器上,所以需要在服务器文件中启动构建后生成的sw文件;有多种方式可以选择:

  • nodejs

  • 通过npm i serve -g全局安装serve包,安装之后可以直接通过serve -s build启动服务器,将build目录下的所有资源作为静态资源暴露出去:

    image-20200423122600725

打开这个链接:

image-20200423123024623

可以看到,成功注册了serviceWorker,随后就可以在Application选项中的Service Workers中看到注册的文件了:

image-20200423123202271

并且可以在Cache中看到存储的资源:

image-20200423123305255

随后在Network中将网络设置为离线Offline

image-20200423123352737

随后刷新网页,网页还是可以显示。这是因为,这些文件都是直接从Service Worker中直接读取的:

image-20200423123556627

这就是PWA:离线访问技术;

十.多进程打包

1.安装

我们知道js引擎是单线程的,同一时间只能干一件事。所以可以通过多进程来优化打包速度;

首先需要下载thread-loader

npm i thread-loader -D

2.配置

一般应用在babel中,一旦需要需要使用多个loader时,就要放在use中:

      //4.js兼容性处理
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          //开启多进程打包
          'thread-loader',
          {
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    corejs: {version: 3},
                    targets: {
                      chrome: '60',
                      firefox: '50'
                    }
                  }
                ]
              ],
              //开启babel缓存
              cacheDirectory: true
            }
          }
        ]
      },

缺点:进程启动需要时间:600ms,进程通信也需要时间开销;所以只有在工作消耗时间比较长时,才需要多进程打包;由于开发中绝大多数都是js文件,babel除了语法检查外还有进行兼容性处理,生成大量兼容性代码,所以应该给babel开启多进程打包;

3.打包

不加thread-loader的打包时间:

image-20200423130120307

加了thread-loader后的打包时间:

image-20200423130307690

需要花费更多的时间。这是因为当前源文件中的js代码量比较少,而开启多进程需要时间,所以代码量小的时候不建议使用多进程打包;只有实际开发中,代码量上去了它的加速效果才会凸显出来。

如果想调整的话,可以将 'thread-loader'改成对象形式:

{
  loader: 'thread-loader',
  options: {
    workers: 2//指定使用2个进程
  }
}

开启多进程会增大开销,千万不能乱用!

十一.Externals和Dll

1.Externals

webpack.config.js中添加externals属性,该属性与五大基本属性同级:

  externals: {
    //拒绝jquery被打包进来
    jquery: 'jQuery'
  }

作用为:可以让webpack不对某些库或包进行打包;不过忽略了之后,比如删除的jquery,就需要在html文件中通过srcipt标签手动引入。

注意写法:字符串内指定包名,不能写错;

2.Dll

表示动态链接库。可以将多个功能(包)打包成一个chunk。比如之前我们都是将node_modules目录下的所有文件打包成一个chunk,而使用dll可以将它分为多个不同的部分,分别打包出多个chunk

也就是对某些库进行单独打包;

生成dll文件

使用dll需要在根目录下添加一个webpack.dll.js配置文件;配置如下:

/**
 * 使用dll技术,对某些库(vue、jquery、react等)进行单独打包
 */

const { resolve } = require('path')
//引入webpack插件
const webpack = require('webpack')

module.exports = {
  entry: {
    //最终打包生成的[name] --> jquery
    //['jquery] --> 要打包的库是jquery
    jquery: ['jquery']
  },
  output: {
    //这里的[name]就是上面的jquery
    filename: '[name].js',
    path: resolve(__dirname, 'dll'),
    library: '[name]_[hash]',//打包的库里面向外暴露的内容叫什么名字
  },
  plugins: [
    //该插件作用为:打包生成一个manifest.json文件 --> 提供和jquery的映射
    new webpack.DllPlugin({
      name: '[name]_[hash]',//映射的库暴露的内容名称
      path: resolve(__dirname, 'dll/manifest.json')//输出的名称
    })
  ],
  mode: 'production'
}

总结以下就是:entryoutput中的配置作用为:单独打包jquery文件,并将打包输出的文件命名为jquery_hash。插件的作用为,生成一个mainfest.json文件,管理与jquery的映射关系;

当运行webpack时,默认查找的是 webpack.config.js 配置文件;需求:需要运行webpack.dll.js文件。这就需要:执行以下指令手动指定执行的配置文件:

webpack --config webpack.dll.js

执行该命令后,打包生成一个dll目录,里面有两个文件:jquery.jsmanifest.json

image-20200423150554532

并且这两个文件内都会拼接上一个hash值,改值与webpack打包时生成的hash一致:

image-20200423150727215

第一个文件:为指定的库单独打包出来的文件;

第二个文件:manifest.json文件记录了哪些库不用经过了单独打包了,不需要重复打包的映射关系;

改变webpack配置

同时,相应的也要修改webpack.config.js的配置,新增两个插件:webpackadd-asset-html-webpack-plugin(记住要先全局下载插件):

const {resolve} = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
//1.引入webpack插件
const webpack = require('webpack')
//3.引入add-asset-html-webpack-plugin插件
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')

module.exports = {
  entry : './src/index.js',
  output : {
    filename: 'built.js',
    path: resolve(__dirname, 'build')
  },
  plugins: [
    //plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    //2.使用webpack插件告诉webpack哪些库不参与打包,同时使用时的名称也得改变
    new webpack.DllReferencePlugin({
      manifest: resolve(__dirname, 'dll/manifest.json')
    }),
    //4.将该插件指定的文件打包输出,并在html中自动引入该资源
    new AddAssetHtmlWebpackPlugin({
      filepath: resolve(__dirname, 'dll/jquery.js')
    })
  ],
  mode: 'development'
}

具体新增配置如代码中的1~4

  • 第一个插件的作用为:根据第一步中生成的manifest.json告知webpack不用打包该文件指定的库(这里是jquery),这样打包出来的built.js也就不含有这些库(jquery)的内容了。

    注意:由于jquery被指定忽略了,即使入口文件index.js中引入了jquery,打包时也就不会打包jquery

  • 第二个插件的作用为:将第一步被单独打包生成的库文件(这里是jquery)自动引入html文件中。

配置后之后,再次执行webpack打包,可以发现,在第一步中单独打包的jquery文件被再次打包了出来:

image-20200423155123290

并且html文件中自动引入了新增的打包文件jquery,这是第二个插件的作用:

image-20200423155024482

使用了webpack插件,打包时就会忽略插件指定的jquery文件。由于打包出来的html只自动引入了打包后的built.js文件,而built.js因为插件的原因并没有打包jquery;这时候就需要在html文件中手动引入jquery文件了。此时有两种方法:

  • 第一:html文件中直接通过script标签引入;
  • 第二:使用上述的add-asset-html-webpack-plugin插件打包引入;

3.总结

经过以上两步操作之后,源码发生改变只需要再次执行webpack.config.js即可,不需要执行webpack.dll.js,也就是说不需要再重复打包jquery了,达到了性能优化的效果;今后项目中会有很多的第三方库,我们都可以使用dll的方式对它们进行单独打包。在使用webpack重新打包的时候就不用再打包这些第三方的库了,这样能让webpack第二次打包的速度快很多;

  • externals:只忽略某些指定的库,不对它们进行打包,使用时需要通过script标签引入;需要依赖外链引入。
  • dll:需要打包一次,将来就不需要重复打包了。如果想要将一些第三方库整合在一起,不依赖外链,就使用这种方式。
posted @ 2020-09-04 09:56  AhuntSun  阅读(595)  评论(0编辑  收藏  举报