From b6de53960fbc5f87b09c92732b93216117717c5a Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 8 Sep 2025 10:39:03 +0200 Subject: [PATCH 1/5] Fix formatting and syntax in use.mdx example --- src/routes/reference/jsx-attributes/use.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/routes/reference/jsx-attributes/use.mdx b/src/routes/reference/jsx-attributes/use.mdx index b622f82c2..f78fad6f7 100644 --- a/src/routes/reference/jsx-attributes/use.mdx +++ b/src/routes/reference/jsx-attributes/use.mdx @@ -12,13 +12,13 @@ function directive(element: Element, accessor: () => any): void Directive functions are called at render time but before being added to the DOM. You can do whatever you'd like in them including create signals, effects, register clean-up etc. ```tsx -const [name, setName] = createSignal("") +const [name, setName] = createSignal(""); function model(el, value) { - const [field, setField] = value() - createRenderEffect(() => (el.value = field())) - el.addEventListener("input", (e) => setField(e.target.value)) -}; + const [field, setField] = value(); + createRenderEffect(() => (el.value = field())); + el.addEventListener("input", (e) => setField(e.target.value)); +} ``` From 8fceeebd6d5537d9c91538fdb4406b235f0beb2a Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 9 Sep 2025 14:31:32 +0200 Subject: [PATCH 2/5] Refine custom directives documentation in use.mdx --- src/routes/reference/jsx-attributes/use.mdx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/routes/reference/jsx-attributes/use.mdx b/src/routes/reference/jsx-attributes/use.mdx index f78fad6f7..d888982b4 100644 --- a/src/routes/reference/jsx-attributes/use.mdx +++ b/src/routes/reference/jsx-attributes/use.mdx @@ -3,10 +3,12 @@ title: use:* order: 5 --- -These are custom directives. In a sense this is just syntax sugar over ref but allows us to easily attach multiple directives to a single element. A directive is a function with the following signature: +Custom directives attach reusable behavior to DOM elements, acting as syntactic sugar over `ref`. They’re ideal for complex DOM interactions like scrolling, tooltips, or resizing, which are cumbersome to repeat in JSX. + +A directive is a function with the following signature ```ts -function directive(element: Element, accessor: () => any): void +function directive(element: HTMLElement, accessor: Signal): void ``` Directive functions are called at render time but before being added to the DOM. You can do whatever you'd like in them including create signals, effects, register clean-up etc. @@ -14,22 +16,22 @@ Directive functions are called at render time but before being added to the DOM. ```tsx const [name, setName] = createSignal(""); -function model(el, value) { - const [field, setField] = value(); - createRenderEffect(() => (el.value = field())); - el.addEventListener("input", (e) => setField(e.target.value)); +function model(element: HTMLInputElement, accessor: Accessor>) { + const [field, setField] = accessor(); + createRenderEffect(() => (element.value = field())); + element.addEventListener("input", ({ target }) => setField(target.value)); } ``` -To register with TypeScript extend the JSX namespace. +To register with TypeScript extend the JSX namespace ```ts declare module "solid-js" { namespace JSX { - interface Directives { - model: [() => any, (v: any) => any] + interface DirectiveFunctions { + model: typeof model; } } } From bd02d027323dcf6a0613f70b658dbb7eaaf5bbe9 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 9 Sep 2025 14:32:59 +0200 Subject: [PATCH 3/5] Update directive function signature in documentation --- src/routes/reference/jsx-attributes/use.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/reference/jsx-attributes/use.mdx b/src/routes/reference/jsx-attributes/use.mdx index d888982b4..1bd2bb952 100644 --- a/src/routes/reference/jsx-attributes/use.mdx +++ b/src/routes/reference/jsx-attributes/use.mdx @@ -8,7 +8,7 @@ Custom directives attach reusable behavior to DOM elements, acting as syntactic A directive is a function with the following signature ```ts -function directive(element: HTMLElement, accessor: Signal): void +function directive(element: HTMLElement, accessor: Accessor): void ``` Directive functions are called at render time but before being added to the DOM. You can do whatever you'd like in them including create signals, effects, register clean-up etc. From 1706c38c50bc7c744dfe067a80148f5eb6838451 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 9 Sep 2025 15:04:25 +0200 Subject: [PATCH 4/5] Enhance documentation for custom directives and TypeScript --- src/routes/reference/jsx-attributes/use.mdx | 53 ++++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/src/routes/reference/jsx-attributes/use.mdx b/src/routes/reference/jsx-attributes/use.mdx index 1bd2bb952..7d96fe89f 100644 --- a/src/routes/reference/jsx-attributes/use.mdx +++ b/src/routes/reference/jsx-attributes/use.mdx @@ -3,29 +3,37 @@ title: use:* order: 5 --- -Custom directives attach reusable behavior to DOM elements, acting as syntactic sugar over `ref`. They’re ideal for complex DOM interactions like scrolling, tooltips, or resizing, which are cumbersome to repeat in JSX. +Custom directives attach reusable behavior to DOM elements, acting as syntactic sugar over `ref`. They’re ideal for complex DOM interactions like scrolling, tooltips, or form handling, which are cumbersome to repeat in JSX. A directive is a function with the following signature ```ts -function directive(element: HTMLElement, accessor: Accessor): void +function directive(element: HTMLElement, accessor: Accessor): void; ``` Directive functions are called at render time but before being added to the DOM. You can do whatever you'd like in them including create signals, effects, register clean-up etc. +## Example + +A `model` directive for two-way data binding + ```tsx -const [name, setName] = createSignal(""); +import type { Accessor, Signal } from "solid-js"; -function model(element: HTMLInputElement, accessor: Accessor>) { - const [field, setField] = accessor(); +function model(element: HTMLInputElement, value: Accessor>) { + const [field, setField] = value(); createRenderEffect(() => (element.value = field())); element.addEventListener("input", ({ target }) => setField(target.value)); } - +const [name, setName] = createSignal(""); + +; ``` -To register with TypeScript extend the JSX namespace +## TypeScript Support + +To type custom directives, extend the `DirectiveFunctions` interface: ```ts declare module "solid-js" { @@ -37,6 +45,37 @@ declare module "solid-js" { } ``` +## Avoiding Tree-Shaking + +When importing a directive `d` from another module and using it only as `use:d`, TypeScript (via [babel-preset-typescript](https://babeljs.io/docs/babel-preset-typescript)) may remove the import, as it doesn’t recognize `use:d` as a reference to `d`. +To prevent this: + +1. Use the `onlyRemoveTypeImports: true` option in `babel-preset-typescript`. For `vite-plugin-solid`, add this to `vite.config.ts` + + ```ts + import solidPlugin from "vite-plugin-solid"; + + export default { + plugins: [ + solidPlugin({ + typescript: { onlyRemoveTypeImports: true } + }) + ], + }; + ``` + + Note: This requires consistent use of `export type` and `import type` in your codebase to avoid issues. + +2. Add a fake access like `false && d;` in the module + + ```tsx + import { model } from "./directives"; + false && model; // Prevents tree-shaking + ; + ``` + + This is removed by bundlers like Terser, unlike a plain `model;` which may remain in the bundle. + :::caution[Limitations] Directives only work with native HTML elements (HTML/SVG/MathML/Custom Elements). Directives are not forwarded and **won't work in user defined components**, such as `` [see also](https://github.com/solidjs/solid/discussions/722) From f5653c99dc8980ec20439da1441840b06463ef0f Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 9 Sep 2025 15:25:14 +0200 Subject: [PATCH 5/5] Enhance TypeScript support section in use.mdx Added information on extending the Directives interface for TypeScript support. --- src/routes/reference/jsx-attributes/use.mdx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/routes/reference/jsx-attributes/use.mdx b/src/routes/reference/jsx-attributes/use.mdx index 7d96fe89f..ebfc504f4 100644 --- a/src/routes/reference/jsx-attributes/use.mdx +++ b/src/routes/reference/jsx-attributes/use.mdx @@ -33,7 +33,7 @@ const [name, setName] = createSignal(""); ## TypeScript Support -To type custom directives, extend the `DirectiveFunctions` interface: +To type custom directives, extend the `DirectiveFunctions` interface ```ts declare module "solid-js" { @@ -45,6 +45,18 @@ declare module "solid-js" { } ``` +If you just want to constrain the second argument to the directive function, you can extend the older `Directives` interface + +```tsx +declare module "solid-js" { + namespace JSX { + interface Directives { + model: Signal; + } + } +} +``` + ## Avoiding Tree-Shaking When importing a directive `d` from another module and using it only as `use:d`, TypeScript (via [babel-preset-typescript](https://babeljs.io/docs/babel-preset-typescript)) may remove the import, as it doesn’t recognize `use:d` as a reference to `d`.