# 静态页面(多页应用)
前言
MPA 多页面应用(MultiPage Application) ,指有多个独立页面的应用(多个 html 页面),每个页面必须重复加载 js、css 等相关资源。多页应用跳转,需要整页资源刷新。
对于项目复杂度过高或者页面模块之间差异化较大的项目,我们可以选择构建多页应用来实现,我们可以把多页应用理解为由多个单页构成的应用,那么同样其应该拥有多个独立的入口文件、组件、路由、vuex 等。
多页应用的每个单页都可以拥有单页应用 src 目录下的文件及功能
├── node_modules # 项目依赖包目录
├── build # 项目 webpack 功能目录
├── config # 项目配置项文件夹
├── src # 前端资源目录
│ ├── images # 图片目录
│ ├── components # 公共组件目录
│ ├── pages # 页面目录
│ │ ├── page1 # page1 目录
│ │ │ ├── components # page1 组件目录
│ │ │ ├── router # page1 路由目录
│ │ │ ├── views # page1 页面目录
│ │ │ ├── page1.html # page1 html 模板
│ │ │ ├── page1.vue # page1 vue 配置文件
│ │ │ └── page1.js # page1 入口文件
│ │ ├── page2 # page2 目录
│ │ └── index # index 目录
│ ├── common # 公共方法目录
│ └── store # 状态管理 store 目录
├── .gitignore # git 忽略文件
├── .env # 全局环境配置文件
├── .env.dev # 开发环境配置文件
├── .postcssrc.js # postcss 配置文件
├── babel.config.js # babel 配置文件
├── package.json # 包管理文件
├── vue.config.js # CLI 配置文件
└── yarn.lock # yarn 依赖信息文件
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
除了目录结构的不同外,其实区别单页应用,多页应用在很多配置上都需要进行修改,比如单入口变为多入口、单模板变为多模板等,那么下面我们就来了解一下多页应用的具体实现。
# 1.vue-cli 2.x 中
# 1.1 webpack 配置
多页配置(静态)
module.exports = { ... entry: { page1: '/xxx/pages/page1/page1.js', // 配置页面写死,不好 page2: '/xxx/pages/page2/page2.js', index: '/xxx/pages/index/index.js', }, ... }
1
2
3
4
5
6
7
8
9多页配置(动态)
配置文件
// build/utils.js const path = require("path") const glob = require("glob") // yarn add glob --dev,glob 是 webpack 安装时依赖的一个第三方模块,该模块允许你使用 * 等符号,例如 lib/*.js 就是获取 lib 文件夹下的所有 js 后缀名的文件 const PAGE_PATH = path.resolve(__dirname, "../src/pages") // 取得相应的页面路径,因为之前的配置,所以是 src 文件夹下的 pages 文件夹 exports.getEntries = () => { let entryFiles = glob.sync(PAGE_PATH + "/*/*.js") // 过 glob 模块读取 pages 文件夹下的所有对应文件夹下的 js * 后缀文件 let map = {} entryFiles.forEach((filePath) => { let filename = filePath.substring( filePath.lastIndexOf("/") + 1, filePath.lastIndexOf(".") ) map[filename] = filePath }) return map }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16多页应用入口动态配置
// vue.config.js const utils = require('./build/utils') module.exports = { ... configureWebpack: config => { config.entry = utils.getEntries() }, ... }
1
2
3
4
5
6
7
8
9
# 2.vue-cli 3.x 中
# 2.1 pages 配置
- 多页配置(静态)
/* vue.config.js */ module.exports = { pages: { index: { entry: "src/index/main.js", // page 的入口 template: "public/index.html", // 模板来源 filename: "index.html", // 在 dist/index.html 的输出 title: "Index Page", // 当使用 title 选项时,template 中的 title 标签需要是 <title><%= htmlWebpackPlugin.options.title %></title> chunks: ["chunk-vendors", "chunk-common", "index"], // 在这个页面中包含的块,默认情况下会包含,提取出来的通用 chunk 和 vendor chunk }, subpage: "src/subpage/main.js", //子页面 }, }
1
2
3
4
5
6
7
8
9
10
11
12
13 - 多页配置(动态)
多入口配置文件
exports.setPages = (configs) => { let entryFiles = glob.sync(PAGE_PATH + "/*/*.js") let map = {} entryFiles.forEach((filePath) => { let filename = filePath.substring( filePath.lastIndexOf("/") + 1, filePath.lastIndexOf(".") ) let tmp = filePath.substring(0, filePath.lastIndexOf("/")) let conf = { entry: filePath, // page 的入口 template: tmp + ".html", // 模板来源 filename: filename + ".html", // 在 dist/index.html 的输出 chunks: ["manifest", "vendor", filename], // 页面模板需要加对应的js脚本,如果不加这行则每个页面都会引入所有的js脚本 inject: true, } if (configs) { conf = merge(conf, configs) } if (process.env.NODE_ENV === "production") { conf = merge(conf, { minify: { removeComments: true, // 删除 html 中的注释代码 collapseWhitespace: true, // 删除 html 中的空白符 // removeAttributeQuotes: true // 删除 html 元素中属性的引号 }, chunksSortMode: "manual", // 按 manual 的顺序引入 }) } map[filename] = conf }) return map }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33配置多页应用
/* vue.config.js */ const utils = require('./build/utils') module.exports = { ... pages: utils.setPages(), ... }
1
2
3
4
5
6
7
# 2.2 路由跳转
多页应用中的每个单页都是相互隔离的,无法使用 vue-router 中的方法进行跳转,需要使用原生方法:location.href
或 location.replace
此外为了能够清晰的分辨路由属于哪个单页,我们应该给每个单页路由添加前缀,比如:
- index 单页:/vue/
- page1 单页:/vue/page1/
- page2 单页:/vue/page2/
/vue/ 为项目的二级目录,其后的目录代表路由属于哪个单页,每个单页的路由配置如下
/* page1 单页路由配置 */
import Vue from "vue"
import Router from "vue-router"
const Home = (resolve) => {
require.ensure(["../views/home.vue"], () => {
resolve(require("../views/home.vue"))
})
}
Vue.use(Router)
let base = `${process.env.BASE_URL}` + "page1" // 添加单页前缀
export default new Router({
mode: "history",
base: base,
routes: [
{
path: "/",
name: "home",
component: Home,
},
],
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
我们通过设置路由的 base 值来为每个单页添加路由前缀,如果是 index 单页我们无需拼接路由前缀,直接跳转至二级目录即可。
<template>
<div id="app">
<div id="nav">
<a @click="goFn('')">Index</a> | <a @click="goFn('page1')">Page1</a> |
<a @click="goFn('page2')">Page2</a> |
</div>
<router-view />
</div>
</template>
<script>
export default {
methods: {
goFn(name) {
location.href = `${process.env.BASE_URL}` + name
},
},
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
单页之间的跳转做一下封装,实现一个 Navigator
类,类的代码可以查看本文最后的示例,封装完成后我们可以将跳转方法修改为:
this.$openRouter({
name: name, // 跳转地址
query: {
text: "hello", // 可以进行参数传递
},
})
2
3
4
5
6
使用上述 $openRouter
方法我们还需要一个前提条件,便是将其绑定到 Vue 的原型链上,我们在所有单页的入口文件中添加:
import { Navigator } from "../../common" // 引入 Navigator
Vue.prototype.$openRouter = Navigator.openRouter // 添加至 Vue 原型链
2
需要注意的是因为其本质使用的是 location 跳转,所以必然会产生浏览器的刷新与重载。
重定向问题
跳转 Page1、Page2 仍然处于 Index 父组件下不能正常打开,这是因为浏览器认为你所要跳转的页面还是在 Index 根路由下,同时又没有匹配到 Index 单页中对应的路由,这时我们需要做一次重定向
/* vue.config.js */
let baseUrl = '/vue/';
module.exports = {
...
devServer: {
historyApiFallback: { // 添加 `historyApiFallback` 配置项,该配置项主要用于解决 HTML5 History API 产生的问题
rewrites: [
{ from: new RegExp(baseUrl + 'page1'), to: baseUrl + 'page1.html' },
{ from: new RegExp(baseUrl + 'page2'), to: baseUrl + 'page2.html' },
]
}
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
需要注意的是如果你的应用发布到正式服务器上,你同样需要让服务器或者中间层作出合理解析,参考:HTML5 History 模式 # 后端配置例子 (opens new window)
# 2.3 场景应用
总结
有了多页应用,可以在打包时配置多入口,分流入口包的大小,加快页面加载速度
← 静态页面(单页应用) 静态页面(模板解析) →