vite-plugin-page-html
Version:
A simple and flexible Vite plugin for processing HTML pages, integrating multi-page application (MPA) configuration, EJS template support, and HTML compression.
323 lines (313 loc) • 10.3 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
default: () => createPlugin
});
module.exports = __toCommonJS(src_exports);
// src/pagePlugin.ts
var import_connect_history_api_fallback = __toESM(require("connect-history-api-fallback"), 1);
// src/utils/util.ts
var vite = __toESM(require("vite"), 1);
// src/const.ts
var PLUGIN_NAME = "vite-plugin-page-html";
var bodyInjectRE = /<\/body>/;
var scriptRE = /<script(?=\s)(?=[^>]*type=["']module["'])(?=[^>]*src=["'][^"']*["'])[^>]*>([\s\S]*?)<\/script>/gi;
// src/utils/util.ts
var import_diy_log = require("diy-log");
function errlog(...args) {
(0, import_diy_log.error)(`[${import_diy_log.colors.gray(PLUGIN_NAME)}] `, ...args);
}
function getViteVersion() {
return vite?.version ? Number(vite.version.split(".")[0]) : 2;
}
function cleanUrl(url) {
if (!url) return "/";
const queryRE = /\?.*$/s;
const hashRE = /#.*$/s;
return url.replace(hashRE, "").replace(queryRE, "");
}
function cleanPageUrl(path) {
return path.replace(/(^\/)|(\/$)/g, "").replace(/\.htm(l)?$/i, "");
}
// src/utils/core.ts
var import_ejs = __toESM(require("ejs"), 1);
var import_pathe = require("pathe");
var import_vite = require("vite");
async function compileHtml(ejsOptions = {}, extendData = {}, viteConfig) {
return async function(html, data = {}) {
try {
const ejsData = {
...extendData,
pageHtmlVitePlugin: {
title: data?.title,
entry: data?.entry,
data: data?.inject.data
},
...data
};
let result = await import_ejs.default.render(html, ejsData, ejsOptions);
if (data?.entry) {
result = result.replace(scriptRE, "").replace(
bodyInjectRE,
`<script type="module" src="${(0, import_vite.normalizePath)(data.entry)}"></script>
</body>`
);
}
return result;
} catch (e) {
errlog(e.message);
return "";
}
};
}
function createPage(options = {}) {
const {
entry,
template = "index.html",
title = "Vite App",
data = {},
ejsOptions = {},
inject = {}
} = options;
const defaults = {
entry,
template,
title,
ejsOptions,
inject: {
data: inject.data ?? data,
tags: inject.tags ?? []
}
};
const page = options.page || "index";
const pages = {};
if (typeof page === "string") {
const pageUrl = cleanPageUrl(page);
pages[pageUrl] = {
...defaults,
path: pageUrl
};
} else {
Object.entries(page).forEach(([name, pageItem]) => {
const pageUrl = cleanPageUrl(name);
if (!pageItem || typeof pageItem !== "string" && !pageItem.entry) {
errlog(`page ${name} is invalid`);
return;
}
if (typeof pageItem === "string") {
pageItem = { entry: pageItem };
}
pages[pageUrl] = {
...defaults,
...pageItem,
inject: {
...defaults.inject,
...pageItem.inject ?? {}
},
path: pageUrl
};
});
}
return pages;
}
function createRewire(reg, page, baseUrl, proxyKeys, whitelist) {
const from = typeof reg === "string" ? new RegExp(`^/${reg}*`) : reg;
return {
from,
to: ({ parsedUrl }) => {
const pathname = parsedUrl.path;
const excludeBaseUrl = pathname.replace(baseUrl, "/");
const template = (0, import_pathe.resolve)(baseUrl, page.template);
if (excludeBaseUrl.startsWith("/static")) {
return excludeBaseUrl;
}
if (excludeBaseUrl === "/") {
return template;
}
if (whitelist?.some((reg2) => reg2.test(excludeBaseUrl))) {
return pathname;
}
const isProxyPath = proxyKeys.some((key) => pathname.startsWith((0, import_pathe.resolve)(baseUrl, key)));
return isProxyPath ? pathname : template;
}
};
}
function createWhitelist(rewrites) {
const result = [/^\/__\w+\/$/];
if (rewrites) {
rewrites = Array.isArray(rewrites) ? rewrites : [rewrites];
for (const reg of rewrites) {
result.push(typeof reg === "string" ? new RegExp(reg) : reg);
}
}
return result;
}
function createRewrites(pages, viteConfig, options = {}) {
const rewrites = [];
const baseUrl = viteConfig.base ?? "/";
const proxyKeys = Object.keys(viteConfig.server?.proxy ?? {});
const whitelist = createWhitelist(options.rewriteWhitelist);
Object.entries(pages).forEach(([_, page]) => {
const reg = new RegExp(`${page.path}(\\/|\\.html|\\/index\\.html)?$`, "i");
rewrites.push(createRewire(reg, page, baseUrl, proxyKeys, whitelist));
});
rewrites.push(createRewire("", pages["index"] ?? {}, baseUrl, proxyKeys, whitelist));
return rewrites;
}
// src/utils/file.ts
var import_pathe2 = require("pathe");
var import_promises = __toESM(require("fs/promises"), 1);
var import_fs_extra = __toESM(require("fs-extra"), 1);
async function checkExistOfPath(p, root) {
if (!p || p === "." || p === "./") return "";
const paths = p.replace(root, "").split("/");
if (paths[0] === "") paths.shift();
if (paths.length === 0) return "";
let result = "";
try {
for (let i = 0; i < paths.length; i++) {
result = (0, import_pathe2.resolve)(root, ...paths.slice(0, i + 1));
await import_promises.default.access(result, import_promises.default.constants.F_OK);
}
return result;
} catch {
return result;
}
}
async function copyOneFile(src, dest, root) {
try {
const result = await checkExistOfPath(dest, root);
await import_fs_extra.default.copy(src, dest, { overwrite: false, errorOnExist: true });
return result;
} catch {
return "";
}
}
async function createVirtualHtml(pages, root) {
const _root = root ?? process.cwd();
return Promise.all(
Object.entries(pages).map(
([_, page]) => copyOneFile((0, import_pathe2.resolve)(_root, page.template), (0, import_pathe2.resolve)(_root, `${page.path}.html`), _root)
)
);
}
async function removeVirtualHtml(files) {
if (!files?.length) return;
try {
const uniqueFiles = Array.from(new Set(files.filter(Boolean)));
await Promise.all(uniqueFiles.map((file) => import_fs_extra.default.remove(file)));
} catch (e) {
errlog(e.message);
}
}
// src/pagePlugin.ts
function createPagePlugin(pluginOptions = {}) {
let viteConfig;
let renderHtml;
const pageInput = {};
let needRemoveVirtualHtml = [];
const pages = createPage(pluginOptions);
const transformIndexHtmlHandler = async (html, ctx) => {
let pageUrl = cleanUrl(ctx.originalUrl ?? ctx.path);
if (pageUrl.startsWith(viteConfig.base)) {
pageUrl = pageUrl.replace(viteConfig.base, "");
}
pageUrl = cleanPageUrl(pageUrl) || "index";
const pageData = pages[pageUrl] || pages[`${pageUrl}/index`];
if (pageData) {
html = await renderHtml(html, pageData);
return {
html,
tags: pageData.inject.tags
};
} else {
errlog(`${ctx.originalUrl ?? ctx.path} not found!`);
return html;
}
};
return {
name: PLUGIN_NAME,
enforce: "pre",
async config(config, { command }) {
Object.entries(pages).forEach(([name, current]) => {
const template = command === "build" ? `${current.path}.html` : current.template;
pageInput[name] = template;
});
if (!config.build?.rollupOptions?.input) {
return { build: { rollupOptions: { input: pageInput } } };
}
config.build.rollupOptions.input = pageInput;
},
async configResolved(resolvedConfig) {
viteConfig = resolvedConfig;
if (resolvedConfig.command === "build") {
needRemoveVirtualHtml = await createVirtualHtml(pages, resolvedConfig.root);
}
renderHtml = await compileHtml(
pluginOptions.ejsOptions,
{ ...resolvedConfig.env },
resolvedConfig
);
},
configureServer(server) {
server.middlewares.use(
(0, import_connect_history_api_fallback.default)({
verbose: !!process.env.DEBUG && process.env.DEBUG !== "false",
disableDotRule: void 0,
htmlAcceptHeaders: ["text/html", "application/xhtml+xml"],
rewrites: createRewrites(pages, viteConfig, pluginOptions)
})
);
},
transformIndexHtml: getViteVersion() < 5 ? {
enforce: "pre",
transform: transformIndexHtmlHandler
} : {
order: "pre",
handler: transformIndexHtmlHandler
},
closeBundle() {
if (needRemoveVirtualHtml.length) {
removeVirtualHtml(needRemoveVirtualHtml);
}
}
};
}
// src/index.ts
var import_vite_plugin_minify_html = __toESM(require("vite-plugin-minify-html"), 1);
function createPlugin(pluginOptions = {}) {
const opts = Object.assign({ minify: true }, pluginOptions);
const plugins = [createPagePlugin(opts)];
if (opts.minify) {
plugins.push((0, import_vite_plugin_minify_html.default)(opts.minify));
}
return plugins;
}