htmelt
Version:
Bundle your HTML assets with Esbuild and LightningCSS. Custom plugins, HMR platform, and more.
314 lines (310 loc) • 10.4 kB
JavaScript
import {
compileSeparateEntry,
importGlob_default,
importMetaUrl_default
} from "./chunk-SE5MUBQP.mjs";
import "./chunk-26E6E5EJ.mjs";
import {
findDirectoryUp,
resolveDevMapSources
} from "./chunk-SGZXFKQT.mjs";
// src/plugins/devModules.mts
import {
fileToId,
parseNamespace,
sendFile,
uriToFile,
uriToId
} from "@htmelt/plugin";
import { parse } from "meriyah";
import { nebu } from "nebu";
import { dirname } from "path";
// src/sourceMaps.mts
import remapping from "@ampproject/remapping";
import * as convertSourceMap from "convert-source-map";
import * as fs from "fs";
import * as path from "path";
function appendInlineSourceMap(code, map) {
return code + "\n//# sourceMappingURL=" + mapToDataUrl(map);
}
function mapToDataUrl(map) {
return "data:application/json;charset=utf-8;base64," + Buffer.from(JSON.stringify(map)).toString("base64");
}
// src/plugins/devModules.mts
var devModulesPlugin = async (config) => {
const modules = config.modules;
config.watcher?.on("unlink", (file) => {
const id = fileToId(file);
modules.delete(id);
});
const esbuildDevModules = {
name: "dev-modules",
setup(build) {
build.onTransform({ loaders: ["js"] }, async (args) => {
const program = parse(args.code, {
next: true,
ranges: true,
module: true
});
const resolutions = /* @__PURE__ */ new Map();
await Promise.all(
program.body.map(async (node) => {
if (node.type !== "ImportDeclaration") {
return;
}
const id = node.source.value;
const resolved = await build.resolve(id, {
kind: "import-statement",
importer: args.path,
resolveDir: dirname(args.path)
});
resolutions.set(node, resolved);
})
);
const nebuDevModules = {
Program(program2) {
for (const node of program2.body) {
if (node.isImportDeclaration()) {
let resolved = resolutions.get(node.n);
if (resolved === void 0) {
continue;
}
const resolvedId = fileToId(resolved.path, resolved.namespace);
if (!modules.has(resolvedId)) {
continue;
}
if (node.specifiers.length === 1 && node.specifiers[0].isImportNamespaceSpecifier()) {
const localName = node.specifiers[0].local.name;
node.replace(
`const ${localName} = htmelt.import("${resolvedId}")`
);
} else {
const specifiers = node.specifiers.map((specifier) => {
const exported = specifier.isImportDefaultSpecifier() ? "default" : specifier.imported.name;
return exported + (exported !== specifier.local.name ? ": " + specifier.local.name : "");
});
node.replace(
`const {${specifiers.join(
", "
)}} = htmelt.import("${resolvedId}")`
);
}
} else if (node.isExportAllDeclaration() || node.isExportNamedDeclaration() || node.isExportDefaultDeclaration()) {
node.remove();
}
}
}
};
const result = nebu.process(args.code, {
filename: args.path,
ast: program,
sourceMap: true,
sourceMapHiRes: true,
plugins: [nebuDevModules]
});
return {
code: result.js,
map: result.map
};
});
}
};
config.loadDevModule = async (entry) => {
const sourceRoot = dirname(entry);
const {
outputFiles: [mapFile, jsFile]
} = await compileSeparateEntry(entry, config, {
format: "esm",
outfile: entry.replace(/\.\w+$/, "." + Date.now() + "$&"),
sourcemap: true,
sourceRoot,
plugins: [
...config.esbuild.plugins,
importMetaUrl_default(),
importGlob_default(config.relatedWatcher),
esbuildDevModules
]
});
const map = JSON.parse(mapFile.text);
resolveDevMapSources(map, process.cwd(), sourceRoot);
map.sourceRoot = void 0;
return appendInlineSourceMap(jsFile.text, map);
};
config.esbuild.plugins.push({
name: "dev-exports",
setup(build) {
const nebuDevExports = {
Program(program, { module, resolutions }) {
const exports = [];
for (let node of program.body) {
if (node.isImportDeclaration()) {
module.imports.add(node.source.value);
} else if (node.isExportNamedDeclaration()) {
if (node.source) {
const resolved = resolutions.get(node.n);
if (!resolved) {
continue;
}
const aliases = node.specifiers.map((specifier) => [
specifier.exported.name,
specifier.local.name
]);
exports.push({
from: fileToId(resolved.path, resolved.namespace),
aliases: Object.fromEntries(aliases)
});
continue;
}
if (node.declaration) {
node = node.declaration;
if (node.isVariableDeclaration()) {
for (const decl of node.declarations) {
const { name } = decl.id;
if (node.kind === "const") {
exports.push([name, name]);
} else {
exports.push({
name,
get: jsonAccessor(name)
});
}
}
} else {
const name = node.id.name;
exports.push([name, name]);
}
continue;
}
exports.push({
values: Object.fromEntries(
node.specifiers.map((specifier) => [
specifier.exported.name,
jsonAccessor(specifier.local.name)
])
)
});
} else if (node.isExportDefaultDeclaration()) {
node.before("let __default;");
node.declaration.before(`__default = `);
exports.push(["default", "__default"]);
} else if (node.isExportAllDeclaration()) {
const resolved = resolutions.get(node.n);
if (resolved) {
exports.push({
from: fileToId(resolved.path, resolved.namespace)
});
}
}
}
program.push(
"body",
`
htmelt.export("${module.id}", [` + exports.map(stringifyExport).join(",") + "])"
);
}
};
build.onTransform({ loaders: ["js", "jsx"] }, async (args) => {
if (args.code.trim() === "") {
return null;
}
const id = fileToId(args.initialPath || args.path, args.namespace);
const program = parse(args.code, {
next: true,
ranges: true,
module: true,
jsx: args.loader === "jsx"
});
const resolutions = /* @__PURE__ */ new Map();
await Promise.all(
program.body.map(async (node) => {
if (node.type !== "ExportAllDeclaration" && node.type !== "ExportNamedDeclaration" || !node.source) {
return;
}
const id2 = node.source.value;
const resolved = await build.resolve(id2, {
kind: "import-statement",
importer: args.path,
resolveDir: dirname(args.path)
});
resolutions.set(node, resolved);
})
);
const newId = !modules.has(id);
const newModule = {
id,
imports: /* @__PURE__ */ new Set()
};
modules.set(id, newModule);
const result = nebu.process(args.code, {
ast: program,
filename: args.path,
sourceMap: true,
sourceMapHiRes: true,
plugins: [nebuDevExports],
state: {
module: newModule,
resolutions
}
});
if (newId && id.startsWith("/@fs/") && !id.includes("node_modules"))
for (const dir of config.linkedPackages) {
if (id.startsWith("/@fs" + dir + "/")) {
const file = id.slice(4);
config.watcher.add(file);
const rootDir = findDirectoryUp(
dirname(file),
[".git", "package.json"],
config.fsAllowedDirs
);
if (rootDir && !config.fsAllowedDirs.has(rootDir)) {
config.fsAllowedDirs.add(rootDir);
}
}
}
return {
code: result.js,
map: result.map
};
});
}
});
const moduleRE = /\.([mc]?[tj]s|[tj]sx)$/;
return {
async serve(request, response) {
if (request.searchParams.has("t") && moduleRE.test(request.pathname)) {
const id = uriToId(request.pathname);
const namespace = parseNamespace(id);
const filePath = !namespace ? uriToFile(request.pathname) : void 0;
try {
const data = await config.loadDevModule(filePath || id);
sendFile(request.pathname, response, {
path: filePath,
data,
headers: {
"content-type": "application/javascript"
}
});
} catch {
}
}
}
};
};
function jsonAccessor(name) {
return new Function("", "return () => " + name)();
}
function stringifyExport(e) {
if (Array.isArray(e)) {
return "[" + JSON.stringify(e[0]) + "," + e[1] + "]";
}
if ("from" in e) {
return JSON.stringify(e);
}
if ("values" in e) {
return "{values:{" + Object.entries(e.values).map(([name, get]) => JSON.stringify(name) + ":" + get.toString()).join(",") + "}}";
}
return "{name:" + JSON.stringify(e.name) + ",get:" + e.get.toString() + "}";
}
export {
devModulesPlugin
};