vite 实现多页面打包,html模板插槽自动引入插件
前端项目开发中,多页面打包是常见需求,vite 原生支持多页面打包,但需要手动配置,本文介绍如何使用vite 实现多页面打包。静态html并没有相互引用的功能,很多情况下,html界面的页首页脚是相同的。每个页面都引用相同的html代码,太冗余了,也不方便修改。下面基于vite插件,实现html模板插槽自动引入模板文件功能。
场景
有时候需要登录页、推广页等独立页面和业务 SPA 页分离的情况。尤其是在登录页中使用我觉得是不错的方案。
它可以直接和带有 vue-router、vuex、pinia 的 SPA 应用解耦,避免在登录页调用了一些 SPA 页面初始化行为。
原理
原理上其实也很简单,就是通过 Vite 的多入口、多出口来实现产出多个独立的 HTML 文件。
示例
创建登录页
用最新的 vue3 脚手架为例,先创建项目
npm create vue@latest
然后在根目录创建 login.html,其实就是复制了一份。不过如果是真实项目中可以把 index.html 中注入的各种 JS 和 CSS 清除掉。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/login.js"></script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
然后在 /src
目录下创建登录页的入口文件 login.ts 和具体渲染 login.vue.
import { createApp } from "vue";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import Login from "./Login.vue";
const app = createApp(Login);
// 去掉 pinia、vue-router 等不需要的库
app.use(ElementPlus);
app.mount("#app");
2
3
4
5
6
7
8
9
10
11
12
一个简单的 .vue 文件
<template>
<div>
<el-input v-model="username" placeholder="请输入账号"></el-input>
<el-input
v-model="password"
type="password"
placeholder="请输入密码"
></el-input>
<el-button type="primary" @click="login">登录</el-button>
</div>
</template>
<script setup>
import { ref } from "vue";
const username = ref("");
const password = ref("");
function login() {
window.location.href = "/"; // 登录进入 SPA 页面
}
</script>
<style scoped></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
以上就实现了一个简单的登录页了,其中登录和登出就靠着 window.location.href 来实现
function login() {
window.location.href = "/";
}
function logout() {
window.location.href = "/login.html";
}
2
3
4
5
6
7
另外,其实也可以在进入登录页的时候清空域名下的用户信息、实现真正的前后端登出。
// login.vue
onMounted(() => {
apiLogout()
})
2
3
4
5
Vite 配置
以上就实现了独立登录页功能,但是在执行 npm run build
的时候发现打包并没有把 login.html
包含进去。
所以需要修改下 vite 的配置。
vite 的配置是基于 rollup 的,所以除了 vite 官网外,也可以参考 rollup 打包相关的配置。
// vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import { resolve } from 'node:path'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
build: {
rollupOptions: {
input: {
index: resolve(__dirname, './index.html'),
login: resolve(__dirname, './login.html'),
},
},
},
})
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
在加上了 build.rollupOptions.input
后再执行 npm run build
就可以在 dist 目录下看到 login.html
了。
login.html 路径的隐藏
这个可以让代理服务器的同学加个 Nginx 代理。
server
{
listen 80;
...
location /login {
proxy_pass http://localhost:7001/login.html;
}
}
2
3
4
5
6
7
8
9
10
如此就可以通过 https://domain.com/login
来访问登录页啦~
解析
vite.config.ts
配置多页面入口,与自定义的html模板插件
import { defineConfig } from 'vite';
import { resolve } from 'path';
import myHtmlTemplatePlugin from './plugins/htmlTemplate';
export default defineConfig({
plugins: [myHtmlTemplatePlugin()],
build: {
rollupOptions: {
input: {
index:resolve(__dirname, 'pages/index.html'),
about:resolve(__dirname, 'pages/about.html')
},
},
},
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
html 模板
index.html, about.html
about.html
<div>about</div>
<script type="module" src="/src/about.ts"></script>
2
index.html
<include src="./components/header.html"></include>
<div>index</div>
<include src="./components/footer.html"></include>
<script type="module" src="/src/index.ts"></script>
2
3
4
entry.ts文件
src/index.ts, src/about.ts
about.ts
import './style.css'
console.log('Hello Vite!','about.ts')
2
index.ts
import './style.css'
console.log('Hello Vite!','index.ts')
2
打包
npm run build
输出
dist
├── assets
│ ├──
│─ about.html
│─ index.html
2
3
4
5
htmlTemplate 插件,处理html插槽
在build/dev时处理html模板里的插槽数据,把插槽替换为对应的html文件。
plugins/htmlTemplate.ts
import { Plugin } from 'vite';
import fs from 'fs'
import { dirname, join } from 'path'
function compressHTML(html) {
// 去除注释
html = html.replace(/<!--[\s\S]*?-->/g, "");
// 去除多余空白
html = html.replace(/\s+/g, " ");
// 去除标签之间空格
html = html.replace(/>\s+</g, "><");
return html.trim();
}
export default function myHtmlTemplatePlugin(): Plugin {
return {
name: 'my-html-template-plugin',
// vite特有钩子,填充html文件插槽
transformIndexHtml:{
enforce:'pre',
async transform(html, ctx) {
const directory = dirname(ctx.filename);
html = html.replace(/\<include.*src="(.*)">.*\<\/include\>/gi, (match, p1, offset, string) => {
const filePath = join(directory, p1);
let inc_data = fs.readFileSync(filePath, 'utf8');
return inc_data;
});
html = compressHTML(html);
return { html, tags:[] };
}}
};
}
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
最后
其实原理并不难,记录下此文主要是提供一种思路。面对一些独立性较强的页面的时候不用拘泥于 SPA 中的路由功能,是可以利用 vite 或者 webpack 做多页应用的。
一开始是接入的公司公共 SSO 页,后面要自行开发登录页的情况。也适合用这种多页应用的模式。