Skip to content

Commit

Permalink
- update /readme.md with information about how the library works, a…
Browse files Browse the repository at this point in the history
…nd how to use it.

- update the module documentation of `/src/loader.ts` to include information about how it works.
- export submodules in `/src/mod.ts`, and update the package exports in `/deno.json`.
  • Loading branch information
omar-azmi committed Nov 28, 2024
1 parent 6fc69b8 commit 48da7f3
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 55 deletions.
8 changes: 6 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
},
"exports": {
".": "./src/mod.ts",
"./loader": "./src/loader.ts",
"./fs": "./src/fs.ts",
"./funcdefs": "./src/funcdefs.ts",
"./typedefs": "./src/typedefs.ts"
},
Expand All @@ -36,11 +38,13 @@
"semiColons": false,
"singleQuote": false,
"lineWidth": 800,
"proseWrap": "never",
"proseWrap": "preserve",
"include": [
"./src/",
"./examples/",
"./test/"
"./test/",
"./*.md",
"./*.json"
]
},
"compilerOptions": {
Expand Down
76 changes: 75 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,82 @@

A utility library for building generic file loading plugins for esbuild that are compatible with `Deno` runtime and its esbuild plugin ([`jsr:@luca/esbuild-deno-loader`](https://jsr.io/@luca/esbuild-deno-loader)).

## Super Mandatory Example
## Super mandatory example

```ts
// TODO: never
```

## Documentation link

[`github pages`](https://oazmi.github.io/esbuild-generic-loader/)

## How the loader works:

To put it simply, a subclass of {@link "loader"!GenericLoader} performs the following steps in order:

1. {@link "loader"!GenericLoader.extractDeps} parses the dependencies of the provided `content`.
2. {@link "loader"!GenericLoader.parseToJs} creates a javascript-code that dynamically imports the dependencies, and exports the original `content`.
3. [**you**](https://en.wikipedia.org/wiki/human) pass the javacscript-code to `esbuild` for bundling and transformation of the import statements.
4. {@link "loader"!GenericLoader.unparseFromJs} parses the resulting output javascript-code and extracts the new path names of the dependencies.
5. {@link "loader"!GenericLoader.injectDeps} merges back the parsed dependencies to the original `content`.

## Loader usage example:

Here is how you would typically use a subclass of the {@link "loader"!GenericLoader}:

- instantiate a {@link "loader"!GenericLoader} instance with optional config (which currently does nothing).

```ts
// make sure that you have extended `GenericLoader` and redefined the abstract methods
class MyLoader extends GenericLoader {}

const my_file_loader = new MyLoader({
path: "D:/my/project/my_file.xyz",
})
```

- convert the contents of the file you wish to bundle to equivalent javascript code using the {@link "loader"!GenericLoader.parseToJs} method.

```ts
const js_content = await my_file_loader.parseToJs()
```

- pass the js content to your esbuild plugin's `onLoad` result, or use it as an entrypoint via `stdin`.

```ts
const build_result = await esbuild.build({
absWorkingDir: "D:/my/project/",
splitting: true, // required, so that the bundled `js_content` imports the referenced dependency files, instead of having them injected.
format: "esm", // required for the `splitting` option to work
bundle: true, // required, otherwise all links/dependencies will be treated as "external" and won't be transformed.
outdir: "./out/", // required, for multiple output files
write: false, // required, because the bundled content needs to exist in-memory for us to transform/unparse it back to its original form.
minify: true, // optiotnal, useful for treeshaking.
chunkNames: "[ext]/[name]-[hash]", // optional, useful for specifying the structure of the output directory
assetNames: "assets/[name]-[hash]", // optional, useful for specifying the structure of the output directory
plugins: [...denoPlugins()], // optional, use the Deno esbuild plugin to resolve "http://", "file://", "jsr:", and "npm:" imports.
stdin: {
contents: js_content,
loader: "ts",
resolveDir: "D:/my/project/",
sourcefile: "D:/my/project/my_file.xyz",
},
})
```

- once the build is complete, convert back the bundled entrypoint from javascript to your file's format using the {@link "loader"!GenericLoader.unparseFromJs} method.

```ts
const js_content_bundled = build_result.outputFiles[0].text // assuming that the first output file corresponds to your entrypoint
const my_file_bundled = await my_file_loader.unparseFromJs(js_content_bundled)
```

- merge back the string contents of `my_file_bundled` to `build_results.outputFiles`,
and then write the outputs to the filesystem using the {@link "fs"!writeOutputFiles} utility function.

```ts
const { hash, path } = outputs.outputFiles[0]
build_result.outputFiles[0] = { text: my_file_bundled, hash, path }
await writeOutputFiles(outputs.outputFiles)
```
121 changes: 69 additions & 52 deletions src/loader.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,74 @@
/** a generic esbuild loader.
*
* ## How it works:
* - TODO: explain
*
* ## Usage procedure:
* - instantiate a `GenericLoader` instance with the contents of the file you wish to bundle.
* ```ts
* // make sure that you have extended `GenericLoader` and redefined the abstract methods
* class MyLoader extends GenericLoader { }
*
* const my_file_loader = new MyLoader(
* `include abc from "./another_file.ts"; abc();`,
* { path: "D:/my/project/my_file.xyz" },
* )
* ```
* - convert your loaded file to equivalent javascript/typescript code using the {@link parseToJs} method.
* ```ts
* const js_content = await my_file_loader.parseToJs()
* ```
* ## How the loader works:
*
* To put it simply, a subclass of {@link GenericLoader} performs the following steps in order:
*
* 1. {@link GenericLoader.extractDeps} parses the dependencies of the provided `content`.
* 2. {@link GenericLoader.parseToJs} creates a javascript-code that dynamically imports the dependencies, and exports the original `content`.
* 3. [**you**](https://en.wikipedia.org/wiki/human) pass the javacscript-code to `esbuild` for bundling and transformation of the import statements.
* 4. {@link GenericLoader.unparseFromJs} parses the resulting output javascript-code and extracts the new path names of the dependencies.
* 5. {@link GenericLoader.injectDeps} merges back the parsed dependencies to the original `content`.
*
* ## Loader usage example:
*
* Here is how you would typically use a subclass of the {@link GenericLoader}:
*
* - instantiate a {@link GenericLoader} instance with optional config (which currently does nothing).
*
* ```ts
* // make sure that you have extended `GenericLoader` and redefined the abstract methods
* class MyLoader extends GenericLoader {}
*
* const my_file_loader = new MyLoader({
* path: "D:/my/project/my_file.xyz",
* })
* ```
*
* - convert the contents of the file you wish to bundle to equivalent javascript code using the {@link GenericLoader.parseToJs} method.
*
* ```ts
* const js_content = await my_file_loader.parseToJs()
* ```
*
* - pass the js content to your esbuild plugin's `onLoad` result, or use it as an entrypoint via `stdin`.
* ```ts
* const build_result = await esbuild.build({
* absWorkingDir: "D:/my/project/",
* splitting: true, // required, so that the bundled `js_content` imports the referenced dependency files, instead of having them injected.
* format: "esm", // required for the `splitting` option to work
* bundle: true, // required, otherwise all links/dependencies will be treated as "external" and won't be transformed.
* outdir: "./out/", // required, for multiple output files
* write: false, // required, because the bundled content needs to exist in-memory for us to transform/unparse it back to its original form.
* minify: true, // optiotnal, useful for treeshaking.
* chunkNames: "[ext]/[name]-[hash]", // optional, useful for specifying the structure of the output directory
* assetNames: "assets/[name]-[hash]", // optional, useful for specifying the structure of the output directory
* plugins: [...denoPlugins()], // optional, use the Deno esbuild plugin to resolve "http://", "file://", "jsr:", and "npm:" imports.
* stdin: {
* contents: js_content,
* loader: "ts",
* resolveDir: "D:/my/project/",
* sourcefile: "D:/my/project/my_file.xyz",
* },
* })
* ```
* - once the build is complete, convert back the bundled entrypoint from javascript to your file's format using the {@link unparseFromJs} method.
* ```ts
* const js_content_bundled = build_result.outputFiles[0].text // assuming that the first output file corresponds to your entrypoint
* const my_file_bundled = await my_file_loader.unparseFromJs(js_content_bundled)
* ```
* - merge back the string contents of `my_file_bundled` to `build_results.outputFiles`, and then write the outputs to the filesystem using the {@link writeOutputFiles} utility function.
* ```ts
* const { hash, path } = outputs.outputFiles[0]
* build_result.outputFiles[0] = { text: my_file_bundled, hash, path }
* await writeOutputFiles(outputs.outputFiles)
* ```
*
* ```ts
* const build_result = await esbuild.build({
* absWorkingDir: "D:/my/project/",
* splitting: true, // required, so that the bundled `js_content` imports the referenced dependency files, instead of having them injected.
* format: "esm", // required for the `splitting` option to work
* bundle: true, // required, otherwise all links/dependencies will be treated as "external" and won't be transformed.
* outdir: "./out/", // required, for multiple output files
* write: false, // required, because the bundled content needs to exist in-memory for us to transform/unparse it back to its original form.
* minify: true, // optiotnal, useful for treeshaking.
* chunkNames: "[ext]/[name]-[hash]", // optional, useful for specifying the structure of the output directory
* assetNames: "assets/[name]-[hash]", // optional, useful for specifying the structure of the output directory
* plugins: [...denoPlugins()], // optional, use the Deno esbuild plugin to resolve "http://", "file://", "jsr:", and "npm:" imports.
* stdin: {
* contents: js_content,
* loader: "ts",
* resolveDir: "D:/my/project/",
* sourcefile: "D:/my/project/my_file.xyz",
* },
* })
* ```
*
* - once the build is complete, convert back the bundled entrypoint from javascript to your file's format using the {@link GenericLoader.unparseFromJs} method.
*
* ```ts
* const js_content_bundled = build_result.outputFiles[0].text // assuming that the first output file corresponds to your entrypoint
* const my_file_bundled = await my_file_loader.unparseFromJs(js_content_bundled)
* ```
*
* - merge back the string contents of `my_file_bundled` to `build_results.outputFiles`,
* and then write the outputs to the filesystem using the {@link "fs"!writeOutputFiles} utility function.
*
* ```ts
* const { hash, path } = outputs.outputFiles[0]
* build_result.outputFiles[0] = { text: my_file_bundled, hash, path }
* await writeOutputFiles(outputs.outputFiles)
* ```
*
* @module
*/
Expand Down Expand Up @@ -81,8 +100,6 @@ await import(${json_stringify(import_path)})`
/** the base class for creating custom loaders for any file type that is natively unsupported by `esbuild`.
* - each loader _class_ handles one type of new file type.
* - each loader _instance_ handles **one file**, and can be used only **once**, so that it does not hog onto resources.
*
* TODO: make to possible to enable string literal templating by removing the replacement of the dollarsign from `stringToJsEvalString`
*/
export abstract class GenericLoader<K = string> {
public meta: { imports: ImportMetadata<K> } = { imports: [] }
Expand Down Expand Up @@ -111,7 +128,7 @@ export abstract class GenericLoader<K = string> {
*
* by default, the baseclass {@link GenericLoader} escapes all characters of the `content` parameter,
* so that the string is perfectly preserved after the virtual module's evaluation. <br>
* this is achieved by using `String.raw` and escaping all dollarsigns ("$") and backticks ("\`") with template expressions.
* this is achieved by using `String.raw` and escaping all dollarsigns ("$") and backticks ("\\`") with template expressions.
* however, such a thing may not be desirable, and you may want the evaluation of the template expressions within your `content`, rather than suppressing it.
* or you may wish to introduce additional functions to the script so that it evaluates the output content through a series of transformations. <br>
* in such cases, you would want to overload this method to suit your transformations needs.
Expand Down
5 changes: 5 additions & 0 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
*
* A utility library for building generic file loading plugins for esbuild that are compatible with `Deno` runtime and its esbuild plugin ([`jsr:@luca/esbuild-deno-loader`](https://jsr.io/@luca/esbuild-deno-loader)).
*/

export * from "./funcdefs.ts"
export { GenericLoader } from "./loader.ts"
export type * from "./typedefs.ts"

0 comments on commit 48da7f3

Please sign in to comment.