@marko/vite
Version:
A Marko plugin for Vite
1,562 lines (1,528 loc) • 66.6 kB
JavaScript
// 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, "'") + '"'
);
curString = "";
} else {
curString += ` ${attr.name}="${attr.value.replace(/"/g, "'")}"`;
}
}
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, "'")}"\`
: "";
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.