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跳转到不同页面

<template>
  <div class="h-screen justify-center-center">
    <Loading size="1rem" />
  </div>
</template>

<script>
import { Loading } from 'vant'
import { getLocalStore } from '@/config/global'
export default {
  name: 'Redirect',
  components: { Loading },
  created() {
    const token = getLocalStore('refactor-misc-token')
    if (!token) {
      location.href = 'login.html'
      return
    }
    location.href = 'home.html'
  }
}
</script>

注意我们这里用的 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入口的某个页面点击跳转
<template>
  <div>
    <div class="h-screen justify-center-center" @click="goLogin">findpwd</div>
    <div class="h-screen justify-center-center" @click="toSubling">wwww</div>
  </div>
</template>

<script>
export default {
  name: 'findpwd',
  methods: {
    goLogin() {
     跳转到别的入口页面
      location.href = '/login/test'
    },
    toSubling() {
    单个入口内的
      this.$router.push({ path: '/forgetpwd' })
    }
  }
}
</script>

以上就是配置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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容