diff --git a/packages/bun-plugin-svelte/bun.lock b/packages/bun-plugin-svelte/bun.lock index 9b31525fb1..aa18005aa9 100644 --- a/packages/bun-plugin-svelte/bun.lock +++ b/packages/bun-plugin-svelte/bun.lock @@ -4,12 +4,12 @@ "": { "name": "bun-plugin-svelte", "devDependencies": { + "@threlte/core": "8.0.1", "bun-types": "canary", "svelte": "^5.20.4", }, "peerDependencies": { "svelte": "^5", - "typescript": "^5", }, }, }, @@ -26,6 +26,8 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], + "@threlte/core": ["@threlte/core@8.0.1", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.155" } }, "sha512-vy1xRQppJFNmfPTeiRQue+KmYFsbPgVhwuYXRTvVrwPeD2oYz43gxUeOpe1FACeGKxrxZykeKJF5ebVvl7gBxw=="], + "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="], "@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="], @@ -54,9 +56,11 @@ "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], + "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], + "svelte": ["svelte@5.20.4", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "acorn-typescript": "^1.4.13", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.3", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-2Mo/AfObaw9zuD0u1JJ7sOVzRCGcpETEyDkLbtkcctWpCMCIyT0iz83xD8JT29SR7O4SgswuPRIDYReYF/607A=="], - "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], + "three": ["three@0.174.0", "", {}, "sha512-p+WG3W6Ov74alh3geCMkGK9NWuT62ee21cV3jEnun201zodVF4tCE5aZa2U122/mkLRmhJJUQmLLW1BH00uQJQ=="], "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], diff --git a/packages/bun-plugin-svelte/src/index.spec.ts b/packages/bun-plugin-svelte/src/index.spec.ts index a638169803..ccf550a59c 100644 --- a/packages/bun-plugin-svelte/src/index.spec.ts +++ b/packages/bun-plugin-svelte/src/index.spec.ts @@ -11,7 +11,11 @@ describe("SveltePlugin", () => { expect(() => SveltePlugin(undefined)).not.toThrow(); }); - it.each([null, 1, "hi", {}, "Client"])("throws if forceSide is not 'client' or 'server' (%p)", (forceSide: any) => { + it.each([1, "hi", {}, "Client"])("throws if forceSide is not 'client' or 'server' (%p)", (forceSide: any) => { expect(() => SveltePlugin({ forceSide })).toThrow(TypeError); }); + + it.each([null, undefined])("forceSide may be nullish", (forceSide: any) => { + expect(() => SveltePlugin({ forceSide })).not.toThrow(); + }); }); diff --git a/packages/bun-plugin-svelte/src/options.spec.ts b/packages/bun-plugin-svelte/src/options.spec.ts index fe91c90074..10960e2d29 100644 --- a/packages/bun-plugin-svelte/src/options.spec.ts +++ b/packages/bun-plugin-svelte/src/options.spec.ts @@ -2,7 +2,7 @@ import { describe, beforeAll, it, expect } from "bun:test"; import type { BuildConfig } from "bun"; import type { CompileOptions } from "svelte/compiler"; -import { getBaseCompileOptions, type SvelteOptions } from "./options"; +import { getBaseCompileOptions, validateOptions, type SvelteOptions } from "./options"; describe("getBaseCompileOptions", () => { describe("when no options are provided", () => { @@ -42,4 +42,13 @@ describe("getBaseCompileOptions", () => { ); }, ); -}); +}); // getBaseCompileOptions + +describe("validateOptions(options)", () => { + it.each(["", 1, null, undefined, true, false, Symbol("hi")])( + "throws if options is not an object (%p)", + (badOptions: any) => { + expect(() => validateOptions(badOptions)).toThrow(); + }, + ); +}); // validateOptions diff --git a/packages/bun-plugin-svelte/src/options.ts b/packages/bun-plugin-svelte/src/options.ts index 80aa6c8933..7e69abc180 100644 --- a/packages/bun-plugin-svelte/src/options.ts +++ b/packages/bun-plugin-svelte/src/options.ts @@ -2,7 +2,8 @@ import { strict as assert } from "node:assert"; import { type BuildConfig } from "bun"; import type { CompileOptions, ModuleCompileOptions } from "svelte/compiler"; -export interface SvelteOptions { +type OverrideCompileOptions = Pick; +export interface SvelteOptions extends Pick { /** * Force client-side or server-side generation. * @@ -20,6 +21,11 @@ export interface SvelteOptions { * Defaults to `true` when run via Bun's dev server, `false` otherwise. */ development?: boolean; + + /** + * Options to forward to the Svelte compiler. + */ + compilerOptions?: OverrideCompileOptions; } /** @@ -27,15 +33,24 @@ export interface SvelteOptions { */ export function validateOptions(options: unknown): asserts options is SvelteOptions { assert(options && typeof options === "object", new TypeError("bun-svelte-plugin: options must be an object")); - if ("forceSide" in options) { - switch (options.forceSide) { + const opts = options as Record; + + if (opts.forceSide != null) { + if (typeof opts.forceSide !== "string") { + throw new TypeError("bun-svelte-plugin: forceSide must be a string, got " + typeof opts.forceSide); + } + switch (opts.forceSide) { case "client": case "server": break; default: - throw new TypeError( - `bun-svelte-plugin: forceSide must be either 'client' or 'server', got ${options.forceSide}`, - ); + throw new TypeError(`bun-svelte-plugin: forceSide must be either 'client' or 'server', got ${opts.forceSide}`); + } + } + + if (opts.compilerOptions) { + if (typeof opts.compilerOptions !== "object") { + throw new TypeError("bun-svelte-plugin: compilerOptions must be an object"); } } } @@ -44,7 +59,10 @@ export function validateOptions(options: unknown): asserts options is SvelteOpti * @internal */ export function getBaseCompileOptions(pluginOptions: SvelteOptions, config: Partial): CompileOptions { - let { development = false } = pluginOptions; + let { + development = false, + compilerOptions: { customElement, runes, modernAst, namespace } = kEmptyObject as OverrideCompileOptions, + } = pluginOptions; const { minify = false } = config; const shouldMinify = Boolean(minify); @@ -68,6 +86,10 @@ export function getBaseCompileOptions(pluginOptions: SvelteOptions, config: Part preserveWhitespace: !minifyWhitespace, preserveComments: !shouldMinify, dev: development, + customElement, + runes, + modernAst, + namespace, cssHash({ css }) { // same prime number seed used by svelte/compiler. // TODO: ensure this provides enough entropy @@ -109,3 +131,4 @@ function generateSide(pluginOptions: SvelteOptions, config: Partial } export const hash = (content: string): string => Bun.hash(content, 5381).toString(36); +const kEmptyObject = Object.create(null); diff --git a/packages/bun-plugin-svelte/test/index.test.ts b/packages/bun-plugin-svelte/test/index.test.ts index 04aa966fef..f0add2eb11 100644 --- a/packages/bun-plugin-svelte/test/index.test.ts +++ b/packages/bun-plugin-svelte/test/index.test.ts @@ -24,13 +24,32 @@ afterAll(() => { } }); -it("hello world component", async () => { - const res = await Bun.build({ - entrypoints: [fixturePath("foo.svelte")], - outdir, - plugins: [SveltePlugin()], +describe("given a hello world component", () => { + const entrypoints = [fixturePath("foo.svelte")]; + it("when no options are provided, builds successfully", async () => { + const res = await Bun.build({ + entrypoints, + outdir, + plugins: [SveltePlugin()], + }); + expect(res.success).toBeTrue(); + }); + + describe("when a custom element is provided", () => { + let res: BuildOutput; + + beforeAll(async () => { + res = await Bun.build({ + entrypoints, + outdir, + plugins: [SveltePlugin({ compilerOptions: { customElement: true } })], + }); + }); + + it("builds successfully", () => { + expect(res.success).toBeTrue(); + }); }); - expect(res.success).toBeTrue(); }); describe("when importing `.svelte.ts` files with ESM", () => {