webpack优化

webpack各种优化

上一章节我们已经掌握了webpack常见的所有配置

这一节我们来看看如何实现webpack中的优化,我们先来编写最基本的webpack配置,然后依次实现各种优化!

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = mode => {
  return {
    mode: mode,
    entry: "./src/main.js",
    output: {
      filename: "bundle.js",
      path: path.resolve(__dirname, "dist")
    },
    module: {
      rules: [
        {
          test: /\.(png|jpg|gif)$/,
          use: "file-loader"
        },
        {
          test: /\.js$/,
          use: "babel-loader" // .babelrc已经配置支持react
        },
        {
          test: /\.css$/,
          use: [
            mode !== "development"
              ? MiniCssExtractPlugin.loader
              : "style-loader",
            "css-loader"
          ]
        }
      ]
    },
    plugins: [
      new PurgecssPlugin({
        paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) // 不匹配目录,只匹配文件
      }),
      mode !== "development" &&
        new MiniCssExtractPlugin({
          filename: "css/[name].css"
        }),
      new HtmlWebpackPlugin({
        template: "./src/template.html",
        filename: "index.html"
      })
    ].filter(Boolean)
  };
};

.babelrc配置文件

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ]
}

1.删除无用的Css样式

先来看编写的代码

import './style.css'
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
hello
,document.getElementById('root'));
body{
    background: red
}
.class1{
    background: red
}

这里的.class1显然是无用的,我们可以搜索src目录下的文件,删除无用的样式

const glob = require('glob');
const PurgecssPlugin = require('purgecss-webpack-plugin');

// 需要配合mini-css-extract-plugin插件
mode !== "development" && new PurgecssPlugin({
    paths: glob.sync(`${path.join(__dirname, "src")}/**/*`, { nodir: true }) // 不匹配目录,只匹配文件
}),

2.图片压缩插件

将打包后的图片进行优化

npm install image-webpack-loader --save-dev

在file-loader之前使用压缩图片插件

loader: "image-webpack-loader",
options: {
  mozjpeg: {
    progressive: true,
    quality: 65
  },
  // optipng.enabled: false will disable optipng
  optipng: {
    enabled: false,
  },
  pngquant: {
    quality: [0.90, 0.95],
    speed: 4
  },
  gifsicle: {
    interlaced: false,
  },
  // the webp option will enable WEBP
  webp: {
    quality: 75
  }
}

可以发现图片大小是有了明显的变化

3.CDN加载文件

我们希望通过cdn的方式引入资源

const AddAssetHtmlCdnPlugin = require('add-asset-html-cdn-webpack-plugin')
new AddAssetHtmlCdnPlugin(true,{
    'jquery':'https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js'
})

但是在代码中还希望引入jquery来获得提示

import $ from 'jquery'
console.log('$',$)

但是打包时依然会将jquery进行打包

externals:{
  'jquery':'$'
}

在配置文件中标注jquery是外部的,这样打包时就不会将jquery进行打包了

4.Tree-shaking && Scope-Hoisting

4.1 Tree-shaking

顾名思义就是将没用的内容摇晃掉,来看下面代码

main.js

import { minus } from "./calc";
console.log(minus(1,1));

calc.js

import {test} from './test';
export const sum = (a, b) => {
  return a + b + 'sum';
};
export const minus = (a, b) => {
  return a - b + 'minus';
};

test.js

export const test = ()=>{
    console.log('hello')
}
console.log(test());

观察上述代码其实我们主要使用minus方法,test.js代码是有副作用的!

默认mode:production时,会自动tree-shaking,但是打包后'hello'依然会被打印出来,这时候我们需要配置不使用副作用

package.json中配置

"sideEffects":false,

如果这样设置,默认就不会导入css文件啦,因为我们引入css也是通过import './style.css'

这里重点就来了,tree-shaking主要针对es6模块,我们可以使用require语法导入css,但是这样用起来有点格格不入,所以我们可以配置css文件不是副作用

"sideEffects":[
    "**/*.css"
]

在开发环境下默认tree-shaking不会生效,可以配置标识提示

optimization:{
  usedExports:true 
}

4.2 Scope Hoisting

作用域提升,可以减少代码体积,节约内存

let a = 1;
let b = 2;
let c = 3;
let d = a+b+c
export default d;
// 引入d
import d from './d';
console.log(d)

最终打包后的结果会变成 console.log(6)

  • 代码量明显减少
  • 减少多个函数后内存占用也将减少

5.DllPlugin && DllReferencePlugin

每次构建时第三方模块都需要重新构建,这个性能消耗比较大,我们可以先把第三方库打包成动态链接库,以后构建时只需要查找构建好的库就好了,这样可以大大节约构建时间

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(

hello

,document.getElementById('root'))

5.1 DllPlugin

这里我们可以先将reactreact-dom单独进行打包

单独打包创建webpack.dll.js

const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
    entry:['react','react-dom'],
    mode:'production',
    output:{
        filename:'react.dll.js',
        path:path.resolve(__dirname,'dll'),
        library:'react'
    },
    plugins:[
        new DllPlugin({
            name:'react',
            path:path.resolve(__dirname,'dll/manifest.json')
        })
    ]
}

执行"webpack --config webpack.dll.js命令,可以看到dll目录下创建了两个文件分别是manifest.json,react.dll.js

关系是这个酱紫的,到时候我们会通过manifest.json找到react.dll.js文件中的模块进行加载

5.2 DllReferencePlugin

在我们的项目中可以引用刚才打包好的动态链接库

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
// 构建时会引用动态链接库的内容
new DllReferencePlugin({
  manifest:path.resolve(__dirname,'dll/manifest.json')
}),
// 需要手动引入react.dll.js
new AddAssetHtmlWebpackPlugin(
  { filepath: path.resolve(__dirname,'dll/react.dll.js') }
)

使用DllPlugin可以大幅度提高构建速度

6.动态加载

实现点击后动态加载文件

let btn = document.createElement('button');
btn.innerHTML = '点击加载视频';
btn.addEventListener('click',()=>{
    import('./video').then(res=>{
        console.log(res.default);
    });
});
document.body.appendChild(btn);

给动态引入的文件增加名字

output:{
  chunkFilename:'[name].min.js'
}
import(/* webpackChunkName: "video" */ './video').then(res=>{
    console.log(res.default);
})

这样打包后的结果最终的文件就是 video.min.js

7.打包文件分析工具

安装webpack-bundle-analyzer插件

npm install --save-dev webpack-bundle-analyzer

使用插件

const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
mode !== "development" && new BundleAnalyzerPlugin()

默认就会展现当前应用的分析图表

8.SplitChunks

我们在来看下SplitChunks这个配置,他可以在编译时抽离第三方模块、公共模块

将项目配置成多入口文件

entry:{
  a:'./src/a.js',
  b:'./src/b.js'
}

我们让a,b两个模块同时引用jquery,别忘了去掉之前的externals配置

image

配置SplitChunks插件

默认配置在此,我一个个描述下含义

splitChunks: {
  chunks: 'async', // 分割异步模块
  minSize: 30000, // 分割的文件最小大小
  maxSize: 0, 
  minChunks: 1, // 引用次数
  maxAsyncRequests: 5, // 最大异步请求数
  maxInitialRequests: 3, // 最大初始化请求数
  automaticNameDelimiter: '~', // 抽离的命名分隔符
  automaticNameMaxLength: 30, // 名字最大长度
  name: true,
  cacheGroups: { // 缓存组
    vendors: { // 先抽离第三方
      test: /[\\/]node_modules[\\/]/,
      priority: -10
    },
    default: { 
      minChunks: 2,
      priority: -20, // 优先级
      reuseExistingChunk: true
    }
  }
}

我们将async改为initial

image

我们在为每个文件动态导入lodash库,并且改成async

import('lodash')
image

为每个入口引入c.js,并且改造配置文件

splitChunks: {
  chunks: 'all',
  name: true,
  cacheGroups: {
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      priority: -10
    },
    default: {
      minSize:1, // 不是第三方模块,被引入两次也会被抽离
      minChunks: 2,
      priority: -20,
    }
  }
}
image

这样再反过来看chunks的参数是不是就了然于胸啦!

9.热更新

模块热替换(HMR - Hot Module Replacement)是 webpack 提供的最有用的功能之一。它允许在运行时替换,添加,删除各种模块,而无需进行完全刷新重新加载整个页面

  • 保留在完全重新加载页面时丢失的应用程序的状态
  • 只更新改变的内容,以节省开发时间
  • 调整样式更加快速,几乎等同于就在浏览器调试器中更改样式

启用热更新,默认样式可以支持热更新,如果不支持热更新则采用强制刷新

devServer:{
  hot:true
}
new webpack.NamedModulesPlugin(),

js支持热更新

import sum from './sum';
console.log(sum(1,2));
if(module.hot){ // 如果支持热更新
    module.hot.accept(); // 当入口文件变化后重新执行当前入口文件
}

10.IgnorePlugin

忽略 importrequire语法

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

11.费时分析

可以计算每一步执行的运行速度

const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
const smw = new SpeedMeasureWebpackPlugin();
  module.exports =smw.wrap({
});

12.noParse

module.noParse,对类似jq这类依赖库,内部不会引用其他库,我们在打包的时候就没有必要去解析,这样能够增加打包速率

noParse:/jquery/

13.resolve

resolve: {
  extensions: [".js",".jsx",".json",".css"],
  alias:{},
  modules:['node_modules']
},

14.include/exclude

在使用loader时,可以指定哪些文件不通过loader,或者指定哪些文件通过loader

{
  test: /\.js$/,
  use: "babel-loader",
  // include:path.resolve(__dirname,'src'),
  exclude:/node_modules/
},

15.happypack

多线程打包,我们可以将不同的逻辑交给不同的线程来处理

npm install --save-dev happypack

使用插件

const HappyPack = require('happypack');
rules:[
  {
    test: /\.js$/,
    use: 'happypack/loader?id=jsx'
  },

  {
    test: /\.less$/,
    use: 'happypack/loader?id=styles'
  },
]
new HappyPack({
  id: 'jsx',
  threads: 4,
  loaders: [ 'babel-loader' ]
}),

new HappyPack({
  id: 'styles',
  threads: 2,
  loaders: [ 'style-loader', 'css-loader', 'less-loader' ]
})

webpack.config.js

const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const glob = require("glob"); // 主要功能就是查找匹配的文件
// 主要的作用删除无意义的css,只能配合 mini-css-extract-plugin
const PurgeCssWebpackPlugin = require("purgecss-webpack-plugin");
const AddCdnPlguin = require("add-asset-html-cdn-webpack-plugin");
const DLLReferencePlugin = require("webpack").DllReferencePlugin;
const AddAssetHtmlPlugin = require("add-asset-html-webpack-plugin");
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');
// 写个循环
const smw = new SpeedMeasureWebpackPlugin();

// ts-loader @babel/preset-react
module.exports = env => {
  return smw.wrap({
    mode: env,
    // entry 有三种写法 字符串  数组  对象
    entry: {
      a: "./src/a.js",
      b: "./src/b.js"
    },
    // 在生产环境下 将第三方模块进行抽离

    optimization: {
      splitChunks: {
          // inital 只操作同步的 all 所有的 async 异步的
        chunks: "all", // 默认支持异步的代码分割 import()
        minSize: 30000, // 文件超过30k 我就会抽离他
        maxSize: 0,
        minChunks: 1, // 最少模块引用一次才抽离
        maxAsyncRequests: 5, // 最多5个请求
        maxInitialRequests: 3, // 最多首屏加载3个请求
        automaticNameDelimiter: "~", // xxx~a~b
        automaticNameMaxLength: 30, // 最长名字打大小
        name: true,
        cacheGroups: { // 缓存组
        react: {
            test: /[\\/]node_modules[\\/](jquery)|(lodash)/,
            priority: -2
        },
          react: {
            test: /[\\/]node_modules[\\/](react)|(react-dom)/,
            priority: -2
          },
          vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
          },
          commons: { // common~a~b
            minChunks: 2,
            minSize:1, // 如果公共代码 多一个字节就抽离
            priority: -20,
            reuseExistingChunk: true
          }
        }
      }
    },

    // entry:'./src/index.js', // 入口
    output: {
      // 出口
      filename: "[name].js", // 同步打包的名字
    //   chunkFilename: "[name].min.js",
      path: path.resolve(__dirname, "dist") // 出口必须是绝对路径 都用绝对
    },
    // externals:{
    //     'jquery':'$' // 不去打包代码中的jquery
    // },
    module: {
      rules: [
        {
          test: /\.js/,
          use: {
            loader: "babel-loader",
            options: {
              // .babelrc
              presets: ["@babel/preset-env", "@babel/preset-react"]
            }
          }
        },
        {
          // 降低分辨率 清晰度
          test: /\.(jpe?g|png|gif)/,
          use: [
            {
              loader: "file-loader"
            },
            env !== "development" && {
              // 可以在使用file-loader之前 对图片进行压缩
              loader: "image-webpack-loader",
              options: {
                mozjpeg: {
                  progressive: true,
                  quality: 65
                },
                // optipng.enabled: false will disable optipng
                optipng: {
                  enabled: false
                },
                pngquant: {
                  quality: [0.65, 0.9],
                  speed: 4
                },
                gifsicle: {
                  interlaced: false
                },
                // the webp option will enable WEBP
                webp: {
                  quality: 75
                }
              }
            }
          ].filter(Boolean)
        },
        {
          test: /\.css$/,
          use: [
            // link
            env !== "development"
              ? MiniCssExtractPlugin.loader
              : "style-loader",
            "css-loader"
          ]
        }
      ]
    },
    // optimization:{
    //     usedExports:true // 使用了哪个模块你和我说一下
    // },
    plugins: [
      env !== "development" && new MiniCssExtractPlugin(),
      new HtmlWebpackPlugin({
        template: "./src/index.html",
        filename: "index.html"
        // chunks:['a']
      }),
      // vue-cli 怎么配置 mpa  cli官方 pages
      // new HtmlWebpackPlugin({
      //     template:'./src/index.html',
      //     filename:'login.html',
      //     chunksSortMode:'manual', // 手动按照我的顺序来执行
      //     chunks:['b','a'] // 打包的顺序 按照我自己排列的
      // }),
      new PurgeCssWebpackPlugin({
        paths: glob.sync("./src/**/*", { nodir: true })
      }),
      // 打包的时候 会配置 cleanwebpackplugin
    //   new DLLReferencePlugin({
    //     manifest: path.resolve(__dirname, "dll/manifest.json")
    //   }),
    //   new AddAssetHtmlPlugin({
    //     filepath: path.resolve(__dirname, "./dll/react.dll.js")
    //   }),
      env !== "development" && new BundleAnalyzerPlugin()
      // 当前这个dll.js没有在页面中引用
      // 添加cdn的插件
      // 分割代码
      // new AddCdnPlguin(true,{
      //     'jquery':'https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js'
      // })
    ].filter(Boolean)
  });
};

// 一般我们会用到开发 环境上  dll插件


package.json

{
  "name": "base2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --env=development",
    "dev:build": "webpack --env=development",
    "build": "webpack --env=production",
    "dll": "webpack --config webpack.dll.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "sideEffects": [
    "**/*.css"
  ],
  "devDependencies": {
    "@babel/core": "^7.6.0",
    "@babel/preset-env": "^7.6.0",
    "@babel/preset-react": "^7.0.0",
    "add-asset-html-cdn-webpack-plugin": "^1.0.0",
    "add-asset-html-webpack-plugin": "^3.1.3",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.2.0",
    "file-loader": "^4.2.0",
    "glob": "^7.1.4",
    "html-webpack-plugin": "^3.2.0",
    "image-webpack-loader": "^6.0.0",
    "mini-css-extract-plugin": "^0.8.0",
    "purgecss-webpack-plugin": "^1.6.0",
    "style-loader": "^1.0.0",
    "webpack": "^4.40.2",
    "webpack-bundle-analyzer": "^3.5.0",
    "webpack-cli": "^3.3.9",
    "webpack-dev-server": "^3.8.1"
  },
  "dependencies": {
    "jquery": "^3.4.1",
    "lodash": "^4.17.15",
    "react": "^16.9.0",
    "react-dom": "^16.9.0",
    "speed-measure-webpack-plugin": "^1.3.1"
  }
}


你可能感兴趣的:(webpack优化)