Plugins
Plugins intercept imports and perform custom loading logic: reading files, transpiling code, etc. They can be used to add support for additional file types, like .scss or .yaml. In the context of JSTime’s bundler, plugins can be used to implement framework-level features like CSS extraction, macros, and client-server code co-location.
A plugin is defined as simple JavaScript object containing a name property and a setup function. Register a plugin with JSTime using the plugin function.
import { plugin, type BunPlugin } from "jstime";
const myPlugin: BunPlugin = { name: "Custom loader", setup(build) { // implementation },};Plugins have to be registered before any other code runs! To achieve this, use the preload option in your bunfig.toml. JSTime automatically loads the files/modules specified in preload before running a file.
preload = ["./myPlugin.ts"]To preload files before jstime test:
[test]preload = ["./myPlugin.ts"]Third-party plugins
Section titled “Third-party plugins”By convention, third-party plugins intended for consumption should export a factory function that accepts some configuration and returns a plugin object.
import { plugin } from "jstime";import fooPlugin from "jstime-plugin-foo";
plugin( fooPlugin({ // configuration }),);import { plugin } from "jstime";import mdx from "@mdx-js/esbuild";
plugin(mdx());Loaders
Section titled “Loaders”Plugins are primarily used to extend JSTime with loaders for additional file types. Let’s look at a simple plugin that implements a loader for .yaml files.
import { plugin } from "jstime";
plugin({ name: "YAML", async setup(build) { const { load } = await import("js-yaml"); const { readFileSync } = await import("fs");
// when a .yaml file is imported... build.onLoad({ filter: /\.(yaml|yml)$/ }, (args) => {
// read and parse the file const text = readFileSync(args.path, "utf8"); const exports = load(text) as Record<string, any>;
// and returns it as a module return { exports, loader: "object", // special loader for JS objects }; }); },});With this plugin, data can be directly imported from .yaml files.
import "./yamlPlugin.ts"import {name, releaseYear} from "./data.yml"
console.log(name, releaseYear);name: Fast XreleaseYear: 2023Note that the returned object has a loader property. This tells JSTime which of its internal loaders should be used to handle the result. Even though we’re implementing a loader for .yaml, the result must still be understandable by one of JSTime’s built-in loaders. It’s loaders all the way down.
- Loader
- Extensions
- Output
js.mjs.cjs- Transpile to JavaScript files
jsx.js.jsx- Transform JSX then transpile
ts.ts.mtscts- Transform TypeScript then transpile
tsx.tsx- Transform TypeScript, JSX, then transpile
toml.toml- Parse using JSTime’s built-in TOML parser
json.json- Parse using JSTime’s built-in JSON parser
napi.node- Import a native Node.js addon
wasm.wasm- Import a native Node.js addon
object- none
- A special loader intended for plugins that converts a plain JavaScript object to an equivalent ES module. Each key in the object corresponds to a named export.
Loading a YAML file is useful, but plugins support more than just data loading. Let’s look at a plugin that lets JSTime import *.svelte files.
import { plugin } from "jstime";
await plugin({ name: "svelte loader", async setup(build) { const { compile } = await import("svelte/compiler"); const { readFileSync } = await import("fs");
// when a .svelte file is imported... build.onLoad({ filter: /\.svelte$/ }, ({ path }) => {
// read and compile it with the Svelte compiler const file = readFileSync(path, "utf8"); const contents = compile(file, { filename: path, generate: "ssr", }).js.code;
// and return the compiled source code as "js" return { contents, loader: "js", }; }); },});Note: in a production implementation, you’d want to cache the compiled output and include additional error handling.
The object returned from build.onLoad contains the compiled source code in contents and specifies "js" as its loader. That tells JSTime to consider the returned contents to be a JavaScript module and transpile it using JSTime’s built-in js loader.
With this plugin, Svelte components can now be directly imported and consumed.
import "./sveltePlugin.ts";import MySvelteComponent from "./component.svelte";
console.log(mySvelteComponent.render());Reading the config
Section titled “Reading the config”JSTime.build({ entrypoints: ["./app.ts"], outdir: "./dist", sourcemap: "external", plugins: [ { name: "demo", setup(build) { console.log(build.config.sourcemap); // "external"
build.config.minify = true; // enable minification
// `plugins` is readonly console.log(`Number of plugins: ${build.config.plugins.length}`); }, }, ],});Reference
Section titled “Reference”namespace JSTime { function plugin(plugin: { name: string; setup: (build: PluginBuilder) => void; }): void;}
type PluginBuilder = { onResolve: ( args: { filter: RegExp; namespace?: string }, callback: (args: { path: string; importer: string }) => { path: string; namespace?: string; } | void, ) => void; onLoad: ( args: { filter: RegExp; namespace?: string }, callback: (args: { path: string }) => { loader?: Loader; contents?: string; exports?: Record<string, any>; }, ) => void; config: BuildConfig;};
type Loader = "js" | "jsx" | "ts" | "tsx" | "json" | "toml" | "object";The onLoad method optionally accepts a namespace in addition to the filter regex. This namespace will be be used to prefix the import in transpiled code; for instance, a loader with a filter: /\.yaml$/ and namespace: "yaml:" will transform an import from ./myfile.yaml into yaml:./myfile.yaml.