• webpack系列3:各种优化


    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(<div>hello</div>,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(<h1>hello</h1>,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配置

    配置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

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

    import('lodash')

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

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

    这样再反过来看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' ]
    })
  • 相关阅读:
    spring boot SpringApplication.run 执行过程
    算法 计算四则运算字符串结果
    算法 RingBuffer
    java BigDecimal 四舍五入
    算法 常用函数和近似
    java 多线程执行
    Java 三个线程依次输出ABC
    Java interrupt 中断
    java 垃圾收集器与内存分配策略
    软件项目与软件产品的区别
  • 原文地址:https://www.cnblogs.com/jianxian/p/12760402.html
Copyright © 2020-2023  润新知