Skip to content

Commit a010524

Browse files
committed
feat: hooks() now auto-imports and auto-injects dependencies
Related #45
1 parent bcefa85 commit a010524

File tree

7 files changed

+76
-31
lines changed

7 files changed

+76
-31
lines changed

docs/migration-to-2.0.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ The preprocessor will autoimport sveltify and can also generate the react object
2828

2929
So both `const react = sveltify({ MyComponent })` and the `import { sveltify } from "svelte-preprocess-react"` are optional, but that confuses ESLint.
3030

31-
To avoid the `no-undef` errors in your `eslint.config.js` add `sveltify: true, react: true` to your `globals`.
32-
When using Typescript it's recommended to only add `sveltify: true`, then the eslint warnign acts as a reminder to add a `const react = sveltify({..})` for type-safety.
31+
To avoid the `no-undef` errors in your `eslint.config.js` add `sveltify: true, hooks: true, react: true` to your `globals`.
32+
When using Typescript don't add the `react: true` the eslint warning acts as a reminder to add a `const react = sveltify({..})` for type-safety.
3333

3434
## Why the change?
3535

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export default ts.config(
3333
...globals.node,
3434
...globals.browser,
3535
sveltify: true,
36+
hooks: true,
3637
react: true,
3738
},
3839
parser: svelteParser,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"type": "git",
1111
"url": "https://github.com/bfanger/svelte-preprocess-react.git"
1212
},
13-
"version": "2.0.3",
13+
"version": "2.0.4",
1414
"license": "MIT",
1515
"type": "module",
1616
"scripts": {

src/lib/global.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ declare global {
1616
: Sveltified<T[K]>;
1717
};
1818

19+
function hooks<T>(callback: () => T): Readable<T | undefined>;
20+
1921
const react: {
2022
[component: string]: Component;
2123
};

src/lib/hooks.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ import type ReactDOMServer from "react-dom/server";
22
import * as React from "react";
33
import { getContext, onDestroy } from "svelte";
44
import { type Readable, writable } from "svelte/store";
5-
import type { TreeNode } from "./internal/types";
5+
import type { ReactDependencies, TreeNode } from "./internal/types";
66

77
export default function hooks<T>(
88
callback: () => T,
9-
ReactDOMClient?: any,
10-
renderToString?: typeof ReactDOMServer.renderToString,
9+
dependencies?: Omit<ReactDependencies, "createPortal">,
1110
): Readable<T | undefined> {
1211
const store = writable<T | undefined>();
1312

@@ -26,12 +25,25 @@ export default function hooks<T>(
2625
parent.hooks.splice(index, 1);
2726
}
2827
});
29-
} else if (ReactDOMClient) {
30-
onDestroy(standalone(Hook, ReactDOMClient, renderToString));
31-
} else if (typeof window !== "undefined") {
28+
} else if (!dependencies) {
3229
throw new Error(
33-
"The ReactDOMClient parameter is required for hooks(), because no parent component was a sveltified React component",
30+
"{ ReactDOM } is not injected, check svelte.config.js for: `preprocess: [preprocessReact()],`",
3431
);
32+
} else {
33+
let { ReactDOM, renderToString } = dependencies;
34+
if ("inject$$ReactDOM" in dependencies) {
35+
ReactDOM = dependencies.inject$$ReactDOM as ReactDependencies["ReactDOM"];
36+
}
37+
if ("inject$$renderToString" in dependencies) {
38+
renderToString =
39+
dependencies.inject$$renderToString as ReactDependencies["renderToString"];
40+
}
41+
if (!ReactDOM) {
42+
throw new Error(
43+
"{ ReactDOM } was not injected. Inside *.svelte files hooks() should be called with only 1 argument",
44+
);
45+
}
46+
onDestroy(standalone(Hook, ReactDOM, renderToString));
3547
}
3648
return store;
3749
}

src/lib/preprocessReact.js

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,14 @@ function transform(content, options) {
107107
const components = replaceReactTags(ast.html, s, options.filename);
108108
const aliases = Object.entries(components);
109109

110-
let depsInjected = false;
111-
let imported = false;
110+
/** @type {Set<'sveltify' | 'hooks'>} */
111+
let imported = new Set();
112+
/** @type {Set<'sveltify' | 'hooks'>} */
113+
let used = new Set();
112114
let defined = false;
115+
// import ReactDOMClient from "react-dom/client"; // React 18+,(use "react-dom" for older versions)
116+
// import { renderToString } from "react-dom/server";
117+
// ReactDOMClient, renderToString
113118

114119
/**
115120
* Detect sveltify import and usage
@@ -119,20 +124,38 @@ function transform(content, options) {
119124
*/
120125
function enter(node, parent) {
121126
if (node.type === "Identifier" && node.name === "sveltify" && parent) {
122-
if (parent.type === "ImportSpecifier") {
123-
imported = true;
124-
}
125127
if (
126-
parent.type === "CallExpression" &&
127-
parent?.arguments.length === 1 &&
128-
"end" in parent.arguments[0] &&
129-
typeof parent.arguments[0].end === "number"
128+
parent.type === "ImportSpecifier" ||
129+
parent.type === "ImportDeclaration"
130130
) {
131-
s.appendRight(parent.arguments[0].end, `, { ${deps.join(", ")} }`);
132-
depsInjected = true;
131+
imported.add("sveltify");
132+
} else if (parent.type === "CallExpression") {
133+
if (
134+
parent?.arguments.length === 1 &&
135+
"end" in parent.arguments[0] &&
136+
typeof parent.arguments[0].end === "number"
137+
) {
138+
s.appendRight(parent.arguments[0].end, `, { ${deps.join(", ")} }`);
139+
}
140+
used.add("sveltify");
133141
}
134-
if (parent.type === "ImportDeclaration") {
135-
imported = true;
142+
}
143+
144+
if (node.type === "Identifier" && node.name === "hooks" && parent) {
145+
if (
146+
parent.type === "ImportSpecifier" ||
147+
parent.type === "ImportDeclaration"
148+
) {
149+
imported.add("hooks");
150+
} else if (parent.type === "CallExpression") {
151+
if (
152+
parent?.arguments.length === 1 &&
153+
"end" in parent.arguments[0] &&
154+
typeof parent.arguments[0].end === "number"
155+
) {
156+
s.appendRight(parent.arguments[0].end, `, { ${deps.join(", ")} }`);
157+
}
158+
used.add("hooks");
136159
}
137160
}
138161
if (
@@ -149,15 +172,25 @@ function transform(content, options) {
149172
if (ast.instance) {
150173
walk(ast.instance, { enter });
151174
}
152-
if (!depsInjected && aliases.length === 0) {
175+
if (used.size === 0 && aliases.length === 0) {
153176
return { code: content };
154177
}
155-
if ((depsInjected && !imported) || (!imported && !defined)) {
156-
imports.push(`import { sveltify } from "${packageName}";`);
178+
let declarators = [];
179+
if (
180+
!imported.has("sveltify") &&
181+
(used.has("sveltify") || aliases.length > 0)
182+
) {
183+
declarators.push("sveltify");
184+
}
185+
if (!imported.has("hooks") && used.has("hooks")) {
186+
declarators.push("hooks");
187+
}
188+
if (declarators.length > 0) {
189+
imports.push(`import { ${declarators.join(", ")} } from "${packageName}";`);
157190
}
158191
const script = ast.instance || ast.module;
159192
let wrappers = [];
160-
if (!defined) {
193+
if (!defined && aliases.length > 0) {
161194
wrappers.push(
162195
`const react = sveltify({ ${Object.keys(components)
163196
.map((component) => {

src/routes/hooks/+page.svelte

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
<script lang="ts">
22
import { useState } from "react";
3-
import ReactDOMClient from "react-dom/client"; // React 18+,(use "react-dom" for older versions)
4-
import { renderToString } from "react-dom/server";
5-
import { hooks } from "svelte-preprocess-react";
63
import Nested from "./HookWithContext.svelte";
74
import { type Auth, AuthProvider } from "./react-auth";
85
96
const react = sveltify({ AuthProvider });
107
11-
const countHook = hooks(() => useState(0), ReactDOMClient, renderToString);
8+
const countHook = hooks(() => useState(0));
129
1310
const auth: Auth = $state({ authenticated: false });
1411

0 commit comments

Comments
 (0)