从零学习webpack5(下)

259 阅读4分钟

相关阅读:webpack5基础配置
案例代码仓库:Github地址

简介

本篇文章我们从提升开发体验、提升webpack打包构建速度、减少代码体积、优化代码运行性能四个方面进行配置。

一、提升开发体验

1、使用Source Map

Source Map 是一种文件格式,用于将转换后的代码映射回原始源代码。它是前端开发中调试工具的一部分,能够帮助开发者在浏览器中更准确地定位到源代码中的错误和警告。通过使用 Source Map,开发者可以在调试过程中准确定位到源代码中的错误,而不是转换后的代码。这对于处理压缩过的 JavaScript、合并后的 CSS 或者预处理器编译后的样式表等场景非常有用,因为它们往往难以直接进行调试。这里建议devtool的值设置为eval-cheap-module-source-map,这样可以在发生错误时定位到对应行,打包运行速度也不会很慢,如果需要其他的,可前往官网->配置->Devtool查阅(注:为了防止代码泄露,因此不建议生产环境配置任何Source Map
开发环境配置

module.exports = {
    // 开启source-map
    devtool: 'eval-cheap-module-source-map'
}

二、提升webpack打包构建速度

1、使用oneOf

使用oneOf可以使资源文件一旦被某个loader处理了,就不继续遍历所有rules,加快打包速度,需要注意的是,不能使用两个加载器处理同一种文件,否则会报错
使用方法

module.exports = {
    // 模块配置
    module: {
        // 规则
        rules: [
            {
                // oneOf表示只使用一个加载器, 提高效率, 但是不能有两个加载器处理同一种类型的文件, 否则会报错
                oneOf: [
                    // 处理css,less文件
                    // TODO
                    // 处理图片,小于8kb的图片转为base64格式
                    {
                        test: /\.(png|jpe?g|gif|webp|svg|ico)$/,
                        type: 'asset',
                        parser: {
                            dataUrlCondition: {
                                maxSize: 8 * 1024
                            }
                        },
                        // 生成文件名
                        generator: {
                            filename: 'image/[name].[hash:8][ext]'
                        }
                    },
                    // 处理字体文件及其他媒体文件
                    // TODO
                    // 配置babel,将ES6+转换为ES5
                    // TODO
                    // 处理html中的资源引用
                    // TODO
                ]
            }
        ]
    }
}

2、使用include/exclude

排除或只检测某些文件,处理文件少,打包速度快,需要注意的是两个只能用一个,不能同时使用,例如对babel-loader的使用时,就需要排除node_modules下的文件

module.exports = {
    // 模块配置
    module: {
        // 规则
        rules: [
            // 配置babel,将ES6+转换为ES5
            {
                test: /\.js$/,
                // 排除node_modules目录下的文件
                exclude: /node_modules/,
                use: [
                    {
                        // 使用babel-loader
                        loader: 'babel-loader',
                        // 配置babel
                        options: {
                            // 预设
                            presets: [
                                // 预设包含了ES6、7、8的语法转换规则
                                '@babel/preset-env'
                            ]
                        }
                    }
                ]
            }
        ]
    }
}

3、使用cache

使用cache可以对eslint和babel处理的结果进行缓存,下次检查只处理修改的文件,可以让后续打包速度更快。不过对于eslint的缓存可能会出现无法检测新增的文件,因此慎重开启,配置如下:

module.exports = {
    // 模块配置
    module: {
        // 规则
        rules: [
            // 配置babel,将ES6+转换为ES5
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    {
                        // 使用babel-loader
                        loader: 'babel-loader',
                        // 配置babel
                        options: {
                            // 预设
                            presets: [
                                // 预设包含了ES6、7、8的语法转换规则
                                '@babel/preset-env'
                            ],
                            // 开启缓存
                            cacheDirectory: true,
                            // 关闭缓存压缩
                            cacheCompression: false
                        }
                    }
                ]
            }
        ]
    },
    // 配置插件
    plugins: [
        // eslint插件
        new EslintWebpackPlugin({
            // eslint检查的文件, 只检查src目录下的文件
            context: path.resolve(__dirname, '../src'),
            // 开启缓存
            cache: true
        })
    ]
}

4、使用thread

使用多进程打包,多进程处理Babel和eslint,速度更快,需要注意的是启动进程需要时间,因此根据项目大小选择性开启,项目较大时才会加快打包速度,项目较小时反而会增加打包时间。配置之前我们首先需要获取当前计算机的总进程数和安装thread-loader,一般配置多进程为总进程数减一。配置如下: 安装thread-loader

npm i thread-loader --save-dev

配置多进程打包

// 引入os模块
const os = require('os')

// 获取当前计算机总进程数
const cpus = os.cpus().length

module.exports = {
    // 模块配置
    module: {
        // 规则
        rules: [
            // 配置babel,将ES6+转换为ES5
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    // 开启多进程打包
                    {
                        loader: 'thread-loader',
                        options: {
                            // 进程数
                            workers: cpus - 1
                        }
                    },
                    {
                        // 使用babel-loader
                        loader: 'babel-loader',
                        // 配置babel
                        options: {
                            // 预设
                            presets: [
                                // 预设包含了ES6、7、8的语法转换规则
                                '@babel/preset-env'
                            ],
                            // 开启缓存
                            cacheDirectory: true,
                            // 关闭缓存压缩
                            cacheCompression: false
                        }
                    }
                ]
            }
        ]
    },
    // 配置插件
    plugins: [
        // eslint插件
        new EslintWebpackPlugin({
            // eslint检查的文件, 只检查src目录下的文件
            context: path.resolve(__dirname, '../src'),
            // 开启缓存
            cache: true,
            // 开启多进程打包,设置进程数
            threads: cpus - 1
        })
    ]
}

三、减少代码体积

1、使用Tree Shaking

剔除了没有使用的多于代码,让代码体积更小,此功能已经在webpack5中内嵌,因此不用多余配置,正常打包即可。

2、使用@babel/plugin-transform-runtime

插件对babel进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小
安装插件

npm i @babel/plugin-transform-runtime --save-dev

配置插件

// 引入os模块
const os = require('os')

// 获取当前计算机总进程数
const cpus = os.cpus().length

module.exports = {
    // 模块配置
    module: {
        // 规则
        rules: [
            // 配置babel,将ES6+转换为ES5
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    // 开启多进程打包
                    {
                        loader: 'thread-loader',
                        options: {
                            // 进程数
                            workers: cpus - 1
                        }
                    },
                    {
                        // 使用babel-loader
                        loader: 'babel-loader',
                        // 配置babel
                        options: {
                            // 预设
                            presets: [
                                // 预设包含了ES6、7、8的语法转换规则
                                '@babel/preset-env'
                            ],
                            // 开启缓存
                            cacheDirectory: true,
                            // 关闭缓存压缩
                            cacheCompression: false,
                            // 配置禁用了 Babel 自动对每个文件的 runtime 注入,
                            // 而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用,来避免重复引入
                            plugins: ['@babel/plugin-transform-runtime']
                        }
                    }
                ]
            }
        ]
    }
}

3、使用压缩插件

压缩项目中的文件,体积更小,请求速度更快。根据官方文档,我们将优化相关插件配置到optimization中。压缩插件一般只在生产环境使用,开发环境无需配置。

(1)使用css压缩插件

安装css-minimizer-webpack-plugin

npm i css-minimizer-webpack-plugin --save-dev

配置css-minimizer-webpack-plugin

// 引入css-minimizer-webpack-plugin插件
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')

module.exports = {
  // 优化配置
    optimization: {
        // 配置压缩插件
        minimizer: [
            // 配置css压缩插件
            new CssMinimizerWebpackPlugin()
        ]
    }
}

(2)使用js压缩插件

js压缩插件terser-webpack-plugin已经在webpack5中内置,无需下载,如果不需要配置多进程,在生产模式下该插件默认使用的,下面是单独配置并且开启多进程的例子

// 引入terser-webpack-plugin插件
const TerserWebpackPlugin = require('terser-webpack-plugin')
// 引入os模块
const os = require('os')

// 获取当前计算机总进程数
const cpus = os.cpus().length

module.exports = {
  // 优化配置
    optimization: {
        // 配置压缩插件
        minimizer: [
            // 配置terser压缩插件, 用于压缩js, 默认是terser-webpack-plugin, 
            // 但是webpack5中已经内置了, 如果不需要开启多进程打包, 可以不用配置
            new TerserWebpackPlugin({
                // 开启多进程
                parallel: cpus - 1
            })
        ]
    }
}

四、优化代码运行性能

1、代码分割(Code Split)

由于打包代码时会将所有的js打包到一个文件中,体积太大,因此我们需要代码分割,生成多个js文件,渲染某个页面时只加载该页面的js文件,速度会更快。代码分割有多种途径,以下是常用的几种

(1)多入口打包

入口配置

module.exports = {
    // 多入口入口配置,index和app是自定义的名称
    entry: {
        index: './src/index.js',
        app: './src/app.js'
    }
}

出口配置

const path = require('path')

module.exports = {
    // 单入口配置
    entry: './src/index.js',
    // 出口配置
    output: {
        // 输出路径
        path: path.resolve(__dirname, '../dist'),
        // 输出文件名
        filename: 'js/[name].js',
        // 清空输出目录
        clean: true
    }
}

(2)使用splitChunks

项目中经常有多个模块重复加载一个模块代码的情况,如果不进行代码分割,那每个模块都将引用公共模块代码,使总体体积增大,因此我们配置splitChunks将被多次引用的模块分割出去,单独引用,从而减小打包后的代码体积,该配置有很多自定义配置项,一般我们就用官方配置好的默认项就好,开启方式也很简单。(开发环境生产环境均可配置)如下

module.exports = {
  // 优化配置
    optimization: {
        // 配置代码分割
        splitChunks: {
            // 配置分割规则
            chunks: 'all'
        }
    }
}

(3)使用runtimeChunk

当将 runtimeChunk 设置为 'single' 时,Webpack 将会将运行时代码提取为一个单独的文件,并将其添加到输出的打包文件中。这样做的好处是,当多个入口文件共享相同的运行时代码时,可以有效地减小打包文件的体积,并避免重复打包相同的运行时代码。(开发环境生产环境均可配置)

module.exports = {
  // 优化配置
    optimization: {
        // 配置将当前模块的记录其他模块的hash单独打包为一个文件
        runtimeChunk: 'single'
    }
}

(4)按需导入

当我们在文件中,只有进行某些操作才需要加载一个文件中的代码时,我们就可以使用按需导入, 使用/* webpackChunkName: 'add' */ 注释方法,给分离模块命名。并在output中指定chunkFilename: 'js/[name].chunk.js'

document.getElementById("btn").onclick = function () {
    import(/* webpackChunkName: 'add' */ "./js/math").then(({ add }) => {
        console.log(add(1, 2))
    }).catch(() => {
        console.log("加载失败")
    })
}

配置

const path = require('path')

module.exports = {
    // 单入口配置
    entry: './src/index.js',
    // 出口配置
    output: {
        // 输出路径
        path: path.resolve(__dirname, '../dist'),
        // 输出文件名
        filename: 'js/[name].js',
        // 输出的按需加载的文件的文件名
        chunkFilename: 'js/[name].chunk.js',
        // 清空输出目录
        clean: true
    }
}

2、空闲时加载(Preload/Prefetch)

(1)Preload/Prefetch介绍

当浏览器空闲时,加载资源并进行缓存,当真正需要使用资源时,直接使用,可以解决按需加载时如果文件过大,加载卡顿问题。二者区别:

  1. Preload加载优先级高,Prefetch加载优先级低
  2. Preload只能加载当前页面使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面使用的资源。

缺点:兼容性不好,目前浏览器兼容性Preload:95%,Prefetch:77%,因此需要兼容多种浏览器的慎用。

(2)Preload/Prefetch使用

安装@vue/preload-webpack-plugin

npm i @vue/preload-webpack-plugin --save-dev

配置插件

// 引入preload-webpack-plugin插件
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin')

module.exports = {
    // 配置插件
    plugins: [
        // 预加载插件
        new PreloadWebpackPlugin({
            // 预加载的资源
            rel: 'preload',
            // 仅处理js文件
            as: 'script'
        }),
    ]
}

3、core-js

core-js专门用来补充babel-loader局限性问题,由于babel-loader无法转换es6高级语法,因此我们需要下载安装core-js,并引入项目,实现es6高级语法的转换。
安装core-js

npm i core-js --save-dev

使用core-js,全部引入

import 'core-js/actual'

Promise.resolve(42).then(it => console.log(it)) // => 42

Array.from(new Set([1, 2, 3]).union(new Set([3, 4, 5]))) // => [1, 2, 3, 4, 5]

使用core-js,按需引入

import 'core-js/actual/promise'

Promise.resolve(42).then(it => console.log(it)) // => 42

使用babel预设,直接按需加载自动引入,无需手动引入,配置useBuiltIns: 'usage'

module.exports = {
    // 模块配置
    module: {
        // 规则
        rules: [
            // 配置babel,将ES6+转换为ES5
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: [
                    {
                        // 使用babel-loader
                        loader: 'babel-loader',
                        // 配置babel
                        options: {
                            // 预设
                            presets: [[
                                // 预设包含了ES6、7、8的语法转换规则
                                '@babel/preset-env',
                                // 按需加载,自动引入
                                {
                                    useBuiltIns: 'usage',
                                    corejs: 3
                                }
                            ]]
                        }
                    }
                ]
            }
        ]
    }
}

4、使用PWA

渐进式 Web 应用程序(Progressive Web Application,PWA)是一种结合了网页和原生应用程序功能的现代 Web 应用程序概念。PWA 的目标是提供类似原生应用程序的用户体验,包括离线访问、推送通知、本地缓存等功能。我们使用PWA可以实现离线访问,优化用户体验。实现该功能需要下载安装workbox-webpack-plugin插件

npm i workbox-webpack-plugin --save-dev

配置插件

// 引入workbox-webpack-plugin插件
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')

module.exports = {
    // 配置插件
    plugins: [
        // workbox插件
        new WorkboxWebpackPlugin.GenerateSW({
            // 不需要手动配置service-worker.js文件,会自动生成
            clientsClaim: true,
            skipWaiting: true
        })
    ]
}

注册插件,在入口文件index.js中添加以下代码

if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/service-worker.js').then(registration => {
            console.log('SW registered: ', registration)
        }).catch(registrationError => {
            console.log('SW registration failed: ', registrationError)
        })
    })
}