likes
comments
collection
share

webpack进阶之路

作者站长头像
站长
· 阅读数 51

Webpack进阶之路

前言

谨此一文记录webpack5学习之路。 本文主供自己学习和日后复习所用,参考借鉴 构建webpack5知识体系【近万字总结】所写

Webpack基础篇

1、什么是webpack

本质上讲:webpack是一个现代JavaScript应用程序的静态打包工具,当webpack处理应用程序的时候,它会递归的形成一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或者多个bundle。

webpack进阶之路

简单来说: webpack就是一个静态资源打包工具,负责将项目中依赖的各个模块,打包成一个或多个bundle

2、webpack的优点

  • 拥有依赖管理、动态打包、代码分离、按需加载、代码压缩、静态资源压缩、缓存等配置
  • webpack扩展性强,插件机制完善,开发者可自定义插件(plugin)、loader
  • webpack社区庞大,更新速度快,轮子丰富

3、基础应用

3.1、entry(入口文件)

入口是依赖关系图的开始,从入口开始寻找依赖,打包构建,webpack允许一个或者多个入口配置:

单入口配置

module.exports = {
    entry:"indexjs"
}

多入口配置

module.exports = {
    entry :{
        index: path.join(srcPath,"index.js"),
        other:path.join(scrPath,"other.js")
    }
}

entry更多配置详情参考webpack文档

3.2、output(出口文件)

输出用于配置webpack构建打包的出口,如打包的位置,打包的文件名:

单个出口起点

对于单出口起点,filename回事一个静态的名称

module.exports = {
    output:{
        path: path.resolve(__dirname,"dist"),
        filename:"bundle.js",
    }
}

多个出口起点

对于多个出口起点,代码拆分(code splitting)、或各种插件(plugin) 创建多个bundle,应该使用一下替换方式,来赋予每个bundle一个唯一的名称

使用出口名称
module.exports = {
    output:{
        filename:"[name].bundle.js"
    }
}
使用内部chunk id
module.exports = {
     output:{
         filename:"[hash].bundle.js"
     }
}
使用chunkhash
module.exports = {
    output:{
        filename:"[name].[chunkhash].bundle.js"
    }
}
使用contenthash
module.exports = {
    output:{
        filename:"[name].[contenthash:8],bundle.js"
    }
}

TODO webpack三种hash的区别,webpack缓存相关 output更多配置详情参考webpack文档

3.3、loader(编译转换)

webpack自带JavaScript和JSON文件的打包构建能力,无需格外配置,对于其他类型的文件如CSS文件等,则需要安装loader处理; loader让webpack能够去处理其他类型的文件,并将他们转换为有效模块,以供应用程序使用,以及被添加到依赖图中

module.exports = {
    module:{
        rules:[
            {
                test:/\.css$/i,
                use:["style-loader","css-loader"]
             }
         ]
       }
 }

常用的loader

  • style-loader
  • css-loader
  • less-loader、sass-loader
  • thread-loader

module更多配置详情参考webpack文档

具体loader配置看loader文档

3.4、plugin(插件)

插件则是用于扩展webpack的能力

module.exports = {
    plugin:[new HtmlWebpackPlugin({
        template:'./src/index.html'
    })]
}

具体plugin配置看具体plugin文档

plugin更多配置详情参考webpack文档

3.5、mode模式

webpack提供了模式选择,包括开发模式、生产模式、空模式,并对不同模式做出了对应的内置优化。可以通过配置模式让项目更优。 配置如下:

module.exprots = {
    mode:'development'
}

3.6、resolve(解析)

resolve用于配置模块如何解析,常用配置如下:

  • alias:配置别名,简化模块引入
  • extensions:在引入模块时可不带后缀
  • symlinks:用于配置 npm link 是否生效,禁用可提升编译速度
module.exports = {
    resolve:{
        extensions:['.js','.jsx','.ts','.json','.d.ts'],
        alias:{
            '@':"./src"
         },
         symlinks:false
    }
}    

resolve更多配置详情参考webpack文档

3.7、optimization(优化)

optimization用于自定义webpack的内置优化配置,一般用于生产模式(mode:production) 提升性能,常配置项如下:

  • minimize:是否压缩budle
  • minimizer:是否配置压缩工具,如TerserPlugin、OptimizeCssAssetsPlugin
  • splitChunk:拆分bundle
  • runtimeChunk:是否需要将所有生成chunk之间共享运的运行时文件拆分出来
module.exports = {
    optimization:{
        minimizer:[
            new CssMinimizerPlugin()
         ],
         splitChunks:{
             chunks:'all',
             //重复打包问题
             cacheGroup:{ 
                 vendors:{//node_modules里面的代码
                     test:/[\\/]node_modules[\\/]/,
                     chunks:"all",
                     name:"vendors",//chunks name
                     priority:10, //优先级
                     enforce:true
                 }
            }
         }
     }
 }

完整代码

简单示例

 module.exports = {
     mode:'development'
     entry:"index.js",
     output:{
         filename:[contenthash:8].bundle.js
         path:path.resolve(__dirname,"dist")
     },
     module:{
         rule:[
                 {
                    test:/\.css$/i,
                    use:["style-loader","css-loader"]
                  }
         ]
     },
     plugin:[new HtmlWebpackPlugin({
        template:'./src/index.html'
    })],
    resolve:{
       
        extensions:[".js",".ts",".d.ts",".jsx"],
        symlinks:false
        alias:{ '@':"./src" },
    },
     optimization:{
        minimizer:[
            new CssMinimizerPlugin()
         ],
         splitChunks:{
             chunks:'all',
             //重复打包问题
             cacheGroup:{ 
                 vendors:{//node_modules里面的代码
                     test:/[\\/]node_modules[\\/]/,
                     chunks:"all",
                     name:"vendors",//chunks name
                     priority:10, //优先级
                     enforce:true
                 }
            }
         }
     }
}    

总结

本片主要学习webpack的基本配置模块,具体配置参照官方文档。

Webpack 进阶篇之一实践基础配置

1、实现目标

  • 分离开发环境、生产配置环境
  • 模块化开发
  • sourceMap定位警告和错误
  • 动态引入bundle.js的HTML5文件
  • 实时编译
  • 封装编译、打包命令

2、基本配置

2.1、新建项目目录

//新建webpack-init文件夹
mkdir webpack-init
//进入webpack-init目录
cd ./webpack-init
//初始化项目
npm init -y

新建config文件夹
mkdir config

//新建 webpack.common.js  webpack.dev.js  webpack.prod.js  

2.2、 下载插件

npm install webpack webpack-cli --save-dev
npm i webpack-merge -D

2.3、构建配置文件内容

webpack.common.js

webpack的基本配置,开发和生产环境下均可生效

//webpack.common.js  
module.exports = {}

webpack.dev.js

webpack开发配置,仅在开发环境下生效

//webpack.dev.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common,{})

webpack.prod.js

webpack生产配置,仅在生产环境下生效

//webpack.prod.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common,{})

2.4、入口文件的配置

修改 webpack.commom.js:

module.exports = {
    //入口
    entry:{
        index:'./src/index.js'
     }
}

2.5、出口文件的配置

output属性输出他创建的bundle的位置和命名

生产环境output需要通过contenthash值来区分版本和变动,可达到清除缓存的效果,而本地开发环境为了构建效率,则不引入contenthash

修改webpack.dev.js:

//webpack.dev.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common')
const {resolveApp} = require('./path') //封装的路径工具函数
module.exports = merge(common,{
    output:{
        filename:'[name].bundle.js',
        path:resolveApp('dist'), 
        clean:true //编译前清除目录
})

修改webpack.prod.js

//webpack.dev.js

const { merge } = require('webpack-merge')
const common = require('./webpack.common')
const {resolveApp} = require('./path') //封装的路径工具函数
module.exports = merge(common,{
    output:{
        filename:'[name][contenthash:8].bundle.js', // 【只有这里和开发环境不一样】
        path:resolveApp('dist'), 
        clean:true //编译前清除目录
})

path.js文件,封装路径工具函数

const fs = require('fs')
const path = require('path')
const appDirectory = fs.realpathSync(process.cwd()); 
const resolveApp = relativePath =>path.resolve(appDirectory, relativePath);
module.exports = { resolveApp }

2.6、模式选择

module.exports = merge(common, { 
    // 生产模式
    mode: 'production',
}) 


module.exports = merge(common, {
    // 生产模式 
    mode: 'development',
 })

2.7、 source-map配置

source-map用于快速追踪定位error和warning,将编译后的代码映射回原始代码,提高开发效率

修改webpack.dev.js

module.exports = merge(common, {
    // 生产模式 
    mode: 'development',
    devtool:'eval-cheap-module-source-map',启 source map,编译调试
 })

打包测试

  • 开发环境:npx webpack --config config/webpack.dev.js

webpack进阶之路

  • 生产环境 :npx webpack --config config/webpack.prod.js

webpack进阶之路

2.8 HtmlWepackPlugin插件

引入 HtmlWebpackPlugin 插件,生成一个 HTML5 文件,其中包括使用 script 标签的 body 中的所有 webpack 包;

安装: npm install --save-dev html-webpack-plugin

修改webpack.common.js

module.exports = {
    plugins:[
        new HtmlWebpackPlugin({
        title:"webpack",
        })
   ]
}   

2.9、DevServer

每次编译代码时,手动运行npx webpack --config config/webpack.prod.js会显得麻烦,webpack-dev-server帮助我们在代码发生变化后自动编译代码。

webpack-dev-server 提供了一个基本的 web server,并且具有实时重新加载功能。 webpack-dev-server 默认配置 conpress: true,为每个静态文件开启gizp commpress

安装: npm install --save-dev webpack-dev-server

修改webpack.dev.js

module.exports = merge(common, {
    devServer:{
        //告诉服务器从哪里提供内容,只有在你想要提供静态资源文件时才需要
        static: { 
            directory: resolveApp('dist'), 
        },
        port:8880,
        hot:true
   }
})

webpack进阶之路 输入命令启动: npx webpack serve --open --config config/webpack.dev.js

2.10、区分环境变量变量,执行命令

通过cross-env配置环境变量,生产环境和开发环境

安装npm install --save-dev cross-env

修改package.json

{ 
    "scripts": { 
        "dev": "cross-env NODE_ENV=development webpack serve --open --config config/webpack.dev.js",
            "build": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js"
        }, 
}

webpack进阶篇之二 -实践进阶配置

本篇将继续完善配置,在上述的基础上,使用webpack搭建一个VUE3-TS的项目 将支持以下功能:

  • 加载图片
  • 加载字体
  • 加载CSS/LESS
  • 使用Postcss,并自动为CSS规则添加前缀,解析最新的CSS语法
  • 引入css-modlues解决命名冲突的问题
  • 使用Vue3
  • 使用Typescript

1、加载图片(image)

webpack4中,需要下载并配置特定的loaderurl-loaderfile-laoder)来加载图片,本篇主要学习webpack5内容,所以不使用webpack4配置

webpack5,内置了特定的用于资源模块Asset-Module,它允许使用资源文件(字体、图标等)而无需配置loader

修改webpack.common.js

module.export = {
    module:{
        rules:[
            {
                test:/\.(png|jpg|svg|jpeg|gif)$/i,
                include:[
                    resloveApp("src").
                ],
                type:"asset/resource"
                 generator: {
                filename: "img/[hash][ext][query]", // 局部指定输出位置
            },
             }
         ]
     }
 }

在实际开发过程中,推荐将大图片上传至 CDN,提高加载速度。

2、加载字体

同样使用Asset Module接收字体文件

修改webpack.common.js

module.exports = {
    module:{
        rules:[
            
             {
               test: /.(woff|woff2|eot|ttf|otf)$/i,
               include: [resolveApp("src")],
               type: "asset/resource",
               generator: {
                   filename: "iconfont/[hash][ext][query]", // 局部指定输出位置
               },
           },
}

TODO 还有些bug 后面在填坑

在实际开发过程中,推荐将字体文件压缩上传至 CDN,提高加载速度。如配置字体的文字是固定的,还可以针对固定的文字生成字体文件,可以大幅缩小字体文件体积。

3、加载css、less

安装相关配置

//less相关
npm install --save-dev less less-loader

//css相关
 npm install --save-dev style-loader css-loader

修改webpack.common.js

module.exports = {
     module: {
        rules: [ 
            {
                test: /\.less$/i,
                include: resolveApp("src),
                use: ["style-loader", "css-loader", "less-loader"],
            },
        ]
     }
 }

4、 使用PostCss

postcss是一个JavaScript工具和插件转换css的工具

  • 可以自动为css规则添加前缀
  • 将最新的css语法转换成大多数浏览器都能理解的语法
  • css-modules解决全局命冲突

postcss-loader使用postcss处理CSS的loader

安装Postcss相关依赖 npm install --save-dev postcss-loader postcss postcss-preset-env 修改webpack.common.js:

  {
      test:/\.less$/i,
      include:resolveApp("src"),
      use:[
          'style-loader',
          {
              loader:"css-loader",
              options:{
                  modules:true,
                  importLoaders:2,
               },
           },
           {
               loader:'postcss-loader",
               options:{
                   postcssOptions:{
                       plugins:[
                           [
                               // postcss-preset-env 包含 autoprefixer 
                              'postcss-preset-env',
                            ]
                         ]
                    }
                }
             },
             "less-loader",
         ],   
  }

具体配置参考PostCSS配置文件

5、vue3 + TypeScript

5.1、TypeScript配置

安装Typescript

npm install typescript -D

添加ts解析工具

ts文件并不是webpack能够直接解析的,所以我们需要使用ts-loader来专门处理 npm i ts-loader -D

修改webpack.common.js

module.exports = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: "ts-loader",
      },
    ],
  },
};

babel

使用 ts-loader 处理 ts 文件,但是这会存在一个问题:该 loader 是把 typeScript 转换成 javaScript , 只负责新语法的转换,新增的 API 不会自动添加 polyfill

了解决这个问题,我们还是要祭出 babelbabel 是一个工具链,主要用于旧浏览器或者环境中将 ECMAScript 2015+ 代码转换为向后兼容版本的 javaScript ,包括语法转换、源代码转换等。关注社区的小伙伴可能知道,从 babel7 开始 babel 已经支持 ts 的编译,所以 ts-loader 可以弃用了

安装依赖 npm i babel-loader @babel/core @babel/preset-env @babel/preset-typescript -D

修改webpack.common.js

module.epxorts = { 
    module: { 
        rules: [ 
            { 
                test: /\.(t|j)s$/, 
                exclude: /node_modules/, 
                use: { 
                    loader: "babel-loader", 
                    options: { cacheDirectory: true, 
                     },
                }, 
             }
          ]
      }, 
 };

在根目录添加babel.config.js文件

module.exports = {
  presets: [
    "@babel/preset-env",
    [
      "@babel/preset-typescript",
      {
        allExtensions: true, //支持所有文件扩展名
      },
    ],
  ],
};

5.2、vue3配置

安装vue3

//vue3
npm install vue

此时webpack不能认识.vue文件,它需要我们提供一个loader对其进行处理,这个loader在官方文档中提到

webpack进阶之路 其中 @vitejs/plugin-vue 这个是 vite 才需要的,因此我们只需要 vue-loader@next@vue/compiler-sfc

安装相关依赖 npm install vue-loader@next @vue/compiler-sfc -D 修改webpack.common.js配置

const {VueLoaderPlugin} = require("vue-loader")

modules.exports = {
    module:{
        rules:[
            {
                test:/\.vue$/i,
                use:"vue-loader"
            }
        ]
    },  
    plugins:[
        new VueLoaderPlugin()
    ]
}   

webpack进阶之优化

本章将从优化开发体验,加快编译速度,减少打包体积,加快加载等对webpack项目进行优化

1、效率工具

1.1、编译进度条(不重要)

www.npmjs.com/package/pro…​ 安装 npm i -D progress-bar-webpack-plugin

webpack.common.js 配置方式如下:

const chalk = require('chalk')
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
module.exports = {
  plugins: [
    // 进度条
    new ProgressBarPlugin({
        format: `  :msg [:bar] ${chalk.green.bold(':percent')} (:elapsed s)`
      })
  ],
}

1.2、编译速度分析(不重要)

www.npmjs.com/package/spe…

安装 npm i -D speed-measure-webpack-plugin webpack.common.js 配置方式如下:

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
module.exports = smp.wrap({
  // ...webpack config...
})

1.3、 打包提及分析

www.npmjs.com/package/web…

安装 npm i -D webpack-bundle-analyzer webpack.prod.js 配置方式如下:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  plugins: [
    // 打包体积分析
    new BundleAnalyzerPlugin()
  ],
}

2、优化开发

2.1、 热更新

热更新指的是,在开发过程中,修改代码后,仅更新修改部分的内容,无需刷新整个页面;

webpack.dev.js 配置方式如下:

module.export = {
    devServer: {
        static: {
            directory: resolveApp("dist"),
        },
        port: 8880,
        hot: true,
      },
}

3、构建速度优化

3.1 cache

webpack5较于webpack4,新增了持久化缓存、改进缓存算法的优化,webpack5新特性可查看参考资料

通过配置webpack 持久化缓存,来缓存生成的webpack模块和chunk,改善构建速度,可提速90%左右

修改webpack.common.js

module.exports = {
    cache:{
        type:"filesystem",//使用文件缓存
    
}

引入缓存后,首次构建速度将增加15%,二次构建事件将减少90%

3.2、减少loader、plugins

每个loader、plugin都有其启动时间,尽量减少的使用工具,将非必须的loader、plugin删除

3.3、 指定include

为 loader 指定 include,减少 loader 应用范围,仅应用于最少数量的必要模块。

例如:

module.exports = {
    rules: [
       {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        include: [
          resolveApp('src'),
        ],
        type: 'asset/resource',
      }
    ]
}

3.4、资源管理

使用 webpack 资源模块(asset module) 代替旧的 assets loader(如 file-loader/url-loader/raw-loader 等),减少 loader 配置数量。

module.exports = {
    rules: [
       {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        include: [
          resolveApp('src'),
        ],
        type: 'asset/resource',
      }
    ]
}

3.5、优化resolve配置

resolve用来配置 webpack 如何解析模块,可通过优化 resolve 配置来覆盖默认配置项,减少解析范围;

alias 别名

alias可以创建import或require的别名,用来简化模块引入

webpack.common.js 配置方式如下:

module.exports = {
    resolve:{
        alias:{
            "@":resolve("src")  
         }  
    }
 }
extensions 解析文件类型列表

extensions表示需要解析的文件类型列表。

根据项目的文件类型,定义extensions,以覆盖webpack默认的extensions,加快解析速度。

webpackwebpack 的解析顺序是从左到右,因此要将使用频率高的文件类型放在左侧,如下我将 .vue 放在最左侧 修改webpack.common.js配置

module.exports = {
    resolve: {
         extensions: ['.tsx', '.ts', '.js'],
    }
}

modules

modules 表示 webpack 解析模块时需要解析的目录;

指定目录可缩小 webpack 解析范围,加快构建速度

webpack.common.js配置

module.exports = {
  resolve{
    modules: [
      'node_modules',
       paths.appSrc,
    ]
  }
}
symlinks

如果项目不使用 symlinks(例如 npm link 或者 yarn link),可以设置 resolve.symlinks: false,减少解析工作量。 webpack.common.js配置

module.exports = {
    resolve: {
        symlinks: false,
    },
}

3.6、 多线程(thread-loader)

通过 thread-loader将耗时的 loader 放在一个独立的 worker 池中运行,加快 loader 构建速度

安装npm i -D thread-loader

配置


 {
        test: /.js$/,
        include: path.resolve('src'),
        use: [
          'thread-loader',
          // your expensive loader (e.g babel-loader)
        ],
      },

3.7 区分环境

切忌在开发环境使用生产环境才会用到的工具,如在开发环境下,应该排除 [fullhash]/[chunkhash]/[contenthash] 等工具。

在生产环境,应该避免使用开发环境才会用到的工具,如 webpack-dev-server 等插件;

3.8、devtool

不同的 devtool 设置,会导致性能差异。在多数情况下,最佳选择是 eval-cheap-module-source-map;​

webpack.dev.js配置如下:

export.module = {
    devtool: 'eval-cheap-module-source-map',
}

3.9、输出结果不携带路径信息

默认 webpack 会在输出的 bundle 中生成路径信息,将路径信息删除可小幅提升构建速度

module.exports = {
    output: {
        pathinfo: false,
      },
    };
}

3.10、 IgnorePlugin

IgnorePlugin在构建模块时候直接剔除那些需要被排除的模块,常用于国家化和moment

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

3.11、DllPugin

核心思想是将项目依赖的框架等模块单独构建打包,与普通构建流程区分开。

output: {
    filename: '[name].dll.js',
    // 输出的文件都放到 dist 目录下
    path: distPath,
    library: '_dll_[name]',
  },
   
  plugins: [
    // 接入 DllPlugin
    new DllPlugin({
      // 动态链接库的全局变量名称,需要和 output.library 中保持一致
      // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
      // 例如 react.manifest.json 中就有 "name": "_dll_react"
      name: '_dll_[name]',
      // 描述动态链接库的 manifest.json 文件输出时的文件名称
      path: path.join(distPath, '[name].manifest.json'),
    }),
  ],

3.12、Externals

Webpack 配置中的 externals 和 DllPlugin 解决的是同一类问题:将依赖的框架等模块从构建过程中移除。

他们区别在于:

  • 在 Webpack 的配置方面,externals 更简单,而 DllPlugin 需要独立的配置文件。
  • DllPlugin 包含了依赖包的独立构建流程,而 externals 配置中不包含依赖框架的生成方式,通常使用已传入 CDN 的依赖包。
  • externals 配置的依赖包需要单独指定依赖模块的加载方式:全局对象、CommonJS、AMD 等。
  • 在引用依赖包的子模块时,DllPlugin 无须更改,而 externals 则会将子模块打入项目包中。
// 引入cdn  jq为例
<script
  src="https://code.jquery.com/jquery-3.1.0.js"
  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
  crossorigin="anonymous"
></script>
//vue 
<script
    crossorigin="anonymous" 
    integrity="sha512-9eYPYYqSLRRJlQVcobBpNgDNq7ui/VtXRO/abRajYVXlxLFnV6sBNGfro0+/Us2pqE8DLC2ymO5XT4LIyJZbvQ==" 
    src="https://lib.baomitu.com/vue/3.2.31/vue.cjs.prod.min.js"></script>


// webpack配置
module.exports = {
  //...
  externals: {
    jquery: 'jQuery',
    vue:'Vue'
  },
};

// 页面
import $ from 'jquery';
$('.my-element').animate(/* ... */);

import {Vue} from "vue"
new Vue({})

4.减少打包体积

4.1、代码压缩

4.1.1、js压缩

使用TerserWebpackPlugin来压缩 JavaScript

Webpack5自带最新的terser-webpack-plugin,无需手动安装

terser-webpack-plugin 默认开启了 parallel: true 配置,并发运行的默认数量: os.cpus().length - 1 ,本文配置的parallel数量为4,使用多进程并发运行压缩以提高构建速度;

修改webpack.prod.js

cosnt TerserPlugin = require('terser-webpack-plugin')
module.exports = {
    optimization:{
        minimizer:{
            new TerserPlugin({
                parallel:4,
                terserOptions:{
                    parse:{
                        ecma:8
                    },
                    compress:{
                        ecma:5,
                        warnings:false,
                        comparisons:false,
                        inline:2
                   },
                   mangle: { safari10: true, },
                   output: { 
                        ecma: 5, 
                        comments: false, 
                        ascii_only: true,
                        
                   },
                }          
            })
        }
    }
}
4.1.2、CSS压缩

使用 CssMinimizerWebpackPlugin压缩 CSS 文件

安装:npm install -D css-minimizer-webpack-plugin

webpack.prod.js配置

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin({
          parallel: 4,
        }),
    ],
  }
}


4.2代码分离

代码分离能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,可以缩短页面加载时间;

4.2.1、抽离重复代码

SplitChunksPlugin插件开箱即用,可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk;

webpack 将根据以下条件自动拆分 chunks:

  • 新的chunk可以被共享,或者模块来自node_modules文件夹,
  • 新的chunk体积大于20kb(在进行min+gz之前的体积)
  • 按需加载chunks,并行请求的最大数量小于或者等于30
  • 当加载初始化页面时,并发请求的最大数量小于或者等于30;

注意:切记不要为 cacheGroups 定义固定的 name,因为 cacheGroups.name 指定字符串或始终返回相同字符串的函数时,会将所有常见模块和 vendor 合并为一个 chunk。这会导致更大的初始下载量并减慢页面加载速度;

webpack.prod.js

module.exports = {
  optimization: {
    splitChunks: {
      // include all types of chunks
      chunks: 'all',
      // 重复打包问题
      cacheGroups:{
        // node_modules里的代码
        // 第三方模块
        vendors:{
          test: /[\\/]node_modules[\\/]/,
          chunks: "all",
          // name: 'vendors', 一定不要定义固定的name
          priority: 10, // 优先级
          enforce: true 
        },
        // 公共的模块
      	common: {
          name: 'common', // chunk 名称
          priority: 0, // 优先级
          minSize: 0,  // 公共模块的大小限制
          minChunks: 2  // 公共模块最少复用过几次
       	}
      }
    }
  }
}
4.2.2 css文件分离

MiniCssExtractPlugin插件将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载;

安装:npm install -D mini-css-extract-plugin webpack.common.js 配置方式如下: 注意:MiniCssExtractPlugin.loader 要放在 style-loader 后面

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

const isEnvProduction = process.env.NODE_ENV === "production";
module.exports = {
  plugins: [new MiniCssExtractPlugin()],
  module: {
    rules: [
        {
        test: /\.module\.(scss|sass|less)$/,
        include: paths.appSrc,
        use: [
          'style-loader',
          isEnvProduction && MiniCssExtractPlugin.loader, // 仅生产环境
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  [
                    'postcss-preset-env',
                  ],
                ],
              },
            },
          },
          {
            loader: 'thread-loader',
            options: {
              workerParallelJobs: 2
            }
          },
          'less-loader',
        ]
      },
    ]
  },
};
4.2.3、最小化entry chunk

通过配置 optimization.runtimeChunk = true,为运行时代码创建一个额外的 chunk,减少 entry chunk 体积,提高性能 ; webpack.prod.js 配置方式如下:

module.exports = {
    optimization: {
        runtimeChunk: true,
      },
    };
}

4.3 Tree Shaking(摇树)

1个模块可能有多个⽅法,只要其中的某个方法使⽤到了,则整个⽂件都会被打到 bundle 里面去,tree shaking 就是只把⽤到的方法打入 bundle ,没⽤到的方法会在uglify阶段被擦除掉;

4.3.1、 js

JS Tree Shaking将 JavaScript 上下文中的未引用代码(Dead Code)移除,通过 package.json 的 "sideEffects" 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 "pure(纯正 ES2015 模块)",由此可以安全地删除文件中未使用的部分;

Dead Code一般具有以下几个特征:

  • 代码不会被执行,不可到达
  • 代码执行的结果不会被用到
  • 代码只会影响死变量(只写不读)

webpack5 sideEffects设置 通过 package.json 的 "sideEffects" 属性,来实现这种方式;

{
  "name": "your-project",
  "sideEffects": false
}

需注意的是,当代码有副作用时,需要将 sideEffects 改为提供一个数组,添加有副作用代码的文件路径:

{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js"]
}

对组件库引用的优化

webpack5 sideEffects 只能清除无副作用的引用,而有副作用的引用则只能通过优化引用方式来进行 Tree Shaking;

loadsh

类似 import { throttle } from 'lodash' 就属于有副作用的引用,会将整个 lodash 文件进行打包;

优化方式是使用 import { throttle } from 'lodash-es' 代替 import { throttle } from 'lodash', lodash-esLodash库导出为 ES模块,支持基于 ES modules 的 tree shaking,实现按需引入;

ant-design

ant-design默认支持基于 ES modules 的 tree shaking,对于 js 部分,直接引入 import { Button } from 'antd' 就会有按需加载的效果;

假如项目中仅引入少部分组件,import { Button } from 'antd' 也属于有副作用,webpack不能把其他组件进行tree-shaking。这时可以缩小引用范围,将引入方式修改为 import { Button } from 'antd/lib/button' 来进一步优化。

4.3.2、css

使用 purgecss-webpack-plugin对 CSS Tree Shaking。 安装 :npm i purgecss-webpack-plugin -D

因为打包时 CSS 默认放在 JS 文件内,因此要结合 webpack 分离 CSS 文件插件 mini-css-extract-plugin 一起使用,先将 CSS 文件分离,再进行 CSS Tree Shaking;

webpack.prod.js 配置方式如下:

const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const paths = require('paths')

module.exports = {
  plugins: [
    // 打包体积分析
    new BundleAnalyzerPlugin(),
    // 提取 CSS
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
    // CSS Tree Shaking
    new PurgeCSSPlugin({
      paths: glob.sync(`${resolveApp("src")}/**/*`,  { nodir: true }),
    }),
  ]
}
4.5.3 CDN

通过 CDN 来减小打包体积;

将大的静态资源上传至 CDN:

  • 字体:压缩并上传至CDN
  • 图片:压缩并上传至CDN

5 加快加载速度

5.1、按需加载

通过 webpack 提供的 import() 语法动态导入功能进行代码分离,通过按需加载,大大提升网页加载速度;

<template>
    <button@click = clickHandle>加载loadsh</button>
</tepmlate>
<script lang=:ts setup>

const clickHandle = ()=>{
    import("lodash")
 }   

</script>

5.2浏览器缓存

浏览器缓存,就是进入某个网站后,加载的静态资源被浏览器缓存,再次进入该网站后,将直接拉取缓存资源,加快加载速度。 webpack 支持根据资源内容,创建 hash id,当资源内容发生变化时,将会创建新的 hash id 配置 JS bundle hash,webpack.prod.js 配置方式如下

module.exports = {
  // 输出
  output: {
    // 仅在生产环境添加 hash
    filename: '[name].[contenthash].bundle.js'
  },
}

配置 CSS bundle hash,webpack.prod.js 配置方式如下:

module.exports = {
  plugins: [
    // 提取 CSS
    new MiniCssExtractPlugin({
      filename: "[hash].[name].css",
    }),
  ],
}

配置 optimization.moduleIds,让公共包 splitChunks 的 hash 不因为新的依赖而改变,减少非必要的 hash 变动,webpack.prod.js 配置方式如下:

module.exports = {
  optimization: {
    moduleIds: 'deterministic',
  }
}

通过配置 contenthash/hash,浏览器缓存了未改动的文件,仅重新加载有改动的文件,大大加快加载速度

5.3、使用CDN

上面讲过了

6、总结

  • 在小型项目中,添加过多的优化配置,作用不大。反而会因为额外的loader、plugin增加构建时间
  • 在加快构建时间方面。作用最大的配置时cache配置,可大大加快二次构建速度
  • 在减少打包体积方面,作用最大的时压缩代码,分离重复代码、tree-shaking,可大幅度减少打包体积
  • 在加快加速度方面,按需加载、浏览器缓存、CDN效果都很显著。

未完待续

转载自:https://juejin.cn/post/7071935716824973349
评论
请登录