htmelt
Version:
Bundle your HTML assets with Esbuild and LightningCSS. Custom plugins, HMR platform, and more.
312 lines (309 loc) • 10 kB
JavaScript
import {
createRelatedWatcher
} from "./chunk-QYCXBBSD.mjs";
import {
importHandler
} from "./chunk-QUWWPAKA.mjs";
import {
CaseInsensitiveMap,
findFreeTcpPort
} from "./chunk-SGZXFKQT.mjs";
// src/config.mts
import {
fileToId,
loadConfigFile,
md5Hex
} from "@htmelt/plugin";
import browserslist from "browserslist";
import browserslistToEsbuild from "browserslist-to-esbuild";
import chokidar from "chokidar";
import * as fs from "fs";
import glob from "glob";
import * as lightningCss from "lightningcss";
import * as path from "path";
var env = JSON.stringify;
var Glob = glob.Glob;
async function loadBundleConfig(flags, cli) {
const nodeEnv = process.env.NODE_ENV ||= "development";
const configResult = await loadConfigFile("bundle.config");
const userConfig = configResult?.mod.default ?? {};
if (configResult) {
console.log(
"Loaded %s in %sms",
path.relative(process.cwd(), configResult.filePath),
configResult.loadTime
);
}
const preDefaultPlugins = [
await loadPlugin(import("./alias-W7MWYCDA.mjs")),
await loadPlugin(import("./virtualFiles-HAY7WWK2.mjs")),
await loadPlugin(import("./cssCodeSplit-M2QB2XYU.mjs"))
];
const postDefaultPlugins = [];
if (flags.watch) {
preDefaultPlugins.push(
await loadPlugin(
import("./cssReload-YKJ7Y4MA.mjs")
//
)
);
postDefaultPlugins.push(
await loadPlugin(import("./liveBundles-D3A6KU7Z.mjs")),
await loadPlugin(import("./devModules-LH5EBUTE.mjs"))
);
}
const srcDir = normalizePath(userConfig.src ?? "src");
const outDir = normalizePath(flags.outDir || (userConfig.build ?? "build"));
const srcDirPrefix = srcDir ? srcDir + "/" : srcDir;
const outDirPrefix = outDir + "/";
const devOnlyEntries = nodeEnv !== "development" && userConfig.devOnlyEntries?.map(
(glob2) => new Glob(srcDirPrefix + glob2).minimatch
) || [];
const entries = glob.sync(srcDirPrefix + "**/*.html").filter((file) => {
return !file.startsWith(outDirPrefix) && !devOnlyEntries.some((pattern) => pattern.match(file));
}).concat(userConfig.forcedEntries || []).map((file) => ({
file
}));
const plugins = preDefaultPlugins.concat(
userConfig.plugins || [],
postDefaultPlugins
);
const browsers = userConfig.browsers ?? ">=0.25%, not dead";
const virtualFiles = {};
function setRelatedFiles(filePath, result) {
const relatedPath = result.path || filePath;
result.watchFiles?.forEach((watchedFile) => {
config.relatedWatcher?.watchFile(watchedFile, relatedPath);
});
result.watchDirs?.forEach((watchedDir) => {
config.relatedWatcher?.watchDirectory(watchedDir, relatedPath);
});
}
let serverUrl;
const api = {
setVirtualFile(filePath, virtualFile) {
this.unsetVirtualFile(filePath);
if (virtualFile.promise) {
virtualFile.promise.then((result) => {
if (result) {
setRelatedFiles(filePath, result);
}
});
} else if (virtualFile.current) {
setRelatedFiles(filePath, virtualFile.current);
}
virtualFiles[filePath] = virtualFile;
},
unsetVirtualFile(filePath) {
const virtualFile = virtualFiles[filePath];
if (!virtualFile) {
return;
}
if (virtualFile.promise) {
virtualFile.promise.then((result) => {
if (result) {
config.relatedWatcher?.forgetRelatedFile(result.path || filePath);
}
});
} else if (virtualFile.current) {
config.relatedWatcher?.forgetRelatedFile(
virtualFile.current.path || filePath
);
}
delete virtualFiles[filePath];
},
watch(paths, options) {
let ignored = config.watchIgnore;
if (options?.ignored) {
ignored = ignored.concat(options.ignored);
}
return chokidar.watch(paths, {
atomic: true,
ignoreInitial: true,
ignorePermissionErrors: true,
...options,
ignored
});
},
getBuildPath(file, opts = {}) {
let outFile = file;
const wasAbsolute = path.isAbsolute(file);
if (wasAbsolute) {
file = path.relative(process.cwd(), file);
}
const src = config.src.replace(/^\.\//, "") + "/";
if (file.startsWith(src)) {
outFile = file.replace(src, config.build + "/");
} else {
outFile = path.join(
config.build,
file.startsWith("..") ? path.basename(file) : file
);
}
if (opts.absolute || wasAbsolute && opts.absolute !== false) {
outFile = path.join(process.cwd(), outFile);
}
outFile = outFile.replace(/\.([cm]?)(?:jsx|tsx?)$/, ".$1js");
if (opts.content != null) {
const contentHash = md5Hex(opts.content).slice(0, 8).toUpperCase();
outFile = outFile.replace(/(\.[^./]+)$/, "." + contentHash + "$1");
}
return outFile;
},
resolveDevUrl(id, importer) {
let url = config.resolve(id, importer);
if (url.protocol === "file:") {
url = new URL(fileToId(url.pathname), serverUrl);
}
return url;
},
resolve(id, importer = serverUrl) {
if (typeof importer === "string") {
importer = new URL(importer, "file:");
}
if (id[0] === "/" && importer?.protocol === "file:") {
return new URL("file://" + process.cwd() + id);
}
return new URL(id, importer);
},
mergeServerConfig(config2) {
userConfig.server = {
...userConfig.server,
...config2
};
},
async loadServerConfig() {
const serverConfig = userConfig.server || {};
const https = serverConfig.https != true ? serverConfig.https || void 0 : {};
let port = flags.port ?? (serverConfig.port || 0);
if (port == 0) {
port = await findFreeTcpPort();
}
const protocol = https ? "https" : "http";
const host = flags.host ?? "localhost";
const url = serverUrl = new URL(`${protocol}://${host}:${port}`);
config.esbuild.define["import.meta.env.DEV_URL"] = env(url);
config.esbuild.define["import.meta.env.HMR_URL"] = env(
(https ? "wss" : "ws") + `://${host}:${port}`
);
config.server = {
...serverConfig,
handler: void 0,
handlerContext: void 0,
https,
port,
url
};
if (serverConfig.handler) {
config.server.handlerContext = await importHandler(
serverConfig.handler,
config
);
}
return config.server;
},
// Set by the internal ./plugins/devModules.mjs plugin.
// Not available during plugin setup.
loadDevModule: null
};
const config = {
assets: "public",
deletePrev: false,
...userConfig,
mode: nodeEnv,
src: srcDir,
build: outDir,
base: flags.base ?? userConfig.base ?? "/" + path.relative(process.cwd(), outDir) + "/",
entries,
plugins: [],
bundles: void 0,
virtualFiles,
browsers,
modules: void 0,
watcher: void 0,
watchIgnore: [
"**/{node_modules,.git,.DS_Store}",
"**/{node_modules,.git}/**",
...userConfig.watchIgnore || []
],
linkedPackages: flags.watch ? findLinkedPackages(process.cwd()) : void 0,
fsAllowedDirs: /* @__PURE__ */ new Set(),
copy: userConfig.copy || [],
alias: userConfig.alias || {},
scripts: userConfig.scripts || [],
htmlMinifierTerser: userConfig.htmlMinifierTerser || {},
esbuild: {
...userConfig.esbuild,
plugins: userConfig.esbuild?.plugins || [],
target: userConfig.esbuild?.target ?? browserslistToEsbuild(browsers),
define: {
...userConfig.esbuild?.define,
"process.env.NODE_ENV": env(nodeEnv),
"import.meta.env.DEV": env(nodeEnv != "production"),
"import.meta.env.DEV_URL": "undefined",
"import.meta.env.HMR_URL": "undefined"
}
},
lightningCss: {
...userConfig.lightningCss,
targets: userConfig.lightningCss?.targets ?? lightningCss.browserslistToTargets(browserslist(browsers))
},
server: null,
...api
};
if (flags.watch) {
config.modules = new CaseInsensitiveMap();
config.watcher = config.watch([srcDir, ...userConfig.watchFiles || []]);
config.relatedWatcher = createRelatedWatcher(config);
}
await Promise.all(
plugins.map(async (setup) => {
const plugin = await setup(config, flags);
if (plugin) {
config.plugins.push(plugin);
}
})
);
if (cli) {
for (const plugin of config.plugins) {
plugin.commands?.(cli);
}
}
return config;
}
async function loadPlugin(plugin) {
const module = await plugin;
return module.default ? module.default : Object.values(module)[0];
}
function findLinkedPackages(root, linkedPackages = /* @__PURE__ */ new Set()) {
const nodeModulesDir = path.join(root, "node_modules");
try {
const nodeModules = fs.readdirSync(nodeModulesDir).flatMap(
(name) => name[0] === "@" ? fs.readdirSync(path.join(nodeModulesDir, name)).map((scopedName) => path.join(name, scopedName)) : name
);
const queue = [];
for (const name of nodeModules) {
const dependencyDir = path.join(nodeModulesDir, name);
const resolvedDependencyDir = fs.realpathSync(dependencyDir);
if (resolvedDependencyDir !== dependencyDir && !resolvedDependencyDir.includes("node_modules") && !linkedPackages.has(resolvedDependencyDir) && path.relative(root, resolvedDependencyDir).startsWith("..")) {
queue.push(resolvedDependencyDir);
linkedPackages.add(resolvedDependencyDir);
}
}
for (const root2 of queue) {
findLinkedPackages(root2, linkedPackages);
}
} catch (error) {
if (error.code !== "ENOENT") {
throw error;
}
}
return linkedPackages;
}
function normalizePath(p) {
p = path.normalize(p);
return p === "./" ? "" : p.replace(/\/$/, "");
}
export {
loadBundleConfig
};