bunsai
Advanced tools
Comparing version 2.0.0-preview.7 to 2.0.0
{ | ||
"name": "bunsai", | ||
"version": "2.0.0-preview.7", | ||
"version": "2.0.0", | ||
"module": "./src/core/index.ts", | ||
"repository": "levii-pires/bunsai2", | ||
"devDependencies": { | ||
"@types/bun": "latest" | ||
"@types/bun": "latest", | ||
"@types/urijs": "^1.19.25" | ||
}, | ||
@@ -52,4 +53,4 @@ "exports": { | ||
}, | ||
"./map-router": { | ||
"default": "./src/extra/map-router.ts" | ||
"./asset": { | ||
"default": "./src/extra/asset.ts" | ||
} | ||
@@ -64,3 +65,3 @@ }, | ||
"engines": { | ||
"bun": ">=1.1.6" | ||
"bun": "1.1.7" | ||
}, | ||
@@ -71,3 +72,5 @@ "files": [ | ||
"type": "module", | ||
"dependencies": {}, | ||
"dependencies": { | ||
"urijs": "1.19.11" | ||
}, | ||
"license": "MIT", | ||
@@ -74,0 +77,0 @@ "keywords": [ |
175
README.md
@@ -1,3 +0,174 @@ | ||
# bunsai2 | ||
<p align="center"><img style="width: 25%" src="https://github.com/levii-pires/bunsai2/blob/main/assets/logo.png?raw=true"></p> | ||
todo: create a nice README | ||
<h1 align="center">BunSai 2</h1> | ||
> Bonsai is a japanese art of growing and shaping miniature trees in containers. | ||
> | ||
> Bun + Bonsai = BunSai | ||
BunSai proposes to be the official SSR engine for [Bun](https://bun.sh). | ||
It leverages some of the powers of Bun (like Bundler, preloading and plugin system) to create a fast, DX friendly library. | ||
> NOTE: the full documentation is coming soon... | ||
## Quick start | ||
```bash | ||
bun add bunsai@2 <the optional deps you are gonna use> | ||
``` | ||
| Framework/API | Example | | ||
| --------------------------------------------------- | ------------------------------------------------------------- | | ||
| [Elysia](https://elysiajs.com/) | [`adapters/elysia.ts`](./examples/src/adapters/elysia.ts) | | ||
| [Hono](https://hono.dev/) | [`adapters/hono.ts`](./examples/src/adapters/hono.ts) | | ||
| [Bun.serve](https://bun.sh/docs/api/http#bun-serve) | [`adapters/manifest.ts`](./examples/src/adapters/manifest.ts) | | ||
| [Bun.write](https://bun.sh/docs/api/file-io) | [`adapters/file.ts`](./examples/src/adapters/file.ts) | | ||
| [Byte](https://bytejs.pages.dev/) | `Coming soon...` | | ||
| Static Build | `Coming soon...` | | ||
### Dev mode | ||
To run on dev mode, just use Bun `--hot` flag and add: | ||
```properties | ||
# .env | ||
DEBUG=on | ||
``` | ||
> NOTE: Browser HMR is not yet supported | ||
### BunSai entrypoint | ||
BunSai has an entrypoint (a function) that must be called after all modules are imported and before begining the use of those modules. | ||
```ts | ||
import A from "./module-a"; | ||
import B from "./module-b"; | ||
import C from "./module-c"; | ||
import bunsai from "bunsai"; | ||
// or | ||
import { bunsai } from "bunsai/<adapter>"; | ||
const result = await bunsai(/* config here */); | ||
/* your code here */ | ||
``` | ||
BunSai also offers a convenience tool: | ||
```ts | ||
import "bunsai/with-config"; // WRONG | ||
import A from "./module-a"; | ||
import B from "./module-b"; | ||
import "bunsai/with-config"; // WRONG | ||
import C from "./module-c"; | ||
import "bunsai/with-config"; // RIGHT | ||
/* your code here */ | ||
``` | ||
```ts | ||
// bunsai.config.ts | ||
import type { BunsaiConfig } from "bunsai"; | ||
const config: BunsaiConfig = { | ||
/* ... */ | ||
}; | ||
export default config; | ||
``` | ||
## Helpers | ||
BunSai has (currently) 1 helper: `asset`. | ||
### Asset | ||
No big deal, just a helper function to allow you to use Bun asset import across BunSai (both server and client side): | ||
> NOTE: Svelte plugin automatically injects it and makes it available module scope as `asset`. | ||
> You can opt out by setting `SvelteConfig.bunsai2.useAsset` to `false`. | ||
```ts | ||
import createAssetGetter from "bunsai/asset"; | ||
import myPng from "./asset.png"; | ||
const asset = createAssetGetter(import.meta); | ||
asset(myPng); | ||
``` | ||
## How it works | ||
BunSai is divided in 3 parts: Plugin, Adapter and Module. | ||
### Plugin | ||
A Plugin is the part of BunSai that converts things like Svelte code into a [BunSai Module](#module). | ||
Let's take the builtin Svelte Plugin as an example: | ||
```toml | ||
# bunfig.toml | ||
preload = ["bunsai/svelte/preload.ts"] | ||
``` | ||
```jsonc | ||
// tsconfig.json | ||
{ | ||
// Svelte global references 'bunsai/global.d.ts', | ||
// so if you are not using Svelte, you should include it yourself | ||
"types": ["bunsai/svelte/global.d.ts"] | ||
} | ||
``` | ||
> NOTE: if you use the Svelte Extension for VSCode, make sure the `Enable-TS-plugin` option is disabled | ||
> NOTE: The Svelte plugin uses config file (`svelte.config.ts`). For proper typing, please use the global `SvelteConfig` type | ||
### Adapter | ||
As the name implies, an adapter adapts the BunSai Result (a.k.a. build) to be consumed by another Framework/API. | ||
This time we'll take the Elysia adapter: | ||
```ts | ||
import { plug, plugged, bunsai, Elysia } from "bunsai/elysia"; | ||
import { render } from "./module.svelte"; | ||
const result = await bunsai(); | ||
// or | ||
import "bunsai/with-config"; | ||
/* | ||
If you are going to use 'bunsai/with-config', | ||
the adapter will try to get the result from the global scope. | ||
You dont even need to store the 'bunsai()' returned value, | ||
nor pass it to the adapter entrypoint ('plug' in this case). | ||
*/ | ||
/* | ||
Or you can use 'plugged' and "ignore" both 'bunsai()' and 'bunsai/with-config' | ||
*/ | ||
await plugged(); | ||
// as plugin | ||
const app = new Elysia().use(plug(result)).get("/", render).listen(3000); | ||
// as the main instance | ||
const app = plug(result).get("/", render).listen(3000); | ||
console.log("Elysia Ready!"); | ||
``` | ||
### Module | ||
The module is just an Svelte file (in our case). It is the plugin's sole responsibility to transpile/adapt the source code into a BunSai Module. | ||
The Svelte plugin uses `svelte/compiler` to transpile our source code into vanilla javascript, then it injects BunSai's `register` function and makes changes to exports so it can be consumed as an out-of-the-box BunSai Module. | ||
> Check the [StandaloneModule](./src/core/module.ts) interface |
@@ -41,6 +41,6 @@ type Reserved<K extends string | number | symbol> = { | ||
return { | ||
body_attrs: objToAttrs(attrs.body_attrs || defaults.body_attrs || {}), | ||
body_attrs: objToAttrs({ ...defaults.body_attrs, ...attrs.body_attrs }), | ||
html_lang: attrs.html_lang || defaults.html_lang || "", | ||
root_attrs: objToAttrs(attrs.root_attrs || defaults.root_attrs || {}), | ||
root_attrs: objToAttrs({ ...defaults.root_attrs, ...attrs.root_attrs }), | ||
}; | ||
} |
@@ -18,5 +18,5 @@ // import { mkdir } from "fs/promises"; unused | ||
const dumpFolder = mkdtempSync(join(tmpdir(), "bunsai-temp-")); | ||
export const dumpFolder = mkdtempSync(join(tmpdir(), "bunsai-temp-")); | ||
export async function buildClient(prefix: string) { | ||
export async function buildClient(prefix: string, root?: string) { | ||
Util.log.debug("creating client build..."); | ||
@@ -34,2 +34,3 @@ | ||
const { logs, outputs, success } = await Bun.build({ | ||
root, | ||
entrypoints: files, | ||
@@ -40,2 +41,5 @@ minify: !IsDev(), | ||
target: "browser", | ||
naming: { | ||
asset: "[dir]/[name].[ext]", | ||
}, | ||
outdir: dumpFolder, | ||
@@ -42,0 +46,0 @@ }); |
@@ -0,0 +0,0 @@ import { createPath } from "./build"; |
declare namespace NodeJS { | ||
export interface ProcessEnv { | ||
DEBUG?: "on" | "verbose" | "silent"; | ||
BUNSAI_USE_CONFIG?: string; | ||
// BUNSAI_USE_CONFIG?: string; not used | ||
} | ||
} |
import type { BunSai } from "."; | ||
import type { BunPlugin } from "bun"; | ||
import { createHolder, type Holder } from "./util"; | ||
import { ModuleSymbol as $ms } from "./module"; | ||
@@ -20,2 +21,2 @@ // to avoid type errors | ||
export { ModuleSymbol } from "./module"; | ||
export const ModuleSymbol: symbol = ($global.$$$bunsai_module_symbol ||= $ms); |
@@ -11,6 +11,12 @@ /// <reference path="./global.d.ts"/> | ||
import { CurrentBunSai } from "./globals"; | ||
import { resolve } from "path"; | ||
export interface BunSai { | ||
prefix: string; | ||
root: string; | ||
declarations: { path: string; handle: () => Response }[]; | ||
render(module: Module, context: Record<string, any>): Response; | ||
render<Context extends Record<string, any>>( | ||
module: Module<Context>, | ||
context: Context | ||
): Response; | ||
} | ||
@@ -20,2 +26,10 @@ | ||
/** | ||
* Root folder where all your files will be placed. | ||
* | ||
* This is used on `Bun.build` to ensure the correct path resolution. | ||
* | ||
* @default process.cwd() | ||
*/ | ||
root?: string; | ||
/** | ||
* Where the client build will be served. | ||
@@ -34,9 +48,16 @@ * | ||
const $global: any = global; | ||
export default async function bunsai( | ||
config: BunsaiConfig = {} | ||
): Promise<BunSai> { | ||
const { prefix = "/__bunsai__/", defaults } = config; | ||
const { prefix = "/__bunsai__/", defaults, root = process.cwd() } = config; | ||
const result = await buildClient(prefix); | ||
// deps: extra/asset.ts | ||
$global.$$$bunsai_build_root = resolve(root); | ||
// deps: extra/asset.ts | ||
$global.$$$bunsai_build_prefix = prefix; | ||
const result = await buildClient(prefix, root); | ||
if (!result) { | ||
@@ -46,2 +67,4 @@ Util.log.loud("empty client endpoints. No module was registered"); | ||
const retorno = { | ||
prefix, | ||
root, | ||
declarations: [], | ||
@@ -68,2 +91,4 @@ render() { | ||
const retorno: BunSai = { | ||
prefix, | ||
root, | ||
render: (module, context) => { | ||
@@ -105,3 +130,3 @@ const { $m_meta: meta, $m_render, $m_gen_script } = module; | ||
Util.log.verbose("client endpoints (", paths.join(" | "), ")"); | ||
Util.log.debug("client endpoints (", paths.join(" | "), ")"); | ||
@@ -108,0 +133,0 @@ CurrentBunSai(retorno); |
@@ -18,7 +18,7 @@ import type { BunPlugin } from "bun"; | ||
bun: BP; | ||
browser: BunPlugin; | ||
browser?: BunPlugin; | ||
}) { | ||
BrowserBuildPlugins.push(browser); | ||
if (browser) BrowserBuildPlugins.push(browser); | ||
ServerBuildPlugins.push(bun); | ||
return Bun.plugin(bun); | ||
} |
@@ -6,5 +6,13 @@ import type { Attributes } from "./attrs"; | ||
export interface ModuleProps { | ||
jsMap: object | undefined; | ||
jsMap: object | null | undefined; | ||
/** | ||
* Module scoped css | ||
*/ | ||
css: string | null; | ||
cssHash: string | null; | ||
/** | ||
* | ||
*/ | ||
cssHash: string; | ||
cssMap: object | null | undefined; | ||
@@ -16,4 +24,4 @@ path: string; | ||
export type ModuleRenderer = (props: { | ||
context: Record<string, any>; | ||
export type ModuleRenderer<Context extends Record<string, any>> = (props: { | ||
context: Context; | ||
attrs: Attributes; | ||
@@ -26,11 +34,21 @@ isServer: boolean; | ||
export interface Module { | ||
export interface Module< | ||
Context extends Record<string, any> = Record<string, any> | ||
> { | ||
$m_meta: ModuleProps; | ||
$m_symbol: typeof ModuleSymbol; | ||
$m_render: ModuleRenderer; | ||
$m_render: ModuleRenderer<Context>; | ||
/** | ||
* Client side hydration script tag generator | ||
*/ | ||
$m_gen_script(data: ScriptData): string; | ||
} | ||
render: StandaloneRenderer; | ||
export interface StandaloneModule< | ||
Context extends Record<string, any> = Record<string, any> | ||
> extends Module<Context> { | ||
render: StandaloneRenderer<Context>; | ||
} | ||
export const ModuleSymbol = Symbol("bunsai.module"); |
@@ -0,0 +0,0 @@ import { Util } from "./util"; |
import type { Module } from "./module"; | ||
import { CurrentBunSai } from "./globals"; | ||
export type StandaloneRenderer = (context: Record<string, any>) => Response; | ||
export type StandaloneRenderer<Context extends Record<string, any>> = ( | ||
context: Context | ||
) => Response; | ||
export const registry = new Map<string, Module>(); | ||
export const registry = new Map<string, Module<any>>(); | ||
@@ -15,3 +17,5 @@ class StandaloneRendererError extends Error { | ||
*/ | ||
export function register(component: Module): StandaloneRenderer { | ||
export function register< | ||
Context extends Record<string, any> = Record<string, any> | ||
>(component: Module<Context>): StandaloneRenderer<Context> { | ||
registry.set(component.$m_meta.path, component); | ||
@@ -22,3 +26,3 @@ | ||
if (!bunsai) | ||
throw new StandaloneRendererError("cannot render before bunsai()"); | ||
throw new StandaloneRendererError("cannot render before 'bunsai()'"); | ||
@@ -25,0 +29,0 @@ return bunsai.render(component, context); |
@@ -0,0 +0,0 @@ import type { ProcessedRenderAttrs } from "./attrs"; |
@@ -0,0 +0,0 @@ import type { ModuleRenderer } from "./module"; |
@@ -107,5 +107,6 @@ export namespace Util { | ||
name = "BunSaiLoadError"; | ||
constructor(options?: ErrorOptions) { | ||
constructor(options?: ErrorOptions, noProvide = false) { | ||
super( | ||
"Could not get current bunsai from globals, and you did not provide one", | ||
"Could not get current bunsai from globals" + | ||
(noProvide ? "." : ", and you did not provide one"), | ||
options | ||
@@ -112,0 +113,0 @@ ); |
@@ -11,5 +11,6 @@ import { resolve } from "path"; | ||
Util.log.debug(err); | ||
Util.log.loud("Using BunSai with default settings"); | ||
} finally { | ||
if (!config) Util.log.loud("Using BunSai with default settings"); | ||
} | ||
await bunsai(config); | ||
export default await bunsai(config); |
@@ -5,2 +5,5 @@ import Elysia from "elysia"; | ||
/** | ||
* @returns An Elysia plugin to resolve the assets and browser modules + the 'render' decorator | ||
*/ | ||
export function plug(result = CurrentBunSai()) { | ||
@@ -19,3 +22,13 @@ if (!result) throw new BunSaiLoadError(); | ||
/** | ||
* Automatically call `bunsai/with-config`, | ||
* then call {@link plug} | ||
*/ | ||
export async function plugged() { | ||
const { default: b } = await import("../core/with-config"); | ||
return plug(b); | ||
} | ||
export { default as Elysia } from "elysia"; | ||
export { default as bunsai } from "../core"; |
@@ -1,18 +0,12 @@ | ||
import { join } from "path"; | ||
import { CurrentBunSai } from "../core/globals"; | ||
import { BunSaiLoadError } from "../core/util"; | ||
import { cp } from "fs/promises"; | ||
import { dumpFolder } from "../core/build"; | ||
export async function writeToDisk( | ||
rootFolder: string, | ||
result = CurrentBunSai() | ||
) { | ||
export function writeToDisk(outFolder: string, result = CurrentBunSai()) { | ||
if (!result) throw new BunSaiLoadError(); | ||
for (const decl of result.declarations) { | ||
const path = join(rootFolder, decl.path); | ||
await Bun.write(path, await decl.handle().arrayBuffer()); | ||
} | ||
return cp(dumpFolder, outFolder, { recursive: true }); | ||
} | ||
export { default as bunsai } from "../core"; |
@@ -1,5 +0,6 @@ | ||
import { Hono, type Context as HonoContext } from "hono"; | ||
import { Hono, type Env, type Context as HonoContext, type Schema } from "hono"; | ||
import type { Module } from "../core/module"; | ||
import { CurrentBunSai } from "../core/globals"; | ||
import { BunSaiLoadError } from "../core/util"; | ||
import type { BlankSchema } from "hono/types"; | ||
@@ -10,3 +11,9 @@ export interface BunSaiHono { | ||
*/ | ||
hono(...args: ConstructorParameters<typeof Hono>): Hono; | ||
hono< | ||
E extends Env = Env, | ||
S extends Schema = BlankSchema, | ||
BasePath extends string = "/" | ||
>( | ||
...args: ConstructorParameters<typeof Hono<E, S, BasePath>> | ||
): Hono<E, S, BasePath>; | ||
@@ -19,3 +26,3 @@ /** | ||
/** | ||
* For a given Module, crates an Hono method handler. | ||
* For a given Module, creates an Hono method handler. | ||
*/ | ||
@@ -25,7 +32,14 @@ handler(module: Module): (context: HonoContext) => Response; | ||
export function create(result = CurrentBunSai()) { | ||
export function plug(result = CurrentBunSai()) { | ||
if (!result) throw new BunSaiLoadError(); | ||
const retorno: BunSaiHono = { | ||
hono(...args) { | ||
hono: function < | ||
E extends Env = Env, | ||
S extends Schema = BlankSchema, | ||
BasePath extends string = "/" | ||
>( | ||
...args: ConstructorParameters<typeof Hono<E, S, BasePath>> | ||
): Hono<E, S, BasePath> { | ||
// @ts-ignore | ||
return retorno.apply(new Hono(...args)); | ||
@@ -47,3 +61,18 @@ }, | ||
/** | ||
* Automatically call `bunsai/with-config`, | ||
* then call {@link plug} | ||
*/ | ||
export async function plugged() { | ||
const { default: b } = await import("../core/with-config"); | ||
return plug(b); | ||
} | ||
/** | ||
* @deprecated Use {@link plug} instead | ||
*/ | ||
export const create = plug; | ||
export { Hono } from "hono"; | ||
export { default as bunsai } from "../core"; |
import { CurrentBunSai } from "../core/globals"; | ||
import { BunSaiLoadError } from "../core/util"; | ||
export function createManifest(result = CurrentBunSai()) { | ||
export function plug(result = CurrentBunSai()) { | ||
if (!result) throw new BunSaiLoadError(); | ||
@@ -19,2 +19,17 @@ | ||
/** | ||
* Automatically call `bunsai/with-config`, | ||
* then call {@link plug} | ||
*/ | ||
export async function plugged() { | ||
const { default: b } = await import("../core/with-config"); | ||
return plug(b); | ||
} | ||
/** | ||
* @deprecated Use {@link plug} instead | ||
*/ | ||
export const createManifest = plug; | ||
export { default as bunsai } from "../core"; |
@@ -168,2 +168,8 @@ import type { | ||
overrideIsDev?: boolean; | ||
/** | ||
* Add `bunsai/asset` as global. | ||
* | ||
* @default true | ||
*/ | ||
useAsset?: boolean; | ||
}; | ||
@@ -170,0 +176,0 @@ } |
@@ -0,7 +1,27 @@ | ||
/// <reference path="../../core/global.d.ts"/> | ||
/// <reference path="../../core/global.d.ts"/> | ||
/// <reference path="../../core/global.d.ts"/> | ||
declare module "*.svelte" { | ||
const mod: import("../../core/module").Module; | ||
type SvelteModule = import("../../core/module").StandaloneModule; | ||
export = mod; | ||
const module: SvelteModule; | ||
export = module; | ||
} | ||
declare type SvelteConfig = import("./config").Config; | ||
/** | ||
* Must **NOT** be used in `context="module"` script | ||
* | ||
* Converts Bun asset import into a BunSai compatible URL. | ||
* | ||
* NOTE: Unavailable if `SvelteConfig.bunsai2.useAsset` is set to `false`. | ||
* | ||
* @example | ||
* import logo from "./assets/logo.png"; | ||
* | ||
* asset(logo); // /__bunsai__/assets/logo.png | ||
*/ | ||
declare var asset: import("../asset").Asset; |
@@ -0,0 +0,0 @@ import { createHolder, type Holder } from "../../core/util"; |
/// <reference path="./global.d.ts" /> | ||
export type { Config as SvelteConfig } from "./config"; |
@@ -9,3 +9,8 @@ import type { BunPlugin } from "bun"; | ||
export default function createPlugins(svelteConfig: ResolvedSvelteConfig) { | ||
const { extensions, preprocess, compilerOptions } = svelteConfig; | ||
const { | ||
extensions, | ||
preprocess, | ||
compilerOptions, | ||
bunsai2: { useAsset }, | ||
} = svelteConfig; | ||
@@ -56,3 +61,7 @@ const rxExtensions = extensions | ||
'import { genScript as $$$sv_gen_script } from "bunsai/svelte/script.ts";\n' + | ||
js + | ||
(useAsset == false | ||
? "" | ||
: 'import $$$create_asset_getter from "bunsai/asset";\n' + | ||
"const asset = $$$create_asset_getter(import.meta);\n") + | ||
js.replace("export default", "") + | ||
`\nconst _css = ${JSON.stringify(css)}, path = ${JSON.stringify( | ||
@@ -71,3 +80,4 @@ args.path | ||
"\nexport const $m_gen_script = $$$sv_gen_script;" + | ||
"\nexport const render = $$$sv_reg({$m_meta,$m_render,$m_symbol,$m_gen_script})", | ||
"\nexport const render = $$$sv_reg({$m_meta,$m_render,$m_symbol,$m_gen_script});" + | ||
"\nexport default {$m_meta,$m_render,$m_symbol,$m_gen_script,render}", | ||
loader: "js", | ||
@@ -91,3 +101,3 @@ }; | ||
const { | ||
js: { code: contents }, | ||
js: { code: js }, | ||
warnings, | ||
@@ -107,3 +117,8 @@ } = svelte.compile(code, { | ||
return { | ||
contents, | ||
contents: | ||
useAsset == false | ||
? js | ||
: 'import $$$create_asset_getter from "bunsai/asset";\n' + | ||
js + | ||
"\nconst asset = $$$create_asset_getter(import.meta);", | ||
loader: "js", | ||
@@ -110,0 +125,0 @@ }; |
@@ -0,0 +0,0 @@ /// <reference path="./global.d.ts" /> |
@@ -0,0 +0,0 @@ import type { ScriptData } from "../../core/script"; |
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
41233
30
1008
1
175
5
2
+ Addedurijs@1.19.11
+ Addedelysia@1.0.25(transitive)
+ Addedhono@4.4.9(transitive)
+ Addedurijs@1.19.11(transitive)
- Removedelysia@1.0.26(transitive)
- Removedhono@4.4.10(transitive)