webpack常用优化项及配置文件拆分

webpack优化项

在上一篇webpack基础配置及常用loader中,详细介绍了webpack基础知识及常用loader的使用方法,本篇是对上一篇的延续,对webpack搭建前端工程常用的优化项简单进行一下归纳

noParse

  • noParse作用主要是过滤不需要解析的文件,比如打包的时候依赖了三方库(jquyer、lodash)等,而这些三方库里面没有其他依赖,可以通过配置noParse不去解析文件,提高打包效率

  • 配置项

module: {
  noParse: '/jquery|lodash/'
}

IgnorePlugin

  • IgnorePlugin 是一个 webpack 内置的插件,用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去,比如我们使用 moment.js,直接引用后,里边有大量的 i18n 的代码,导致最后打包出来的文件比较大,而实际场景并不需要这些 i18n 的代码,这时可以通过webpack的IgnorePlugin忽略locale下的文件,使用时只加载需要的语言包
  • 配置项
// webapck.config.js
plugins: [ 
    new webpack.IgnorePlugin(/\.\/locale/, /moment/), 
]

// index.js
import moment from 'moment';
import 'moment/locale/zh-cn'; // 主动引入所需语言包
moment.locale('zh-cn'); // 设置语言 

Dllplugin

  • 通常来说,我们的代码分为业务代码和第三方库。如果不做处理,每次构建时都需要把所有的代码重新构建一次,耗费大量的时间。在我们的依赖版本没有升级的情况下,很多第三方库的代码并不会发生变更,这时就可以用到dll,把第三方模块打包到动态链接库中,每次构建只重新打包业务代码。

  • 使用dll时,可以把构建过程分成dll构建过程和主构建过程,所以需要两个构建配置文件,例如webpack.config.js和webpack.dll.config.js(只需要在版本升级的时候构建一次即可)。

  • 此插件会生成一个名为 manifest.json 的文件,这个文件是用于让 DllReferencePlugin 能够映射到相应的依赖上

  • 在项目build目录下新建一个webpack.config.dll.js,具体用法在webpack环境区分有讲解

Thread-loader

  • 多进程打包,提升打包速度

  • webpack4及以上其实已经融合了多线程机制(terser-webpack-plugin),因此针对小项目来说Thread-loader的作用不是很明显。

  • HappyPack 和 Thread-loader,由于 HappyPack 官方已经不再维护了,多进程这方面采用与其类似的 Thread-loader

  • 配置时需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行

  • 安装依赖

npm i thread-loader -D
  • 增加配置项
{
    test: /\.m?js$/,
    include: path.resolve(__dirname, 'src'),
    exclude: /node_modules/,
    use: [{
        loader: 'babel-loader',
        options: {
            presets: [['@babel/preset-env', {
                useBuiltIns: "entry",
                corejs: 3,
                targets: {
                    chrome: "58",
                    ie: "11"
                }
            }]],
            plugins: ["@babel/plugin-transform-runtime"]
        }
    }, {
        loader: "thread-loader", // 开启多进程,这种方法同样适用于css、vue等处理
    }]
},

HMR

  • Hot Module Replacement,也称为 HMR,是一种在应用程序运行时替换、添加或删除模块的技术,无需完全刷新页面就能更新这些模块

  • 修改 JS 文件,无需刷新页面,而能够直接在页面进行代码更新。

  • 修改 CSS 文件,无需刷新页面,改动的样式能直接呈现

  • webpack-dev-server默认会开启HMR,但需要做一些特殊的配置,而且HRM只能在开发环境中使用

  • webpack-dev-server开启HMR
// webpack.config.js
const webpack = require('webpack');

module.exports = {
    devServer: {
        hot: true
    },
    plugins: [
        // 模块热替换插件
        new webpack.HotModuleReplacementPlugin()
    ]
}

  • 业务代码中使用
// 入口文件中,index.js
if(module.hot) {
    module.hot.accept()
}

webpack环境区分

配置文件拆分

  • 目录结构
│  package-lock.json
│  package.json
│  postcss.config.js
│
├─build
│      webpack.config.base.js
│      webpack.config.dev.js
│      webpack.config.dll.js
│      webpack.config.prod.js
│
├─config
│      dev.env.js
│      prod.env.js
│      test.env.js
│
├─public
│  ├─lib
│  │      vendor.dll.dev.js
│  │      vendor.dll.prod.js
│  │      vendor.manifest.dev.json
│  │      vendor.manifest.prod.json
│  │
│  └─others
└─src
    │  a.js
    │  App.vue
    │  index.css
    │  index.html
    │  index.js
    │  other.js
    │
    ├─assets
    │  ├─fonts
    │  │      DIGITAL-Regular.ttf
    │  │
    │  ├─images
    │  │      logo.png
    │  │
    │  └─medias
    └─pages
            index.vue
  • 模板html修改(html-webpack-plugin默认支持此语法)
<title><%= htmlWebpackPlugin.options.title %></title>
  
<body>
    <div id="app"></div>
    <!-- 引入通过Dllplugin生成的第三方依赖包 -->
    <script type="text/javascript" src="<%= htmlWebpackPlugin.options.path %>"></script>
</body>
  • 配置文件的合并,需要通过 webpack-merge 插件
npm i webpack-merge -D
  • 定义多个配置文件,区分开发和生产环境

项目下新建build文件夹,并依次创建以下文件

  • webpack.config.base.js
  • webpack.config.dev.js
  • webpack.config.prod.js
  • webpack.config.dll.js
  • 修改打包命令脚本
"scripts": {
  "dev": "cross-env ENV_CONFIG=dev webpack-dev-server --config build/webpack.config.dev.js",
  "build": "cross-env ENV_CONFIG=prod webpack --config build/webpack.config.prod.js",
  "dll:dev": "cross-env ENV_CONFIG=dev webpack --config build/webpack.config.dll.js",
  "dll:prod": "cross-env ENV_CONFIG=prod webpack --config build/webpack.config.dll.js"
},

webpack.config.dll.js

/**
 * 提取第三方依赖,单独打包
 * 只有当相关依赖有更新、升级或着在该文件中添加vendor时,需重新打包
 * npm run dll:dev  开发环境使用
 * npm run dll:prod 线上环境使用
 * 打包后会在项目根目录下的public/lib文件夹下生成对应的清单及文件
 * 项目打包发布时会自动将public/lib文件夹拷贝至dist目录
 */
const path = require('path');
const webpack = require('webpack');
const TerserJSPlugin = require('terser-webpack-plugin');
const DllPlugin = require('webpack/lib/DllPlugin');
const envMode = process.env.ENV_CONFIG === 'dev' ? 'development' : 'production'
const dllMode = process.env.ENV_CONFIG
module.exports = {
    mode: envMode,
    entry: {
        vendor: [
            "core-js",
            "element-plus",
            "vue",
            "vue-router",
            "vuex"
        ]
    },
    output: {
        path: path.resolve(__dirname, `../public/lib`),
        filename: `[name].dll.${dllMode}.js`,  // 打包文件的名字
        library: '[name]_library'   // 暴露出的全局变量名
    },
    plugins: [
        new DllPlugin({
            path: path.resolve(__dirname, `../public/lib/[name].manifest.${dllMode}.json`), //描述生成的manifest文件
            name: '[name]_library', // 暴露出的全局变量名
        })
    ],
    optimization: {
        minimizer: [
            new TerserJSPlugin({        // 优化js
                parallel: true,
                extractComments: false, //不将注释提取到单独的文件中
                terserOptions: {
                    compress: {
                        drop_console: false,    // 移除console
                        drop_debugger: true,   // 移除debugger
                    },
                },
            }),
        ],
    },
}

webpack.config.base.js

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { VueLoaderPlugin } = require('vue-loader')
const webpack = require('webpack');
const envConfig = require(`../config/${process.env.ENV_CONFIG}.env.js`)

module.exports = {
    // 解析入口点(entry point)和加载器(loader)的上下文
    // 也即配置文件中的相对路径参照点,这里使用的是项目根目录
    context: path.resolve(__dirname, '../'),
    entry: {    // 配置多入口
        'home': './src/index.js',
        'other': './src/other.js'
    },
    output: {
        filename: 'js/[name].[contenthash].js',     // 文件添加hash戳
        path: path.resolve(__dirname, '../dist'),   // 在根目录生成一个dist的文件夹
        clean: true,    // 在生成文件之前清空 output 目录
    },
    plugins: [
        // 字符串务必用两个引号,如 '"production"'
        new webpack.DefinePlugin({
            _WBPACK_ENV_VARIABLE: JSON.stringify(envConfig)
        }),
        // vue-loader在15.*之后的版本需要添加此插件
        new VueLoaderPlugin()
    ],
    module: {
        rules: [
            // 务必把此loader放在第一个
            {
                test: /\.vue$/,
                use: [
                    {
                        loader: 'vue-loader',
                        options: {
                            hotReload: true     // 开启HMR
                        }
                    },
                    'thread-loader'
                ]
            },
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader',
                    'thread-loader'     // 使用多进程处理
                ]
            },
            {
                test: /\.less$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'postcss-loader',
                    'less-loader',
                    'thread-loader'     // 使用多进程处理
                ]
            },
            {
                test: /\.(png|jp?g|gif|svg)(\?.*)?$/,
                type: 'javascript/auto',
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 10 * 1024,
                            esModule: false,
                            outputPath: "images",
                        }
                    }
                ],
            },
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                type: 'javascript/auto',
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 10 * 1024,
                            esModule: false,
                            outputPath: "fonts",
                        }
                    }
                ],
            },
            {
                test: /\.(mp4?|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
                type: 'javascript/auto',
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 10 * 1024,
                            esModule: false,
                            outputPath: "medias",
                        }
                    }
                ],
            },
        ]
    },
    resolve: {
        modules: [path.resolve('node_modules')],        // 缩小查找范围
        extensions: ['.js', '.css', '.json', '.vue'],   // 引入这些类型的文件,可以不用写后缀
        alias: {    // 给路径起别名
            '@': path.resolve(__dirname, '../src'),
            '@assets': path.resolve(__dirname, '../src/assets'),
        }
    }
}

webpack.config.dev.js

const { merge } = require('webpack-merge')
const base = require('./webpack.config.base.js')
const webpack = require('webpack');
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const publicPath = '/'

module.exports = merge(base, {
    mode: 'development',
    devtool: 'source-map',
    target: 'web',
    output: {
        publicPath: publicPath, // 开发环境只能是/或者为空,否则热更新失效或无法访问
    },
    devServer: {
        port: 3001,             // 服务端口
        host: '0.0.0.0',        // 服务器可以被外部访问
        client: {
            progress: true,     // 在浏览器中以百分比显示编译进度
        },
        compress: true,         // 是否启用gzip压缩
        /**
         * 开发时,我们修改了其中一个模块代码,webpack 默认会将所有模块全部重新打包,速度很慢
         * 开启HMR后,只有改动的模块重新打包,其他的模块不变,这样打包速度就可以加快
         * hot: true|false  开启 HMR,默认是 true
         * 开发环境下,css 文件默认支持 HMR,是因为 style-loader 做了支持,
         * 如果使用 MiniCssExtractPlugin.loader 则 css 的 HMR 会失效,
         * 所以建议开发环境下使用 style-loader,生产环境下使用 MiniCssExtractPlugin.loader
        */
        hot: true,
        proxy: [{               // 配置代理信息
            context: ['/api'],
            target: 'http://localhost:3000',
        }],
    },
    plugins: [
        // 将打包后的js文件注入到html中,可通过chunks属性选择性注入
        // 如果有多个页面,需要写多个插件
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html',
            chunks: ['home'],       // 将对应的打包文件对号入座
            hash: true,             // 在引入js文件时,在路径后面添加hash戳
            inject: 'body',         // 打包文件插入位置 head或body
            title: 'webpack前端工程搭建',
            path: `${publicPath}lib/vendor.dll.dev.js`  // 引用动态链接库时使用
        }),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'main.html',
            chunks: ['other'],       
            hash: true,
            inject: 'body',
            title: '多页面打包测试',
            path: `${publicPath}/lib/vendor.dll.dev.js`
        }),
        new MiniCssExtractPlugin({
            // 低版本使用需要把[fullhash]替换为[hash]
            // 开发环境热更新不兼容,去掉fullhash就好了,不知道为什么???
            filename: 'css/[name].css' //: '[name].[fullhash].css'
        }),
        // 将那些文件拷贝到打包后的文件夹中
        new CopyWebpackPlugin({
            patterns: [
                {
                    from: './public',
                    to: './',
                    globOptions: {
                        ignore: [
                            '**/vendor.dll.prod.js',
                            '**/vendor.manifest.prod.json'
                        ],
                    },
                },
            ],
        }),
        // 告诉webpack那些库不参与打包
        new webpack.DllReferencePlugin({
            manifest: path.resolve(__dirname, `../public/lib/vendor.manifest.dev.json`)
        }),
        // 热更新模块配置
        new webpack.HotModuleReplacementPlugin()
    ]
})

webpack.config.prod.js

const { merge } = require('webpack-merge')
const base = require('./webpack.config.base.js')
const path = require('path');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
/**
 * 
 * 打包后的文件添加公共前缀,可以是相对路径、绝对路径或域名
 * 绝对路径:'/dist/',部署服务器如nginx、或live-server等
 * 相对路径:'./',可通过浏览器直接打开访问(相关loader需要添加publicPath)
 * 域名:'http://www.xx.com/',域名访问
 * 
 */
const publicPath = '/dist/'

module.exports = merge(base, {
    mode: 'production',
    devtool: 'eval-source-map',
    output: {
        publicPath: publicPath,
    },
    optimization: {
        minimizer: [
            new CssMinimizerPlugin({        // 优化css
                parallel: true,             // 多进程并发执行,提升构建速度
            }),
            new TerserJSPlugin({            // 优化js
                parallel: true,             // 多进程并发执行,提升构建速度
                extractComments: false,     // 是否将注释提取到单独的文件中
                terserOptions: {
                    compress: {
                        drop_console: false,   // 移除console
                        drop_debugger: true,   // 移除debugger
                    },
                },
            }),
        ],
        // 分割代码块
        // splitChunks: {
        //     /*
        //         initial 入口 chunk,对于异步导入的文件不处理
        //         async 异步 chunk,只对异步导入的文件处理
        //         all 全部 chunk
        //     */
        //     chunks: 'all',
        //     // 缓存分组
        //     cacheGroups: {
        //         // 第三方模块
        //         vendor: {
        //             name: 'vendor', // chunk 名称
        //             priority: 1, // 权限更高,优先抽离
        //             test: /node_modules/,  // 一般第三方模块都在node_modules文件夹
        //             minSize: 0,  // 大小限制
        //             minChunks: 1  // 最少复用过几次
        //         },
        //         // 公共的模块
        //         common: {
        //             name: 'common', // chunk 名称
        //             priority: 0, // 优先级
        //             minSize: 0,  // 公共模块的大小限制
        //             minChunks: 2  // 公共模块最少复用过几次
        //         }
        //     }
        // }
    },
    module: {
        rules: [
            // 为提升编译速度,开发环境可以不启用babel转换,只在生产环境使用
            {
                test: /\.m?js$/,
                include: path.resolve(__dirname, '../src'),
                exclude: /node_modules/,
                use: [{
                    loader: 'babel-loader',
                    options: {
                        presets: [['@babel/preset-env', {
                            useBuiltIns: "entry",
                            corejs: 3,
                            targets: {
                                chrome: "58",
                                ie: "11"
                            }
                        }]],
                        plugins: ["@babel/plugin-transform-runtime"]
                    }
                }, {
                    loader: "thread-loader",    // 使用多进程处理
                }]
            }],
    },
    plugins: [
        // 将打包后的js文件注入到html中,可通过chunks属性选择性注入
        // 如果有多个页面,需要写多个插件
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'index.html',
            //对打包的html页面进行最小化操作
            minify: {
                removeAttributeQuotes: true,    //删除属性的双引号
                collapseWhitespace: true        //所有代码一行显示,折叠空行
            },
            chunks: ['home'],       // 将对应的打包文件对号入座
            hash: true,             // 在引入js文件时,在路径后面添加hash戳
            inject: 'body',         // 打包文件插入位置 head或body
            title: 'webpack前端工程搭建',
            path: `${publicPath}lib/vendor.dll.prod.js`  // 引用动态链接库时使用
        }),
        new HtmlWebpackPlugin({
            template: './src/index.html',
            filename: 'main.html',
            chunks: ['other'],          // 将对应的打包文件对号入座
            hash: true,                 //在引入js文件时,在路径后面添加hash戳
            inject: 'body',
            title: '多页面打包测试',
            path: `${publicPath}/lib/vendor.dll.prod.js`  // 引用动态链接库时使用
        }),
        new MiniCssExtractPlugin({
            // 解决和开发环境不兼容问题
            filename: 'css/[name].[fullhash].css'
        }),
        // 将那些文件拷贝到打包后的文件夹中
        new CopyWebpackPlugin({
            patterns: [
                {
                    from: './public',
                    to: './',
                    globOptions: {
                        ignore: [
                            '**/vendor.dll.dev.js',
                            '**/vendor.manifest.dev.json'
                        ],
                    },
                },
            ],
        }),
        // 告诉webpack那些库不参与打包
        new webpack.DllReferencePlugin({
            manifest: path.resolve(__dirname, `../public/lib/vendor.manifest.prod.json`)
        }),
    ]
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青春~不散

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值