前端必懂的webpack优化

# webpack优化

- 知识准备

    - [webpack基础配置](https://juejin.im/post/5d274e335188254a9b37099e)

    - [必懂的webpack高级配置](https://juejin.im/post/5d33147f51882572ad278c5b)

## 1.production 模式打包自带优化

- tree shaking

  > tree shaking是一个术语、通常用于打包时移除js中未引用的代码(dead-code),它依赖于ES6模块系统中的import 和 export 的***静态结构特性***

  >

  > 开发时引入一个模块时,如果只引用其中一个功能,上线打包时只会把用到的功能打包进bundle中,其他没有用到的功能都不会打包进来,可以实现最简单的基本优化

  1. 创建一个 math.js, 抛出两个方法

  ```

  export const add = (a, b) => a + b

  export const minus = (a, b) => a- b

  ```

  2. 在 main.js 中使用

  ```

  // tree shaking 分析

  // 若是此时使用 require 引入,不管 math 中的方法是否使用,都会被打包

  const math = require('./utils/math')

  // 若是使用 import 引入, 只会打包使用了 math 的方法

  import { add } from './utils/math'

  console.log('index 页面',math.add(1,2));

  console.log('index 页面',add(1,2));

  ```

  3. 根据不同的引入方式进行打包,观察打包后的文件

- scope hoisting

  > Scope hositing 作用:是将模块之间的关系进行结果推测,可以让webpack文件打包出来的代码文件更小、运行的更快

  >

  > scope hositing实现原理:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中,但是前提是不能造成代码冗余, 因此只有哪些被引用了一次的模块可能被合并

  >

  > 由于scope hositing 需要分析出模块之间的依赖关系,因此源码必须使用ES6模块化语句,不然就不能生效,原因和 tree shaking一样

  1. 在 main.js 中定义几个变量并输出

    ```

    const a = 1

    const b = 2

    const c = 3

    // webpack 在这里会进行 预执行,将结果推断后打包放在这里

    console.log(a + b + c)

    console.log(a, b, c)

    ```

  2. 打包之后代码变成

    ```

    console.log(6),console.log(1,2,3)

    ```

    >因为三个变量只是在这个地方定义并且使用,并没有在其他位置使用,webpack会直接以具体的数值进行打包,节省了三个变量的定义

- 代码压缩

  >所有代码使用UglifyJsPlugin进行压缩、混淆

## 2.CSS优化

### 2.1  将CSS提取到独立文件中

> Mini-css-extract-plugin 是用于将 CSS 提取为独立的文件的插件,对每个包含css的js文件都会创建一个css文件,支持按需加载css和sourceMap

- 只能用于webpack4中,优势

  - 异步加载

  - 不重复编译,性能更好

  - 更容易使用

  - 只针对css

- 使用

  - 安装

    ```

    npm i -D mini-css-extract-plugin

    ```

  - 引用

    ```

    const MiniCssExtractPlugin = require('mini-css-extract-plugin');

    ```

  - 创建插件对象,配置抽离的css文件名,支持placeholder语法

    ```

    new MiniCssExtractPlugin({

    filename:'[name].css' // [name] 就是 placeholder 语法

    })

    ```

  - 将原来配置的所有 style-loader 替换为 MiniCssExtractPlugin.loader

    ```

    {

      test:/\.css$/,

      use:[MiniCssExtractPlugin.loader, 'css-loader']

    },

    {

      test:/\.less$/,

      use:[MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']

    },

    {

      test:/\.scss$/,

      use:[MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']

    },

    ```

### 2.2 自动添加CSS前缀

> 使用 postcss,需要使用 postcss-loader 和 autoprefixer

1. 安装

  ```

  npm i -D postcss-loader autoprefixer

  ```

2. 修改配置文件,将 postcss-loader 放置在 css-loader 右边

  ```

  {

    test:/\.css$/,

    use:[MiniCssExtractPlugin.loader, 'css-loader',  'postcss-loader',]

  },

  {

    test:/\.less$/,

    use:[MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader']

  },

  {

    test:/\.scss$/,

    use:[MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader']

  },

  ```

3. 项目根目录下添加 postcss 的配置文件: postcss.config.js

  ```

  module.exports = {

    plugins: [

      require('autoprefixer')({

        browsers: [

          // 加这个后可以出现额外的兼容性前缀

          "> 0.01%"

        ]

      })

    ]

  }

  ```

### 2.3 开启CSS压缩

> 需要使用 optimize-css-assets-webpack-plugin 插件来完成css压缩

>

> 但是由于配置css压缩时会覆盖掉webpack默认的优化设置,导致JS代码无法压缩,所以还需要把JS代码压缩插件倒入进来 terser-webpack-plugin

1. 安装

  ```

  npm i -D terser-webpack-plugin optimize-css-assets-webpack-plugin

  ```

2. 引用

  ```

  const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');

  const TerserPlugin = require('terser-webpack-plugin');

  ```

3. 配置

  ```

  optimization:{

    minimizer: [

      new TerserPlugin({}),

      new OptimizeCssAssetsPlugin({})

    ]

  }

  ```

> webpack4默认采用的JS压缩插件是 uglifyjs-webpack-plugin,在 mini-css-extract-plugin上一个版本中还推荐使用该插件,但是新的版本却建议使用 terser-webpack-plugin

## 3.JS优化

> code splitting 是webpack打包时用到的重要的优化特性之一、此特性能够把代码分离到不同的bundle中,然后可以按需加载或者并行加载这些文件,代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果能够合理的使用能够极大影响加载时间

- 三种常见的代码分离方法

  - 入口起点:使用entry配置,手动的分离代码

  - 放置重复:使用 SplitChunksPlugin 去重和分离 chunk

  - 动态导入:通过模块的内联函数调用来分离代码

### 3.1手动配置多入口

- 手动配置多入口会存在一些问题

  - 如果入口chunks之间包含重复的模块,哪些重复的模块都会被引入到各个打包后的js文件中

  - 方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码

1. 在webpack配置文件中配置多个入口

  ```

  entry:{

  main: './src/main.js',

  other: './src/other.js'

  },

  output:{

  path: path.join(__dirname, '..', './dist'),

  filename: '[name].js',

  publickPath: '/'

  }

  ```

2. 在main.js 和 other.js 中都共同引入一个模块, 并使用其功能

  1. Main.js

      ```

      import $ from 'jquery'


      $(() => {

      $('<div></div>').html('main').appendTo('body')

      })

      ```

  2. other.js

      ```

      import $ from 'jquery'


      $(() => {

      $('<div></div>').html('other').appendTo('body')

      })

      ```

3. 打包文件,可以看到 main 和 other 打包的文件中都加载的了 jquery

### 3.2抽取公共代码

> webpack4 以上使用的插件为 SplitChunksPlugin,webpack4 之前的使用的 CommonChunkPlugin已经被移除,最新版本的webpack中只需要在配置文件中的optimization节点下添加一个splitChunks属性即可进行相关配置

1. 修改配置文件

  ```

  optimization

    splitChunks:{

      chunks: "all"

    }

  }

  ```

2. 打包查看文件

  > 打包之后会将各自的入口文件进行打包,额外会再生产一份js文件,此文件中就是各个chunk中所引用的公共部分

- splitChunksPlugin 配置参数

  > SplitChunksPlugin 的配置只需要在 optimization 节点下的 splitChunks 进行修改即可,如果没有任何修改,则会使用默认设置

  >

  > 默认的 SplitChunksPlugin 配置适用于绝大多数用户

  - webpack 会基于如下默认原则自动分割代码

    - 公用代码块或者来自 node_modules 文件夹的组件模块

    - 打包的代码块大小超过30kb,最小化压缩之前的

    - 按需加载代码块时,同时发送的请求最大数量不应该超过5

    - 页面初始化时,同时发送的请求最大数量不应该超过3

  - SplitChunksPlugin 默认配置

  ```

  module.exports = {

    optimization: {

      splitChunks: {

        chunks: 'async', // 只对异步加载的模块进行拆分,import('jquery').then()就是典型的异步加载,可选项还有 all | initial

        minSize: 30000, // 模块最少大于 30kb 才会拆分

        maxSize: 0, // 为0时模块大小无上限,只要大于 30kb 都会拆分。若是非0,超过了maxSize的值,会进一步拆分

        minChunks: 1, // 模块最少引用一次才会拆分

        maxAsyncRequests: 5, // 异步加载时同时发送的请求数量最大不能超过5,超过5的部分不拆分

        maxInitialRequests: 3, // 页面初始化时,同时发送的请求数量最大不能超过3,超过3的不跟不拆分

        automaticNameDelimiter: '~', // 默认的连接符

        name: true, // 拆分的chunk名,设置为true表示根据模块名和CacheGroup的key来自动生成,使用上面的连接符连接

        cacheGroups: { // 缓存组配置,上面配置读取完成后进行拆分,如果需要把多个模块拆分到一个文件,就需要缓存,所以命名为缓存组

          vendors: { // 自定义缓存组名

            test: /[\\/]node_modules[\\/]/, // 检查 node_modules 目录,只要模块在该目录下就使用上面配置拆分到这个组

            priority: -10, // 权重为-10,决定了那个组优先匹配,假如node_modules下面有个模块要拆分,同时满足vendors和default组,此时就会分到 priority 值比较大的组,因为 -10 > -20 所以分到 vendors 组

            filename:'vendoes.js'

          },

          default: { // 默认缓存组名

            minChunks: 2, // 最少引用两次才会被拆分

            priority: -20, // 权重 -20

            reuseExistingChunk: true // 如果主入口中引入了两个模块,其中一个正好也引用了后一个,就会直接复用,无需引用两次

          }

        }

      }

    }

  };

  ```

### 3.3动态导入(懒加载)

> webpack4默认是允许import语法动态导入的,但是需要babel的插件支持,最新版babel的插件包为:@babel/plugin-syntax-dynamic-import,需要注意动态导入最大的好处就是实现了懒加载,用到那个模块才会加载那个模块,可以提高SPA应用程序的首屏加载速度,三大框架的路由懒加载原理一样

1. 安装

  ```

  npm i -D @babel/plugin-syntax-dynamic-import

  ```

2. 修改 .babelrc ,添加 @babel/plugin-syntax-dynamic-import 插件

  ```

  {

  "presets": ["@babel/env"],

  "plugins": [

  "@babel/plugin-proposal-class-properties",

  "@babel/plugin-syntax-dynamic-import"

  ]

  }

  ```

3. 将jq模块动态导入

  ```

  function getDivDom(){

  // import('jquery') 返回的是一个 promise,若是低版本需要注意

  return import('jquery').then(({default: $}) => {

  return $('<div></div>').html('动态导入')

  })

  }

  ```

4. 给某个按钮添加点击事件,点击后调用getDivDom函数创建元素并添加到页面

  ```

  window.onload = () => {

    document.getElementById('btn').addEventListener('click',() => {

      getDivDom().then(item => {

        item.appendTo('body')

      })

    })

  }

  ```

## 4.noParse

> 在引入一些第三方模块时,如jq等,我们知道其内部肯定不会依赖其他模块,因为我们用到的只是一个单独的js或者css文件,所以此时如果webpack再去解析他们的内部依赖关系,其实是非常浪费时间的,就需要阻止webpack浪费精力去解析这些明知道没有依赖的库,可以在webpack的配置文件的module节点下加上noParse,并配置正则来确定不需要解析依赖关系的模块

```

module:{

noParse: /jquery|bootstrap/  // jquery|bootstrap 之间不能加空格变成 jquery | bootstrap, 会无效

}

```

## 5.IgnorePlugin

> 在引入一些第三方模块时,例如momentJS、dayJS,其内部会做i18n处理,所以会包含很多语言包,而语言包打包时会比较占用空间,如果项目只需要用到中文或者少数语言,可以忽略掉所有的语言包,然后按需引入语言包,从而使得构建效率更高,打包生成的文件更小

- 以moment为例

  ```

  import moment from 'moment'

  moment.locale('zh-CN') // 设置为中文


  console.log(moment().subtract(6, 'days').calendar())

  ```

1. 首先要找到moment依赖的语言包时什么,通过查看moment的源码来分析

  ```

  function loadLocale(name) {

      var oldLocale = null;

      // TODO: Find a better way to register and load all the locales in Node

      if (!locales[name] && (typeof module !== 'undefined') &&

              module && module.exports) {

          try {

              oldLocale = globalLocale._abbr;

              var aliasedRequire = require;

              aliasedRequire('./locale/' + name);

              getSetGlobalLocale(oldLocale);

          } catch (e) {}

      }

      return locales[name];

  }

  ```

  > 通过 aliasedRequire('./locale/' + name) 可以知道momentJS的多语言目录是locale,所有的语言JS文件都在这个目录中

2. 使用IgnorePlugin插件忽略其依赖

  > 将momentJS的多语言目录locale忽略

  ```

  new webpack.IgnorePlugin(/\.\/locale/, /moment/)

  ```

3. 需要使用某些依赖时自行手动引入

  > 忽略其依赖之后,moment.locale('zh-CN')就会失效,因为其所依赖的语言包全都被忽略了,需要手动将其引入

  ```

  import moment from 'moment'

  import  'moment/locale/zh-cn' // 需要手动引入方可生效

  moment.locale('zh-CN')


  console.log(moment().subtract(6, 'days').calendar())

  ```

## 6.DLLPlugin

> 在引入一些第三方模块时,例如Vue、React等,这些框架的文件一般都是不会修改的,而每次打包都需要去解析他们,也会影响打包速度,就算是做了拆分,也只是提高了上线后的用户访问速度,并不会提高构建速度,所以如果需要提高构建速度,应该使用动态链接库的方式,类似windows的dll文件

>

> 借助DLLPlugin插件实现将这些框架作为一个个的动态链接库,只构建一次,以后的每次构建都只会生成自己的业务代码,可以很好的提高构建效率

>

> 猪哟思想在于,讲一些不做修改的依赖文件,提前打包,这样我们开发代码发布的时候就不需要再对这些代码进行打包,从而节省了打包时间,主要使用两个插件: DLLPlugin和DLLReferencePlugin

>

> 需要注意的是,若是使用的DLLPlugin,CleanWebpackPlugin插件会存在冲突,需要移除CleanWebpackPlugin插件

- DLLPlugin

  >使用一个单独webpack配置创建一个dll文件,并且它还创建一个manifest.json,DLLReferencePlugin使用该json文件来做映射依赖性,这个文件会告诉webpack哪些文件已经提取打包好了

  - 配置参数

    - context(可选):manifest文件中请求的上下文,默认为该webpack文件上下文

    - name:公开的dll函数的名称,和output.library保持一致即可

    - path:manifest.json 生成的文件夹及名称

- DLLReferencePlugin

  > 该插件主要用于主webpack配置,它引用的dll需要预先构建的依赖该系

  - 配置参数

    - context: manifest文件中的请求上下文

    - manifest: DLLPlugin插件生成的manifest.json

    - content(可选): 请求的映射模块id(默认为manifest.content)

    - name(可选): dll暴露的名称

    - scope(可选): 前缀用于访问dll的文件

    - sourceType(可选): dll是如何暴露(libraryTarget)

### 将VUE项目中的库抽取成DLL

1. 准备一份将VUE打包成DLL的webpack配置文件。

- 在build目录下新建一个文件webpack.vue.js,专门用于打包vue的DLL的。

- 配置入口:将多个要做成dll的库全放进来

- 配置出口:一定要设置library属性,将打包好的结果暴露在全局

- 配置plugin:设置打包后dll文件名和manifest文件所在地

```

// 此配置文件 是打包VUE全家桶的

const path = require('path')

const webpack = require('webpack')

module.exports = {

  mode: 'production',

  entry:{

    vue: [

      'vue/dist/vue',

      'vue-router'

    ]

  },

  output:{

    path: path.resolve(__dirname, '../dist'),

    filename: '[name]_dll.js',

    library: '[name]_dll' // 最终会在全局暴露出一个[name]_dll的对象

  },

  plugins:[

    new webpack.DllPlugin({

      name: '[name]_dll',

      path: path.resolve(__dirname, '../dist/manifest.json'),

    })

  ]

}

```

> webpack.vue.js 只是用来打包生成 [name]_dd.js 文件和 manifest.json文件的,是不需要参与到业务代码打包的,因为只会在每一次修改了需要生成dll文件的时间才会执行一次,否则不需要参与到打包

2. 在`webpack.base.js`中进行插件的配置

> 使用DllReferencePlugin指定manifest文件的位置即可

```

new webpack.DllReferencePlugin({

manifest: path.resolve(__dirname, '../dist/manifest.json'),

})

```

3. 由于[name]_dll文件生成之后,并没有动态的引入进去,所以需要一个插件可以动态的将生成的dll文件引入

> 安装add-asset-html-webpack-plugin

```

npm i -D add-asset-html-webpack-plugin

```

> 配置插件自动添加script标签到HTML中,需要注意的是,必须在HtmlWebpackPlugin后面引入,因为HtmlWebpackPlugin是生产一个html文件,AddAssetHtmlWebpackPlugin是在已有的html中注入一个script,否则会被覆盖

```

new AddAssetHtmlWebpackPlugin({

filepath: path.resolve(__dirname, '../dist/vue_dll.js')

})

```

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容

  • 全局安装webpack 全局安装webpack会有个问题,就是当你有两个项目依赖于不同版本的webpack,就会有...
    説好的妹紙呢阅读 1,803评论 0 11
  • 在现在的前端开发中,前后端分离、模块化开发、版本控制、文件合并与压缩、mock数据等等一些原本后端的思想开始...
    Charlot阅读 5,429评论 1 32
  • 目录第1章 webpack简介 11.1 webpack是什么? 11.2 官网地址 21.3 为什么使用 web...
    lemonzoey阅读 1,731评论 0 1
  • 妈妈说:“你小时候性格很倔,四岁时因不愿在外婆家睡觉,把本想留宿在外婆家的你爸的手臂咬了两排很深的牙齿印。” ...
    Chivada阅读 400评论 0 0
  • 十月飞雪飘飘起, 春过...
    蜜语清风阅读 320评论 1 5