UNPKG

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
"use strict"; 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; }