Code Splitting
Learn how code splitting and tree-shaking work in Fragno
Fragments are "full-stack" libraries. As such, they contain both server-side and client-side components. Since frameworks differ in how they handle code splitting, Fragno must provide a way to split the code between client and server bundles on the Fragment level.
Code Splitting
The @fragno-dev/unplugin-fragno plugin must be used to split the Fragment's code between client
and server bundles. This plugin is only required for Fragment authors, not for end-users.
When creating the client bundle with the plugin, calls to defineFragment and defineRoute will be
transformed. The contents of the withDependencies and providesService methods are removed from
the client bundle, as well as the contents of the handler functions in defineRoute. All code and
imports that become unused after this transformation are removed from the client bundle
(tree-shaking). At this time, there's no specific transformation step that happens for
server-side code. All code is included in the final server bundle.
Unplugin
Unplugin is a unified plugin system that supports "Vite, Rollup, webpack, esbuild, and every framework built on top of them." This means that we'll integrate with your preferred build tool.
Building
Get started by installing @fragno-dev/unplugin-fragno as a development dependency.
npm install --save-dev @fragno-dev/unplugin-fragnoDepending on your build tool, you can import the plugin from the unplugin-fragno package into your
build configuration:
// esbuild:
import unpluginFragno from "@fragno-dev/unplugin-fragno/esbuild";
// rollup / rolldown:
import unpluginFragno from "@fragno-dev/unplugin-fragno/rollup";
// webpack:
import unpluginFragno from "@fragno-dev/unplugin-fragno/webpack";
// rspack:
import unpluginFragno from "@fragno-dev/unplugin-fragno/rspack";
// farm:
import unpluginFragno from "@fragno-dev/unplugin-fragno/farm";Other build tools are also supported: /vite, /nuxt, /astro. But it's unlikely that you'll
build your Fragment with these tools.
TSDown Example
The Fragno monorepo uses TSDown to build the packages. A minimal example is the following:
import { defineConfig } from "tsdown";
import unpluginFragno from "@fragno-dev/unplugin-fragno/rollup";
export default defineConfig([
{
entry: [
"./src/index.ts",
"./src/client/react.ts",
"./src/client/svelte.ts",
"./src/client/solid.ts",
"./src/client/vanilla.ts",
"./src/client/vue.ts",
],
platform: "browser",
outDir: "./dist/browser",
plugins: [unpluginFragno({ platform: "browser" })],
noExternal: [/^@fragno-dev\/core\//],
},
{
entry: "./src/index.ts",
outDir: "./dist/node",
plugins: [unpluginFragno({ platform: "node" })],
},
]);We need to add an entrypoint for every supported frontend framework, so that the user's application
only imports the correct bundle. For tsdown all dependencies are externalized by default, therefore
make sure to exclude @fragno-dev/core as an external dependency. Note that this default is
different for other bundlers.
Plugin in the server build
The plugin can be omitted in the server build, because it currently doesn't do anything. This might change in the future.
Package.json Example
As we have now created two builds, we need to set the exports field in the package.json file.
{
"name": "@fragno-dev/chatno",
"main": "./dist/node/index.js",
"module": "./dist/node/index.js",
"types": "./dist/node/index.d.ts",
"exports": {
".": {
"types": "./dist/browser/index.d.ts",
"default": "./dist/node/index.js"
},
"./react": {
"types": "./dist/browser/client/react.d.ts",
"browser": "./dist/browser/client/react.js",
"default": "./dist/browser/client/react.js"
},
"./svelte": {
"types": "./dist/browser/client/svelte.d.ts",
"browser": "./dist/browser/client/svelte.js",
"default": "./dist/browser/client/svelte.js"
},
"./vanilla": {
"types": "./dist/browser/client/vanilla.d.ts",
"browser": "./dist/browser/client/vanilla.js",
"default": "./dist/browser/client/vanilla.js"
},
"./solid": {
"types": "./dist/browser/client/solid.d.ts",
"browser": "./dist/browser/client/solid.js",
"default": "./dist/browser/client/solid.js"
},
"./vue": {
"types": "./dist/browser/client/vue.d.ts",
"browser": "./dist/browser/client/vue.js",
"default": "./dist/browser/client/vue.js"
}
},
"type": "module"
}The key field here is "browser". This is all we need to make the user's framework import the correct bundle.
The '.' import is server-only.
Note that the main import ('.') is server-only, and the "main" field as well.
External Dependencies
The last thing we need to make sure of is that the frontend frameworks are handled as external dependencies, so that they're not included in your Fragment's bundle.
Most bundlers will look at the peerDependencies field in your package.json file to determine
which dependencies to keep out of the bundle.
Make sure to add the following to your package.json file:
{
"peerDependencies": {
"react": ">=18.0.0",
"svelte": ">=4.0.0",
"vue": ">=3.0.0",
"solid-js": ">=1.0.0"
}
}Alternatively, your bundler might also have support for directly marking dependencies as external in your bundler configuration. This is unnecessary in most cases.
export default defineConfig([
{
// ...
external: ["react", "svelte", "vue"],
},
]);