config.js

const webpack = require('webpack')
const BundlePlugin = require('webpack-bundle-analyzer')
const CompressionPlugin = require('compression-webpack-plugin')

const envTime = new Date().getTime()
const envNode = process.env.VUE_APP_MODE
const product = Boolean(envNode === 'pro')

// CDN引入文件配置列表
const scriptCdn = {
  css: ['//cdn.jsdelivr.net/npm/vant@2.12.37/lib/index.min.css'],
  js: [
    '//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js',
    '//cdn.jsdelivr.net/npm/vue-router@3.2.0/dist/vue-router.min.js',
    '//cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js',
    '//cdn.jsdelivr.net/npm/axios@0.24.0/dist/axios.min.js',
    '//cdn.jsdelivr.net/npm/vant@2.12.3/lib/vant.min.js',
    '//cdn.jsdelivr.net/npm/weixin-js-sdk@1.6.0/index.min.js',
    '//cdn.jsdelivr.net/npm/echarts@5.2.2/dist/echarts.min.js',
    '//cdn.jsdelivr.net/npm/node-sass@6.0.1/lib/index.min.js',
    '//cdn.jsdelivr.net/npm/html2canvas@1.3.3/dist/html2canvas.min.js',
    '//cdn.jsdelivr.net/npm/signature_pad@3.0.0-beta.4/dist/signature_pad.umd.min.js',
    '//cdn.jsdelivr.net/npm/vuex-persistedstate@4.1.0/dist/vuex-persistedstate.min.js'
  ]
}

module.exports = {
  publicPath: '/gdyb-h5/',
  assetsDir: 'static',
  filenameHashing: false,
  productionSourceMap: product,
  css: {
    sourceMap: envNode === 'sit',
    loaderOptions: {
      sass: {
        additionalData: `
          @import "@/assets/scss/variable.scss";
          @import "@/assets/scss/mixin.scss";
        `
      }
    },
    extract: product ? { ignoreOrder: true } : false
  },
  pages: {
    index: {
      entry: './src/main.js',
      template: './public/index.html',
      filename: 'index.html',
      title: 'gdyb-h5',
      chunks: ['chunk-vendors', 'chunk-common', 'index'],
      cdn: scriptCdn
    }
  },
  configureWebpack: (config) => {
    // 配置CDN,打包忽略文件
    config.externals = {
      vue: 'Vue',
      vuex: 'Vuex',
      axios: 'axios',
      vant: 'vant',
      echarts: 'echarts',
      'node-sass': 'node-sass',
      'vue-router': 'VueRouter',
      html2canvas: 'html2canvas',
      'signature-pad': 'SignaturePad',
      'weixin-js-sdk': 'weixin-js-sdk',
      'vuex-persistedstate': 'vuex-persistedstate'
    }

    // 文件添加时间戳
    config.output = Object.assign(config.output, {
      filename: `static/js/[name]-${envTime}.js`,
      chunkFilename: `static/js/[name]-${envTime}.js`
    })

    const compression = new CompressionPlugin({
      algorithm: 'gzip',
      filename: '[path].gz[query]',
      test: /\.js$|\.html$|\.css$/,
      minRatio: 1,
      threshold: 10240,
      deleteOriginalAssets: false
    })

    const limitchuck = new webpack.optimize.LimitChunkCountPlugin({
      maxChunks: 10,
      minChunkSize: 1000
    })

    const bundleAnalyzer = new (BundlePlugin.BundleAnalyzerPlugin)()

    config.plugins.push(compression, limitchuck, bundleAnalyzer)
    config.entry.app = ['babel-polyfill', './src/main.js']
  },
  devServer: {
    host: 'localhost',
    port: 8080,
    proxy: process.env.VUE_APP_BASE_API
  }
}

index.html(src/public/index)

<!DOCTYPE html>
<html lang="">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="renderer" content="webkit">
    <meta name="referrer" content="never">
    <meta name="x5-orientation" content="portrait">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0,minimum-scale=1.0,maximum-scale=1.0">
    <title>xxxxxxx</title>
    <% for (const i in htmlWebpackPlugin.options.cdn.css) { %>
        <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
    <% } %>
</head>
<body>

<noscript>
    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
        Please enable it to continue.</strong>
    </noscript>

    <div id="app"></div>
    <!-- built files will be auto injected -->

    <% for (const i in htmlWebpackPlugin.options.cdn.js) { %>
        <script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
    <% } %>
</body>
</html>

报错信息汇总:


// compression-webpack-plugin版本太高,建议使用@5.0.1
ERROR TypeError:Cannot read property ‘tapPromise‘ of undefined


// 代码分析报告必须使用new BundleAnalyzerPlugin()
TypeError: Class constructor BundleAnalyzerPlugin cannot be invoked without 'new';


// 端口占用错误提示,杀掉占用端口即可
throw er; // Unhandled 'error' event
Error: listen EADDRINUSE: address already in use 127.0.0.1:8888


// 计算文件性能入口文件大小限制
The following entrypoint(s) combined asset size exceeds the recommended limit


// mini-css-extract-plugin,css引入顺序不一致
chunk chunk-common [mini-css-extract-plugin] Conflicting order.

推荐阅读相关[mini-css-extract-plugin]问题解析:
https://laysent.com/til/2019-11-28_conflicting-order-in-mini-css-extract-plugin