目的:搭建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页面是个单页面应用
如果你使用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配置
注意点:
- splitChunks拆包拆出来vant和jsencrypt,jsencrypt只有login页面需要使用,所以拆出来打包到login.js中,减少公共依赖chunk-libs的体积
- 上面目录结构中讲到每个views中都有自己的icons,chainWebpack中使用svg-sprite-loader处理icons也需要每个页面都处理
- 如果有需要删除预加载,需要加上页面名称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
由于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 跳转
修改完上面三步就可以正常访问了,我们看一下打包信息
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",
可以看到
- jsencrypt打包后存在login.js中没有被单独拆出来,是因为上面vue.config.js配置中LimitChunkCountPlugin--maxChunks设置的是最大5个chunks,而jsencrypt只被login使用就被打包进去了。LimitChunkCountPlugin--maxChunks这个配置在单页面合并js上是很有用的,这个就不展开了。这里我们一定要拆出来jsencrypt是可以删除改配置或者将其修改大一些的
- vant在这里我使用按需引入,vant被单独拆出来了
- 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' }
]
}
},
}
2. 每个入口页面都有自己的router和pages,每个router配置当前入口中的所有路由
我们以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
跳转的时候还能看到重写的路径记录
开发环境利用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;
}
后续信息记录
- 打包报错信息: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'')
},
- 上面提到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代码仓库
- 至于vue3 + webpack5 多页面大同小异,主要关注新语法即可。这里直接摆出项目地址vue3-multip