UNPKG

@marko/vite

Version:
1,562 lines (1,528 loc) 66.6 kB
// src/index.ts import * as compiler3 from "@marko/compiler"; import anyMatch from "anymatch"; import crypto from "crypto"; import glob2 from "fast-glob"; import fs4 from "fs"; import path8 from "path"; import { relativeImportPath as relativeImportPath3 } from "relative-import-path"; // src/cjs-interop-translate.ts import { types as t } from "@marko/compiler"; import { importNamed } from "@marko/compiler/babel-utils"; // src/resolve.ts import fs from "fs"; import path from "path"; import Resolve from "resolve"; var moduleNameReg = /^(?:@[^/\\]+[/\\])?[^/\\]+/; var modulePathReg = /^.*[/\\]node_modules[/\\]((?:@[^/\\]+[/\\])?[^/\\]+[/\\])/; var cjsModuleLookup = /* @__PURE__ */ new Map(); function isCJSModule(id, fromFile) { if (/\.cjs$/.test(id)) return true; if (/\.mjs$/.test(id)) return false; if (id[0] === ".") return isCJSModule(fromFile, fromFile); const isAbsolute = path.isAbsolute(id); const moduleId = moduleNameReg.exec( isAbsolute ? id.replace(modulePathReg, "$1").replace(/\\/g, "/") : id )?.[0]; if (!moduleId) return false; let isCJS = cjsModuleLookup.get(moduleId); if (isCJS === void 0) { try { if (isAbsolute) { const pkgPath = path.join(modulePathReg.exec(id)[0], "package.json"); const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); isCJS = pkg.type !== "module" && !pkg.exports; } else { Resolve.sync(moduleId + "/package.json", { basedir: path.dirname(fromFile), filename: fromFile, pathFilter(pkg, _pkgFile, relativePath) { isCJS = pkg.type !== "module" && !pkg.exports; return relativePath; } }); isCJS ??= false; } } catch { isCJS = false; } cjsModuleLookup.set(moduleId, isCJS); } return isCJS; } // src/cjs-interop-translate.ts var cjsInteropHelpersId = "\0marko-cjs-interop.js"; var cjsInteropHelpersCode = `export const importNS = m => m && (m.default === void 0 || m.__esModule ? m : m.default); export const importDefault = m => m?.default?.__esModule ? m.default : m; `; var cjs_interop_translate_default = { Program: { exit(program) { const { cjsInteropMarkoVite } = program.hub.file.markoOpts; if (!cjsInteropMarkoVite) return; const { filter } = cjsInteropMarkoVite; const children = program.get("body"); for (let i = children.length; i--; ) { const child = children[i]; if (child.isImportDeclaration()) { translateImport(child, filter); } } } } }; function translateImport(importDecl, filter) { if (!importDecl.node.specifiers.length || /\.(?:mjs|marko)$|\?/.test(importDecl.node.source.value) || filter?.(importDecl.node.source.value) === false || !isCJSModule( importDecl.node.source.value, importDecl.hub.file.opts.filename )) { return; } let namespaceId; let defaultImportId; let imports; for (const s of importDecl.node.specifiers) { if (t.isImportSpecifier(s)) { (imports ||= []).push({ name: t.isStringLiteral(s.imported) ? s.imported.value : s.imported.name, alias: s.local.name }); } else if (t.isImportDefaultSpecifier(s)) { defaultImportId = s.local; } else if (t.isImportNamespaceSpecifier(s)) { namespaceId = s.local; } } const rawImport = importDecl.scope.generateUidIdentifier( namespaceId?.name || defaultImportId?.name || importDecl.node.source.value ); importDecl.node.specifiers = [t.importNamespaceSpecifier(rawImport)]; if (defaultImportId) { importDecl.insertAfter( t.variableDeclaration("const", [ t.variableDeclarator( t.objectPattern([ t.objectProperty(t.identifier("default"), defaultImportId) ]), t.callExpression( importNamed( importDecl.hub.file, cjsInteropHelpersId, "importDefault" ), [rawImport] ) ) ]) ); } if (namespaceId) { importDecl.insertAfter( t.variableDeclaration("const", [ t.variableDeclarator( namespaceId, t.callExpression( importNamed(importDecl.hub.file, cjsInteropHelpersId, "importNS"), [rawImport] ) ) ]) ); } if (imports) { importDecl.insertAfter( t.variableDeclaration("const", [ t.variableDeclarator( t.objectPattern( imports.map( ({ name, alias }) => t.objectProperty( t.identifier(name), t.identifier(alias), false, name === alias ) ) ), t.callExpression( importNamed(importDecl.hub.file, cjsInteropHelpersId, "importNS"), [rawImport] ) ) ]) ); } } // src/cjs-to-esm.ts var getESTransform = async function getESTransformLib() { const mod = await import("@chialab/estransform").catch(() => null); getESTransform = () => mod; return mod; }; var UMD_REGEXES = [ /\btypeof\s+(module\.exports|module|exports)\s*===?\s*['|"]object['|"]/, /['|"]object['|"]\s*===?\s*typeof\s+(module\.exports|module|exports)/, /\btypeof\s+define\s*===?\s*['|"]function['|"]/, /['|"]function['|"]\s*===?\s*typeof\s+define/ ]; var EXPORTS_KEYWORDS = /\b(module\.exports\b|exports\b)/; var CJS_KEYWORDS = /\b(module\.exports\b|exports\b|require[.(])/; var REQUIRE_FUNCTION = "__cjs_default__"; var GLOBAL_HELPER = `((typeof window !== 'undefined' && window) || (typeof self !== 'undefined' && self) || (typeof global !== 'undefined' && global) || (typeof globalThis !== 'undefined' && globalThis) || {})`; var REQUIRE_HELPER = `function ${REQUIRE_FUNCTION}(requiredModule) { var Object = ${GLOBAL_HELPER}.Object; var isEsModule = false; var specifiers = Object.create(null); var hasNamedExports = false; var hasDefaultExport = false; Object.defineProperty(specifiers, '__esModule', { value: true, enumerable: false, configurable: true, }); if (requiredModule) { var names = Object.getOwnPropertyNames(requiredModule);; names.forEach(function(k) { if (k === 'default') { hasDefaultExport = true; } else if (!hasNamedExports && !(k === '__esModule' || k === 'module.exports')) { try { hasNamedExports = requiredModule[k] != null; } catch (err) { // } } Object.defineProperty(specifiers, k, { get: function () { return requiredModule[k]; }, enumerable: true, configurable: false, }); }); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(requiredModule); symbols.forEach(function(k) { Object.defineProperty(specifiers, k, { get: function () { return requiredModule[k]; }, enumerable: false, configurable: false, }); }); } Object.preventExtensions(specifiers); Object.seal(specifiers); if (Object.freeze) { Object.freeze(specifiers); } } if (hasNamedExports) { return specifiers; } if (hasDefaultExport) { if (Object.isExtensible(specifiers.default) && !('default' in specifiers.default)) { Object.defineProperty(specifiers.default, 'default', { value: specifiers.default, configurable: false, enumerable: false, }) } return specifiers.default; } return specifiers; } `; async function cjs_to_esm_default(code, id) { const estransform = await getESTransform(); if (!estransform) { return; } const { parse, parseCommonjs, parseEsm, walk } = estransform; if (!CJS_KEYWORDS.test(code)) { throw new Error("Cannot convert mixed modules"); } try { const [imports, exports2] = await parseEsm(code); if (imports.length !== 0 || exports2.length !== 0) { throw new Error("Cannot convert mixed modules"); } } catch { } const specs = /* @__PURE__ */ new Map(); const ns = /* @__PURE__ */ new Map(); const { ast, helpers } = await parse(code, id); const isUmd = UMD_REGEXES.some((regex) => regex.test(code)); let insertHelper = false; if (!isUmd) { const ignoredExpressions = []; walk(ast, { TryStatement(node) { walk(node.block, { CallExpression(node2) { if (node2.callee.type !== "Identifier" || node2.callee.name !== "require") { return; } ignoredExpressions.push(node2); } }); } }); const callExpressions = []; walk(ast, { CallExpression(node) { if (node.callee.type !== "Identifier" || node.callee.name !== "require") { return; } if (ignoredExpressions.includes(node)) { return; } const specifier = node.arguments[0]; if (specifier.type === "StringLiteral") { callExpressions.push(node); } } }); await Promise.all( callExpressions.map(async (callExp) => { const specifier = callExp.arguments[0]; let spec = specs.get(specifier.value); if (!spec) { let id2 = `$cjs$${specifier.value.replace(/[^\w_$]+/g, "_")}`; const count = (ns.get(id2) || 0) + 1; ns.set(id2, count); if (count > 1) { id2 += count; } spec = { id: id2, specifier: specifier.value }; specs.set(specifier, spec); } insertHelper = true; helpers.overwrite( callExp.callee.start, callExp.callee.end, REQUIRE_FUNCTION ); helpers.overwrite( specifier.start, specifier.end, `typeof ${spec.id} !== 'undefined' ? ${spec.id} : {}` ); }) ); } const { exports, reexports } = await parseCommonjs(code); const named = exports.filter( (entry) => entry !== "__esModule" && entry !== "default" ); const isEsModule = exports.includes("__esModule"); const hasDefault = exports.includes("default"); if (isUmd) { let endDefinition = code.indexOf("'use strict';"); if (endDefinition === -1) { endDefinition = code.indexOf('"use strict";'); } if (endDefinition === -1) { endDefinition = code.length; } helpers.prepend(`var __umdGlobal = ${GLOBAL_HELPER}; var __umdExports = []; var __umdRoot = new Proxy(__umdGlobal, { get: function(target, name) { var value = Reflect.get(target, name); if (__umdExports.indexOf(name) !== -1) { return value; } if (typeof value === 'function' && !value.prototype) { return value.bind(__umdGlobal); } return value; }, set: function(target, name, value) { __umdExports.push(name); return Reflect.set(target, name, value); }, }); var __umdFunction = function ProxyFunction(code) { return __umdGlobal.Function(code).bind(__umdRoot); }; __umdFunction.prototype = Function.prototype; (function(window, global, globalThis, self, module, exports, Function) { `); helpers.append(` }).call(__umdRoot, __umdRoot, __umdRoot, __umdRoot, __umdRoot, undefined, undefined, __umdFunction); export default (__umdExports.length !== 1 && __umdRoot[__umdExports[0]] !== __umdRoot[__umdExports[1]] ? __umdRoot : __umdRoot[__umdExports[0]]);`); } else if (exports.length > 0 || reexports.length > 0) { helpers.prepend(`var global = ${GLOBAL_HELPER}; var exports = {}; var module = { get exports() { return exports; }, set exports(value) { exports = value; }, }; `); if (named.length) { const conditions = ["Object.isExtensible(module.exports)"]; if (!hasDefault && !isEsModule) { conditions.push( `Object.keys(module.exports).length === ${named.length}` ); } helpers.append(` var ${named.map((_name, index) => `__export${index}`).join(", ")}; if (${conditions.join(" && ")}) { ${named.map((name, index) => `__export${index} = module.exports['${name}'];`).join("\n ")} }`); helpers.append( ` export { ${named.map((name, index) => `__export${index} as "${name}"`).join(", ")} }` ); } if (isEsModule) { if (!isUmd && (hasDefault || named.length === 0)) { helpers.append( "\nexport default (module.exports != null && typeof module.exports === 'object' && 'default' in module.exports ? module.exports.default : module.exports);" ); } } else { helpers.append("\nexport default module.exports;"); } reexports.forEach((reexport) => { helpers.append(` export * from '${reexport}';`); }); } else if (EXPORTS_KEYWORDS.test(code)) { helpers.prepend(`var global = ${GLOBAL_HELPER}; var exports = {}; var module = { get exports() { return exports; }, set exports(value) { exports = value; }, }; `); helpers.append("\nexport default module.exports;"); } if (insertHelper) { helpers.prepend(`// Require helper for interop ${REQUIRE_HELPER}`); } specs.forEach((spec) => { helpers.prepend(`import * as ${spec.id} from "${spec.specifier}"; `); }); if (!helpers.isDirty()) { return; } return helpers.generate({ sourcemap: true, sourcesContent: false }); } // src/glob-import-transform.ts import { types as t2 } from "@marko/compiler"; import glob from "fast-glob"; import path2 from "path"; import { relativeImportPath } from "relative-import-path"; var programGlobImports = /* @__PURE__ */ new WeakMap(); var glob_import_transform_default = { MetaProperty(tag) { const memberExpression = tag.parentPath; if (memberExpression.node.type === "MemberExpression" && memberExpression.node.property.type === "Identifier" && memberExpression.node.property.name === "glob") { const callExpression = memberExpression.parentPath; if (callExpression?.node.type === "CallExpression") { const args = callExpression.get("arguments").map((arg) => arg.evaluate().value); if (args[1]?.eager) { const program = tag.hub.file.path; const existing = programGlobImports.get(program); if (!existing) { programGlobImports.set(program, [args]); } else { existing.push(args); } } } } }, Program: { exit(program) { const globImports = programGlobImports.get(program); if (!globImports) { return; } const { cwd, filename } = program.hub.file.opts; const dir = path2.dirname(filename); const seen = /* @__PURE__ */ new Set(); for (const [patterns, options] of globImports) { const results = glob.globSync(patterns, { cwd: dir, absolute: true, dot: !!options.exhaustive, ignore: options.exhaustive ? [] : [path2.join(cwd, "**/node_modules/**")] }); for (const file of results) { if (file.endsWith(".marko") && file !== filename && !seen.has(file)) { seen.add(file); program.node.body.push( t2.importDeclaration( [], t2.stringLiteral(relativeImportPath(filename, file)) ) ); } } } } } }; // src/manifest-generator.ts import { ElementType as ElementType2 } from "domelementtype"; import { DomHandler } from "domhandler"; import { Parser } from "htmlparser2"; // src/serializer.ts import { ElementType } from "domelementtype"; var voidElements = /* @__PURE__ */ new Set([ "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr" ]); function serialize(basePath, nodes, preload, parts) { let curString = parts ? parts.pop() : ""; parts ??= []; for (const node of nodes) { switch (node.type) { case ElementType.Tag: case ElementType.Style: case ElementType.Script: { const tag = node; const { name } = tag; let urlAttr; let isDedupe = 0; switch (tag.tagName) { case "script": if (tag.attribs.src) { if (curString) { parts.push(curString); curString = ""; } isDedupe = parts.push(2 /* Dedupe */, tag.attribs.src, 0) - 1; } parts.push(`${curString}<${name}`, 0 /* AssetAttrs */); curString = ""; urlAttr = "src"; break; case "style": parts.push(`${curString}<${name}`, 0 /* AssetAttrs */); curString = ""; break; case "link": if (tag.attribs.href) { if (curString) { parts.push(curString); curString = ""; } isDedupe = parts.push( 2 /* Dedupe */, [tag.attribs.rel || "", tag.attribs.href, tag.attribs.as].filter((it) => it != null).join("#"), 0 ) - 1; } urlAttr = "href"; if (tag.attribs.rel === "stylesheet" || tag.attribs.rel === "modulepreload" || tag.attribs.as === "style" || tag.attribs.as === "script") { parts.push(`${curString}<${name}`, 0 /* AssetAttrs */); curString = ""; } else { curString += `<${name}`; } break; default: curString += `<${name}`; break; } for (const attr of tag.attributes) { if (attr.value === "") { curString += ` ${attr.name}`; } else if (attr.name === urlAttr) { const id = stripBasePath(basePath, attr.value).replace(/^\.\//, ""); if (tag.name === "script") { preload.push(id); } curString += ` ${attr.name}="`; parts.push( curString, 1 /* PublicPath */, id.replace(/"/g, "&#39;") + '"' ); curString = ""; } else { curString += ` ${attr.name}="${attr.value.replace(/"/g, "&#39;")}"`; } } curString += ">"; if (tag.children.length) { parts.push(curString); serialize(basePath, tag.children, preload, parts); curString = parts.pop(); } if (!voidElements.has(name)) { curString += `</${name}>`; } if (isDedupe) { if (curString) { parts.push(curString); curString = ""; } parts[isDedupe] = parts.length - isDedupe - 1; } break; } case ElementType.Text: { const text = node.data; if (!/^\s*$/.test(text)) { curString += text; } break; } case ElementType.Comment: curString += `<!--${node.data}-->`; break; } } if (curString) { parts.push(curString); } return parts; } function stripBasePath(basePath, path9) { if (path9.startsWith(basePath)) return path9.slice(basePath.length); return path9; } // src/manifest-generator.ts var MARKER_COMMENT = "MARKO_VITE"; function generateDocManifest(basePath, rawHtml) { return new Promise((resolve, reject) => { const parser = new Parser( new DomHandler(function(err, dom) { if (err) { return reject(err); } const htmlChildren = dom.find(isElement).childNodes; const preload = []; const headPrepend = []; const head = []; const bodyPrepend = []; const body = []; splitNodesByMarker( htmlChildren.find( (node) => isElement(node) && node.tagName === "head" ).childNodes, headPrepend, head ); splitNodesByMarker( htmlChildren.find( (node) => isElement(node) && node.tagName === "body" ).childNodes, bodyPrepend, body ); resolve({ preload, "head-prepend": serializeOrNull(basePath, headPrepend, preload), head: serializeOrNull(basePath, head, preload), "body-prepend": serializeOrNull(basePath, bodyPrepend, preload), body: serializeOrNull(basePath, body, preload) }); }) ); parser.write(rawHtml); parser.end(); }); } function generateInputDoc(entry) { return `<!DOCTYPE html><html><head><!--${MARKER_COMMENT}--></head><body><!--${MARKER_COMMENT}--><script async type="module" src=${JSON.stringify( entry )}></script></body></html>`; } function serializeOrNull(basePath, nodes, preload) { const result = serialize(basePath, nodes, preload); if (result.length) { return result; } return null; } function splitNodesByMarker(nodes, before, after) { for (let i = 0; i < nodes.length; i++) { let node = nodes[i]; if (node.data === MARKER_COMMENT) { i++; for (; i < nodes.length; i++) { node = nodes[i]; after.push(node); } break; } before.push(node); } } function isElement(node) { return node.type === ElementType2.Tag; } // src/normalize-path.ts import path3 from "path"; var POSIX_SEP = "/"; var WINDOWS_SEP = "\\"; var normalizePath = path3.sep === WINDOWS_SEP ? (id) => id.replace(/\\/g, POSIX_SEP) : (id) => id; // src/read-once-persisted-store.ts import { promises as fs2 } from "fs"; import os from "os"; import path4 from "path"; var noop = () => { }; var tmpFile = path4.join(os.tmpdir(), "marko-vite-storage.json"); var values = /* @__PURE__ */ new Map(); var loadedFromDisk; var ReadOncePersistedStore = class { constructor(uid) { this.uid = uid; } uid; write(value) { values.set(this.uid, value); } async read() { const { uid } = this; if (values.has(uid)) { const value = values.get(uid); values.delete(uid); return value; } if (loadedFromDisk === true) { throw new Error(`Value for ${uid} could not be loaded.`); } await (loadedFromDisk ||= fs2.readFile(tmpFile, "utf-8").then(syncDataFromDisk).catch(finishLoadFromDisk)); return this.read(); } }; function syncDataFromDisk(data) { finishLoadFromDisk(); fs2.unlink(tmpFile).catch(noop); for (const [k, v] of JSON.parse(data)) { values.set(k, v); } } function finishLoadFromDisk() { loadedFromDisk = true; } process.once("beforeExit", (code) => { if (code === 0 && values.size) { fs2.writeFile(tmpFile, JSON.stringify([...values])).catch(noop); } }); // src/relative-assets-transform.ts var attrSrc = /* @__PURE__ */ new Set(["src"]); var attrHref = /* @__PURE__ */ new Set(["href"]); var assetAttrsByTag = /* @__PURE__ */ new Map([ ["audio", attrSrc], ["embed", attrSrc], ["iframe", attrSrc], ["img", /* @__PURE__ */ new Set(["src", "srcset"])], ["input", attrSrc], ["source", attrSrc], ["track", attrSrc], ["video", /* @__PURE__ */ new Set(["src", "poster"])], ["a", attrHref], ["area", attrHref], ["link", attrHref], ["object", /* @__PURE__ */ new Set(["data"])], ["body", /* @__PURE__ */ new Set(["background"])], ["script", /* @__PURE__ */ new Set(["src"])] ]); var assetFileReg = /(?:^\..*\.(?:a?png|jpe?g|jfif|pipeg|pjp|gif|svg|ico|web[pm]|avif|mp4|ogg|mp3|wav|flac|aac|opus|woff2?|eot|[ot]tf|webmanifest|pdf|txt)(\?|$)|\?url\b)/; function transform(tag, t3) { const { name, attributes } = tag.node; if (name.type !== "StringLiteral") { return; } const assetAttrs = assetAttrsByTag.get(name.value); if (!assetAttrs) { return; } for (const attr of attributes) { if (attr.type === "MarkoAttribute" && attr.value.type === "StringLiteral" && assetAttrs.has(attr.name)) { const { value } = attr.value; if (assetFileReg.test(value)) { const importedId = tag.scope.generateUid(value); attr.value = t3.identifier(importedId); tag.hub.file.path.unshiftContainer( "body", t3.importDeclaration( [t3.importDefaultSpecifier(t3.identifier(importedId))], t3.stringLiteral(value) ) ); } } } } // src/render-assets-runtime.ts var renderAssetsRuntimeId = "\0marko-render-assets.mjs"; function getRenderAssetsRuntime(opts) { return `${opts.basePathVar && opts.isBuild ? `const base = globalThis.${opts.basePathVar}; if (typeof base !== "string") throw new Error("${opts.basePathVar} must be defined when using basePathVar."); if (!base.endsWith("/")) throw new Error("${opts.basePathVar} must end with a '/' when using basePathVar.");` : "const base = import.meta.env.BASE_URL;"} export function getPrepend(g) { return ( g.___viteRenderAssets("head-prepend") + g.___viteRenderAssets("head") + g.___viteRenderAssets("body-prepend") ); } export function getAppend(g) { return ( g.___viteRenderAssets("body-prepend") ); } export function addAssets(g, newEntries) { const entries = g.___viteEntries; if (entries) { g.___viteEntries = entries.concat(newEntries); return true; } g.___viteEntries = newEntries; g.___viteRenderAssets = renderAssets; g.___viteInjectAttrs = g.cspNonce ? \` nonce="\${g.cspNonce.replace(/"/g, "&#39;")}"\` : ""; g.___viteSeenIds = new Set(); ${opts.runtimeId ? `g.runtimeId = ${JSON.stringify(opts.runtimeId)};` : ""} } function renderAssets(slot) { const entries = this.___viteEntries; let html = ""; if (entries) { const seenIds = this.___viteSeenIds; const slotWrittenEntriesKey = \`___viteWrittenEntries-\${slot}\`; const lastWrittenEntry = this[slotWrittenEntriesKey] || 0; const writtenEntries = (this[slotWrittenEntriesKey] = entries.length); ${opts.basePathVar ? `if (!this.___flushedMBP && slot !== "head-prepend") { this.___flushedMBP = true; html += \`<script\${this.___viteInjectAttrs}>${opts.runtimeId ? `$mbp_${opts.runtimeId}` : "$mbp"}=\${JSON.stringify(base)}</script>\` }` : ""} for (let i = lastWrittenEntry; i < writtenEntries; i++) { let entry = entries[i]; if (typeof entry === "string") { entry = __MARKO_MANIFEST__[entry] || {}; }${opts.isBuild ? "" : ` else if (slot === "head") { // In dev mode we have is a list entries of the top level modules that need to be imported. // To avoid FOUC we will hide the page until all of these modules are loaded. const { preload } = entry; if (preload) { let sep = ""; html += \`<style marko-vite-preload\${this.___viteInjectAttrs}>html{visibility:hidden !important}</style>\`; html += \`<script marko-vite-preload async blocking=render type=module\${this.___viteInjectAttrs}>\`; html += "await Promise.allSettled(["; for (const id of preload) { html += sep + \`import(\${JSON.stringify(base + id)})\`; sep = ","; } html += "]);"; html += "document.querySelectorAll('[marko-vite-preload]').forEach(el=>el.remove());"; html += \`</script>\`; } }`} const parts = entry[slot]; if (parts) { for (let i = 0; i < parts.length; i++) { const part = parts[i]; switch (part) { case 0: /** InjectType.AssetAttrs */ html += this.___viteInjectAttrs; break; case 1: /** InjectType.PublicPath */ html += base; break; case 2: /** InjectType.Dedupe */ { const id = parts[++i]; const skipParts = parts[++i]; if (seenIds.has(id)) { i += skipParts; } else { seenIds.add(id); } break; } default: html += part; break; } } } } } return html; } `; } // src/render-assets-transform.ts var render_assets_transform_default = (tag, t3) => { if (tag.hub.file.markoOpts.markoViteLinked) { const body = tag.get("body"); const tagName = tag.get("name").node.value; body.unshiftContainer("body", renderAssetsCall(t3, `${tagName}-prepend`)); body.pushContainer("body", renderAssetsCall(t3, tagName)); } }; function renderAssetsCall(t3, slot) { return t3.markoPlaceholder( t3.optionalCallExpression( t3.memberExpression( t3.identifier("$global"), t3.identifier("___viteRenderAssets") ), [t3.stringLiteral(slot)], true ), false ); } // src/rolldown-plugin.ts import * as compiler2 from "@marko/compiler"; import path6 from "path"; // src/scan.ts import * as compiler from "@marko/compiler"; import fs3 from "fs"; import { createParser, TagType } from "htmljs-parser"; import path5 from "path"; import { relativeImportPath as relativeImportPath2 } from "relative-import-path"; var nmsReg = /[\\/]node_modules[\\/]/; var cache = /* @__PURE__ */ new Map(); function clearScanCache() { cache = /* @__PURE__ */ new Map(); } function scan(filename, code) { let deps = cache.get(filename); let imports = ""; if (!deps) { deps = /* @__PURE__ */ new Set([filename]); scanFile(filename, code, deps); cache.set(filename, deps); } for (const dep of deps) { if (dep !== filename) { imports += ` import "${relativeImportPath2(filename, dep)}";`; } } return imports; } function scanFile(filename, code, result) { const lookup = compiler.taglib.buildLookup(path5.dirname(filename)); const parser = createParser({ onError() { }, onOpenTagName(range) { const tagDef = range.expressions.length ? void 0 : lookup.getTag(parser.read(range)); const parseOptions = tagDef?.parseOptions; if (parseOptions) { if (parseOptions.statement) { return TagType.statement; } if (parseOptions.openTagOnly) { return TagType.void; } if (parseOptions.text) { return TagType.text; } } if (tagDef && !tagDef.html && tagDef.template) { trackTag(tagDef.template, result); } return TagType.html; } }); try { parser.parse(code); } catch { } } function trackTag(template, result) { if (!nmsReg.test(template) || !template.endsWith(".marko") || result.has(template)) { return; } let deps = cache.get(template); if (!deps) { deps = /* @__PURE__ */ new Set([template]); try { scanFile(template, fs3.readFileSync(template, "utf-8"), deps); } catch { } cache.set(template, deps); } for (const dep of deps) { result.add(dep); } } // src/rolldown-plugin.ts var virtualFileReg = /\.marko-virtual\./; var nodeModulesReg = /[\\/]node_modules[\\/]/; function rolldownPlugin(config, virtualFiles2, cacheVirtualFile) { const baseConfig = { ...config, hot: false, sourceMaps: "inline" }; return [ { name: "marko:virtual", resolveId: { filter: { id: virtualFileReg }, async handler(source, importer) { if (!importer) return null; const id = normalizePath(path6.resolve(importer, "..", source)); if (!/\.(?:[cm]?[jt]s|json)$/i.test(id)) { const virtualId = await cacheVirtualFile(id); if (virtualId) { return { id: virtualId, external: "absolute" }; } } return id; } }, load: { filter: { id: virtualFileReg }, async handler(id) { const file = virtualFiles2.get(id); return file && { code: (await file).code, moduleType: path6.extname(id).slice(1) }; } } }, { name: "marko", resolveId: { filter: { id: /^<([^>]+)>$/ }, handler(source, importer) { const tagName = importer && source.slice(1, -1); if (tagName) { const tagDef = compiler2.taglib.buildLookup(path6.dirname(importer)).getTag(tagName); const tagFile = tagDef && (tagDef.template || tagDef.renderer); if (tagFile) { return { id: tagFile }; } } } }, load: { filter: { id: /\.marko$/ }, async handler(id) { const code = await this.fs.readFile(id, { encoding: "utf8" }); const compiled = await compiler2.compile(code, id, baseConfig); return { code: nodeModulesReg.test(id) ? compiled.code : compiled.code + scan(id, code), moduleType: "js" }; } } } ]; } // src/server-entry-template.ts import path7 from "path"; var server_entry_template_default = async (opts) => { const fileNameStr = JSON.stringify(`./${path7.basename(opts.fileName)}`); if (opts.tagsAPI) { return ` <!-- use tags --> import Template from ${fileNameStr}; export * from ${fileNameStr}; import { addAssets, getPrepend, getAppend } from "${renderAssetsRuntimeId}"; static function flush($global, html) { return getPrepend($global) + html + getAppend($global); } static function setFlush($global) { $global.__flush__ = flush; } <const/writeSync=addAssets($global, [${opts.entryData.join(",")}]) || setFlush($global)/> -- $!{writeSync && getPrepend($global)} <Template ...input/> -- $!{writeSync && getAppend($global)} `; } return ` <!-- use class --> import Template from ${fileNameStr}; export * from ${fileNameStr}; import { addAssets, getPrepend, getAppend } from "${renderAssetsRuntimeId}"; <if(addAssets($global, [${opts.entryData.join(",")}]))> $!{getPrepend($global)} <Template ...input/> $!{getAppend($global)} </> <else> <__flush_here_and_after__>$!{getPrepend($global)}</> <Template ...input/> <init-components/> <await-reorderer/> <__flush_here_and_after__>$!{getAppend($global)}</> </> `; }; // src/index.ts var TEMPLATE_ID_HASH_OPTS = { outputLength: 3 }; var virtualFiles = /* @__PURE__ */ new Map(); var virtualFilesForTemplate = /* @__PURE__ */ new Map(); var ssrTransformCache = /* @__PURE__ */ new Map(); var importTagReg = /^<([^>]+)>$/; var optionalWatchFileReg = /[\\/](?:([^\\/]+)\.)?(?:marko-tag.json|(?:style|component|component-browser)\.\w+)$/; var noClientAssetsRuntimeId = "\0no_client_bundles.mjs"; var markoExt = ".marko"; var htmlExt = ".html"; var clientEntryExt = ".client-entry.marko"; var serverEntryExt = ".server-entry.marko"; var virtualFileInfix = "-virtual"; var virtualFileReg2 = /^(.*\.marko)-virtual((?:\.[^\\/?]+)+)$/; var resolveOpts = { skipSelf: true }; var cache2 = /* @__PURE__ */ new Map(); var babelConfig = { babelrc: false, configFile: false, browserslistConfigFile: false, caller: { name: "@marko/vite", supportsStaticESM: true, supportsDynamicImport: true, supportsTopLevelAwait: true, supportsExportNamespaceFrom: true } }; var optimizeKnownTemplatesForRoot = /* @__PURE__ */ new Map(); var registeredTagLib = false; function noop2() { } function markoPlugin(opts = {}) { let { linked = true } = opts; let runtimeId; let basePathVar; let baseConfig; let clientConfig; let clientEntryConfig; let serverConfig; let serverCJSConfig; let serverEntryConfig; let virtualFileCacheDirPromise; const resolveVirtualDependency = (from, dep) => { const { virtualPath } = dep; const normalizedFrom = normalizePath(from); const sourceBaseName = path8.basename(from); const virtualBaseName = path8.basename(virtualPath); const virtualExt = virtualFileInfix + (virtualBaseName.startsWith(sourceBaseName) ? virtualBaseName.slice(sourceBaseName.length) : `.${virtualBaseName.replace(/[^a-z0-9_.-]+/gi, "_")}`); const id = normalizedFrom + virtualExt; const virtualFile = { code: dep.code, map: stripSourceRoot(dep.map) }; if (devServer) { const prev = virtualFiles.get(id); if (isDeferredPromise(prev)) { prev.resolve(virtualFile); } let files = virtualFilesForTemplate.get(normalizedFrom); if (!files) { virtualFilesForTemplate.set(normalizedFrom, files = /* @__PURE__ */ new Set()); } files.add(id); } virtualFiles.set(id, virtualFile); return `./${sourceBaseName + virtualExt}`; }; let root; let cacheDir; let rootResolveFile; let devEntryFile; let devEntryFilePosix; let renderAssetsRuntimeCode; let isTest = false; let isBuild = false; let hasBuildApp = false; let isBuildApp = false; let devServer; let serverManifest; let basePath = "/"; let getMarkoAssetFns; let checkIsEntry = () => true; const entryIds = /* @__PURE__ */ new Set(); const cachedSources = /* @__PURE__ */ new Map(); const transformWatchFiles = /* @__PURE__ */ new Map(); const transformAnalyzedTags = /* @__PURE__ */ new Map(); const store = new ReadOncePersistedStore( `vite-marko${runtimeId ? `-${runtimeId}` : ""}` ); const isTagsApi = (api) => api === "tags"; return [ { name: "marko-vite:pre", enforce: "pre", // Must be pre to allow us to resolve assets before vite. sharedDuringBuild: true, async buildApp(builder) { const { ssr, client } = builder.environments; isBuildApp = true; if (hasBuildApp || !linked || !ssr || !client) return; await builder.build(ssr); await builder.build(client); }, async config(config, env) { let optimize = env.mode === "production"; isTest = env.mode === "test"; isBuild = env.command === "build"; hasBuildApp = !!config.builder?.buildApp; if (isTest) { const { test } = config; linked = false; if (test.environment?.includes("dom")) { config.resolve ??= {}; config.resolve.conditions ??= []; config.resolve.conditions.push("browser"); (test.execArgv ||= []).push("-C", "browser"); } } if ("MARKO_DEBUG" in process.env) { optimize = process.env.MARKO_DEBUG === "false" || process.env.MARKO_DEBUG === "0"; } else { process.env.MARKO_DEBUG = optimize ? "false" : "true"; } runtimeId = opts.runtimeId; basePathVar = opts.basePathVar; checkIsEntry = opts.isEntry || checkIsEntry; if ("BASE_URL" in process.env && config.base == null) { config.base = process.env.BASE_URL; } root = normalizePath(config.root || process.cwd()); rootResolveFile = path8.join(root, "_.js"); baseConfig = { cache: cache2, optimize, runtimeId, babelConfig, sourceMaps: true, writeVersionComment: false, resolveVirtualDependency, optimizeKnownTemplates: optimize ? getKnownTemplates(root) : void 0 }; if (linked) { baseConfig.markoViteLinked = linked; } const cjsInteropMarkoVite = { filter: isBuild || isTest ? void 0 : (path9) => !/^\./.test(path9) }; clientConfig = { ...baseConfig, output: "dom" }; clientEntryConfig = { ...baseConfig, output: "hydrate", sourceMaps: false }; serverConfig = { ...baseConfig, output: "html" }; serverCJSConfig = { ...serverConfig, cjsInteropMarkoVite }; serverEntryConfig = { ...serverConfig, sourceMaps: false }; compiler3.configure(baseConfig); devEntryFile = path8.join(root, "index.html"); devEntryFilePosix = normalizePath(devEntryFile); renderAssetsRuntimeCode = getRenderAssetsRuntime({ isBuild, basePathVar, runtimeId }); if (!registeredTagLib) { registeredTagLib = true; compiler3.taglib.register("@marko/vite", { translate: cjs_interop_translate_default, transform: glob_import_transform_default, "<head>": { transformer: render_assets_transform_default }, "<body>": { transformer: render_assets_transform_default }, "<*>": { transformer: transform } }); } if (basePathVar) { config.experimental ??= {}; if (config.experimental.renderBuiltUrl) { throw new Error( "Cannot use @marko/vite `basePathVar` with Vite's `renderBuiltUrl` option." ); } const assetsDir = config.build?.assetsDir?.replace(/[/\\]$/, "") ?? "assets"; const assetsDirLen = assetsDir.length; const assetsDirEnd = assetsDirLen + 1; const trimAssertsDir = (fileName) => { if (fileName.startsWith(assetsDir)) { switch (fileName[assetsDirLen]) { case POSIX_SEP: case WINDOWS_SEP: return fileName.slice(assetsDirEnd); } } return fileName; }; config.experimental.renderBuiltUrl = (fileName, { hostType, ssr }) => { switch (hostType) { case "html": return trimAssertsDir(fileName); case "js": return { runtime: `${ssr ? basePathVar : `$mbp${runtimeId ? `_${runtimeId}` : ""}`}+${JSON.stringify(trimAssertsDir(fileName))}` }; default: return { relative: true }; } }; } return { resolve: { alias: [ { find: /^~(?!\/)/, replacement: "" } ] } }; }, configEnvironment(name, config) { const isSSR = name === "ssr"; if (isSSR) { const { noExternal } = config.resolve ??= {}; if (noExternal !== true) { const noExternalReg = /\.marko$/; if (noExternal) { if (Array.isArray(noExternal)) { config.resolve.noExternal = [...noExternal, noExternalReg]; } else { config.resolve.noExternal = [noExternal, noExternalReg]; } } else { config.resolve.noExternal = noExternalReg; } } } if (isBuild) { config.build ??= {}; if (!config.build.rolldownOptions?.output) { config.build.rolldownOptions ??= {}; config.build.rolldownOptions.output = { // By default use `_[hash]` instead of `[name]-[hash]` because for chunk names the `[name]` is // not deterministic (it's based on chunk.moduleIds order). // For the server build vite will still output code split chunks to the `assets` directory by default. // this is problematic since you might have server assets in your client assets folder. // Here we change the default chunkFileNames config to instead output to the outDir directly. chunkFileNames: isSSR ? "_[hash].js" : `${config.build?.assetsDir?.replace(/[/\\]$/, "") ?? "assets"}/_[hash].js` }; } } else { const optimizeDeps = config.optimizeDeps ??= {}; (optimizeDeps.extensions ??= []).push(".marko"); if (!isSSR) { const domDeps = compiler3.getRuntimeEntryFiles( "dom", opts.translator ); optimizeDeps.include = optimizeDeps.include ? [...optimizeDeps.include, ...domDeps] : domDeps; } if (!isTest) { optimizeDeps.entries ??= [ "**/*.marko", "!**/__snapshots__/**", `!**/__tests__/**`, `!**/coverage/**` ]; } (optimizeDeps.rolldownOptions ??= {}).plugins = [ optimizeDeps.rolldownOptions.plugins || [], rolldownPlugin( isSSR ? serverConfig : clientConfig, virtualFiles, async (id) => { if (cacheDir) { const file = virtualFiles.get(id); if (file) { await (virtualFileCacheDirPromise ||= fs4.promises.mkdir( cacheDir, { recursive: true } )); const virtualId = virtualPathToCacheFile( id, root, cacheDir ); await fs4.promises.writeFile(virtualId, (await file).code); return virtualId; } } } ) ]; } }, configResolved(config) { basePath = config.base; cacheDir = config.cacheDir && normalizePath(config.cacheDir); getMarkoAssetFns = void 0; for (const plugin of config.plugins) { const fn = plugin.api?.getMarkoAssetCodeForEntry; if (fn) { if (getMarkoAssetFns) { getMarkoAssetFns.push(fn); } else { getMarkoAssetFns = [fn]; } } } }, configureServer(_server) { if (!isTest) { clientConfig.hot = serverConfig.hot = serverEntryConfig.hot = true; } devServer = _server; devServer.watcher.on("all", (type, originalFileName) => { const fileName = normalizePath(originalFileName); cachedSources.delete(fileName); if (type === "unlink") { entryIds.delete(fileName); transformWatchFiles.delete(fileName); transformAnalyzedTags.delete(fileName); virtualFilesForTemplate.delete(fileName); } for (const [id, files] of transformWatchFiles) { if (anyMatch(files, fileName)) { devServer.watcher.emit("change", id); } } if (type === "unlink" || type === "add") { const optionalMatch = optionalWatchFileReg.exec(fileName); if (optionalMatch) { const markoFile = fileName.slice(0, optionalMatch.index + 1) + (optionalMatch[1] || "index") + ".marko"; if (transformWatchFiles.has(markoFile)) { devServer.watcher.emit("change", markoFile); } } } }); }, async hotUpdate(ctx) { compiler3.taglib.clearCaches(); baseConfig.cache.clear(); clearScanCache(); const modules = new Set(ctx.modules); const fileName = normalizePath(ctx.file); for (const [parent, files] of transformAnalyzedTags) { if (files.has(fileName)) { const mods = this.environment.moduleGraph.getModulesByFile(parent); if (mods) { for (const mod of mods) { modules.add(mod); } } } } const virtualFileIds = virtualFilesForTemplate.get(fileName); if (virtualFileIds) { for (const id of virtualFileIds) { if (!isDeferredPromise(virtualFiles.get(id))) { virtualFiles.set(id, createDeferredPromise()); } const mods = this.environment.moduleGraph.getModulesByFile(id); if (mods) { for (const mod of mods) { this.environment.moduleGraph.invalidateModule(mod); modules.add(mod); } } } } if (linked && this.environment.name === "ssr") { const previous = /* @__PURE__ */ new Map(); for (const mod of modules) { const code = ssrTransformCache.get(mod.id); if (code !== void 0) { previous.set(mod, code); } } if (previous.size) { let reload = false; for (const [mod, prevCode] of previous) { await this.environment.transformRequest(mod.id); if (prevCode !== ssrTransformCache.get(mod.id) && !devServer.environments.client.moduleGraph.getModulesByFile( mod.id )) { reload = true; } } if (reload) { devServer.hot.send({ type: "full-reload" }); } } } if (modules.size !== ctx.modules.length) { return [...modules]; } }, async options(inputOptions) { if (linked && isBuild) { if (this.environment.name === "ssr") { serverManifest = { entries: {}, entrySources: {}, chunksNeedingAssets: [], ssrAssetIds: [] }; } else { if (!isBuildApp && !serverManifest) { serverManifest = await store.read().catch(noop2); } if (serverManifest) { if (isEmpty(serverManifest.entries)) { inputOptions.input = noClientAssetsRuntimeId; } else { inputOptions.input = toHTMLEntries( root, serverManifest.entries ); for (const entry in serverManifest.