使用示例
import createHtmlPlugin from './vite-plugin-html.js'
export default async ()=>{
const pages=[
{
template: 'templates/index.html',
injectOptions: {
data: {
}
}
},
{
filename: 'station',
entry: 'station/MP/main.js',
template: 'templates/MP.html',
injectOptions: {
data: {
}
}
},
]
return defineConfig({
build:{
rollupOptions:{
input:{},
output:{
entryFileNames:'/[name].js',
chunkFileNames:'/[name].js',
assetFileNames:'[name].[ext]'
},
}
},
plugins:[
createHtmlPlugin({
minify:false,
pages,
pageKey:'app'
})
]
})
}
插件源码
import { normalizePath,loadEnv,createFilter } from 'vite';
import { render } from 'ejs';
import { parse } from 'node-html-parser';
import { minify } from 'html-minifier-terser';
import path from 'pathe';
const fs=require('fs');
function createPlugin({entry,template='./index.html',pages=[],verbose=false,inject={},pageKey=''}) {
const env=loadEnv(process.env.NODE_ENV,process.cwd()),
rewrites=[];
let viteConfig;
return {
name: "vite:html",
enforce: "pre",
config(conf) {
const filename=path.basename(template);
if(!pages?.length){
const to = path.resolve(conf.root, template);
rewrites.push({from:new RegExp('^\\/$'),to});
return {
build: {
rollupOptions: {
input:{
[filename.replace(/\.html/,'')]: to
}
}
}
};
}
let getRegStr,getInputStr,indexPage=null;
if(pageKey){
getRegStr=page=>`^\\/[^#\\\?]*?[\\?&]${pageKey}=${page}(&.+|#\\\/.*)?`;
getInputStr=page=>'/?'+pageKey+'='+page;
}else{
getRegStr=page=>`^\\/${page}(\\?\w.*|\\/[^\\.]*)?$`;
getInputStr=page=>'/'+page;
}
const input = {};
pages.forEach(page=>{
const to={...page};
if(!to.template) to.template=template;
if(!to.filename) to.filename=path.basename(to.template);
if (to.filename !== 'index.html'&&to.filename !== 'index') {
rewrites.push({from:new RegExp(getRegStr(to.filename.replaceAll('.','\\.'))),to});
input[to.filename.replace(/\.html/,'')]=getInputStr(to.filename);
} else {
indexPage = to;
}
if(!to.filename.endsWith('.html')) to.filename+='.html';
});
if(indexPage){
rewrites.push({from:new RegExp('^\\/(index\\.html)?$'),to:indexPage});
input.index='/index.html';
}
return {
build: {
rollupOptions: {
input
}
}
};
},
configResolved(resolvedConfig) {
viteConfig = resolvedConfig;
},
configureServer(server) {
const baseUrl=viteConfig.base??'/',
proxyKeys=viteConfig.server?.proxy?Object.keys(viteConfig.server.proxy).map(key=>new RegExp(key)):[];
server.middlewares.use((rqst, resp, next)=>{
if(!['GET','HEAD'].includes(rqst.method)||!rqst.headers||proxyKeys.some(k=>rqst._parsedUrl.path.match(k))) return next();
const {accept} = rqst.headers;
if(typeof accept!=='string'||!['text/html','application/xhtml+xml'].some(mime=>accept.includes(mime))) return next();
const parsedUrl = rqst._parsedUrl,
rewrite=rewrites.find(r=>parsedUrl.path.match(r.from));
if (!rewrite) {
if(rqst.url==='/') rqst.url='/index.html';
return next();
}
if(typeof rewrite.to==='string'){
rqst.url=rewrite.to;
return next();
}
server.transformIndexHtml(
path.resolve(baseUrl,rewrite.to.filename),
fs.readFileSync(path.resolve(viteConfig.root,rewrite.to.template)).toString()
).then(html=>{resp.end(html)});
});
},
resolveId(source,importer){
const rewrite=rewrites.find(r=>source.match(r.from));
if(!rewrite) return null;
if(typeof rewrite.to==='string') return rewrite.to;
return path.resolve(viteConfig.root,rewrite.to.filename);
},
load(id){
if(typeof id!=='string') return null;
const rewrite=rewrites.filter(r=>typeof r.to!=='string')
.find(r=>path.resolve(viteConfig.root,r.to.filename)===id);
return rewrite?fs.readFileSync(path.resolve(viteConfig.root,rewrite.to.template)).toString():null;
},
transformIndexHtml:{
enforce:'pre',
async transform(html, ctx) {
let injectOptions,pageEntry;
const rewrite=rewrites.filter(r=>typeof r.to!=='string')
.find(r=>path.resolve(viteConfig.root,r.to.filename)===ctx.filename);
if(rewrite){
injectOptions=rewrite.to.injectOptions||{};
pageEntry=rewrite.to.entry||entry;
}else{
injectOptions=inject;
pageEntry=entry;
}
html=await render(
html,
{
...viteConfig?.env ?? {},
...viteConfig?.define ?? {},
...env || {},
...injectOptions.data
},
injectOptions.ejsOptions
);
if(pageEntry){
const root=parse(html),
scriptNodes=root.querySelectorAll('script[type=module]');
if(scriptNodes?.length){
const removedNode=scriptNodes.map(item => {
item.parentNode.removeChild(item);
return item.toString();
});
if(verbose) console.warn(`vite-plugin-html: Since you have already configured entry, ${removedNode.toString()} is deleted. You may also delete it from the index.html.`);
}
html=root.toString().replace(/<\/body>/,`