Skip to content

Commit

Permalink
feat: hooks() now auto-imports and auto-injects dependencies
Browse files Browse the repository at this point in the history
Related #45
  • Loading branch information
bfanger committed Nov 10, 2024
1 parent bcefa85 commit a010524
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 31 deletions.
4 changes: 2 additions & 2 deletions docs/migration-to-2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ The preprocessor will autoimport sveltify and can also generate the react object

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

To avoid the `no-undef` errors in your `eslint.config.js` add `sveltify: true, react: true` to your `globals`.
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.
To avoid the `no-undef` errors in your `eslint.config.js` add `sveltify: true, hooks: true, react: true` to your `globals`.
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.

## Why the change?

Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export default ts.config(
...globals.node,
...globals.browser,
sveltify: true,
hooks: true,
react: true,
},
parser: svelteParser,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"type": "git",
"url": "https://github.com/bfanger/svelte-preprocess-react.git"
},
"version": "2.0.3",
"version": "2.0.4",
"license": "MIT",
"type": "module",
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ declare global {
: Sveltified<T[K]>;
};

function hooks<T>(callback: () => T): Readable<T | undefined>;

const react: {
[component: string]: Component;
};
Expand Down
26 changes: 19 additions & 7 deletions src/lib/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import type ReactDOMServer from "react-dom/server";
import * as React from "react";
import { getContext, onDestroy } from "svelte";
import { type Readable, writable } from "svelte/store";
import type { TreeNode } from "./internal/types";
import type { ReactDependencies, TreeNode } from "./internal/types";

export default function hooks<T>(
callback: () => T,
ReactDOMClient?: any,
renderToString?: typeof ReactDOMServer.renderToString,
dependencies?: Omit<ReactDependencies, "createPortal">,
): Readable<T | undefined> {
const store = writable<T | undefined>();

Expand All @@ -26,12 +25,25 @@ export default function hooks<T>(
parent.hooks.splice(index, 1);
}
});
} else if (ReactDOMClient) {
onDestroy(standalone(Hook, ReactDOMClient, renderToString));
} else if (typeof window !== "undefined") {
} else if (!dependencies) {
throw new Error(
"The ReactDOMClient parameter is required for hooks(), because no parent component was a sveltified React component",
"{ ReactDOM } is not injected, check svelte.config.js for: `preprocess: [preprocessReact()],`",
);
} else {
let { ReactDOM, renderToString } = dependencies;
if ("inject$$ReactDOM" in dependencies) {
ReactDOM = dependencies.inject$$ReactDOM as ReactDependencies["ReactDOM"];
}
if ("inject$$renderToString" in dependencies) {
renderToString =
dependencies.inject$$renderToString as ReactDependencies["renderToString"];
}
if (!ReactDOM) {
throw new Error(
"{ ReactDOM } was not injected. Inside *.svelte files hooks() should be called with only 1 argument",
);
}
onDestroy(standalone(Hook, ReactDOM, renderToString));
}
return store;
}
Expand Down
67 changes: 50 additions & 17 deletions src/lib/preprocessReact.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,14 @@ function transform(content, options) {
const components = replaceReactTags(ast.html, s, options.filename);
const aliases = Object.entries(components);

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

/**
* Detect sveltify import and usage
Expand All @@ -119,20 +124,38 @@ function transform(content, options) {
*/
function enter(node, parent) {
if (node.type === "Identifier" && node.name === "sveltify" && parent) {
if (parent.type === "ImportSpecifier") {
imported = true;
}
if (
parent.type === "CallExpression" &&
parent?.arguments.length === 1 &&
"end" in parent.arguments[0] &&
typeof parent.arguments[0].end === "number"
parent.type === "ImportSpecifier" ||
parent.type === "ImportDeclaration"
) {
s.appendRight(parent.arguments[0].end, `, { ${deps.join(", ")} }`);
depsInjected = true;
imported.add("sveltify");
} else if (parent.type === "CallExpression") {
if (
parent?.arguments.length === 1 &&
"end" in parent.arguments[0] &&
typeof parent.arguments[0].end === "number"
) {
s.appendRight(parent.arguments[0].end, `, { ${deps.join(", ")} }`);
}
used.add("sveltify");
}
if (parent.type === "ImportDeclaration") {
imported = true;
}

if (node.type === "Identifier" && node.name === "hooks" && parent) {
if (
parent.type === "ImportSpecifier" ||
parent.type === "ImportDeclaration"
) {
imported.add("hooks");
} else if (parent.type === "CallExpression") {
if (
parent?.arguments.length === 1 &&
"end" in parent.arguments[0] &&
typeof parent.arguments[0].end === "number"
) {
s.appendRight(parent.arguments[0].end, `, { ${deps.join(", ")} }`);
}
used.add("hooks");
}
}
if (
Expand All @@ -149,15 +172,25 @@ function transform(content, options) {
if (ast.instance) {
walk(ast.instance, { enter });
}
if (!depsInjected && aliases.length === 0) {
if (used.size === 0 && aliases.length === 0) {
return { code: content };
}
if ((depsInjected && !imported) || (!imported && !defined)) {
imports.push(`import { sveltify } from "${packageName}";`);
let declarators = [];
if (
!imported.has("sveltify") &&
(used.has("sveltify") || aliases.length > 0)
) {
declarators.push("sveltify");
}
if (!imported.has("hooks") && used.has("hooks")) {
declarators.push("hooks");
}
if (declarators.length > 0) {
imports.push(`import { ${declarators.join(", ")} } from "${packageName}";`);
}
const script = ast.instance || ast.module;
let wrappers = [];
if (!defined) {
if (!defined && aliases.length > 0) {
wrappers.push(
`const react = sveltify({ ${Object.keys(components)
.map((component) => {
Expand Down
5 changes: 1 addition & 4 deletions src/routes/hooks/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
<script lang="ts">
import { useState } from "react";
import ReactDOMClient from "react-dom/client"; // React 18+,(use "react-dom" for older versions)
import { renderToString } from "react-dom/server";
import { hooks } from "svelte-preprocess-react";
import Nested from "./HookWithContext.svelte";
import { type Auth, AuthProvider } from "./react-auth";
const react = sveltify({ AuthProvider });
const countHook = hooks(() => useState(0), ReactDOMClient, renderToString);
const countHook = hooks(() => useState(0));
const auth: Auth = $state({ authenticated: false });
Expand Down

0 comments on commit a010524

Please sign in to comment.