UNPKG

vite-plugin-virtual-html

Version:
499 lines (492 loc) 15.2 kB
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/html/types.ts var POS = /* @__PURE__ */ ((POS2) => { POS2[POS2["before"] = 0] = "before"; POS2[POS2["after"] = 1] = "after"; return POS2; })(POS || {}); // src/html/Base.ts import { createFilter, normalizePath } from "vite"; import * as path from "path"; import * as fs from "fs"; import glob from "fast-glob"; import debug from "debug"; import { createRequire } from "node:module"; import MagicString from "magic-string"; var _require = import.meta.url !== void 0 ? createRequire(import.meta.url) : __require; var fsp = fs.promises; var DEFAULT_GLOB_PATTERN = [ "**/*.html", "!node_modules/**/*.html", "!.**/*.html" ]; var VIRTUAL_HTML_CONTENT = new MagicString(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>#TITLE#</title> <script src="#ENTRY#" type="module"></script> </head> <body> #BODY# </body> </html> `); var DEFAULT_INJECTCODE_ALL = "*"; var Base = class { constructor(virtualHtmlOptions) { this.cwd = normalizePath(process.cwd()); this.logger = debug("vite-plugin-virtual-html"); /** * load html file * @param args */ this._load = async (...args) => { const [id] = args; if (this._filter(id)) { let newId = this.getHtmlName(id, this._config?.root); const maybeIndexName1 = (newId + "/").replace("//", "/"); const maybeIndexName2 = (newId + "/index").replace("//", "/"); const maybeIndexName3 = newId.replace("index", "").replace("//", "/"); const pageOption = this._pages[newId] || this._pages[maybeIndexName1] || this._pages[maybeIndexName2] || this._pages[maybeIndexName3]; if (pageOption !== void 0) { if (typeof pageOption === "string") { const page = await this.generatePageOptions( pageOption, this._globalData, this._globalRender ); return await this.readHtml(page); } if ("template" in pageOption) { const page = await this.generatePageOptions( pageOption, this._globalData, this._globalRender ); return await this.readHtml(page); } if ("entry" in pageOption) { return await this.generateVirtualPage(pageOption); } } } return void 0; }; /** * transform code to inject some code into original code * @param args */ this._transform = async (...args) => { const [code, id] = args; if (this._filter(id)) { const ids = id.split("/"); const key = ids[ids.length - 1]; let _code = code; if (DEFAULT_INJECTCODE_ALL in this._injectCode) { _code = this.generateInjectCode( this._injectCode[DEFAULT_INJECTCODE_ALL], _code ); } if (key in this._injectCode) { _code = this.generateInjectCode(this._injectCode[key], _code); } return _code; } return null; }; /** * get html file's name * @param id * @param root */ this.getHtmlName = (id, root) => { const _root = (root ?? "").replace(this.cwd, ""); const _id = id.replace(this.cwd, ""); const result = _id.replace(".html", "").replace(_root !== "" ? this.addTrailingSlash(_root) : "", ""); return result.startsWith("/") ? result.substring(1, result.length) : result; }; /** * add trailing slash on path * @param {string} path * @returns {string} */ this.addTrailingSlash = (path3) => { const _path = normalizePath(path3.replace(this.cwd, "")); return _path.endsWith("/") ? _path : `${_path}/`; }; /** * generate URL * @param url */ this.generateUrl = (url) => { if (!url) { return "/"; } if (url.indexOf("?") > 0) { return url.split("?")[0]; } return url; }; /** * read HTML file from disk and generate code from template system(with render function) * @param template * @param data * @param render */ this.readHtml = async ({ template = "", data = {}, render = this.defaultRender }) => { const templatePath = path.resolve(this.cwd, `.${template}`); if (!fs.existsSync(templatePath)) { this.logger("[vite-plugin-virtual-html]: template file must exist!"); return ""; } return await this.renderTemplate(templatePath, render, data); }; /** * render template * @param templatePath * @param render * @param data */ this.renderTemplate = async (templatePath, render, data) => { const code = await this.readTemplate(templatePath); return render( code, data, templatePath.substring(templatePath.lastIndexOf(path.sep) + 1) ); }; /** * read html file's content to render with render function * @param templatePath */ this.readTemplate = async (templatePath) => { const result = await fsp.readFile(templatePath); return result.toString(); }; /** * generate page option from string/object to object * @param page * @param globalData * @param globalRender */ this.generatePageOptions = async (page, globalData, globalRender) => { if (typeof page === "string") { return { template: page, data: { ...globalData }, render: globalRender }; } const { data = {}, render, template } = page; return { template, data: { ...globalData, ...data }, render: render ?? globalRender ?? this.defaultRender }; }; /** * directly use find\replacement / replacement\find to replace find * @param {pos, find, replacement} * @param code */ this.generateInjectCode = ({ pos, find, replacement }, code) => { if (pos === 1 /* after */) { return code.replace(find, `${find} ${replacement}`); } if (pos === 0 /* before */) { return code.replace(find, ` ${replacement} ${find}`); } return code; }; /** * generate page from virtual page * @param vPages */ this.generateVirtualPage = async (vPages) => { const { entry, title = "", body = '<div id="app"></div>' } = vPages; return VIRTUAL_HTML_CONTENT.replace("#ENTRY#", entry).replace("#TITLE#", title).replace("#BODY#", body).toString(); }; /** * find all html file in project and return it as Pages */ this.findAllHtmlInProject = (extraGlobPattern = []) => { const pages = {}; let realPattern = []; if (extraGlobPattern.length === 0) { realPattern = DEFAULT_GLOB_PATTERN; } else { const set = /* @__PURE__ */ new Set(); DEFAULT_GLOB_PATTERN.forEach((dg) => set.add(dg)); extraGlobPattern.forEach((dg) => set.add(dg)); for (let key of set.keys()) { realPattern.push(key); } } const files = glob.sync(realPattern); files.forEach((file) => { const filePathArr = file.split("/"); pages[filePathArr[filePathArr.length - 1].replace(".html", "")] = `/${file}`; }); return pages; }; this.defaultRender = (template, data) => { try { const resolved = _require.resolve("ejs"); return _require(resolved).render(template, data, { delimiter: "%", root: process.cwd() }); } catch (e) { } return template; }; const { pages: pagesObj, indexPage = "index", render = this.defaultRender, data = {}, extraGlobPattern = [], injectCode = {}, cwd = normalizePath(process.cwd()) } = virtualHtmlOptions; if (pagesObj === true || pagesObj === void 0) { this._pages = this.findAllHtmlInProject(extraGlobPattern); } else { this._pages = pagesObj; } this._indexPage = indexPage; this._globalData = data; this._globalRender = render; this._injectCode = injectCode; this._filter = createFilter(/\.html|\/$/); this.cwd = cwd; } }; // src/history-api/historyApiFallbackPlugin.ts import history from "connect-history-api-fallback"; var historyApiFallbackPlugin = (historyApiOptions) => { const { rewrites, usePreview } = historyApiOptions; const configureServerHookName = usePreview ? "configurePreviewServer" : "configureServer"; return { name: "vite-plugin-virtual-html:history", [configureServerHookName](server) { if (rewrites) { buildHistoryApiFallback(server, rewrites); } } }; }; function buildHistoryApiFallback(server, rewrites) { server.middlewares.use(history({ disableDotRule: void 0, htmlAcceptHeaders: ["text/html", "application/xhtml+xml"], rewrites })); } // src/html/Serve.ts import { normalizePath as normalizePath2, createFilter as createFilter2 } from "vite"; var HTML_INCLUDE = [/\.html$/, /\/$/]; var HTML_FILTER = createFilter2(HTML_INCLUDE); var Serve = class extends Base { constructor(virtualHtmlOptions) { super(virtualHtmlOptions); this._configureServer = (server) => { if (this._rewrites) { buildHistoryApiFallback(server, this._rewrites); } return () => { server.middlewares.use(async (req, res, next) => { const originalUrl = req.originalUrl; const reqUrl = req.url; let url = decodeURI(this.generateUrl(originalUrl?.endsWith("/") ? originalUrl : reqUrl)); if (this._urlTransformer) { url = this._urlTransformer(url, req); } if (!HTML_FILTER(url) && url !== "/") { return next(); } let htmlCode; if (url === "/" || url === "/index.html") { url = `/${this._indexPage}.html`; } htmlCode = await this._load(normalizePath2(url)); if (htmlCode === void 0) { res.statusCode = 404; res.end(); return next(); } const transformResult = await this._transform(htmlCode, url); if (transformResult === null) { return next(); } res.end(await server.transformIndexHtml(url, transformResult)); next(); }); }; }; this._rewrites = virtualHtmlOptions.rewrites; this._urlTransformer = virtualHtmlOptions.urlTransformer; } }; // src/html/Build.ts import { normalizePath as normalizePath3 } from "vite"; import fs2, { promises as fsp2 } from "fs"; import path2 from "path"; var Build = class extends Base { constructor(virtualHtmlOptions) { super(virtualHtmlOptions); this._needRemove = []; } /** * check html file's parent directory * @param html * @param needRemove */ async checkVirtualPath(html, needRemove, root) { const cwd = normalizePath3(path2.resolve(this.cwd, root)); const pathArr = html.split("/"); const fileName = pathArr[pathArr.length - 1]; const middlePath = html.replace(fileName, "").replace(cwd, ""); const firstPath = middlePath.split("/")[1]; if (!fs2.existsSync(middlePath)) { needRemove.push(normalizePath3(path2.resolve(cwd, `./${firstPath}`))); await fsp2.mkdir(path2.resolve(cwd, `./${middlePath}`), { recursive: true }); } } async _buildConfig(config) { this._config = config; const pagesKey = Object.keys(this._pages); for (let i = 0; i < pagesKey.length; i++) { const key = pagesKey[i]; const pageOption = this._pages[key]; const vHtml = normalizePath3(path2.resolve(this.cwd, `./${config.root ? this.addTrailingSlash(config.root) : ""}${this.htmlNameAddIndex(key)}.html`)); if (!fs2.existsSync(vHtml)) { this._needRemove.push(vHtml); await this.checkVirtualPath(vHtml, this._needRemove, config.root ?? ""); if (typeof pageOption === "string" || "template" in pageOption) { const genPageOption = await this.generatePageOptions(pageOption, this._globalData, this._globalRender); await fsp2.copyFile(path2.resolve(this.cwd, `.${genPageOption.template}`), vHtml); } if (typeof pageOption !== "string" && "entry" in pageOption) { await fsp2.writeFile(path2.resolve(this.cwd, vHtml), await this.generateVirtualPage(pageOption)); } } } this.logger("[vite-plugin-virtual-html]: This plugin cannot use in library mode!"); this._distDir = config.build?.outDir ?? "dist"; config.build = { ...config.build, rollupOptions: { ...config.build?.rollupOptions, input: { ...config.build?.rollupOptions?.input, ...this.extractHtmlPath(this._pages) } } }; } _closeBundle() { for (let vHtml of this._needRemove) { if (fs2.existsSync(vHtml)) { fsp2.rm(vHtml, { recursive: true }).catch(() => { }); } } } /** * use pages' key as html name * @param pages */ extractHtmlPath(pages) { const newPages = {}; Object.keys(pages).forEach((key) => { newPages[key] = `/${this.htmlNameAddIndex(key)}.html`; }); return newPages; } htmlNameAddIndex(htmlName) { return htmlName.endsWith("/") ? htmlName + "index" : htmlName; } }; // src/html/VirtualHtmlPlugin.ts var VirtualHtmlPlugin = (virtualHtmlOptions) => { let _htmlOptions = virtualHtmlOptions; let _config; let _instance = null; return { name: "vite-plugin-virtual-html", async config(config, { command }) { _config = config; if (command === "serve") { if (_htmlOptions.useCustom ?? true) { config.appType = "custom"; } _instance = new Serve(_htmlOptions); } else if (command === "build") { _instance = new Build(_htmlOptions); await _instance._buildConfig.call(_instance, config); } }, configureServer(server) { if (_instance._configureServer) { return _instance._configureServer(server); } }, async load(...args) { if (_instance?._load) { return await _instance._load(...args); } }, async transform(...args) { if (_instance?._transform) { return await _instance._transform(...args); } }, closeBundle() { if (_instance._closeBundle) { return _instance._closeBundle(); } } }; }; // src/index.ts var index_default = (virtualHtmlOptions) => { return VirtualHtmlPlugin(virtualHtmlOptions); }; export { Build, POS, Serve, VirtualHtmlPlugin, buildHistoryApiFallback, index_default as default, historyApiFallbackPlugin }; //# sourceMappingURL=index.js.map