webpack4基础配置

87 阅读1分钟

前言

webpack就是一个函数,这个函数参数包含了一系列的配置,webpack根据这些配置执行查找、编译、转换、合并、压缩等一系列操作。

项目打包配置组成

1、config.js----根据环境区分打包配置

'use strict'
const path = require('path')
const API= ['user', 'proxy']
let proxy = {}
API.forEach(function (api) {
    proxy['/' + api] = {
        target: 'https://baidu.com',
        changeOrigin: true,
        pathRewrite: {}
    }
    proxy['/' + api].pathRewrite['^/' + api] = '/' + api})
// 是否构建生产包
const buildForProduction = process.argv[2].indexOf('prod') >= 0
// 前端灰度目录
const fv = process.argv[3] === 'v0' ? '' : `${process.argv[3]}/`
const output = 'admin'
const getLocalIPv4 = () => {
    const networkInterfaces = require('os').networkInterfaces()
    for(const name in networkInterfaces){
        const networkInterface = networkInterfaces[name]
        for(let i = 0; i < networkInterface.length; i++){
            const alias = networkInterface[i]
            if(alias.family === 'IPv4' && alias.address !== '127.0.0.1' 
&& !alias.internal){
                return alias.address
            }
        }
    }
    return 'localhost'
}

module.exports = {
    dev: {
        assetsSubDirectory: 'static',
        assetsPublicPath: '/',
        proxyTable: proxy,
        host: 'localhost',
        port: 8081,
        autoOpenBrowser: false,
        errorOverlay: true,
        notifyOnErrors: true,
        poll: false,
        useEslint: true,
        showEslintErrorsInOverlay: true,
        devtool: 'cheap-module-eval-source-map',
        cacheBusting: true,
        cssSourceMap: true
    },
    build: {
        // index.html路径
        index: path.resolve(__dirname, `../base/${output}/index.html`),
        // 资源路径
        assetsRoot: path.resolve(__dirname, `../base/${output}`),
        assetsSubDirectory: 'static',
        assetsPublicPath: buildForProduction ? 
`//baidu.com/${fv}base/${output}/` : `//baidu.com/${fv}base/${output}/`,
        productionSourceMap: true, // 生产测试都需要打出map文件
        devtool: buildForProduction? false : 'eval-source-map',
        // 请先安装npm install --save-dev compression-webpack-plugin
        productionGzip: true, // 先不压缩文件包为zip
        productionGzipExtensions: ['js', 'css'],
        needWorkboxSW: true, // 是否需要 workbox 配置 service worker
        workboxConfig: null, // workbox 配置 null 表示默认配置
        // 分析打包产物:`npm run build --report`
        // npm run build --report dev v1 2023090101
        bundleAnalyzerReport: process.env.npm_config_report
    },
    buildForProduction
}

2、build.js----webpack函数调用

'use strict'
require('./check-versions')()
process.env.NODE_ENV = 'production'
const ora = require('ora')
const rm = require('rimraf')
const path = require('path')
const chalk = require('chalk')
const webpack = require('webpack')
const config = require('./config')
const webpackConfig = require('./webpack.prod.conf')
const gulp = require('gulp')
const pump = require('pump')
const inject = require('gulp-inject-string')

// npm run build prod v0 2023090101
const projectVersion = process.argv[3] === 'v0' ? '' : `${process.argv[3]}`
const projectVersionPath = projectVersion === '' ? '' 
: '/' + projectVersion

const buildConfig = new Map([
    ['dev', {
        html: `<script src="//baidu.com/assets/sdks/consolex.min.js"></script>`,
        ft: process.argv[4] || new Date().getTime()
    }],
    ['prod', {
        html: `<!-- ${new Date().toString()} -->`,
        ft: process.argv[4] || new Date().getTime()
    }]]).get((process.argv[2].split('@'))[0] || 'prod')

const workboxHOST = process.argv[2].indexOf('prod') >= 0 
? '//baidu.com' 
: '//baidu.com'

const serviceworkerHOST = process.argv[2].indexOf('prod') >= 0 
? '/baidu.com' 
: '//baidu.com'const output = 'base'// 修改前端目录,v1,v2,v0代表根目录

pump(
    gulp.src(path.resolve(__dirname, '../src/router/index.js')),
    inject.replace(/base: '\/.*\/*base/, `base: '${projectVersionPath}/admin`),
    inject.replace(`base`, output),
    gulp.dest(path.resolve(__dirname, '../src/router/')))

pump(
    gulp.src(path.resolve(__dirname, '../src/assets/scripts/common/vp.js')),
    inject.replace('window.ft = ', `window.ft = '${buildConfig.ft}' // `),
    gulp.dest(path.resolve(__dirname, '../src/assets/scripts/common/'))
)

const spinner = ora('building for production...')
spinner.start()

rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
    if (err) throw err
    webpack(webpackConfig, (err, stats) => {
        spinner.stop()
        if (err) throw err
        process.stdout.write(stats.toString({
            colors: true,
            modules: false,
            children: false,
             chunks: false,
            chunkModules: false
        }) + '\n\n')
        if (stats.hasErrors()) {
            console.log(chalk.red('Build failed with errors.\n'))
            process.exit(1)
        }

        pump(
            gulp.src(path.resolve(__dirname, `../admin/${output}/index.html`)),
            inject.replace('<div id=app></div>', `<div id=app></div>${buildConfig.html}`),
            inject.replace('service-worker.js', `${serviceworkerHOST}${projectVersionPath}/admin/${output}/service-worker.js?ft=${buildConfig.ft}`),
            gulp.dest(path.resolve(__dirname, `../admin/${output}/`))
        )

        pump(
            gulp.src(path.resolve(__dirname, `../admin/${output}/service-worker.js`)),
            inject.replace('workbox.core.setCacheNameDetails', 
`workbox.setConfig({modulePathPrefix: '${workboxHOST}${projectVersionPath}/assets/sdks/workbox', 
debug: ${process.argv[2].indexOf('dev') >= 0}});\nworkbox.core.setCacheNameDetails`),
            gulp.dest(path.resolve(__dirname, `../admin/${output}/`))
        )

        console.log(chalk.cyan('  Build complete.\n'))

        console.log(chalk.yellow(
            '  Tip: built files are meant to be served over an HTTP server.\n' +
            '  Opening index.html over file:// won\'t work.\n'
        ))

        console.warn(chalk.yellow('-打包成功了-\n'))
    })})

3、webpack.base.conf.js----webpack基础配置

'use strict'
const path = require('path')
const utils = require('./utils')
const config = require('../config')
const vueLoaderConfig = require('./vue-loader.conf')
const webpack = require('webpack')
const { getGray } = require('../../assets/scripts/util')
const buildForProduction = process.argv[2].indexOf('prod') >= 0
function resolve(dir) {
    return path.join(__dirname, '..', dir)
}
module.exports = {
    context: path.resolve(__dirname, '../'),
    entry: {
        app: ['babel-polyfill', './src/index.js']
    },
    output: {
        path: config.build.assetsRoot,
        filename: '[name].js',
        publicPath: process.env.NODE_ENV === 'production'
            ? config.build.assetsPublicPath
            : config.dev.assetsPublicPath
    },
    resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            'vue$': 'vue/dist/vue.esm.js',
            '@': resolve('src'),
            'STATIC': resolve('static')
        }
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: vueLoaderConfig
            },
            {
                test: /\.js$/,
                loader: 'babel-loader',
                include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
            },
            {
                test: /\.svg$/,
                loader: 'svg-sprite-loader',
                include: [resolve('src/assets/svg')],
                options: {
                    symbolId: '[name]'
                }
            },
            {
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                include: [resolve('src/assets/images/big-page')],
                loader: 'url-loader',
                options: {
                    limit: 10,
                    publicPath: buildForProduction ? 'https://baidu.com' : 'https://baidu.test.com',
                    name: 'assets/images/projectName/big-page/[name].[ext]'
                }
            },
            {
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                exclude: [resolve('src/assets/svg')],
                loader: 'url-loader',
                options: {
                    limit: 1024,
                    name: utils.assetsPath('img/[name].[hash:7].[ext]')
                }
            },
            {
                test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
                loader: 'url-loader',
                options: {
                    limit: 10000,
                    name: utils.assetsPath('media/[name].[hash:7].[ext]')
                }
            },
            {
                test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
                loader: 'url-loader',
                options: {
                    limit: 10000,
                    name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
                }
            }
        ]
    },
    node: {
        // prevent webpack from injecting useless setImmediate polyfill because Vue
        // source contains it (although only uses it if it's native).
        setImmediate: false,
        // prevent webpack from injecting mocks to Node native modules
        // that does not make sense for the client
        dgram: 'empty',
        fs: 'empty',
        net: 'empty',
        tls: 'empty',
        child_process: 'empty'
    },
    plugins: [
        new webpack.DefinePlugin({
            'PROJECT_GRAY': JSON.stringify(getGray(process.argv[3])),
            'TIME_STAMP': new Date().getTime()
        })
    ],
    externals: {}
}

4、webpack.prod.conf.js----webpack生产环境配置

'use strict'
const path = require('path')
const utils = require('./utils')
const webpack = require('webpack')
const config = require('../config')
const merge = require('webpack-merge')
const baseWebpackConfig = require('./webpack.base.conf')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const WorkboxPlugin = require('workbox-webpack-plugin')
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')

// 打包信息展示插件
const reportPlugin = []
let workboxHOST = process.argv[2].indexOf('prod') >= 0 ? '//baidu.com' : '//baidu.test.com'
const workboxPATH = process.argv[3] === 'v0' ? '' : `/${process.argv[3]}`
const output = 'admin'

// 打包分离map插件需要区分生产/测试环境
 const buildEnv = process.argv[2].indexOf('prod') >= 0 ? 'production' : 'test'
if (config.build.bundleAnalyzerReport) {
    const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
    reportPlugin.push(new BundleAnalyzerPlugin({ analyzerPort: 2018 }))
}
const webpackConfig = merge(baseWebpackConfig, {
    mode: 'production',
    module: {
        rules: utils.styleLoaders({
            sourceMap: config.build.productionSourceMap,
            extract: true,
            usePostCSS: true
        })
    },
    devtool: config.build.productionSourceMap ? config.build.devtool : false,
    output: {
        path: config.build.assetsRoot,
        filename: utils.assetsPath('js/[name].[chunkhash].js'),
        chunkFilename: utils.assetsPath('js/[name].[chunkhash].js')
    },
    optimization: {
        runtimeChunk: {
            name: 'manifest'
        },
        minimizer: [
            new UglifyJsPlugin({
                cache: true,
                parallel: true,
                sourceMap: config.build.productionSourceMap,
                uglifyOptions: {
                    warnings: false
                }
            }),
            new OptimizeCSSPlugin({
                cssProcessor: require('cssnano'),
                canPrint: true,
                cssProcessorOptions: config.build.productionSourceMap
                    ? {
                        parser: require('postcss-safe-parser'),
                        map: { inline: false },
                        autoprefixer: true,
                        // 避免 cssnano 重新计算z-index
                        safe: true
                    }
                    :
                    {
                        parser: require('postcss-safe-parser'),
                        autoprefixer: true,
                        safe: true
                    }
            })
        ],
        splitChunks: {
            chunks: 'all', // initial(初始块)、async(按需加载块)、all(全部块)
            minSize: 20000,
            minChunks: 1,
            maxAsyncRequests: 1024,
            maxInitialRequests: 1024,
            name: true,
            cacheGroups: {
                default: false,
                vue: {
                    name: 'vue',
                    chunks: 'initial',  // 表示显示块的范围,有三个可选值:initial(初始块)、async(按需加载块)、all(全部块,默认)
                    minChunks: 1,
                    priority: 3,
                    reuseExistingChunk: false,
                    test: /vue/
                },
                router: {
                    name: 'router',
                    chunks: 'initial',
                    minChunks: 1,
                    priority: 3,
                    reuseExistingChunk: false,
                    test: /router|service/
                },
                vendorCore: {
                    name: 'vendor-core',
                    chunks: 'initial',
                    minChunks: 1,
                    priority: 3,
                    reuseExistingChunk: true,
                    test: /core-js/
                },
                vendor: {
                    name: 'vendor',
                    chunks: 'initial',
                    minChunks: 1,
                    priority: 2,
                    reuseExistingChunk: true,
                    test: /[\\/]node_modules[\\/]/
                },
                assets: {
                    name: 'assets',
                    chunks: 'all',
                    minChunks: 2,
                    priority: 3,
                    reuseExistingChunk: true,
                    test: path.resolve('src/assets')
                },
                swiper: {
                    name: 'swiper',
                    chunks: 'initial',
                    minChunks: 1,
                    priority: 3,
                    reuseExistingChunk: true,
                    test: /swiper|bscroll/i
                },
                components: {
                    name: 'components',
                    chunks: 'all',
                    minChunks: 2,
                    priority: 3,
                    reuseExistingChunk: true,
                    test: path.resolve('src/components')
                },
                styles: {
                    name: 'styles',
                    chunks: 'all',
                    minChunks: 2,
                    reuseExistingChunk: true,
                    enforce: true,
                    test: /\.(styl|scss|css)$/
                }
            }
        }
    },
    plugins: [
        new CleanWebpackPlugin([`${output}`], { root: path.resolve('') }),
        // http://vuejs.github.io/vue-loader/en/workflow/production.html
        new MiniCssExtractPlugin({
            filename: utils.assetsPath('css/[name].[contenthash:12].css'),
            allChunks: true,
            ignoreOrder: true   // Enable to remove warnings about conflicting order
        }),
        // generate dist index.html with correct asset hash for caching.
        // you can customize output by editing /index.html
        // see https://github.com/ampedandwired/html-webpack-plugin
        new HtmlWebpackPlugin({
            filename: config.build.index,
            template: 'index.html',
            inject: true,
            minify: {
                removeComments: true,
                collapseWhitespace: true,
                removeAttributeQuotes: true
                // more options:
                // https://github.com/kangax/html-minifier#options-quick-reference
            },
            // necessary to consistently work with multiple chunks via CommonsChunkPlugin
            chunksSortMode: 'dependency',
            isProduction: config.buildForProduction
        }),
        // keep module.id stable when vendor modules does not change
        new webpack.HashedModuleIdsPlugin(),
        // enable scope hoisting
        new webpack.optimize.ModuleConcatenationPlugin(),
        // copy custom static assets
        new CopyWebpackPlugin([
            {
                from: path.resolve(__dirname, '../static'),
                to: config.build.assetsSubDirectory,
                ignore: ['.*']
            }
        ]),
        new WorkboxPlugin.GenerateSW({
            mode: 'production',
            skipWaiting: true,
            clientsClaim: true,
            cleanupOutdatedCaches: true,  // 清理过期缓存
            swDest: 'service-worker.js',  // 输出 Service worker 文件
            navigateFallback: `${workboxHOST}${workboxPATH}/admin/index.html`,  // 当路由匹配到一个未缓存的页面时重定向的路由
            navigateFallbackAllowlist: [/\/admin(\/[\w-]*)+(\?.*)?$/i],  // 未缓存页面重定向路由适用范围,白名单
            navigateFallbackDenylist: [/^[^?]+\.(css|js|png|gif|jpg|eot|svg|ttf|woff|woff2|json|html)(\?.+)?$/i],
            cacheId: 'admin',
            maximumFileSizeToCacheInBytes: 3 * 1024 * 1024, // 3M
            exclude: [/index\.html/i, /\.map/i, /(assets|static)\/.*/], // 排除 html (webpack 生成的为 cdn 地址,所以需要排除)、map 文件
            additionalManifestEntries: [{
                url: `${workboxHOST}${workboxPATH}/admin/index.html`,
                revision: `${new Date().getTime()}`
            }], // 补充预缓存 index.html 文件
            runtimeCaching: [
                {
                    // 缓存assets目录下JS资源
                    urlPattern: /^(https:)?\/\/(baidu)\.com\/assets\/.+\.min\.js(\?.+)?$/i,
                    handler: 'StaleWhileRevalidate',
                    options: {
                        cacheName: 'admin-assets-js',
                        cacheableResponse: {
                            statuses: [0, 200]
                        }
                    }
                },
                {
                    // 缓存assets目录下json资源
                    urlPattern: /^(https:)?\/\/(baidu)\.com\/assets\/.+\.json(\?.+)?$/i,
                    handler: 'NetworkFirst',
                    options: {
                        cacheName: 'admin-assets-json',
                        networkTimeoutSeconds: 2,
                        cacheableResponse: {
                            statuses: [0, 200]
                        }
                    }
                },
                {
                    // 缓存strapi接口
                    urlPattern: /^(https:)?\/\/(www|baidu)\.com\/(strapi|uploads)\/.+(\?.+)?$/i,
                    handler: 'NetworkFirst',
                    options: {
                        cacheName: 'admin-strapi',
                        networkTimeoutSeconds: 2,
                        cacheableResponse: {
                            statuses: [200]
                        }
                    }
                },
                {
    // 项目下的 图片、字体、css、js 等资源
                    urlPattern: /^(https:)?\/\/(baidu)\.com\/v(\-)?\d+\/admin\/static(\/[\w-]+)*\/(\w|\-|\~)+\.\w+\.(css|js|png|gif|jpg|eot|svg|ttf|woff|woff2)(\?.+)?$/i,
                    handler: 'NetworkFirst',
                    options: {
                        cacheName: 'admin-cache-static',
                        cacheableResponse: {
                            statuses: [0, 200]
                        },
                        expiration: {
                            maxEntries: 120, // 缓存最大数量
                            maxAgeSeconds:  7 * 24 * 60 * 60, // 缓存过期时间为 7 天
                        }
                    }
                },
                {    // 项目下的libs资源
                    urlPattern: /\/admin\/static\/libs\/[\.\w\-\~]+?\.js(\?.+)?$/i,
                    handler: 'StaleWhileRevalidate',
                    options: {
                        cacheName: 'admin-cache2-static',
                        cacheableResponse: {
                            statuses: [200]
                        }
                    }
                },
                {
                    // 业务相关图片
                    urlPattern: /(https?:)?\/\/baidu.com(:\d+)?.*\.(jpg|png|jpeg|gif)\??.*$/,
                    handler: 'NetworkFirst',
                    options: {
                        cacheName: 'admin-external-static',
                        cacheableResponse: {
                            statuses: [0, 200]
                        },
                        expiration: {
                            maxEntries: 120, // 缓存最大数量
                            maxAgeSeconds:  7 * 24 * 60 * 60, // 缓存过期时间为 7 天
                        }
                    }
                },
                {    // 其它 css、js、html、json等资源
                    urlPattern: /\/(\w|\d|-)+\.(css|js|json|html)(\?.+)?$/i,
                    handler: 'NetworkFirst',
                    options: {
                        cacheName: 'admin-net-static',
                        networkTimeoutSeconds: 2,
                        cacheableResponse: {
                            statuses: [200]
                        }
                    }
                },
                {    // 其它 图片、字体等资源
                    urlPattern: /\/(\w|\d|-)+\.(png|gif|jpg|eot|svg|ttf|woff|woff2)(\?.+)?$/i,
                    handler: 'CacheFirst',
                    options: {
                        cacheName: 'admin-cache-static',
                        expiration: {
                            maxEntries: 100, // 缓存最大数量
                            maxAgeSeconds:  7 * 24 * 60 * 60, // 缓存过期时间为 7 天
                        },
                    }
                }
            ]
        }),
        new HardSourceWebpackPlugin({
            configHash: function(webpackConfig) {
                return require('node-object-hash')({sort: false}).hash(webpackConfig);
            },
            environmentHash: {
                root: process.cwd(),
                directories: [],
                files: ['package-lock.json', 'yarn.lock'],
            },
            //自动清除缓存
            cachePrune: {
                //缓存最长时间(默认7天)
                maxAge: 7 * 24 * 60 * 60 * 1000
            }
        }),
        ...reportPlugin
    ]})
if (config.build.productionGzip) {
    const CompressionWebpackPlugin = require('compression-webpack-plugin')
    webpackConfig.plugins.push(
        new CompressionWebpackPlugin({
            asset: '[path].gz[query]',
            algorithm: 'gzip',
            test: new RegExp('\\.(' + config.build.productionGzipExtensions.join('|') + ')$'),
            threshold: 10240,
            minRatio: 0.8
        })
    )}
module.exports = webpackConfig

参考:

Webpack 4 配置最佳实践

详解webpack4之splitchunksPlugin代码包分拆

Webpack之SplitChunks插件用法详解

如何使用 splitChunks 精细控制代码分割

webpack4 SplitChunks实现代码分隔详解

webpack的异步加载原理及分包策略

关于webpack4的14个知识点,童叟无欺

带你深度解锁Webpack系列(进阶篇)

从零开始配置webpack(基于webpack 4 和 babel 7版本)

webpack4 多页面,多环境配置

「一劳永逸」由浅入深配置webpack4

webpack4配置详解之慢嚼细咽

webpack性能优化(2):splitChunks用法详解

webpack之optimization.runtimeChunk作用

useless-files-webpack-plugin 打包工具使用