UNPKG

htmelt

Version:

Bundle your HTML assets with Esbuild and LightningCSS. Custom plugins, HMR platform, and more.

312 lines (309 loc) 10 kB
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 };