vue2/vue3 + webpack多页面遇到的问题和思考

目的:搭建vue2/vue3 + webpack多页面,
vite/vue3 请看下一篇vite + vue3多页面配置记录references,loadEnv等

目录结构

├── public
└── build // 多页面生成
└── src
    ├── api  // 请求
    ├── assets // 静态资源
    ├── components  // 公共组件
    ├── config  // 公用类
    └── views  // 页面
         └── home  // 页面
                ├── lang  // 多语言
                ├── icons // svg图标
                ├── components  // 组件
                ├── router  // 路由,选择性需要
                ├── store  // 选择性需要
                ├── main.js  // 
                ├── index.vue // 

主要点:
1.views页面文件夹中,每个页面(比如home)都有自己的main.js/index.vue/icons/lang.
2.router/store 选择性需要,比如你希望home页面是个单页面应用

目录文件夹.png

如果你使用vue-cli新建了一个vue2 + webpack单页面应用,要将其更改为多页面只需要三步

第一步、新建一个build文件夹,放入两个文件pages.js和getPages.js
pages.js
pages: 所有即将生产的页面,注意chunks放入只属于该页面的依赖,通过配置vue.config.js 
 中的splitchunk打包就会只打进该页面的js中(比如下面chunk-jsencrypt就只会被打包到login.js中)
module.exports = [
  {
    pagePath: 'login',
    pageName: 'login',
    chunks: ['chunk-vant', 'chunk-jsencrypt']
  },
  {
    pagePath: 'home',
    pageName: 'home',
    chunks: ['chunk-vant']
  },
  {
    pagePath: 'index',
    pageName: 'redirect',
    chunks: ['chunk-vant']
  }
]

getPages.js引入上面的页面,生成了pages并导出。 我们可以看到这里生成pages对象中entry/template/chunks/filename才是真正将被应用到vue.config.js的配置。
而其中的chunk-libs', 'chunk-commons' 都是splitchunk拆出来的公共依赖

// getPages.js
const pagesArray = require('./pages.js')
const pages = {}
pagesArray.forEach(item => {
  const itemChunks = item.chunks
    ? [item.pagePath, ...item.chunks]
    : [item.pagePath]
  pages[item.pagePath] = {
    entry: `src/views/${item.pagePath}/main.js`,
    template: 'public/index.html',
    filename: `${item.pagePath}.html`,
    title: item.pageName,
    chunks: ['chunk-libs', 'chunk-commons', ...itemChunks]
  }
})

module.exports = pages
第二步、修改vue.config.js配置

注意点:

  1. splitChunks拆包拆出来vant和jsencrypt,jsencrypt只有login页面需要使用,所以拆出来打包到login.js中,减少公共依赖chunk-libs的体积
  2. 上面目录结构中讲到每个views中都有自己的icons,chainWebpack中使用svg-sprite-loader处理icons也需要每个页面都处理
  3. 如果有需要删除预加载,需要加上页面名称delete('preload-login'),所以也放到pagesArray循环里面删除吧
const webpack = require('webpack')
let pages = require('./build/getPages.js')
const pagesArray = require('./build/pages.js')
const path = require('path')
function resolve(dir) {
  return path.join(__dirname, dir)
}
module.exports = {
  pages,
  configureWebpack: {
plugins: [
      new webpack.optimize.LimitChunkCountPlugin({
       maxChunks: 5,
        minChunkSize: 100
       })
    ],
    optimization: {
      splitChunks: {
        chunks: 'all',
        minSize: 20000, // 允许新拆出 chunk 的最小体积,也是异步 chunk 公共模块的强制拆分体积
        maxAsyncRequests: 6, // 每个异步加载模块最多能被拆分的数量
        maxInitialRequests: 6, // 每个入口和它的同步依赖最多能被拆分的数量
        enforceSizeThreshold: 50000, // 强制执行拆分的体积阈值并忽略其他限制
        cacheGroups: {
          libs: {
            // 第三方库
            name: 'chunk-libs',
            test: /[\\/]node_modules[\\/]/,
            priority: 10
            // chunks: "initial" // 只打包初始时依赖的第三方
          },
          vant: {
            // vant 单独拆包
            name: 'chunk-vant',
            test: /[\\/]node_modules[\\/]vant[\\/]/,
            priority: 20 // 权重要大于 libs
          },
          jsencrypt: {
            // jsencrypt 单独拆包
            name: 'chunk-jsencrypt',
            test: /[\\/]node_modules[\\/]jsencrypt[\\/]/,
            priority: 30 // 权重要大于 libs
          },
          commons: {
            // 公共模块包
            name: `chunk-commons`,
            minChunks: 2,
            priority: 0,
            reuseExistingChunk: true
          }
        }
      }
    }
  },
  chainWebpack(config) {
    // config.plugins.delete('preload-login')
    // config.plugins.delete('preload-home')
    // svg设置
    pagesArray.forEach(item => {
      const icons = `src/views/${item.pagePath}/icons`
      config.module.rule('svg').exclude.add(resolve(icons)).end()
      config.module
        .rule('icons')
        .test(/\.svg$/)
        .include.add(resolve(icons))
        .end()
        .use('svg-sprite-loader')
        .loader('svg-sprite-loader')
        .options({
          symbolId: 'icon-[name]'
        })
        .end()
    })
  }
}

第三步、修改页面结构,将src下面的main.js移动到每个页面下面,每个页面都有自己的main.js/index.vue
目录结构.png
indexPath.png

由于indexPath默认是index.html,所以可以将上面index目录改为一个loading页面,根据是否有token跳转到不同页面




注意我们这里用的 location.href 跳转
修改完上面三步就可以正常访问了,我们看一下打包信息

打包后.png
dist文件夹.png

vue-cli早已集成了webpack-bundle-analyzer,我们通yarn run build --report 或者 npm run build -- --report(注意npm传递参数要多一个--)打包后dist文件夹生成的report.html观察一下打包后的体积
直接加一个命令更合适。更多打包命令可以到cue-cli源码查看,源码位置:@vue\cli-service\lib\commands\build\index.js

 "build": "vue-cli-service build --mode prod --report",
打包后.png

可以看到

  1. jsencrypt打包后存在login.js中没有被单独拆出来,是因为上面vue.config.js配置中LimitChunkCountPlugin--maxChunks设置的是最大5个chunks,而jsencrypt只被login使用就被打包进去了。LimitChunkCountPlugin--maxChunks这个配置在单页面合并js上是很有用的,这个就不展开了。这里我们一定要拆出来jsencrypt是可以删除改配置或者将其修改大一些的
  2. vant在这里我使用按需引入,vant被单独拆出来了
  3. chunk-libs包含所有其他依赖,vue/vue-i18n/md5等等。chunk-commons因为最低要求被两个页面及以上公用才会生成,所以没有看到

思考1:vant还可以继续细分当前页面使用的组件打包到每个页面的js中吗?期待网友回答

思考2: 使用vue-router跳转多页面怎么配置?

上面我们提到多页面使用location.href = 'login.html' 进行跳转,url都是html结尾。

如果项目本身是单页面,只是有两三个入口而已,是可以使用vue-router进行跳转保持url一致性的。如果你的项目都是一个个独立的html页面,还是直接跳转html更好

实现多页面使用vue-router跳转

1. 更改vue.config.js
1.  publicPath一定是默认的"/",官方文档有说明多页面的时候
2. 利用historyApiFallback的能力重写路由路径,有几个入口就写几个
module.exports = {
  publicPath: '/',  
  devServer: {
    historyApiFallback: {
      verbose: true,
      rewrites: [
        { from: /^\/index\/.*$/, to: '/index.html' },
        { from: /^\/login\/.*$/, to: '/login.html' }
      ]
    }
  },
}
publicPath说明.png
2. 每个入口页面都有自己的router和pages,每个router配置当前入口中的所有路由
每个入口页面都有自己的router和pages.png

我们以login入口为例,看看router文件,主要看base和mode即可

这个base很重要,不同的入口路由使用不同的base进行区分,如此跳转的时候才知道是到了另一个新的入口页面
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/test',
    name: 'Test',
    meta: { title: '登陆' },
    component: () => import('@/views/login/pages/login')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: '/login',    
  routes
})

export default router
3. 从index入口的某个页面点击跳转




以上就是配置vue-router跳转多页面内容,重点主要在于historyApiFallback的能力。
在浏览器的效果就是http://localhost:8080/main/findpwd 点击后跳转到了 http://localhost:8080/login/test

跳转的时候还能看到重写的路径记录


路径重写.png

开发环境利用historyApiFallback重写了路径,生产环境就要为入口文件配置nginx

 location / {
            root   /usr/local/dist;
            index  index.html index.htm;
        }
匹配上面为每个入口配置的不同base路径
 location /login { # 这里的配置就是相当于配置了个路径,然后nginx做了一层拦截
             root   /usr/local/dist;
             index login.html;
             try_files $uri $uri/ /login.html;
        }

后续信息记录

  1. 打包报错信息:file.split is not a function
    TypeError: file.split is not a function
    at removeLoaders (/Users/ruios/web/web-m-refactor-misc/node_modules/@soda/friendly-errors-webpack-plugin/src/formatters/defaultError.js:23:22)
    at displayError (/Users/ruios/web/web-m-refactor-misc/node_modules/@soda/friendly-errors-webpack-plugin/src/formatters/defaultError.js:10:21)

这个问题主要是动态引入路由自定义包名webpackChunkName命名冲突导致

{
    path: '/test',
    name: 'Test',
    meta: { title: '登陆' },
    component: () => import(/* webpackChunkName: "test" */ ''@/views/login/pages/login'')
  },

  1. 上面提到vant单独拆包了,如果你在多页面中使用了vue-router,一定要注意splitchunks拆包规则:由于路由是异步import引入的,vant会拆不出来。它和jsencrypt不同,jsencrypt是同步引入的,因为jsencrypt应用在main.js中
    解决方式
1. 使用同步路由
import login from '@/views/login/pages/login'
{
    path: '/test',
    name: 'Test',
    meta: { title: '登陆' },
    component: login
  },
2. 加入配置  chunks: 'async',
vant: {
      // vant 单独拆包
        name: 'chunk-vant',
       test: /[\\/]node_modules[\\/]vant[\\/]/,
       chunks: 'async',
       priority: 20 // 权重要大于 libs
  },

vue2-multip代码仓库
vue2 + webpack + vue-router代码仓库

  1. 至于vue3 + webpack5 多页面大同小异,主要关注新语法即可。这里直接摆出项目地址vue3-multip

你可能感兴趣的:(vue2/vue3 + webpack多页面遇到的问题和思考)