前言:

一直以来都没用上多页面打包,这次遇到个业务特殊情况,想到用多页面打包的方案来解决,于是按照vue cli 官方文档在自己的项目上配置跑了一下,果然一跑就是一堆问题。只能说事情总是比我们想象的要曲折的多。然后捯饬半天,算是搞定了。想想网上很多配置也是星星点点,很碎,说的也不全,希望后面有需要的人看到能少踩坑,也方便自己不时之需,于是心一狠就有了这篇文章~。好了,话不多说,进入正题

1. 按照官网说明,先贴出自己 vue.config.js 里的pages配置
let pages = {
  index: {
    // page 的入口
    entry: 'src/main.js',
    // 模板来源
    template: 'public/index.html',
    // 在 dist/index.html 的输出
    filename: 'index.html',
    // 当使用 title 选项时,
    // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
    // title: 'Index Page',
    // 在这个页面中包含的块,默认情况下会包含
    // 提取出来的通用 chunk 和 vendor chunk。
    chunks: ['chunk-libs', 'chunk-vant', 'chunk-commons', 'runtime', 'index'],
  },
  subpage: {
    entry: 'src/main.js',
    template: 'public/index-wx.html',
    filename: 'index-wx.html',
    chunks: ['chunk-libs', 'chunk-vant', 'chunk-commons', 'runtime', 'subpage'],
  },
}

module.exports = {
	// 其他配置项...
	pages
}
2. 这里要注意点就是 chunks 这个属性,我也是主要在这踩了半天

如果你打包配置了代码分割splitChunks,就是有下面这样的配置

config.optimization.splitChunks({
        chunks: 'all',
        cacheGroups: {
          libs: {
            name: 'chunk-libs',
            test: /[\\/]node_modules[\\/]/,
            priority: 10,
            chunks: 'initial', // only package third parties that are initially dependent
          },
          elementUI: {
            name: 'chunk-vant', // split vant into a single package
            priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
            test: /[\\/]node_modules[\\/]_?vant(.*)/, // in order to adapt to cnpm
          },
          commons: {
            name: 'chunk-commons',
            test: resolve('src/components'), // can customize your rules
            minChunks: 3, //  minimum common number
            priority: 5,
            reuseExistingChunk: true,
          },
        },
      })

      config.optimization.runtimeChunk('single')

那么上面那个chunks数组里一定要配置splitChunks里面的name的值,可以对照上面示例看看,
另外就是这个单独的runtime,如果你配置了单独内嵌vue 的runtime,这个也一定要写到chunks里面,不然页面也是白屏或者其他问题。至于提取runtime的配置,这里不做说明,这个网上很多,后面我会贴出我整体的vue.config.js的配置,需要的可以瞅瞅

3. 另外一点要注意的就是,这里引用下官网原文:

当在 multi-page 模式下构建时,webpack 配置会包含不一样的插件 (这时会存在多个 html-webpack-plugin
和 preload-webpack-plugin 的实例)。如果你试图修改这些插件的选项,请确认运行 vue inspect。

具体到代码里就是和那两个插件相关的配置要结合多页面pages里面的名称使用,不明白什么意思的直接看下面代码配置示例:

Object.keys(pages).forEach((element) => {
        config.plugin('preload-' + element).tap(() => [
          {
            rel: 'preload',
            // to ignore runtime.js
            fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
            include: 'initial',
          },
        ])
        config
          .plugin('ScriptExtHtmlWebpackPlugin')
          .after('html-' + element)
          .use('script-ext-html-webpack-plugin', [
            {
              // `runtime` must same as runtimeChunk name. default is `runtime`
              inline: /runtime\..*\.js$/,
            },
          ])
          .end()
        config.plugins.delete('prefetch-' + element)
      })

原来没有多页面配置时,是这样的:

        config.plugin('preload').tap(() => [
          {
            rel: 'preload',
            // to ignore runtime.js
            fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
            include: 'initial',
          },
        ])
        config
          .plugin('ScriptExtHtmlWebpackPlugin')
          .after('html')
          .use('script-ext-html-webpack-plugin', [
            {
              // `runtime` must same as runtimeChunk name. default is `runtime`
              inline: /runtime\..*\.js$/,
            },
          ])
          .end()
        config.plugins.delete('prefetch')
      })

对比下就知道了,多页面配置下,html插件的实例名称会和页面名称结合变成html-indexhtml-subpage这种形式,preload插件也是如此

然后到这基本要说的和注意点就这么多了,其他的都很简单,看看文档就明白了,跑起来应该没问题了。我这里只用了两个页面打包配置,要是有更多的页面,配置也是大同小异,只要注意好上面这几点就行了。还有及时多页面的cdn什么的配置,我这暂时没用上,就不说了,网上也多,如果后面踩坑了再给来更新…。如果我说的对你有帮助,方便帮我点个赞哦~

最后,贴上目前项目使用的整体vue.config.js配置,各位看官好有个整体感知
const path = require('path')

const resolve = (dir) => {
  return path.join(__dirname, dir)
}

//读取npm指令 process.argv 获取⾃定义参数
let argvs = process.argv.filter((e) => e.includes('='))
for (const iterator of argvs) {
  let diyArg = iterator.split('=')
  process.env['VUE_APP_' + diyArg[0].slice(2)] = diyArg[1]
}
process.env.VUE_APP_ENV = process.env.VUE_APP_ENV || process.env.NODE_ENV
const env = require('./env')

process.env.VUE_APP_TITLE = require('./package.json').description

let pages = {
  index: {
    // page 的入口
    entry: 'src/main.js',
    // 模板来源
    template: 'public/index.html',
    // 在 dist/index.html 的输出
    filename: 'index.html',
    // 当使用 title 选项时,
    // template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title>
    // title: 'Index Page',
    // 在这个页面中包含的块,默认情况下会包含
    // 提取出来的通用 chunk 和 vendor chunk。
    chunks: ['chunk-libs', 'chunk-vant', 'chunk-commons', 'runtime', 'index'],
  },
  subpage: {
    entry: 'src/main.js',
    template: 'public/index-wx.html',
    filename: 'index-wx.html',
    chunks: ['chunk-libs', 'chunk-vant', 'chunk-commons', 'runtime', 'subpage'],
  },
}
module.exports = {
  publicPath: env.BASE_URL || '/',
  outputDir: 'h5', // 打包名称
  // 打包时不生成.map文件
  productionSourceMap: false,
  pages,
  // 如果你不需要使用eslint,把lintOnSave设为false即可
  lintOnSave: false,
  // 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串
  devServer: {
    host: '0.0.0.0',
    port: 8082,
    // 自动打开浏览器
    open: true,
    proxy: {
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      ['/api']: {
        target: env.BASE_API,
        changeOrigin: true,
        pathRewrite: {
          ['^/api']: '',
        },
      },
    },
    disableHostCheck: true,
  },
  pluginOptions: {
    'style-resources-loader': {
      preProcessor: 'less',
      patterns: [path.resolve(__dirname, './src/styles/varibles.less')],
    },
  },
  css: {
    loaderOptions: {
      less: {
        modifyVars: {
          // 直接覆盖变量
          // 或者可以通过 less 文件覆盖(文件路径为绝对路径)
          hack: `true; @import "${path.resolve(__dirname, './src/styles/varibles.less')}";`,
        },
      },
    },
  },

  configureWebpack: {
    resolve: {
      alias: {
        '@': resolve('src'),
      },
    },
  },
  chainWebpack(config) {
    // set svg-sprite-loader
    config.module
      .rule('svg')
      .exclude.add(resolve('src/assets/icons'))
      .end()
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(resolve('src/assets/icons/svg'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        // icon symbolId, 文件目录结构  => 命名: svg本级下的svg直接使用文件名,eg: icon-文件名;svg子目录下的svg使用需要带上该子目录文件夹名 eg:icon-文件夹名-文件名
        // # src/icons/svg
        // - icon1.svg        => icon-icon1
        // - icon2.svg        => icon-icon2
        // - dir/icon1.svg    => icon-dir-icon1
        symbolId: (filePath) => {
          let split = filePath.includes('/') ? '/icons/svg' : '\\icons\\svg'
          let str = filePath.slice(filePath.lastIndexOf(split) + split.length, -4) // eg: '/ase/as
          return 'icon' + str.replace(/[\/\\]/g, '-') // icon-aa-ss
        },
        // symbolId: 'icon-[name]',
      })
      .end()

    config.when(process.env.NODE_ENV !== 'development', (config) => {
      Object.keys(pages).forEach((element) => {
        config.plugin('preload-' + element).tap(() => [
          {
            rel: 'preload',
            // to ignore runtime.js
            fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
            include: 'initial',
          },
        ])
        config
          .plugin('ScriptExtHtmlWebpackPlugin')
          .after('html-' + element)
          .use('script-ext-html-webpack-plugin', [
            {
              // `runtime` must same as runtimeChunk name. default is `runtime`
              inline: /runtime\..*\.js$/,
            },
          ])
          .end()
        config.plugins.delete('prefetch-' + element)
      })

      // config.plugin('preload').tap(() => [
      //   {
      //     rel: 'preload',
      //     // to ignore runtime.js
      //     fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
      //     include: 'initial',
      //   },
      // ])
      // config.plugins.delete('prefetch')

      // config
      //   .plugin('ScriptExtHtmlWebpackPlugin')
      //   .after('html')
      //   .use('script-ext-html-webpack-plugin', [
      //     {
      //       // `runtime` must same as runtimeChunk name. default is `runtime`
      //       inline: /runtime\..*\.js$/,
      //     },
      //   ])
      //   .end()

      config.optimization.minimizer('terser').tap((options) => {
        options[0].terserOptions.compress.drop_console = true
        options[0].terserOptions.compress.drop_debugger = true
        return options
      })

      config.optimization.splitChunks({
        chunks: 'all',
        cacheGroups: {
          libs: {
            name: 'chunk-libs',
            test: /[\\/]node_modules[\\/]/,
            priority: 10,
            chunks: 'initial', // only package third parties that are initially dependent
          },
          elementUI: {
            name: 'chunk-vant', // split vant into a single package
            priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
            test: /[\\/]node_modules[\\/]_?vant(.*)/, // in order to adapt to cnpm
          },
          commons: {
            name: 'chunk-commons',
            test: resolve('src/components'), // can customize your rules
            minChunks: 3, //  minimum common number
            priority: 5,
            reuseExistingChunk: true,
          },
        },
      })

      config.optimization.runtimeChunk('single')
    })
  },
}

Logo

前往低代码交流专区

更多推荐