@svelte-put/preaction
GithubCompatible with or powered directly by Svelte runes.
Introduction
This is a proof-of-concept Svelte preprocessor that allows usage of "preaction" - an extension to Svelte action with the ability to add static attributes on server-side.
Installation
Be aware that @svelte-put/preaction
requires at least Svelte 5 at the time of this writing.
npm install --save-dev @svelte-put/preaction
pnpm add -D @svelte-put/preaction
yarn add -D @svelte-put/preaction
Demo
The example below shows a pattern using Popover API. In this scenario, preaction
helps set up SSR-friendly attributes that complies to Popover specs while allowing the use of Svelte action to progressively enhance runtime experience.
The source code is:
<script lang="ts">
import { make, apply } from '@svelte-put/preaction';
import type { HTMLAttributes, HTMLButtonAttributes } from 'svelte/elements';
const popover = {
control: make((id: string) => {
return {
action: (node: HTMLButtonElement) => {
// regular runtime Svelte action business
console.log('popover control', node);
},
attributes: {
popovertarget: id,
class: 'c-btn',
popovertargetaction: 'show',
} as HTMLButtonAttributes,
};
}),
target: make((id: string) => {
return {
action: (node: HTMLDivElement) => {
// regular runtime Svelte action business
console.log('popover target', node);
},
attributes: {
id,
class: 'border-2 p-10 m-auto',
popover: 'auto',
} as HTMLAttributes<HTMLDivElement>,
};
}),
};
</script>
<button use:apply={popover.control('my-popover')}>
Open Popover
</button>
<div use:apply={popover.target('my-popover')}>
My simple popover
</div>
Which is equivalent to:
<script lang="ts">
const popover = { /** truncated, same as above */ } ;
const control = popover.control('my-popover');
const target = popover.target('my-popover');
</script>
<button {...(control.attributes ?? {})} use:control.action={'my-popover'}>
Open Popover
</button>
<div {...(target.attributes ?? {})} use:target.action={'my-popover'}>
My simple popover
</div>
Attribute spread is added before any other use
directives or attributes to avoid potential conflicts with user-defined attributes.
Guides
0 set up preaction
preprocessor
Start by adding preaction
to your Svelte config.
import { preaction } from '@svelte-put/preaction';
/** @type {import('@sveltejs/kit').Config} */
export default {
preprocess: [
preaction(),
// other preprocessors
],
// ...truncated...
}
1 make
a preaction
Next, prepare a "preaction" using the make
helper.
import type { HTMLAttributes } from 'svelte/elements';
import { make } from '@svelte-put/preaction';
export const setMyColor = make((initialColor = 'blue') => {
// this code runs both both on server and client, as if it is top-level script code.
// So don't rely on browser stuff such as `document` or `window`.
return {
action = (node: HTMLElement, color: string) => {
// this code is a typical Svelte action and runs only on client
return {
update: (newColor: string) => {
// update the color attribute dynamically at runtime
node.dataset.color = newColor;
},
}
},
attributes: {
// this attributes will be statically spread onto the node
'data-color': initialColor;
} as HTMLAttributes<HTMLElement>,
};
});
// For typescript users, to take full advantage of language tooling,
// like make sure to provide type parameters and attributes as seen above.
Input to make
is a function that returns an object with the action
and optionally attributes
:
function make<Node, Param>(preaction: Preaction<Node, Param>);
// note: if your action does not use any param, simply exclude it from the declaration
type Preaction<Node, Param> = (param: Param) => {
action: import('svelte/action').Action<Node, Param>;
attributes?: Record<string, any>;
};
make
can be used anywhere in your codebase, not just .svelte
files
2 apply
a preaction
Finally, call your preaction (created with make
) within use:apply
directive to apply the encapsulated action and attributes to an element.
<script>
import { apply } from '@svelte-put/preaction';
import { setMyColor } from './set-my-color.ts'; // see previous step
</script>
<!-- add data-color on server, and apply action on client -->
<div use:apply={setMyColor('red')}></div>
Why would I need this? (and alternatives)
You probably don't! The intended use case is to extract HTML attribute setup with Svelte action logic into reusable modules. These can already be solved today using either native Svelte action and manual prop spreading...
<!-- alternative: just spread prop manually -->
<button use:myaction {...myprops}></button>
...or encapsulate such logic into a component...
<script>
// preparation logics, hidden from user of this component
const myaction = () => { /** action logic */ };
const myprops = { /** props */ };
</script>
<button use:myaction {...myprops}></button>
Unlike in Svelte 4, the introduction of runes in Svelte 5 as well as simplification of prop declaration and event handler now allows for easier encapsulation than ever.
Shoutout
This package is greatly inspired by Melt UI.
Happy pre-acting!