A powerful, secure, and flexible runtime component compiler for Svelte 5+ applications.
Note
Live demo is available at https://dynamic-component-engine.matthewdavis.io!
- Runtime Component Compilation: Transform Svelte component strings into fully functional components on the fly.
- Type Safety: Comprehensive TypeScript support with detailed interfaces and type definitions.
- Svelte 5 Ready: Full support for Svelte 5 runes and the latest features.
npm install @mateothegreat/dynamic-component-engine svelte
First, you need to update your index.html
to include the following importmap:
<script type="importmap">
{
"imports": {
"svelte": "https://esm.sh/[email protected]",
"svelte/internal/disclose-version": "https://esm.sh/[email protected]/internal/disclose-version",
"svelte/internal/": "https://esm.sh/[email protected]/internal/"
}
}
</script>
Note
This is necessary to ensure that the Svelte runtime is available to the dynamic component engine. See index.html for a working implementation.
Here's an example of how to use the dynamic component engine to render a component from a remote source (in this case, a locally compiled Svelte component served by vite):
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import {
load,
render,
type Rendered
} from "@mateothegreat/dynamic-component-engine";
let renderRef: HTMLDivElement;
let component: Rendered;
async function create() {
try {
const source = await fetch(
"https://dynamic-component-engine.matthewdavis.io/entry.js"
).then((res) => res.text());
const fn = await load(source);
component = await render(fn, {
componentSource: source,
target: renderRef,
props: {
foo: "bar"
}
});
} catch (error) {
console.error(error);
}
}
onMount(async () => {
create();
});
onDestroy(() => {
component.destroy();
});
</script>
<div bind:this={renderRef}></div>
You must first compile your Svelte component(s) down to a string and serve it to the client (http endpoint, websockets, etc.) then you can use the load
and render
functions to dynamically render the component(s) in the browser.
Using the compile-components
script
I've provided an easy button to compile your components down to a single file that can be served to the client.
Simply run npm run compile
and it will compile all the components in the shared-components
directory down to a single files that can be served to the client.
Your output will look like this:
➜ npm run compile
> [email protected] compile
> node bin/compile-components
Discovering components via ./shared-components/**/entry.ts
+ Discovered component entrypoint: shared-components/simple/entry.ts
Compiling (1) component...
public/entry.js 2.7kb
⚡ Done in 191ms
import esbuild from "esbuild";
import esbuildSvelte from "esbuild-svelte";
import { sveltePreprocess } from "svelte-preprocess";
async function bundleSvelte(entry) {
const build = await esbuild.build({
logLevel: "debug",
entryPoints: Array.isArray(entry) ? entry : [entry],
target: "esnext",
format: "esm",
splitting: false,
packages: "external",
banner: {
js: "// I'm compiled from entry.ts which imports simple.svelte using esbuild-svelte."
},
bundle: true,
outdir: "./public",
plugins: [
esbuildSvelte({
preprocess: sveltePreprocess()
})
]
});
return build.outputFiles;
}
bundleSvelte(["./shared-components/simple/entry.ts"]);
After running npm run compile
the output will be a single file in the public
directory and will look like this:
const compiledComponentSource = `
// I'm compiled from entry.ts which imports simple.svelte using esbuild-svelte.
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
// src/components/entry.ts
import { mount, unmount } from "svelte";
// src/components/simple.svelte
import "svelte/internal/disclose-version";
import * as $ from "svelte/internal/client";
var on_click = /* @__PURE__ */ __name((_, testState) => $.update(testState), "on_click");
var root = $.from_html(`<div></div> <button> </button>`, 1);
function Simple($$anchor) {
let name = "I'm but a simple component";
let testState = $.state(0);
var fragment = root();
var div = $.first_child(fragment);
var button = $.sibling(div, 2);
button.__click = [on_click, testState];
var text = $.child(button, true);
$.reset(button);
$.template_effect(() => $.set_text(text, $.get(testState)));
$.append($$anchor, fragment);
}
__name(Simple, "Simple");
$.delegate(["click"]);
// src/components/entry.ts
var factory = /* @__PURE__ */ __name((target, props) => {
const component = mount(Simple, { target, props });
return {
component,
name: Simple.name,
destroy: /* @__PURE__ */ __name(() => {
console.log("entry.ts -> simple.svelte", "destroying component", component);
unmount(component);
}, "destroy")
};
}, "factory");
export {
factory as default
};
`;
We welcome contributions! Whether it's bug reports, feature requests, or pull requests, all contributions are appreciated.
MIT License - feel free to use in personal and commercial projects.
Built with ❤️ from Texas for the Svelte community.