diff --git a/examples/1_html/build.ts b/examples/1_html/build.ts
index 91965e1..0608b29 100644
--- a/examples/1_html/build.ts
+++ b/examples/1_html/build.ts
@@ -2,6 +2,7 @@ import { denoPlugins } from "jsr:@luca/esbuild-deno-loader@0.11.0"
import { fromFileUrl } from "jsr:@std/path"
import esbuild from "npm:esbuild"
import { resolveAsUrl } from "../../src/deps.ts"
+import { writeOutputFiles } from "../../src/fs.ts"
import { HtmlLoader } from "./loader.ts"
@@ -11,7 +12,7 @@ const
html_file_content = await (await fetch(html_file_path)).text()
const html_file_loader = new HtmlLoader({ path: fromFileUrl(html_file_path) })
-const js_txt = await html_file_loader.parseToJs(html_file_content)
+const html_in_js = await html_file_loader.parseToJs(html_file_content)
const results = await esbuild.build({
absWorkingDir: fromFileUrl(this_dir_path),
@@ -19,7 +20,7 @@ const results = await esbuild.build({
target: "esnext",
platform: "browser",
stdin: {
- contents: js_txt,
+ contents: html_in_js,
loader: "ts",
resolveDir: fromFileUrl(resolveAsUrl("./", html_file_path)),
sourcefile: fromFileUrl(html_file_path),
@@ -34,11 +35,24 @@ const results = await esbuild.build({
plugins: [...denoPlugins()],
})
-const js_compiled_text = results.outputFiles[0].text
-const html_compiled_text = await html_file_loader.unparseFromJs(js_compiled_text)
-console.log("%c" + "bundled html file output:", "color: green; font-weight: bold;")
+const
+ [html_in_js_compiled, ...other_output_files] = results.outputFiles,
+ html_compiled_text = await html_file_loader.unparseFromJs(html_in_js_compiled.text)
+
+console.log("%c" + `bundled html file: "0", path: "${html_in_js_compiled.path}"`, "color: green; font-weight: bold;")
console.log(html_compiled_text)
-results.outputFiles.slice(1).forEach((js_file, index) => {
- console.log("%c" + `bundled js file: ${index}`, "color: green; font-weight: bold;")
+other_output_files.forEach((js_file, index) => {
+ console.log("%c" + `bundled js file: "${index + 1}", path: "${js_file.path}"`, "color: green; font-weight: bold;")
console.log(js_file.text)
})
+
+await writeOutputFiles([
+ {
+ path: html_in_js_compiled.path.replace(/\.js$/, ".html"),
+ text: html_compiled_text
+ }, ...other_output_files
+], {
+ dir: "./output/",
+ log: "verbose",
+ dryrun: false,
+})
diff --git a/src/deps.ts b/src/deps.ts
index 7b8aacc..0f9b474 100644
--- a/src/deps.ts
+++ b/src/deps.ts
@@ -1,5 +1,5 @@
-export { json_parse, json_stringify, math_min, object_entries, object_fromEntries, object_keys, object_values, promise_all } from "jsr:@oazmi/kitchensink@0.8.5/alias"
-export { getUriScheme, joinPaths, relativePath, resolveAsUrl } from "jsr:@oazmi/kitchensink@0.8.5/pathman"
+export { console_log, json_parse, json_stringify, math_min, object_entries, object_fromEntries, object_keys, object_values, promise_all } from "jsr:@oazmi/kitchensink@0.8.5/alias"
+export { ensureEndSlash, getUriScheme, joinPaths, pathToPosixPath, relativePath, resolveAsUrl, resolvePathFactory } from "jsr:@oazmi/kitchensink@0.8.5/pathman"
export { isArray, isString } from "jsr:@oazmi/kitchensink@0.8.5/struct"
/** flags used for minifying (or eliminating) debugging logs and asserts, when an intelligent bundler, such as `esbuild`, is used. */
diff --git a/src/fs.ts b/src/fs.ts
new file mode 100644
index 0000000..5536a37
--- /dev/null
+++ b/src/fs.ts
@@ -0,0 +1,108 @@
+/** this submodule contains utility functions for writing esbuild's virtual output to the filesystem.
+ *
+ * this submodule is separated from the rest since it performs filesystem operations, which is runtime-dependant,
+ * and we don't want to impact the portability of the main module.
+ *
+ * @module
+*/
+
+import { ensureFile } from "jsr:@std/fs@1.0.6"
+import { console_log, ensureEndSlash, pathToPosixPath, promise_all, resolvePathFactory } from "./deps.ts"
+
+
+/** get the current working directory (`Deno.cwd`) in posix path format. */
+export const getCwdPath = () => { return ensureEndSlash(pathToPosixPath(Deno.cwd())) }
+
+/** resolve a file path so that it becomes absolute, with unix directory separator ("/").
+ * TODO: refactor the name `pathResolve` to `resolvePath`
+*/
+export const pathResolve = resolvePathFactory(getCwdPath)
+
+/** the tuple description of a writable (or appendable) file.
+ * - the first entry of the array must describe the destination path of the file,
+ * relative to the directory defined in {@link CreateFilesConfig.dir}).
+ * - the second entry should be the file's contents, which can either be a `string` text, a `ReadableStream`, or a `Uint8Array` binary.
+ * - the third and optional entry lets you specify additional {@link Deno.WriteFileOptions | deno specific file writing options},
+ * such as `"append"` the new text, or permit the creation (`"create"`) of new file if it doesn't exist, etc...
+*/
+export type WritableFileConfig = [
+ destination: string,
+ content: string | Uint8Array,
+ options?: Deno.WriteFileOptions,
+]
+
+/** configuration options for {@link createFiles}. */
+export interface CreateFilesConfig {
+ /** the desired output directory.
+ * if a relative path is provided, then it will be resolved as a path relative to Deno's current working directory. (which is generally where `deno.json` resides.)
+ */
+ dir?: string
+
+ /** select logging level:
+ * - `false` or `"none"`: skip logging.
+ * - `true` or `"basic"`: log what is being carried out at the top level.
+ * - `"verbose"`: in addition to basic logging, it also logs which files/folders are being copied or generated.
+ *
+ * @defaultValue `"basic"`
+ */
+ log?: boolean | "none" | "basic" | "verbose"
+
+ /** enable `dryrun` if you wish for nothing to be written onto the the filesystem.
+ *
+ * @defaultValue `false`
+ */
+ dryrun?: boolean
+}
+
+/** the in-memory output file description generated by `esbuild`. */
+export interface EsbuildOutputFile {
+ path: string
+ text?: string
+ contents?: Uint8Array
+ hash?: string
+}
+
+/** print some basic useful information on the console.
+ * the print will only appear if the logging-level is either set to `"basic"` or `"verbose"` via {@link setLog}
+*/
+const logBasic = (log_level: NonNullable, ...data: any[]): void => {
+ if (log_level === "basic" || log_level === "verbose") { console_log(...data) }
+}
+
+/** print verbose details on the console.
+ * the print will only appear if the logging-level is either set to `"verbose"` via {@link setLog}
+*/
+const logVerbose = (log_level: NonNullable, ...data: any[]): void => {
+ if (log_level === "verbose") { console_log(...data) }
+}
+
+/** write a collection of virtual files to your filesystem.
+ * this function accepts virtual files that are either in text (`string`), binary (`Uint8Array`), or streamable text/binary (`ReadableStream`) formats.
+ * it is important that you provide the configuration parameter's {@link config["dir"] | `dir`} field, so that relative paths can be resolved according to the provided directory.
+*/
+export const createFiles = async (virtual_files: Array, config: CreateFilesConfig = {}): Promise => {
+ const { dir = "./", log = "basic", dryrun = false } = config
+ // writing text or binary files
+ logBasic(log, "[in-fs] writing additional text/binary files to your build directory")
+ await promise_all(virtual_files.map(async ([dst_path, content, options]) => {
+ const abs_dst = pathResolve(dir, dst_path)
+ logVerbose(log, `[in-fs] writing file to: "${abs_dst}"`, "with the configuration:", options)
+ if (!dryrun) {
+ await ensureFile(abs_dst)
+ if (typeof content === "string") {
+ await Deno.writeTextFile(abs_dst, content, options)
+ } else {
+ await Deno.writeFile(abs_dst, content, options)
+ }
+ }
+ }))
+}
+
+export const writeOutputFiles = async (virtual_files: Array, config: CreateFilesConfig = {}): Promise => {
+ return createFiles(virtual_files.map((virtual_file): WritableFileConfig => {
+ const
+ { path, contents, text } = virtual_file,
+ content = contents ?? text ?? ""
+ return [path, content]
+ }), config)
+}