Webpack 知识大全

937 阅读13分钟

一、webpack基础知识

定义

webpack是一个静态资源打包工具,将项目中的依赖的各个模块,打包成一个或多个文件。

如何用webpack来优化项目的性能

  • 开发环境:开发环境我们需要的是更快的构建速度、模块热替换、更加友好的Source map

    • 通过cache: { type: 'systemfile'} 开启缓存构建可以加快二次构建的效率
    • 通过模块热替换可以做到局部更新变化,提高开发效率
    • 根据设置 devtool: cheap-eval-source-map 在保证构建效率的同时又能进行代码调试
    • 使用Thread-loader以多进程的方式运行资源加载逻辑
    • 通过 stats 来分析性能做优化
  • 生产环境:生产环境我们需要的是更小的体积,更稳定又快的性能

    • 压缩代码:使用UglifyJsPluginParallelUglifyPlugin来压缩代码 利用cssnano(css-loader? minimize)来压缩css
    • 利用CDN:可以使用CDN来加快对静态资源的访问,提高用户的使用体验
    • Tree Shaking: 删除没用到的代码
    • 提取公共第三方库: 使用SplitChunksPlugin插件来进行公共模块抽取
    • 使用TerserWebpackPlugin多进程执行代码压缩、uglify 功能

webpack与grunt、gulp的不同?

grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。

webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能

webpack的核心概念

  • Entry: 入口(告诉 webpack 从哪个文件开始构建,这个文件将作为 webpack 依赖关系图的起点)

    • 配置单个入口
    // 第一种写法
    module.export =  {
       entry:'./src/main.js'
    }
    // 第二种写法
    module.export = {
       entry:{
           main:'./src/main.js',
       }
    }
    
    • 配置多个入口
    // 场景一:分离 应用程序(app) 和 第三方库(vendor) 入口
    
       module.exports = {
         entry: {
           app: './src/app.js',
           vendors: './src/vendors.js'
         }
       };
    
     // 场景二:多页面应用程序,告诉 webpack 需要 3 个独立分离的依赖图
    
       module.exports = {
         entry: {
           pageOne: './src/pageOne/index.js',
           pageTwo: './src/pageTwo/index.js',
           pageThree: './src/pageThree/index.js'
         }
       };
    
  • Module:模块,webpack中一切皆是模块

  • Chunk:代码库,一个chunk由多个模块组合而成,用于代码合并与分割

  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容

  • Plugin:扩展插件,在webpack构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要做的事情

  • Output: 输出结果(告诉 webpack 在哪里输出 构建后的包、包的名称 等)

    • 配置单个出口
    const path = require('path');
    module.exports = {
         entry: main: './src/main.js',
         output: {
           filename: 'main.js',
           path: path.resolve(__dirname, 'dist')
         }
     };
    
    • 配置多个入口
    const path = require('path');
    
     module.exports = {
       entry: {
         app: './src/app.js',
         vendors: './src/vendors.js'
       },
       output: {
         filename: '[name].js',
         path: path.resolve(__dirname, 'dist')
       }
     }
    

常见的Loader?他们是解决什么问题的?

  • file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件

  • url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去

  • source-map-loader:加载额外的 Source Map 文件,以方便断点调试

  • image-loader:加载并且压缩图片文件

  • babel-loader:把 ES6 转换成 ES5

  • css-loader:加载 CSS,支持模块化、压缩、文件导入等特性

  • style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。

  • eslint-loader:通过 ESLint 检查 JavaScript 代码

常见的Plugin?他们是解决什么问题的?

  • define-plugin:定义环境变量
  • html-webpack-plugin: 根据模板自动生成html代码,自动引用css和js文件
  • commons-chunk-plugin:提取公共代码
  • uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码
  • Hot-Module-Replacement-Plugin: 热更新
  • clean-wenpack-plugin: 清理每次打包下没有使用的文件
  • optimize-css-assets-webpack-plugin: 不同组件中重复的css快速去重

Loader和Plugin的不同?

  • 作用不同:

(1) Loader本质是一个函数,在该函数中对接收到的内容进行转换,返回转后的结果。是webpack只能解析原生js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。

(2) Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

  • 用法不同:

(1)Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,内部包含了类型文件(test),使用什么加载(loader)和使用的参数(options)

(2) Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。

webpack的构建流程是什么

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

  • 初始化参数:解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果。
  • 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
  • 确定入口:从配置中的entry找出所有的入口文件,开始解析文件构建AST语法树,找出依赖,递归下去。
  • 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  • 输出完成:输出所有的chunk到文件系统。

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

简单的说:

  • 初始化:自动构建,读取与合并配置参数,加载plugin,实例化Compiler
  • 编译:从Entry出发,针对每个Module串行调用对应的Loader去编译文件的内容,再找到该Module依赖的Module,递归地进行编译处理
  • 输出:将编译后的Module组合成Chunk,将Chunk转换成文件,输出到文件系统中

怎么配置单页应用?怎么配置多页应用?

  • 单页应用可以理解为webpack的标准模式,直接在entry中指定单页应用的入口即可
  • 多页应用的话,可以使用webpack的 AutoWebPlugin来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范。 多页应用中要注意的是:

1:每个页面都有公共的代码,可以将这些代码抽离出来,避免重复的加载。比如,每个页面都引用了同一套css样式表

2:随着业务的不断扩展,页面可能会不断的追加,所以一定要让入口的配置足够灵活,避免每次添加新页面还需要修改构建配置

webpack的hash

  • hash:跟整个项目的构建相关,构建生成的文件hash值都是一样的,只要项目里有文件更改,整个项目构建的hash值都会更改。
  • chunkhash:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的hash值。
  • contenthash:由文件内容产生的hash值,内容不同产生的contenthash值也不一样。

文件指纹是什么?怎么用?

文件指纹是打包后输出的文件名的后缀。

  • Hash:和整个项目的构建相关,只要项目文件有修改,整个项目构建的 hash 值就会更改
  • Chunkhash:和 Webpack 打包的 chunk 有关,不同的 entry 会生出不同的 chunkhash
  • Contenthash:根据文件内容来定义 hash,文件内容不变,则 contenthash 不变

JS的文件指纹设置

设置 output 的 filename,用 chunkhash。

module.exports = {   
    entry: {        
        app: './scr/app.js',     
        search: './src/search.js'   
    },   
    output: {      
        filename: '[name][chunkhash:8].js',       
        path:__dirname + '/dist'    
    }
  }

CSS的文件指纹设置

设置 MiniCssExtractPlugin 的 filename,使用 contenthash。

module.exports = {    
    entry: {        
        app: './scr/app.js',        
        search: './src/search.js'  
    },    
    output: {       
        filename: '[name][chunkhash:8].js',     
        path:__dirname + '/dist'  
    },    
    plugins:[       
        new MiniCssExtractPlugin({         
           filename: `[name][contenthash:8].css` })    
     ]
}

source map是什么?生产环境怎么用?

source map 是将编译、打包、压缩后的代码映射回源代码的过程。打包压缩后的代码不具备良好的可读性,想要调试源码就需要 soucre map。

map文件只要不打开开发者工具,浏览器是不会加载的。

线上环境一般有三种处理方案:

  • hidden-source-map:借助第三方错误监控平台 Sentry 使用
  • nosources-source-map:只会显示具体行数以及查看源代码的错误栈。安全性比 sourcemap 高
  • sourcemap:通过 nginx 设置将 .map 文件只对白名单开放(公司内网)

注意:避免在生产中使用 inline-eval-,因为它们会增加 bundle 体积大小,并降低整体性能。

如何在vue项目中实现按需加载?

Vue UI组件库的按需加载 为了快速开发前端项目,经常会引入现成的UI组件库如ElementUI、iView等,但是他们的体积和他们所提供的功能一样,是很庞大的。 而通常情况下,我们仅仅需要少量的几个组件而已,但是却将庞大的组件库打包到源码中,造成了不必要的开销。

解决方案,在.babelrc配置中或babel-loader的参数中进行设置,即可实现组件按需加载了。

随着业务的不断扩展,会面临一个严峻的问题——首次加载的代码量会越来越多,影响用户的体验。单页应用的按需加载

通过import()语句来控制加载时机, webpack内置了对于import()的解析,会将import()中引入的模块作为一个新的入口在生成一个chunk。 当代码执行到import()语句时,会去加载Chunk对应生成的文件。import()会返回一个Promise对象,所以为了让浏览器支持,需要事先注入Promise polyfill。

webpack自适配问题

  • 使用模版html

html-webpack-plugin 可以指定template模板文件,将会在output目录下,生成html文件,并引入打包后的js.

安装依赖:

npm install --save-dev html-webpack-plugin

在webpack.config.js 增加 plugins 配置:

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    //...other code
    plugins: [
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'src/index.html')
        })
    ]
}
  • 配置 webpack-dev-server

webpack-dev-server提供了一个简单的Web服务器和实时热更新的能力.

安装依赖:

npm install --save-dev webpack-dev-server

在 webpack.config.js 增加 devServer 配置:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    //...other code
    devServer: {
        contentBase: './dist',
        port: '8080',
        host: 'localhost'
    }
}

在 package.json 的 scripts 字段中增加:

webpack-dev-server --mode development

可以通过 npm run dev , 来启动服务.

  • 支持加载css文件

通过使用不同的 style-loader 和 css-loader, 可以将 css 文件转换成JS文件类型。

安装依赖:

npm install --save-dev style-loader css-loader

在 webpack.config.js 中增加 loader 的配置。

module.exports = {
    //other code
    module: {
        rules: [
            {
                test: /\.css/,
                use: ['style-loader', 'css-loader'],
                exclude: /node_modules/,
                include: path.resolve(__dirname, 'src')
            }
        ]
    }
}

  • 支持编译less和sass

安装对应的依赖:

npm install --save-dev less less-loader
npm install --save-dev node-sass sass-loader

在 webpack.config.js 中增加 loader 的配置(module.rules 数组中)。

module.exports = {
    //other code
    module: {
        rules: [
            {
                test: /\.less/,
                use: ['style-loader', 'css-loader', 'less-loader'],
                exclude: /node_modules/,
                include: path.resolve(__dirname, 'src')
            },
            {
                test: /\.scss/,
                use: ['style-loader', 'css-loader', 'sass-loader'],
                exclude: /node_modules/,
                include: path.resolve(__dirname, 'src')
            }
        ]
    }
}   
  • 支持加载图片

安装依赖:

npm install --save-dev url-loader file-loader
  • file-loader: 解决CSS等文件中的引入图片路径问题
  • url-loader: 当图片小于limit的时候会把图片Base64编码,大于limit参数的时候还是使用file-loader进行拷贝

在 webpack.config.js 中增加 loader 的配置(增加在 module.rules 的数组中)。

module.exports = {
    //other code
    module: {
        rules: [
            {
                test: /\.(gif|jpg|png|bmp|eot|woff|woff2|ttf|svg)/,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192,
                            outputPath: 'images'
                        }
                    }
                ]
            }
        ]
    }
} 
  • 支持转义 ES6/ES7/JSX

ES6/ES7/JSX 转义需要 Babel 的依赖,支持装饰器。

安装依赖:

npm install --save-dev @babel/core babel-loader @babel/preset-env @babel/preset-react @babel/plugin-proposal-decorators @babel/plugin-proposal-object-rest-spread

在 webpack.config.js 中增加 loader 的配置(module.rules 数组中)。

module.exports = {
    //other code
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: ['@babel/preset-env', '@babel/react'],
                            plugins: [
                                ["@babel/plugin-proposal-decorators", { "legacy": true }]
                            ]
                        }
                    }
                ],
                include: path.resolve(__dirname, 'src'),
                exclude: /node_modules/
            },
        ]
    }
}
  • 压缩JS文件

安装依赖:

npm install --save-dev uglifyjs-webpack-plugin

在 webpack.config.js 中增加 optimization 的配置

const UglifyWebpackPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
    //other code
    optimization: {
        minimizer: [
            new UglifyWebpackPlugin({
                parallel: 4
            })
        ]
    }
}
  • 压缩CSS文件

安装依赖:

npm install --save-dev optimize-css-assets-webpack-plugin

在 webpack.config.js 中的 optimization 中增加配置

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

module.exports = {
    //other code
    optimization: {
        minimizer: [
            new OptimizeCssAssetsWebpackPlugin()
        ]
    }
}
  • 打包前先清空输出目录

安装依赖:

npm install --save-dev clean-webpack-plugin

在 webpack.config.js 中增加 plugins 的配置

const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    //other code
    plugins: [
        new CleanWebpackPlugin()
    ]
}

webpack之loader的加载顺序?

Webpack选择了compose方式,在compose中是采用reduceRight,所以loader的顺序编程是从右往左

webpack 多文件如何进行代码切割?

代码切割,我们需要借助 webpack 内置的插件 CommonsChunkPlugin

module.exports = {
    // 其他代码
    plugins:[
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: 2,
    }),
    ]
}

webpack中的hash、chunkhash、contenthash

  • hash hash是根整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值就会更改,并且全部文件都共用相同的hash值。
  • chunkhash

它根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的哈希值。

简单说:根据不同入口来配置,只要这些没有改变,对应生成的js的hash值就不会改变。

  • contenthash contenthash主要是处理关联性,比如一个js文件中引入css,会生成一个js文件,一个css文件。但是入口是一个,导致他们的hash值也相同。只有当js修改时,关联输出的css、img等文件的hash值也会改变。

  • 使用hash值的作用;

    • 提升webpack打包速度和项目体积:

      将webpack入口的chunk文件中所有公共的代码提取出来,减少代码体积,同时提升webpack打包速度

    • 利用缓存机制

      依赖的公共模块文件一般很少更改或不会更改,这样独立模块文件提取出可以长期缓存

    • 利用浏览器缓存

      线上代码发版后及时读取最新的js文件、css文件,防止出现缓存问题

npm run dev的时候webpack做了什么事情

执行 npm run dev 时候最先执行的 build/dev-server.js 文件,该文件主要完成下面几件事情:

1、检查node和npm的版本、引入相关插件和配置

2、webpack对源码进行编译打包并返回compiler对象

3、创建express服务器

4、配置开发中间件(webpack-dev-middleware)和热重载中间件(webpack-hot-middleware)

5、挂载代理服务和中间件

6、配置静态资源

7、启动服务器监听特定端口(8080)

8、自动打开浏览器并打开特定网址(localhost:8080)

二、webpack进阶知识

1、优化构建速度

1.1构建费时分析

使用插件speed-measure-webpack-plugin

1:安装插件 npm i -D speed-measure-webpack-plugin
2:修改配置文件webpack.config.js
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
const smp = new SpeedMeasurePlugin()
...
const config = {...}
module.exports = (env,argv)=>{
  ...
  return smp.wrap(config)
}

1.2优化resolve配置

  • alias用于创建import或require的别名,用来简化模块引用
  • noPrse用于不需要解析依赖的第三方库,提高构建速度
  • babel-loader:babel在转移js过程中时间开销比较大,将babel-loader的执行结果缓存利埃,重新打包时,直接读取缓存,缓存位置:node_modules/.cache/babel-loader

2、优化构建结果

2.1构建结果分析

借助插件webpack-bundle-analyzer,可以直观看到打包结果,文件体积大小 ,各模块依赖关系等

1:安装插件
2:配置插件 
const BundleAnlyzerplugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

const config = {
  plugins:[
    new BundleAnalyzerPlugin({
     // analyzerMode: 'disabled', // 不启动展示打包报告的http服务器 
     // generateStatsFile: true, // 是否生成stats.json文件
    })
  ]
}
3:修改启动命令:
“scripts”:{
  "analyzer":"cross-env NODE_ENV=prod webpack --progress --mode production"
}
4:执行命令:npm run analyzer

2.2优化

  • 压缩CSS:使用插件optimize-css-assets-webpack-plugin
  • 压缩JS:webpack5内置了terser-webpack-plugin插件
  • 清除无用的CSS:purgecss-webpack-plugin
  • Scope Hoisting :作用域提升,将多个模块放在同一个作用域下,并 重命名防止命名冲突,减少函数声明和内存开销。

三、高阶知识点

自写loader

自定义 clean-log-loader,用来清理JS代码中的console.log

// clean-log-loader.js
module.exports = function(content){
  return content.replace(/console\.log\(.*\);?/g,'')
}
// webpack.config.js
module:{
  rules:[
    {
      loader:'./loaders/clean-log-loader'
    }
  ]
}

自定义plugin

自定义plugin步骤

  • 一个 JavaScript 函数或者类

  • 在函数原型(prototype)中定义一个注入compiler对象的apply方法。

  • apply函数中通过compiler插入指定的事件钩子,在钩子回调中拿到compilation对象

  • 使用compilation操纵修改webapack内部实例数据。

  • 异步插件,数据处理完后使用callback回调

自定义CleanWebpackPlugin

作用:在webpack打包输出前将上次打包内容清空。webpack5内置了该功能clean: true 就可以了。 开发思路

  • 如何在打包输出前执行? 需要使用 compiler.hooks.emit 钩子,它是打包输出前触发。

  • 如何清空上次打包内容?

    • 获取打包输出目录: 通过 compiler 对象。
    • 通过文件操作清空内容:通过 compiler.outputFileSystem 操作文件。
    // clean-webpack-plugin.js
    class CleanWebpackPlugin {
        apply(compiler) {
            // 2. 获取打包输出的目录
            const outputPath = compiler.options.output.path

            const fs = compiler.outputFileSystem

            // 1. 注册钩子:在打包输出之前 emit
            compiler.hooks.emit.tap('CleanWebpackPlugin', compilation => {
                // 3. 通过fs删除打包输出的目录下的所有文件
                this.removeFiles(fs, outputPath)
            })
        }
        removeFiles(fs, filepath) {
            // 想要删除打包输出目录下所有资源,需要贤将目录下资源删除,才能删除这个目录
            // 1. 读取当前目录下所有资源
            const files = fs.readdirSync(filepath)
            // 2. 遍历一个个删除
            // 2.1 遍历所有文件,判断是文件夹还是文件,
            files.forEach(file => {
                const path = `${filepath}/${file}`
                const fileStat = fs.statSync(path)
                // console.log(path)
                if (fileStat.isDirectory()) {
                    // 2.2 是文件夹,就得删除下面所有文件,才能删除文件夹
                    this.removeFiles(fs, path)
                } else {
                     // 2.3 是文件,直接删除
                    fs.unlinkSync(path)
                }
            });
        }
    }
    module.exports = CleanWebpackPlugin
    // webpack-config.js
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')

    const CleanWebpackPlugin = require('./plugins/clean-webpack-plugin')

    module.exports = {
        entry: './src/main.js',
        output: {
            path: path.resolve(__dirname, './dist'),
            filename: 'js/[name].js',
            // webpack5通过这个配置实现了这个功能,
            // clean: true, // 这边注释下,模拟 自定义 CleanWebpackPlugin插件 实现 clean: true功能
        },
        module: {
            rules: []
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: path.resolve(__dirname, 'public/index.html')
            }),
            // new TestPlugin(),
            
            new CleanWebpackPlugin()
        ],
        mode: 'production'
    }

自定义AnalyzeWebpackPlugin

作用:分析 webpack 打包资源大小,并输出分析文件。

开发思路:

  • 在哪做? compiler.hooks.emit,它是在打包输出前触发,我们需要分析资源大小同时添加上分析后的 md 文件
    // analyze-webpack-plugin
    class AnalyzeWebpackPlugin {
        apply(compiler) {
            // emit 是异步串行钩子
            compiler.hooks.emit.tap('AnalyzeWebpackPlugin', compilation => {
                // Object.entries 将对象变成二维数组。二维数组中第一项值是key, 第二项是value
                // 二维数组: [[key, value], [key, value]]
                const assets = Object.entries(compilation.assets)
                // md 表格语法:
                /*
                    | 资源名 | 资源大小 |
                    | --- | ---|
                    |xxx.js | 22 |
                */
                let source = `# 分析打包资源大小 \n| 名称 | 大小 | \n | --- | --- |`;
                // 1. 遍历所有即将输出的文件,得到其大小
                assets.forEach(([filename, file]) => {
                    source += `\n| ${filename} | ${file.size()} |`
                    // 带单位 多少kb
                    // source += `\n| ${filename} | ${Math.ceil(file.size() / 1024)}kb |`
                })
                // 2. 生成一个 md 文件
                // 添加资源
                compilation.assets["analyze.md"] = {
                    source() {
                        return source
                    },
                    size() {
                        return source.length
                    }
                }
            })
        }
    }
    module.exports = AnalyzeWebpackPlugin


    // webpack-config.js
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')

    const BannerWebpackPlugin = require('./plugins/banner-webpack-plugin.js')
    const CleanWebpackPlugin = require('./plugins/clean-webpack-plugin')

    module.exports = {
        entry: './src/main.js',
        output: {
            path: path.resolve(__dirname, './dist'),
            filename: 'js/[name].js',
            // webpack5通过这个配置实现了这个功能,
            // clean: true, // 这边注释下,模拟 自定义 CleanWebpackPlugin插件 实现 clean: true功能
        },
        module: {
            rules: []
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: path.resolve(__dirname, 'public/index.html')
            }),
            // new TestPlugin(),
            new BannerWebpackPlugin({
                author: "老王"
            }),
            new CleanWebpackPlugin(),
            new AnalyzeWebpackPlugin(),
        ],
        mode: 'production'
    }
    


InlineChunkWebpackPlugin

作用:webpack打包生成的runtime文件太小了,额外发送请求性能不好,所有需要将其内联到js中,从而减少请求数量。

开发思路:

  • 我们需要借助 html-webpack-plugin 来实现

    • html-webpack-plugin输出index.html前将内联runtime注入进去
    • 在删除多余的 runtime 文件。
  • 如何操作 html-webpack-plugin

npm i safe-require -D
    // inline-chunk-webpack-plugin.js
    // plugins/inline-chunk-webpack-plugin.js
    const HtmlWebpackPlugin = require('safe-require')('html-webpack-plugin')
    class InlineChunkWebpackPlugin {
        constructor(tests) {
            this.tests = tests
        }
        apply(compiler) {
            compiler.hooks.compilation.tap("InlineChunkWebpackPlugin", compilation => {
                // 1. 获取 html-webpack-plugin的 hooks
                const hooks = HtmlWebpackPlugin.getHooks(compilation)
                // 2. 注册 html-webpack-plugin 的 hooks --> alterAssetTagGroups

                hooks.alterAssetTagGroups.tap('InlineChunkWebpackPlugin', assets => {
                    // 3. 从里面将 script 的runtime 文件,变成 inline script
                    assets.headTags = this.getInlineChunk(assets.headTags, compilation.assets)
                    assets.bodyTags = this.getInlineChunk(assets.bodyTags, compilation.assets)
                })
                // 删除 runtime文件
                hooks.afterEmit.tap('InlineChunkHtmlPlugin', () => {
                    Object.keys(compilation.assets).forEach(assetName => {
                        if (this.tests.some((test) => assetName.match(test))) {
                            delete compilation.assets[assetName]
                        }
                    })
                })
            })
        }
        getInlineChunk(tags,assets) {
            return tags.map(tag => {
                if (tag.tagName !== 'script') return tag
                // 获取文件资源路径
                const filepath = tag.attributes.src
                if(!filepath) return tag
                if (!this.tests.some((test) => test.test(filepath))) return tag

                return {
                    tagName: 'script',
                    innerHTML: assets[filepath].source(),
                    closeTag: true,
                }
            })
        }
    }
    module.exports = InlineChunkWebpackPlugin
    


    // webpack-config.js
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')

    const BannerWebpackPlugin = require('./plugins/banner-webpack-plugin.js')
    const CleanWebpackPlugin = require('./plugins/clean-webpack-plugin')
    const InlineChunkWebpackPlugin = require('./plugins/inline-chunk-webpack-plugin')
    
    module.exports = {
        entry: './src/main.js',
        output: {
            path: path.resolve(__dirname, './dist'),
            filename: 'js/[name].js',
            // webpack5通过这个配置实现了这个功能,
            // clean: true, // 这边注释下,模拟 自定义 CleanWebpackPlugin插件 实现 clean: true功能
        },
        module: {
            rules: []
        },
        plugins: [
            new HtmlWebpackPlugin({
                template: path.resolve(__dirname, 'public/index.html')
            }),
            // new TestPlugin(),
            new BannerWebpackPlugin({
                author: "老王"
            }),
            new CleanWebpackPlugin(),
            new AnalyzeWebpackPlugin(),
            new InlineChunkWebpackPlugin([/runtime(.*)\.js$/g]),
        ],
        optimization: {
            splitChunks: {
                chunks: 'all',
            },
            runtimeChunk: {
                name: entrypoint => `runtime~${entrypoint.name}.js`
            }
        },
        mode: 'production'
    }

webpack-bundle-analyzer 优化实战

同一个库存在多个版本

  • 原因:当引入第三方库时会有依赖其他类库,依赖同时,也会把其依赖的类库引入进来。
  • 解决方式:将 @ant-design/icons 升级到 4.4.0 版本即可。

第三方包太大

monment.js包只使用处理时间的少量功能,体积太大,更换成date-fn包

没有按需加载

修改引入方式,做到具体的引入

import uniqBy from 'loadsh/uniqBy'

只有在某些场景下才会使用到的包

解决方式:动态加载

externals提取项目依赖

使用 externals 来提取这些依赖包,告诉 webpack 这些依赖是外部环境提供的,在打包时可以忽略它们,就不会再打到 chunk-vendors.js 中。 1)vue.config.js 中配置:

module.exports = {
 configureWebpack: {
   externals: {
     vue: 'Vue',
     'vue-router': 'VueRouter',
     axios: 'axios',
     echarts: 'echarts'
   }
}

2)在 index.html 中使用 CDN 引入依赖

  <body>
   <script src="http://lib.baomitu.com/vue/2.6.14/vue.min.js"></script>
   <script src="http://lib.baomitu.com/vue-router/3.5.1/vue-router.min.js"></script>
   <script src="http://lib.baomitu.com/axios/1.2.1/axios.min.js"></script>
   <script src="http://lib.baomitu.com/echarts/5.3.2/echarts.min.js"></script>
 </body>

tree-shaking

移除JS、CSS中未使用的代码,依赖于ES module,webpack跟踪整个应用程序的import/export语句,导入的内容没有被使用,称为“死代码”

webpack中,Tree-shaking的实现一是先标记出模块导出值中哪些没有被使用,二是使用Terser删除没有被用到的导出语句。步骤如下:

  • Make 阶段,收集模块导出变量并记录到模块依赖关系图 ModuleGraph 变量中
  • Seal 阶段,遍历 ModuleGraph 标记模块导出变量有没有被使用
  • 生成产物时,若变量没有被其它模块使用则删除对应的导出语句

HMR(热更新)【无需完全刷新整个页面的同时,更新模块】

Webpack5

Webpack5相比于Webpack4有哪些提升?

  • 更快的构建速度:Webpack5在构建速度方面进行了大量优化,尤其是在开发模式下,构建速度有了明显提升。

  • Tree Shaking优化:Webpack5进一步改进了Tree Shaking算法,可以更准确地判断哪些代码是无用的,从而更好地优化构建输出的文件大小。

  • 内置的持久化缓存:Webpack5在持久化缓存方面进行了优化,可以缓存每个模块的编译结果,从而加速后续的构建。

  • 支持WebAssembly:Webpack5增加了对WebAssembly的原生支持。

  • 模块联邦(Module Federation):Webpack5引入了模块联邦的概念,可以实现多个独立的Webpack构建之间的模块共享和远程加载,为微前端架构提供了更好的支持。

模块联邦

模块联邦是实现多个项目之间共享代码的机制。