From 886d563d213bf7864121d7c65084b98699a59c00 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Thu, 28 Jan 2021 02:10:02 -0800 Subject: [PATCH 01/14] New Async SSR WIP --- .gitignore | 5 +- documentation/api.md | 4 - documentation/suspense.md | 54 ++--- package-lock.json | 24 +- package.json | 10 +- packages/babel-preset-solid/index.js | 2 - packages/babel-preset-solid/package.json | 2 +- packages/solid-element/src/index.ts | 26 ++- packages/solid-rx/test/observable.spec.ts | 12 +- packages/solid-ssr/examples/async/index.js | 8 +- .../shared/src/components/Profile/index.js | 4 +- .../solid-ssr/examples/shared/src/index.js | 2 +- packages/solid-ssr/examples/ssg/index.js | 5 +- packages/solid-ssr/examples/ssr/index.js | 5 +- packages/solid-ssr/examples/stream/index.js | 5 +- packages/solid-ssr/package.json | 6 +- packages/solid-ssr/static/index.js | 2 +- packages/solid/bench/{bench.js => bench.cjs} | 0 packages/solid/html/src/index.ts | 4 +- packages/solid/package.json | 20 +- packages/solid/rollup.config.js | 30 +-- packages/solid/src/index.ts | 2 +- packages/solid/src/reactive/resourceState.ts | 137 ----------- packages/solid/src/reactive/signal.ts | 131 ++++++----- packages/solid/src/reactive/stateModifiers.ts | 2 +- packages/solid/src/render/Suspense.ts | 29 +-- packages/solid/src/render/component.ts | 51 +---- packages/solid/src/render/flow.ts | 25 +- packages/solid/src/render/hydration.ts | 23 ++ packages/solid/src/render/index.ts | 3 +- packages/solid/src/static/index.ts | 7 +- packages/solid/src/static/reactive.ts | 24 +- packages/solid/src/static/rendering.ts | 216 +++++++++++++----- packages/solid/test/resource.spec.ts | 140 +++--------- packages/solid/web/server-async/asyncSSR.js | 1 - packages/solid/web/server-async/index.ts | 42 ---- packages/solid/web/server/index.ts | 4 +- packages/solid/web/server/server.js | 1 + packages/solid/web/server/syncSSR.js | 1 - packages/solid/web/src/client.js | 1 + packages/solid/web/src/core.ts | 5 +- packages/solid/web/src/index.ts | 210 ++++++++--------- packages/solid/web/src/runtime.js | 1 - packages/solid/web/test/element.spec.tsx | 4 +- packages/solid/web/test/suspense.spec.tsx | 31 --- packages/solid/web/test/switch.spec.tsx | 47 +++- 46 files changed, 585 insertions(+), 783 deletions(-) rename packages/solid/bench/{bench.js => bench.cjs} (100%) delete mode 100644 packages/solid/src/reactive/resourceState.ts create mode 100644 packages/solid/src/render/hydration.ts delete mode 100644 packages/solid/web/server-async/asyncSSR.js delete mode 100644 packages/solid/web/server-async/index.ts create mode 100644 packages/solid/web/server/server.js delete mode 100644 packages/solid/web/server/syncSSR.js create mode 100644 packages/solid/web/src/client.js delete mode 100644 packages/solid/web/src/runtime.js diff --git a/.gitignore b/.gitignore index 21f823ef3..734b66391 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,5 @@ coverage/ types/ packages/solid/src/jsx.d.ts -packages/solid/web/server-async/asyncSSR.d.ts -packages/solid/web/server/syncSSR.d.ts -packages/solid/web/src/runtime.d.ts \ No newline at end of file +packages/solid/web/server/server.d.ts +packages/solid/web/src/client.d.ts \ No newline at end of file diff --git a/documentation/api.md b/documentation/api.md index f8b810b21..af041914d 100644 --- a/documentation/api.md +++ b/documentation/api.md @@ -80,10 +80,6 @@ Creates a conditional signal that only notifies subscribers when entering or exi Creates a new resource signal that can hold an async resource. Resources when read while loading trigger Suspense. The `loadFn` takes a Promise whose resolved value is set in the resource. -### `createResourceState(initialValue, options: { name }): [state, loadState, setState]` - -Creates a new Resource State object. Similar to normal state except each immediate property is a resource. - ### `lazy(() => ): Component` Used to lazy load components to allow for things like code splitting and Suspense. diff --git a/documentation/suspense.md b/documentation/suspense.md index dd157e679..d5365cf1a 100644 --- a/documentation/suspense.md +++ b/documentation/suspense.md @@ -164,9 +164,7 @@ const App = () => { ## Data Loading -Solid ships with two resource containers to handle async loading. One is a signal created by `createResource` and the other a state object created by `createResourceState`. The signal is a simple reactive atom so it's reactivity is not deeply nested. Whereas state deeply nests reactive properties. - -Both have trackable `loading` property. On the signal it's a boolean. On the state object it is an object with a boolean per key. +Solid ships with a primitive to handle async data loading, `createResource`. It has a trackable `loading` property. ```jsx import { createResource } from "solid-js"; @@ -194,36 +192,32 @@ export default const UserPanel = props => { } ``` +This example handles the different loading states. However, you can expand this example to use Suspense instead by wrapping with the `Suspense` Component. -```jsx -import { createResourceState } from "solid-js"; +`load` also supports a second argument to transform the data before you store it. This can be useful for creating resource caches. +```js +// fetch all the posts +loadPosts(fetchPosts); -// notice returns a function that returns a promise -const fetchUser = id => - () => fetch(`https://swapi.co/api/people/${id}/`).then(r => r.json()); +// fetch a single post and add it to the resource +loadPosts(fetchPost(id), (post, prev) => { ...prev, [id]: post }); +``` -export default const UserPanel = props => { - let [state, load] = createResourceState(); - load({ user: fetchUser(props.userId) }); +Or even if you store it as state be able to deep data diff: +```js +const [user, loadUser] = createResource(); +const [state] = createState({ + get user() { return user() } +}) - return
- - Loading... - { user => -

{user.name}

-
    -
  • Height: {user.height}
  • -
  • Mass: {user.mass}
  • -
  • Birth Year: {user.birthYear}
  • -
- }
-
-
-} -``` +// get the user initially +loadUser(fetchUser(id)) -These examples handle the different loading states. However, you can expand this example to use Suspense instead by wrapping with the `Suspense` Component. +// refresh user info +loadUser(fetchUser(id), (user, prevUser) => reconcile(user)(prevUser)); +``` +Since it is in state and being reconciled only the properties that have updated on the server will notify change. > **For React Users:** At the time of writing this React has not completely settled how their Data Fetching API will look. Solid ships with this feature today, and it might differ from what React ultimately lands on. @@ -233,8 +227,10 @@ It is important to note that Suspense is tracked based on data requirements of t ```jsx // start loading data before any part of the page is executed. -const [state, load] = createResourceState(); -load({ user: fetchUser(), posts: fetchPosts() }); +const [user, loadUser] = createResource(); +const [posts, loadPosts] = createResource(); +loadUser(fetchUser); +loadPosts(fetchPosts); function ProfilePage() { return ( diff --git a/package-lock.json b/package-lock.json index 4acd5c8eb..1657d0088 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4431,9 +4431,9 @@ } }, "babel-plugin-jsx-dom-expressions": { - "version": "0.24.7", - "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.24.7.tgz", - "integrity": "sha512-WaBX9z81sL0NDRVjeXr1GhJPkIMbz9GiT42/I6RqouN4y4kf6hEaARbG7NMk6YKOmc31o64fb0mYo3gj78nYmQ==", + "version": "0.25.0-beta.2", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.25.0-beta.2.tgz", + "integrity": "sha512-CUtzPKOqHBWXvy1kbgpWsLO2XQVlCBvJd5STgW0H8KUjHKuldIzysO7v8pMAp16D4qc522McTbz/unnQJmzEnw==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.10.4", @@ -5875,9 +5875,9 @@ } }, "dom-expressions": { - "version": "0.24.7", - "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.24.7.tgz", - "integrity": "sha512-A0CADsk23P7LA/ITZi6ac/LLr1Lcd6aUpEudO/8gmig1CPyl3NOzVb2+aSum/klZY5kxm7mnqtPBk+EQerGN1A==", + "version": "0.25.0-beta.2", + "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.25.0-beta.2.tgz", + "integrity": "sha512-Pr+TLahPyt3oT88/abJT4Gtt0HYGFQne2wl6vSItx8uhu4jYI8cjw5UITFIsyJqZG170HC5+sSgenyvLhivYDQ==", "dev": true, "requires": { "babel-plugin-transform-rename-import": "^2.3.0" @@ -7754,9 +7754,9 @@ } }, "hyper-dom-expressions": { - "version": "0.24.7", - "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.24.7.tgz", - "integrity": "sha512-U//Zs/SPO6cLhsVT41vSR6DnLAOgnjogplZ74urK4NQZL9BHbBJ+ema7ru09lp8hPFP5bp7Z/sIe4/vMvvFa4w==", + "version": "0.25.0-beta.2", + "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.25.0-beta.2.tgz", + "integrity": "sha512-W3ys3bzAyFplOUhd06ujeWglliuIYMwfqzLrpaK+jvI/SjDPfOm/u0NnYL7EtroVACphJuOqRiq+pUzgDirsUQ==", "dev": true }, "iconv-lite": { @@ -10917,9 +10917,9 @@ "dev": true }, "lit-dom-expressions": { - "version": "0.24.7", - "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.24.7.tgz", - "integrity": "sha512-lVeKCMX6Gaqzx3PUEEVpaaT20l3okBLphDZ8/6DFONWMVZzg50OrMH9fOcixM3aub2tDgFvgWRpULcwbxDqNZg==", + "version": "0.25.0-beta.2", + "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.25.0-beta.2.tgz", + "integrity": "sha512-+kYztDMXOKaZUOiVYCvgyBVtavkIZ9nFfbh8QolNcIui2u2VA0W0b04FkU5zwrR2MfoQYgLLtWNrQm0LTIMOEA==", "dev": true }, "load-json-file": { diff --git a/package.json b/package.json index aa5f4e8b3..9e94fe17e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "solid-js", "description": "A declarative JavaScript library for building user interfaces.", - "version": "0.23.0", + "version": "0.24.0", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -32,14 +32,14 @@ "@rollup/plugin-replace": "2.3.3", "@types/jest": "^26.0.14", "babel-jest": "^26.6.3", - "babel-plugin-jsx-dom-expressions": "~0.24.7", + "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.2", "coveralls": "^3.1.0", - "dom-expressions": "0.24.7", - "hyper-dom-expressions": "0.24.7", + "dom-expressions": "0.25.0-beta.2", + "hyper-dom-expressions": "0.25.0-beta.2", "jest": "~26.6.3", "jest-ts-webcompat-resolver": "^1.0.0", "lerna": "^3.22.1", - "lit-dom-expressions": "0.24.7", + "lit-dom-expressions": "0.25.0-beta.2", "ncp": "2.0.0", "npm-run-all": "^4.1.5", "rimraf": "^3.0.2", diff --git a/packages/babel-preset-solid/index.js b/packages/babel-preset-solid/index.js index b3e1e675c..4ed9486e6 100644 --- a/packages/babel-preset-solid/index.js +++ b/packages/babel-preset-solid/index.js @@ -19,10 +19,8 @@ module.exports = function (context, options = {}) { "Dynamic", "ErrorBoundary" ], - delegateEvents: true, contextToCustomElements: true, wrapConditionals: true, - wrapSpreads: false, generate: "dom" }, options diff --git a/packages/babel-preset-solid/package.json b/packages/babel-preset-solid/package.json index c8df29d19..6c220f9bf 100644 --- a/packages/babel-preset-solid/package.json +++ b/packages/babel-preset-solid/package.json @@ -14,6 +14,6 @@ "test": "node test.js" }, "dependencies": { - "babel-plugin-jsx-dom-expressions": "~0.24.7" + "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.2" } } diff --git a/packages/solid-element/src/index.ts b/packages/solid-element/src/index.ts index ae3f8ba59..4ad5b18cc 100644 --- a/packages/solid-element/src/index.ts +++ b/packages/solid-element/src/index.ts @@ -8,30 +8,38 @@ import { } from "component-register"; export { hot, getCurrentElement } from "component-register"; export type ComponentType = mComponentType; -import { createRoot, createState } from "solid-js"; +import { createRoot, createSignal } from "solid-js"; import { insert } from "solid-js/web"; +function createProps(raw: T) { + const keys = Object.keys(raw) as (keyof T)[]; + const props = {}; + for (let i = 0; i < keys.length; i++) { + const [get, set] = createSignal(raw[keys[i]]); + Object.defineProperty(props, keys[i], { + get, + set + }); + } + return props as T; +} + function withSolid(ComponentType: ComponentType): ComponentType { return (rawProps: T, options: ComponentOptions) => { const { element } = options as { element: ICustomElement & { _context?: any }; }; return createRoot((dispose: Function) => { - const [props, setProps] = createState(rawProps); + const props = createProps(rawProps); - element.addPropertyChangedCallback((key: string, val: any) => - setProps({ [key]: val } as any) - ); + element.addPropertyChangedCallback((key: string, val: any) => (props[key as keyof T] = val)); element.addReleaseCallback(() => { element.renderRoot.textContent = ""; dispose(); }); const comp = (ComponentType as FunctionComponent)(props as T, options); - return insert( - element.renderRoot, - comp - ); + return insert(element.renderRoot, comp); }, (element.assignedSlot && element.assignedSlot._context) || element._context); }; } diff --git a/packages/solid-rx/test/observable.spec.ts b/packages/solid-rx/test/observable.spec.ts index 7824a3922..116812a57 100644 --- a/packages/solid-rx/test/observable.spec.ts +++ b/packages/solid-rx/test/observable.spec.ts @@ -3,15 +3,17 @@ import { observable } from "../src"; describe("Observable operator", () => { test("to observable", async () => { + let out: string; + let set: (string) => void; createRoot(() => { - let out: string; - const [s, set] = createSignal("Hi"), + const [s, _set] = createSignal("Hi"), obsv$ = observable(s); + set = _set; obsv$.subscribe({ next: v => (out = v) }); - expect(out).toBe("Hi"); - set("John"); - expect(out).toBe("John"); }); + expect(out).toBe("Hi"); + set("John"); + expect(out).toBe("John"); }); }); diff --git a/packages/solid-ssr/examples/async/index.js b/packages/solid-ssr/examples/async/index.js index bf014d4c4..9ca296641 100644 --- a/packages/solid-ssr/examples/async/index.js +++ b/packages/solid-ssr/examples/async/index.js @@ -2,7 +2,7 @@ import express from "express"; import path from "path"; import { awaitSuspense } from "solid-js"; -import { renderToString, generateHydrationScript } from "solid-js/web"; +import { renderToStringAsync } from "solid-js/web"; import { extractCss } from "solid-styled-components"; import App from "../shared/src/components/App"; @@ -15,7 +15,7 @@ app.use(express.static(path.join(__dirname, "../public"))); app.get("*", async (req, res) => { let html; try { - const string = await renderToString(awaitSuspense(() => )); + const string = await renderToStringAsync(awaitSuspense(() => )); const style = extractCss(); html = ` @@ -23,10 +23,6 @@ app.get("*", async (req, res) => { - ${style ? `` : ""}
${string}
diff --git a/packages/solid-ssr/examples/shared/src/components/Profile/index.js b/packages/solid-ssr/examples/shared/src/components/Profile/index.js index 3d95e59c0..5ecc9c480 100644 --- a/packages/solid-ssr/examples/shared/src/components/Profile/index.js +++ b/packages/solid-ssr/examples/shared/src/components/Profile/index.js @@ -3,8 +3,8 @@ const Profile = lazy(() => import("./Profile")); // this component lazy loads data and code in parallel export default () => { - const [user, loadUser] = createResource(undefined, { name: "profile" }), - [info, loadInfo] = createResource([], { name: "profile_info" }); + const [user, loadUser] = createResource(), + [info, loadInfo] = createResource([]); loadUser( () => // simulate data loading diff --git a/packages/solid-ssr/examples/shared/src/index.js b/packages/solid-ssr/examples/shared/src/index.js index 949ce219e..602838bd9 100644 --- a/packages/solid-ssr/examples/shared/src/index.js +++ b/packages/solid-ssr/examples/shared/src/index.js @@ -2,4 +2,4 @@ import { hydrate } from "solid-js/web"; import App from "./components/App"; // entry point for browser -hydrate(App, document.getElementById("app")); \ No newline at end of file +hydrate(() => , document.getElementById("app")); \ No newline at end of file diff --git a/packages/solid-ssr/examples/ssg/index.js b/packages/solid-ssr/examples/ssg/index.js index 38f10765a..c34a22c33 100644 --- a/packages/solid-ssr/examples/ssg/index.js +++ b/packages/solid-ssr/examples/ssg/index.js @@ -1,12 +1,12 @@ import { awaitSuspense } from "solid-js"; -import { renderToString, generateHydrationScript } from "solid-js/web"; +import { renderToStringAsync } from "solid-js/web"; import { extractCss } from "solid-styled-components"; import App from "../shared/src/components/App"; const lang = "en"; // entry point for server render export default async req => { - const string = await renderToString(awaitSuspense(() => )); + const string = await renderToStringAsync(awaitSuspense(() => )); const style = extractCss(); return ` @@ -14,7 +14,6 @@ export default async req => { - ${style ? `` : ""}
${string}
diff --git a/packages/solid-ssr/examples/ssr/index.js b/packages/solid-ssr/examples/ssr/index.js index 0a5621ff0..83c65f966 100644 --- a/packages/solid-ssr/examples/ssr/index.js +++ b/packages/solid-ssr/examples/ssr/index.js @@ -1,7 +1,7 @@ import express from "express"; import path from "path"; -import { renderToString, generateHydrationScript } from "solid-js/web"; +import { renderToString } from "solid-js/web"; import App from "../shared/src/components/App"; const app = express(); @@ -20,10 +20,9 @@ app.get("*", (req, res) => { -
${string}
- + `; } catch (err) { console.error(err); diff --git a/packages/solid-ssr/examples/stream/index.js b/packages/solid-ssr/examples/stream/index.js index 2d6661093..bde640d77 100644 --- a/packages/solid-ssr/examples/stream/index.js +++ b/packages/solid-ssr/examples/stream/index.js @@ -1,7 +1,7 @@ import express from "express"; import path from "path"; -import { renderToNodeStream, generateHydrationScript } from "solid-js/web"; +import { renderToNodeStream } from "solid-js/web"; import App from "../shared/src/components/App"; const app = express(); @@ -19,9 +19,6 @@ app.get("*", (req, res) => { -
`; diff --git a/packages/solid-ssr/package.json b/packages/solid-ssr/package.json index 12609d16d..d5014b448 100644 --- a/packages/solid-ssr/package.json +++ b/packages/solid-ssr/package.json @@ -16,12 +16,12 @@ "scripts": { "build:example:async": "rollup -c examples/async/rollup.config.js", "start:example:async": "node examples/async/lib/index.js", - "build:example:ssg": "rollup -c examples/ssg/rollup.config.js && node examples/ssg/export.js", + "build:example:ssg": "rollup -c examples/ssg/rollup.config.js && node --trace-warnings examples/ssg/export.js", "start:example:ssg": "npx serve examples/ssg/public -l 8080", "build:example:ssr": "rollup -c examples/ssr/rollup.config.js", - "start:example:ssr": "node --conditions=server examples/ssr/lib/index.js", + "start:example:ssr": "node examples/ssr/lib/index.js", "build:example:stream": "rollup -c examples/stream/rollup.config.js", - "start:example:stream": "node --conditions=server examples/stream/lib/index.js" + "start:example:stream": "node examples/stream/lib/index.js" }, "devDependencies": { "babel-preset-solid": "^0.23.8", diff --git a/packages/solid-ssr/static/index.js b/packages/solid-ssr/static/index.js index a9844f047..289b35ab1 100644 --- a/packages/solid-ssr/static/index.js +++ b/packages/solid-ssr/static/index.js @@ -4,7 +4,7 @@ const execFile = require("util").promisify(require("child_process").execFile); const pathToRunner = path.resolve(__dirname, "writeToDisk.js"); async function run({ entry, output, url }) { - const { stdout, stderr } = await execFile("node", [pathToRunner, entry, output, url]); + const { stdout, stderr } = await execFile("node", [pathToRunner, entry, output, url, "--trace-warnings"]); if (stdout.length) console.log(stdout); if (stderr.length) console.log(stderr); } diff --git a/packages/solid/bench/bench.js b/packages/solid/bench/bench.cjs similarity index 100% rename from packages/solid/bench/bench.js rename to packages/solid/bench/bench.cjs diff --git a/packages/solid/html/src/index.ts b/packages/solid/html/src/index.ts index 24612bb32..734454bbe 100644 --- a/packages/solid/html/src/index.ts +++ b/packages/solid/html/src/index.ts @@ -12,7 +12,7 @@ import { Aliases, Properties, ChildProperties, - NonComposedEvents, + DelegatedEvents, SVGElements, SVGNamespace } from "solid-js/web"; @@ -30,7 +30,7 @@ export default createHTML({ Aliases, Properties, ChildProperties, - NonComposedEvents, + DelegatedEvents, SVGElements, SVGNamespace }); diff --git a/packages/solid/package.json b/packages/solid/package.json index 5bbb3b379..37d53faae 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -29,7 +29,11 @@ ], "exports": { ".": { - "server": { + "browser": { + "import": "./dist/solid.js", + "require": "./dist/solid.cjs" + }, + "node": { "import": "./dist/static.js", "require": "./dist/static.cjs" }, @@ -37,21 +41,13 @@ "require": "./dist/solid.cjs" }, "./web": { - "server-async": { - "import": "./web/dist/server-async.js", - "require": "./web/dist/server-async.cjs" - }, - "server": { - "import": "./web/dist/server.js", - "require": "./web/dist/server.cjs" - }, "browser": { "import": "./web/dist/web.js", "require": "./web/dist/web.cjs" }, "node": { - "import": "./web/dist/server-async.js", - "require": "./web/dist/server-async.cjs" + "import": "./web/dist/server.js", + "require": "./web/dist/server.cjs" }, "import": "./web/dist/web.js", "require": "./web/dist/web.cjs" @@ -82,7 +78,7 @@ "build:types-web": "tsc --project ./web/tsconfig.json && tsconfig-replace-paths --project ./web/tsconfig.types.json", "build:types-html": "tsc --project ./html/tsconfig.json", "build:types-h": "tsc --project ./h/tsconfig.json", - "bench": "node --allow-natives-syntax bench/bench.js", + "bench": "node --allow-natives-syntax bench/bench.cjs", "test": "jest && npm run test:types", "test:coverage": "jest --coverage && npm run test:types", "test:types": "tsc --project tsconfig.test.json", diff --git a/packages/solid/rollup.config.js b/packages/solid/rollup.config.js index 83fc6c4ac..a98c9e6c6 100644 --- a/packages/solid/rollup.config.js +++ b/packages/solid/rollup.config.js @@ -103,10 +103,10 @@ export default [ copy({ targets: [ { - src: ["../../node_modules/dom-expressions/src/runtime.d.ts"], + src: ["../../node_modules/dom-expressions/src/client.d.ts"], dest: "./web/src/" }, - { src: "../../node_modules/dom-expressions/src/runtime.d.ts", dest: "./web/types/" } + { src: "../../node_modules/dom-expressions/src/client.d.ts", dest: "./web/types/" } ] }) ].concat(plugins) @@ -160,35 +160,11 @@ export default [ copy({ targets: [ { - src: ["../../node_modules/dom-expressions/src/syncSSR.d.ts"], + src: ["../../node_modules/dom-expressions/src/server.d.ts"], dest: "./web/server" } ] }) ].concat(plugins) - }, - { - input: "web/server-async/index.ts", - output: [ - { - file: "web/dist/server-async.cjs", - format: "cjs" - }, - { - file: "web/dist/server-async.js", - format: "es" - } - ], - external: ["solid-js"], - plugins: [ - copy({ - targets: [ - { - src: ["../../node_modules/dom-expressions/src/asyncSSR.d.ts"], - dest: "./web/server-async" - } - ] - }) - ].concat(plugins) } ]; diff --git a/packages/solid/src/index.ts b/packages/solid/src/index.ts index 07d13749a..c2aa83bb1 100644 --- a/packages/solid/src/index.ts +++ b/packages/solid/src/index.ts @@ -18,6 +18,7 @@ export { useTransition, createContext, useContext, + children, getContextOwner, equalFn, serializeGraph @@ -26,7 +27,6 @@ export type { Resource } from "./reactive/signal"; export { createState, unwrap, $RAW } from "./reactive/state"; export type { State, SetStateFunction } from "./reactive/state"; -export * from "./reactive/resourceState"; export * from "./reactive/mutable"; export { reconcile, produce } from "./reactive/stateModifiers"; diff --git a/packages/solid/src/reactive/resourceState.ts b/packages/solid/src/reactive/resourceState.ts deleted file mode 100644 index 28d5635c2..000000000 --- a/packages/solid/src/reactive/resourceState.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { batch, createSignal, Listener, createResource, hashValue, registerGraph } from "./signal"; - -import { - updatePath, - wrap, - unwrap, - isWrappable, - getDataNodes, - $RAW, - $NODE, - $PROXY, - StateNode, - SetStateFunction, - State, - setProperty, - proxyDescriptor -} from "./state"; - -function createResourceNode(v: any, name?: string) { - const [r, load] = createResource(v, { name }); - return [() => r(), (v: any) => load(() => v), load, () => r.loading]; -} - -export interface LoadStateFunction { - ( - v: { [P in keyof T]?: () => Promise | T[P] }, - reconcilerFn?: (v: Partial) => (state: State) => void - ): void; -} - -export function createResourceState( - state: T | State, - options: { name?: string } = {} -): [ - State, - LoadStateFunction, - SetStateFunction -] { - const loadingTraps: ProxyHandler = { - get(nodes, property: string | number) { - const node = - nodes[property] || - (nodes[property] = createResourceNode( - undefined, - options.name && `${options.name}:${property}` - )); - return node[3](); - }, - - set() { - return true; - }, - - deleteProperty() { - return true; - } - }; - - const resourceTraps: ProxyHandler = { - get(target, property, receiver) { - if (property === $RAW) return target; - if (property === $PROXY) return receiver; - if (property === "loading") return new Proxy(getDataNodes(target), loadingTraps); - const value = target[property as string | number]; - if (property === $NODE || property === "__proto__") return value; - - const wrappable = isWrappable(value); - if (Listener && (typeof value !== "function" || target.hasOwnProperty(property))) { - let nodes, node; - if (wrappable && (nodes = getDataNodes(value))) { - node = - nodes._ || - (nodes._ = "_SOLID_DEV_" - ? createSignal(undefined, false, { internal: true }) - : createSignal()); - node[0](); - } - nodes = getDataNodes(target); - node = - nodes[property] || - (nodes[property] = createResourceNode(value, `${options.name}:${property as string}`)); - node[0](); - } - return wrappable - ? wrap(value, "_SOLID_DEV_" && options.name && `${options.name}:${property as string}`) - : value; - }, - - set() { - return true; - }, - - deleteProperty() { - return true; - }, - getOwnPropertyDescriptor: proxyDescriptor - }; - - const unwrappedState = unwrap(state || {}, true), - wrappedState = wrap( - unwrappedState as any, - "_SOLID_DEV_" && ((options && options.name) || hashValue(unwrappedState)), - true, - resourceTraps - ); - if ("_SOLID_DEV_") { - const name = (options && options.name) || hashValue(unwrappedState); - registerGraph(name, { value: unwrappedState }); - } - function setState(...args: any[]): void { - batch(() => updatePath(unwrappedState, args)); - } - function loadState( - v: { [P in keyof T]: () => Promise | T[P] }, - r?: (v: Partial) => (state: State) => void - ) { - const nodes = getDataNodes(unwrappedState), - keys = Object.keys(v); - for (let i = 0; i < keys.length; i++) { - const k = keys[i], - node = - nodes[k] || (nodes[k] = createResourceNode(unwrappedState[k], `${options.name}:${k}`)), - resolver = (v?: T[keyof T]) => ( - r - ? setState(k, r(v as Partial)) - : setProperty(unwrappedState, k as string | number, v), - unwrappedState[k] - ); - node[2](() => { - const p = v[k](); - return typeof p === "object" && "then" in p ? p.then(resolver) : resolver(p); - }); - } - } - - return [wrappedState, loadState as LoadStateFunction, setState]; -} diff --git a/packages/solid/src/reactive/signal.ts b/packages/solid/src/reactive/signal.ts index c61bacec7..3c6d9c584 100644 --- a/packages/solid/src/reactive/signal.ts +++ b/packages/solid/src/reactive/signal.ts @@ -1,5 +1,6 @@ // Inspired by S.js[https://github.com/adamhaile/S] by Adam Haile import { requestCallback, Task } from "./scheduler"; +import { sharedConfig } from "../render/hydration"; import type { JSX } from "../jsx"; export const equalFn = (a: T, b: T) => a === b; @@ -25,6 +26,10 @@ let Transition: Transition | null = null; let ExecCount = 0; let rootCount = 0; +declare global { + var _$afterUpdate: () => void; +} + interface Signal { value?: T; observers: Computation[] | null; @@ -78,11 +83,7 @@ export function createRoot(fn: (dispose: () => void) => T, detachedOwner?: Ow ? UNOWNED : { owned: null, cleanups: null, context: null, owner, attached: !!detachedOwner }; - if ("_SOLID_DEV_" && owner) - root.name = - (owner as Computation).name + - "-r" + - rootCount++; + if ("_SOLID_DEV_" && owner) root.name = (owner as Computation).name + "-r" + rootCount++; Owner = root; Listener = null; let result: T; @@ -129,7 +130,6 @@ export function createRenderEffect(fn: (v?: T) => T, value?: T): void { } export function createEffect(fn: (v?: T) => T, value?: T): void { - if (globalThis._$HYDRATION && globalThis._$HYDRATION.asyncSSR) return; runEffects = runUserEffects; const c = createComputation(fn, value, false), s = SuspenseContext && lookup(Owner, SuspenseContext.id); @@ -391,6 +391,11 @@ export function useContext(context: Context): T { return lookup(Owner, context.id) || context.defaultValue; } +export function children(fn: () => any) { + const children = createMemo(fn); + return createMemo(() => resolveChildren(children())); +} + // Resource API type SuspenseContextType = { increment?: () => void; @@ -416,17 +421,20 @@ export interface Resource { } export function createResource( init?: T, - options: { name?: string; notStreamed?: boolean } = {} + options: { notStreamed?: boolean } = {} ): [Resource, (fn: () => Promise | T) => Promise] { const contexts = new Set(), - h = globalThis._$HYDRATION || {}, [s, set] = createSignal(init, true), [track, trigger] = createSignal(), [loading, setLoading] = createSignal(false, true); let err: any = null, pr: Promise | null = null, - ctx: any; + id: string | null = null; + + if (sharedConfig.context) { + id = `${sharedConfig.context!.id}${sharedConfig.context!.count++}`; + } function loadEnd(p: Promise | null, v: T, e?: any) { if (pr === p) { err = e; @@ -447,14 +455,11 @@ export function createResource( } function completeLoad(v: T) { batch(() => { - if (ctx) h.context = ctx; - if (h.asyncSSR && options.name) h.resources![options.name] = v; set(v); setLoading(false); for (let c of contexts.keys()) c.decrement!(); contexts.clear(); }); - if (ctx) h.context = ctx = undefined; } function read() { @@ -475,24 +480,23 @@ export function createResource( } return v; } - function load(fn: () => Promise | T) { + function load(fn: () => Promise | T, transform = (v: T, prev: T | undefined) => v) { err = null; let p: Promise | T; - const hydrating = h.context && !!h.context.registry; - if (hydrating) { - if (h.loadResource && !options.notStreamed) { - fn = h.loadResource; - } else if (options.name && h.resources && options.name in h.resources) { + if (sharedConfig.context) { + if (sharedConfig.loadResource && !options.notStreamed) { + fn = () => sharedConfig.loadResource!(id!); + } else if (sharedConfig.resources && id && id in sharedConfig.resources) { fn = () => { - const data = h.resources![options.name!]; - delete h.resources![options.name!]; + const data = sharedConfig.resources![id!]; + delete sharedConfig.resources![id!]; return data; }; } - } else if (h.asyncSSR && h.context) ctx = h.context; + } p = fn(); if (typeof p !== "object" || !("then" in p)) { - loadEnd(pr, p); + loadEnd(pr, transform(p, untrack(s))); return Promise.resolve(p); } pr = p; @@ -501,7 +505,7 @@ export function createResource( trigger(); }); return p.then( - v => loadEnd(p as Promise, v), + v => loadEnd(p as Promise, transform(v, untrack(s))), e => loadEnd(p as Promise, undefined as any, e) ); } @@ -697,45 +701,49 @@ function runUpdates(fn: () => void, init: boolean) { } catch (err) { handleError(err); } finally { - if (Updates) { - runQueue(Updates); - Updates = null; - } - if (wait) return; - if (Transition && Transition.running) { - if (Transition.promises.size) { - Transition.running = false; - Transition.effects.push.apply(Transition.effects, Effects); - Effects = null; - setTransPending(true); - return; - } - // finish transition - const sources = Transition.sources; - Transition = null; - batch(() => { - sources.forEach(v => { - v.value = v.tValue; - if ((v as Memo).owned) { - for (let i = 0, len = (v as Memo).owned!.length; i < len; i++) - cleanNode((v as Memo).owned![i]); - } - if ((v as Memo).tOwned) (v as Memo).owned = (v as Memo).tOwned!; - delete v.tValue; - delete (v as Memo).tOwned; - }); - setTransPending(false); - }); + completeUpdates(wait); + } +} + +function completeUpdates(wait: boolean) { + if (Updates) { + runQueue(Updates); + Updates = null; + } + if (wait) return; + if (Transition && Transition.running) { + if (Transition.promises.size) { + Transition.running = false; + Transition.effects.push.apply(Transition.effects, Effects!); + Effects = null; + setTransPending(true); + return; } - if (Effects.length) - batch(() => { - runEffects(Effects!); - Effects = null; + // finish transition + const sources = Transition.sources; + Transition = null; + batch(() => { + sources.forEach(v => { + v.value = v.tValue; + if ((v as Memo).owned) { + for (let i = 0, len = (v as Memo).owned!.length; i < len; i++) + cleanNode((v as Memo).owned![i]); + } + if ((v as Memo).tOwned) (v as Memo).owned = (v as Memo).tOwned!; + delete v.tValue; + delete (v as Memo).tOwned; }); - else { + setTransPending(false); + }); + } + if (Effects!.length) + batch(() => { + runEffects(Effects!); Effects = null; - if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate(); - } + }); + else { + Effects = null; + if ("_SOLID_DEV_") globalThis._$afterUpdate && globalThis._$afterUpdate(); } } @@ -838,7 +846,7 @@ function lookup(owner: Owner | null, key: symbol | string): any { ); } -function resolveChildren(children: any): any { +function resolveChildren(children: any): unknown { if (typeof children === "function") return resolveChildren(children()); if (Array.isArray(children)) { const results: any[] = []; @@ -855,8 +863,7 @@ function createProvider(id: symbol) { return function provider(props: { value: unknown; children: any }) { return (createMemo(() => { Owner!.context = { [id]: props.value }; - const children = createMemo(() => props.children); - return createMemo(() => resolveChildren(children())); + return children(() => props.children); }) as unknown) as JSX.Element; }; } diff --git a/packages/solid/src/reactive/stateModifiers.ts b/packages/solid/src/reactive/stateModifiers.ts index 06c788a8e..115747e6a 100644 --- a/packages/solid/src/reactive/stateModifiers.ts +++ b/packages/solid/src/reactive/stateModifiers.ts @@ -110,7 +110,7 @@ export function reconcile( const { merge, key = "id" } = options, v = unwrap(value); return state => { - if (!isWrappable(state)) return v as T extends NotWrappable ? T : State; + if (!isWrappable(state) || !isWrappable(v)) return v as T extends NotWrappable ? T : State; applyState(v, { state }, "state", merge, key); return state; }; diff --git a/packages/solid/src/render/Suspense.ts b/packages/solid/src/render/Suspense.ts index 4682f9f38..ba5824ef5 100644 --- a/packages/solid/src/render/Suspense.ts +++ b/packages/solid/src/render/Suspense.ts @@ -2,7 +2,6 @@ import { createComponent } from "./component"; import { createSignal, untrack, - createRenderEffect, createComputed, createContext, useContext, @@ -23,24 +22,6 @@ type SuspenseListContextType = { }; const SuspenseListContext = createContext(); -let trackSuspense = false; -export function awaitSuspense(fn: () => any) { - const SuspenseContext = getSuspenseContext(); - if (!trackSuspense) { - let count = 0; - const [active, trigger] = createSignal(false); - SuspenseContext.active = active; - SuspenseContext.increment = () => count++ === 0 && trigger(true); - SuspenseContext.decrement = () => --count <= 0 && trigger(false); - trackSuspense = true; - } - return () => - new Promise(resolve => { - const res = fn(); - createRenderEffect(() => !SuspenseContext.active!() && resolve(res)); - }); -} - export function SuspenseList(props: { children: JSX.Element; revealOrder: "forwards" | "backwards" | "together"; @@ -122,16 +103,10 @@ export function Suspense(props: { fallback: JSX.Element; children: JSX.Element } SuspenseContext = getSuspenseContext(), store = { increment: () => { - if (++counter === 1) { - setFallback(true); - trackSuspense && SuspenseContext.increment!(); - } + if (++counter === 1) setFallback(true); }, decrement: () => { - if (--counter === 0) { - setFallback(false); - trackSuspense && setTimeout(SuspenseContext.decrement!); - } + if (--counter === 0) setFallback(false); }, inFallback, effects: [], diff --git a/packages/solid/src/render/component.ts b/packages/solid/src/render/component.ts index 6b3879fc5..39e9534c4 100644 --- a/packages/solid/src/render/component.ts +++ b/packages/solid/src/render/component.ts @@ -1,4 +1,5 @@ import { untrack, createResource, createMemo } from "../reactive/signal"; +import { sharedConfig, nextHydrateContext, setHydrateContext } from "./hydration"; import type { JSX } from "../jsx" type PropsWithChildren

= P & { children?: JSX.Element }; @@ -18,6 +19,13 @@ export type ComponentProps< ? JSX.IntrinsicElements[T] : {}; export function createComponent(Comp: (props: T) => JSX.Element, props: T): JSX.Element { + if (sharedConfig.context) { + const c = sharedConfig.context; + setHydrateContext(nextHydrateContext()); + const r = untrack(() => Comp(props as T)); + setHydrateContext(c); + return r; + } return untrack(() => Comp(props as T)); } @@ -102,11 +110,9 @@ export function splitProps(props: T, ...keys: [(keyof T)[]]) { export function lazy>(fn: () => Promise<{ default: T }>): T { let p: Promise<{ default: T }>; return ((props: any) => { - const h = globalThis._$HYDRATION || {}, - hydrating = h.context && h.context.registry, - ctx = nextHydrateContext(), + const ctx = sharedConfig.context, [s, l] = createResource(undefined, { notStreamed: true }); - if (hydrating && h.resources) { + if (sharedConfig.context && sharedConfig.resources) { (p || (p = fn())).then(mod => { setHydrateContext(ctx); l(() => mod.default); @@ -119,7 +125,7 @@ export function lazy>(fn: () => Promise<{ default: T }> (Comp = s()) && untrack(() => { if (!ctx) return Comp!(props); - const c = h.context; + const c = sharedConfig.context; setHydrateContext(ctx); const r = Comp!(props); setHydrateContext(c); @@ -128,38 +134,3 @@ export function lazy>(fn: () => Promise<{ default: T }> ); }) as T; } - -function setHydrateContext(context?: HydrationContext): void { - globalThis._$HYDRATION.context = context; -} - -function nextHydrateContext(): HydrationContext | undefined { - const hydration = globalThis._$HYDRATION; - return hydration && hydration.context - ? { - id: `${hydration.context.id}.${hydration.context.count++}`, - count: 0, - registry: hydration.context.registry - } - : undefined; -} - -type HydrationContext = { - id: string; - count: number; - registry?: Map; -}; - -type GlobalHydration = { - context?: HydrationContext; - register?: (v: Promise) => void; - loadResource?: () => Promise; - resources?: { [key: string]: any }; - asyncSSR?: boolean; - streamSSR?: boolean; -}; - -declare global { - var _$HYDRATION: GlobalHydration; - var _$afterUpdate: () => void; -} diff --git a/packages/solid/src/render/flow.ts b/packages/solid/src/render/flow.ts index 968e0ad98..0844b795e 100644 --- a/packages/solid/src/render/flow.ts +++ b/packages/solid/src/render/flow.ts @@ -1,4 +1,4 @@ -import { createMemo, untrack, createSignal, onError } from "../reactive/signal"; +import { createMemo, untrack, createSignal, onError, children } from "../reactive/signal"; import { mapArray, indexArray } from "../reactive/array"; import type { JSX } from "../jsx"; @@ -48,23 +48,28 @@ export function Show(props: { } export function Switch(props: { fallback?: JSX.Element; children: JSX.Element }) { - let conditions = (props.children as unknown) as (MatchProps & { keyed: boolean })[]; - Array.isArray(conditions) || (conditions = [conditions]); - const evalConditions = createMemo<[number, unknown?]>( + let conditions = children(() => props.children) as () => (MatchProps & { + keyed: boolean; + })[]; + const evalConditions = createMemo< + [number, unknown?, (MatchProps & { keyed: boolean })?] + >( () => { - for (let i = 0; i < conditions.length; i++) { - const c = conditions[i].when; - if (c) return [i, conditions[i].keyed ? c : !!c]; + let conds = conditions(); + if (!Array.isArray(conds)) conds = [conds]; + for (let i = 0; i < conds.length; i++) { + const c = conds[i].when; + if (c) return [i, conds[i].keyed ? c : !!c, conds[i]]; } return [-1]; }, undefined, - (a: [number, unknown?], b: [number, unknown?]) => a && a[0] === b[0] && a[1] === b[1] + (a: [number, unknown?, unknown?], b: [number, unknown?, unknown?]) => a && a[0] === b[0] && a[1] === b[1] && a[2] === b[2] ); return createMemo(() => { - const [index, when] = evalConditions(); + const [index, when, cond] = evalConditions(); if (index < 0) return props.fallback; - const c = conditions[index].children; + const c = cond!.children; return typeof c === "function" && c.length ? untrack(() => c(when)) : (c as JSX.Element); }); } diff --git a/packages/solid/src/render/hydration.ts b/packages/solid/src/render/hydration.ts new file mode 100644 index 000000000..6fdf3a3a5 --- /dev/null +++ b/packages/solid/src/render/hydration.ts @@ -0,0 +1,23 @@ +type HydrationContext = { id: string, count: number }; + +type SharedConfig = { + context?: HydrationContext; + resources?: { [key: string]: any }; + loadResource?: (id: string) => Promise; + registry?: Map; +} + +export const sharedConfig: SharedConfig = {}; + +export function setHydrateContext(context?: HydrationContext): void { + sharedConfig.context = context; +} + +export function nextHydrateContext(): HydrationContext | undefined { + return sharedConfig.context + ? { + id: `${sharedConfig.context.id}${sharedConfig.context.count++}.`, + count: 0, + } + : undefined; +} \ No newline at end of file diff --git a/packages/solid/src/render/index.ts b/packages/solid/src/render/index.ts index 87c753772..fb9ee6f8a 100644 --- a/packages/solid/src/render/index.ts +++ b/packages/solid/src/render/index.ts @@ -1,3 +1,4 @@ export * from "./component"; export * from "./flow"; -export * from "./Suspense" \ No newline at end of file +export * from "./Suspense" +export { sharedConfig } from "./hydration"; \ No newline at end of file diff --git a/packages/solid/src/static/index.ts b/packages/solid/src/static/index.ts index 40c897b0f..e0fe2cda2 100644 --- a/packages/solid/src/static/index.ts +++ b/packages/solid/src/static/index.ts @@ -28,6 +28,7 @@ export { } from "./reactive"; export { + awaitSuspense, assignProps, splitProps, createComponent, @@ -40,13 +41,13 @@ export { Suspense, SuspenseList, createResource, - createResourceState, useTransition, - lazy + lazy, + sharedConfig } from "./rendering"; export type { State, SetStateFunction } from "./reactive"; -export type { Component, LoadStateFunction, Resource } from "./rendering"; +export type { Component, Resource } from "./rendering"; diff --git a/packages/solid/src/static/reactive.ts b/packages/solid/src/static/reactive.ts index 2d7c45115..72b9194d5 100644 --- a/packages/solid/src/static/reactive.ts +++ b/packages/solid/src/static/reactive.ts @@ -2,7 +2,7 @@ export const equalFn = (a: T, b: T) => a === b; const ERROR = Symbol("error"); const UNOWNED: Owner = { context: null, owner: null }; -let Owner: Owner | null = null; +export let Owner: Owner | null = null; interface Owner { owner: Owner | null; @@ -40,11 +40,7 @@ export function createComputed(fn: (v?: T) => T, value?: T): void { Owner = Owner.owner; } -export function createRenderEffect(fn: (v?: T) => T, value?: T): void { - Owner = { owner: Owner, context: null }; - fn(value); - Owner = Owner.owner; -} +export const createRenderEffect = createComputed; export function createEffect(fn: (v?: T) => T, value?: T): void {} @@ -162,7 +158,21 @@ export function getContextOwner() { return Owner; } -function lookup(owner: Owner | null, key: symbol | string): any { +export function children(fn: () => any) { + return resolveChildren(fn()) +} + +export function runWithOwner(o: Owner, fn: () => any) { + const prev = Owner; + Owner = o; + try { + return fn(); + } finally { + Owner = prev; + } +} + +export function lookup(owner: Owner | null, key: symbol | string): any { return ( owner && ((owner.context && owner.context[key]) || (owner.owner && lookup(owner.owner, key))) ); diff --git a/packages/solid/src/static/rendering.ts b/packages/solid/src/static/rendering.ts index 28656a398..03849a858 100644 --- a/packages/solid/src/static/rendering.ts +++ b/packages/solid/src/static/rendering.ts @@ -1,4 +1,4 @@ -import { State, SetStateFunction, updatePath } from "./reactive"; +import { createMemo, Owner, createContext, useContext, lookup, runWithOwner } from "./reactive"; import type { JSX } from "../jsx"; type PropsWithChildren

= P & { children?: JSX.Element }; @@ -18,10 +18,36 @@ function resolveSSRNode(node: any): string { return String(node); } +type SharedConfig = { + context?: HydrationContext; +}; +export const sharedConfig: SharedConfig = {}; + +function setHydrateContext(context?: HydrationContext): void { + sharedConfig.context = context; +} + +function nextHydrateContext(): HydrationContext | undefined { + return sharedConfig.context + ? { + ...sharedConfig.context, + id: `${sharedConfig.context.id}${sharedConfig.context.count++}.`, + count: 0 + } + : undefined; +} + export function createComponent( Comp: (props: T) => JSX.Element, props: PossiblyWrapped ): JSX.Element { + if (sharedConfig.context) { + const c = sharedConfig.context; + setHydrateContext(nextHydrateContext()); + const r = Comp(props as T); + setHydrateContext(c); + return r; + } return Comp(props as T); } @@ -186,60 +212,78 @@ export interface Resource { loading: boolean; } +type SuspenseContextType = { + completedCount: number; + resources: Set; + completed: () => void; +}; + +const SuspenseContext = createContext(); export function createResource( value?: T ): [Resource, (fn: () => Promise | T) => { then: Function }] { - const resource = () => value; - resource.loading = false; + const contexts = new Set(); + const id = sharedConfig.context!.id + sharedConfig.context!.count++; + const read = () => { + const resolved = sharedConfig.context!.async && id in sharedConfig.context!.resources; + if (sharedConfig.context!.async && !resolved) { + const ctx = useContext(SuspenseContext); + if (ctx) { + ctx.resources.add(id); + contexts.add(ctx); + } + } + return resolved ? sharedConfig.context!.resources[id] : value; + }; + read.loading = false; function load(fn: () => Promise | T) { - if (!globalThis._$HYDRATION.streamSSR) return { then() {} }; - resource.loading = true; + const ctx = sharedConfig.context!; + if (!ctx.async && !ctx.streaming) return { then() {} }; + if (ctx.resources && id in ctx.resources) { + value = ctx.resources[id]; + return { then() {} }; + } + read.loading = true; const p = fn(); if ("then" in p) { - globalThis._$HYDRATION.register && globalThis._$HYDRATION.register(p); + if (ctx.writeResource) { + ctx.writeResource(id, p); + return p; + } + p.then(res => { + ctx.resources[id] = res; + for (const c of contexts) { + if (++c.completedCount === c.resources.size) c.completed(); + } + contexts.clear(); + return res; + }); return p; } return Promise.resolve((value = p)); } - return [resource, load]; -} - -export interface LoadStateFunction { - ( - v: { [P in keyof T]: () => Promise | T[P] }, - reconcilerFn?: (v: Partial) => (state: State) => void - ): void; -} - -export function createResourceState( - state: T | State -): [ - State, - LoadStateFunction, - SetStateFunction -] { - (state as any).loading = {}; - function setState(...args: any[]): void { - updatePath(state, args); - } - function loadState( - v: { [P in keyof T]: () => Promise | T[P] }, - reconcilerFn?: (v: Partial) => (state: State) => void - ) { - const keys = Object.keys(v); - for (let i = 0; i < keys.length; i++) { - const k = keys[i] as keyof T, - [, l] = createResource(state[k]); - l(v[k]); - } - } - return [state as State, loadState, setState]; + return [read, load]; } export function lazy(fn: () => Promise<{ default: any }>): (props: any) => string { - let p: Promise<{ default: any }>; + let resolved: (props: any) => any; + const p = fn(); + const contexts = new Set(); + p.then(mod => (resolved = mod.default)); return (props: any) => { - (p || (p = fn())).then(mod => mod.default(props)); + const id = sharedConfig.context!.id + sharedConfig.context!.count++; + if (resolved) return resolved(props); + const ctx = useContext(SuspenseContext); + if (ctx) { + ctx.resources.add(id); + contexts.add(ctx); + } + p.then(() => { + for (const c of contexts) { + if (++c.completedCount === c.resources.size) c.completed(); + } + contexts.clear(); + }); return ""; }; } @@ -256,22 +300,19 @@ export function useTransition(): [() => boolean, (fn: () => any) => void] { type HydrationContext = { id: string; count: number; - registry?: Map; + writeResource?: (id: string, v: Promise) => void; + resources: Record; + suspense: Record; + async?: boolean; + streaming?: boolean; }; -type GlobalHydration = { - context?: HydrationContext; - register?: (v: Promise) => void; - loadResource?: () => Promise; - resources?: { [key: string]: any }; - asyncSSR?: boolean; - streamSSR?: boolean; +type SuspenseContextValue = { + completedCount: number; + resources: Set; + completed: () => void; }; -declare global { - var _$HYDRATION: GlobalHydration; -} - export function SuspenseList(props: { children: string; revealOrder: "forwards" | "backwards" | "together"; @@ -281,8 +322,73 @@ export function SuspenseList(props: { return props.children; } +const SUSPENSE_GLOBAL = Symbol("suspense-global"); export function Suspense(props: { fallback: string; children: string }) { + const ctx = sharedConfig.context!; // TODO: look at not always going to fallback - props.children; - return props.fallback; + if (ctx.streaming) createComponent(() => props.children, {}); + if (!ctx.async) return props.fallback; + + const id = ctx.id + ctx.count; + const done = ctx.async ? lookup(Owner, SUSPENSE_GLOBAL)(id) : () => {}; + const o = Owner!; + const value: SuspenseContextValue = ctx.suspense[id] || (ctx.suspense[id] = { + completedCount: 0, + resources: new Set(), + completed: () => { + const res = runSuspense(); + if (value.completedCount === value.resources.size) { + done(resolveSSRNode(res)); + } + } + }); + function runSuspense() { + setHydrateContext({ ...ctx, count: 0 }); + return runWithOwner(o, () => { + return createComponent(SuspenseContext.Provider, { + value, + get children() { + return props.children; + } + }); + }); + } + const res = runSuspense(); + if (value.completedCount === value.resources.size) { + done(); + return res; + } + return sharedConfig.context!.async ? { t: `<#${id}#>` } : props.fallback; +} + +const SUSPENSE_REPLACE = /<#([0-9\.]+)\#>/; +export function awaitSuspense(fn: () => any) { + return () => + new Promise(resolve => { + const registry = new Set(); + const cache: Record = Object.create(null); + const res = createMemo(() => { + Owner!.context = { [SUSPENSE_GLOBAL]: getCallback }; + return fn(); + }); + if (!registry.size) resolve(res()); + function getCallback(key: string) { + registry.add(key); + return (value: string) => { + if (value) cache[key] = value; + registry.delete(key); + if (!registry.size) + queueMicrotask(() => { + let source = resolveSSRNode(res()); + let final = ""; + let match: any; + while ((match = source.match(SUSPENSE_REPLACE))) { + final += source.substring(0, match.index); + source = cache[match[1]] + source.substring(match.index + match[0].length); + } + resolve(final + source); + }); + }; + } + }); } diff --git a/packages/solid/test/resource.spec.ts b/packages/solid/test/resource.spec.ts index e6a098072..fdf92abf2 100644 --- a/packages/solid/test/resource.spec.ts +++ b/packages/solid/test/resource.spec.ts @@ -2,8 +2,8 @@ import { createRoot, createSignal, createResource, - createResourceState, createComputed, + createState, createRenderEffect, onError, SetStateFunction, @@ -71,103 +71,6 @@ describe("Simulate a dynamic fetch", () => { }); }); -describe("Simulate a dynamic fetch with state", () => { - let resolve: (v: string) => void, - reject: (r: string) => void, - trigger: (v: number) => void, - load: ( - v: { [k: number]: () => Promise | string }, - r?: (v: any) => (state: any) => void - ) => void, - setUsers: SetStateFunction<{ [id: number]: string }>, - users: State<{ [id: number]: string; loading: { [id: number]: boolean } }>, - count = 0; - function fetcher(): Promise { - return new Promise((r, f) => { - resolve = r; - reject = f; - }); - } - - test("initial async resource", async done => { - createRoot(() => { - const [id, setId] = createSignal(1); - [users, load, setUsers] = createResourceState<{ [id: number]: string }>({ 6: "Rio" }); - trigger = setId; - createComputed(() => load({ [id()]: fetcher })); - createComputed(() => (users[5], count++)); - }); - expect(users[1]).toBeUndefined(); - expect(users.loading[1]).toBe(true); - resolve("John"); - await Promise.resolve(); - await Promise.resolve(); - expect(users[1]).toBe("John"); - expect(users.loading[1]).toBe(false); - done(); - }); - - test("test multiple loads", async done => { - trigger(2); - expect(users.loading[2]).toBe(true); - const resolve1 = resolve; - trigger(3); - const resolve2 = resolve; - resolve2("Jake"); - resolve1("Jo"); - await Promise.resolve(); - await Promise.resolve(); - expect(users[3]).toBe("Jake"); - expect(users.loading[3]).toBe(false); - done(); - }); - - test("promise rejection", async done => { - trigger(4); - expect(users.loading[4]).toBe(true); - reject("Because I said so"); - await Promise.resolve(); - await Promise.resolve(); - expect(users.loading[4]).toBe(false); - done(); - }); - - test("setState", () => { - setUsers(5, "Jordy"); - expect(users[5]).toBe("Jordy"); - expect(count).toBe(2); - }); - - test("test loading same value", () => { - load({ 5: () => "Jordy" }); - expect(users[5]).toBe("Jordy"); - expect(count).toBe(2); - }); - - test("custom reconciler", async done => { - const reconcile = (v: string) => (state: string) => `${state} ${v}`; - load({ 6: () => new Promise(r => r("Jerry")) }, reconcile); - await Promise.resolve(); - await Promise.resolve(); - expect(users[6]).toBe("Rio Jerry"); - done(); - }); - - test("setState tracked", () => { - createRoot(() => { - let runs = 0; - createComputed(() => { - users[7]; - runs++; - }); - expect(runs).toBe(1); - setUsers({ 7: "Jimbo" }); - expect(users[7]).toBe("Jimbo"); - expect(runs).toBe(2); - }); - }); -}); - describe("Simulate a dynamic fetch with state and reconcile", () => { interface User { firstName: string; @@ -177,11 +80,12 @@ describe("Simulate a dynamic fetch with state and reconcile", () => { }; } let resolve: (v: User) => void, + user: Resource, load: ( - v: { [k: number]: () => Promise | User }, - r?: (v: any) => (state: any) => void + v: () => Promise | User , + r?: (v: User, p: User) => User ) => void, - users: State<{ [id: number]: User; loading: { [id: number]: boolean } }>, + state: { user?: User, userLoading: boolean }, count = 0; function fetcher() { return new Promise(r => { @@ -194,28 +98,36 @@ describe("Simulate a dynamic fetch with state and reconcile", () => { test("initial async resource", async done => { createRoot(() => { - [users, load] = createResourceState<{ [id: number]: User }>({}); - createComputed(() => (users[0], count++)); + [user, load] = createResource(); + [state] = createState<{ user?: User, userLoading: boolean }>({ + get user() { + return user(); + }, + get userLoading() { + return user.loading; + } + }); + createComputed(() => (state.user, count++)); }); - load({ 0: fetcher }); - expect(users[0]).toBeUndefined(); - expect(users.loading[0]).toBe(true); + load(fetcher); + expect(state.user).toBeUndefined(); + expect(state.userLoading).toBe(true); resolve(data[0]); await Promise.resolve(); await Promise.resolve(); - expect(users[0]).toStrictEqual(data[0]); - expect(users.loading[0]).toBe(false); + expect(state.user).toStrictEqual(data[0]); + expect(state.userLoading).toBe(false); expect(count).toBe(2); - load({ 0: fetcher }, reconcile); - expect(users.loading[0]).toBe(true); + load(fetcher, (value, prev) => reconcile(value)(prev)); + expect(state.userLoading).toBe(true); resolve(data[1]); await Promise.resolve(); await Promise.resolve(); - expect(users[0]).toStrictEqual(data[0]); - expect(users[0].firstName).toBe("Joseph"); - expect(users[0].address).toStrictEqual(data[0].address); - expect(users.loading[0]).toBe(false); + expect(state.user).toStrictEqual(data[0]); + expect(state.user!.firstName).toBe("Joseph"); + expect(state.user!.address).toStrictEqual(data[0].address); + expect(state.userLoading).toBe(false); expect(count).toBe(2); done(); }); diff --git a/packages/solid/web/server-async/asyncSSR.js b/packages/solid/web/server-async/asyncSSR.js deleted file mode 100644 index 40ed8600a..000000000 --- a/packages/solid/web/server-async/asyncSSR.js +++ /dev/null @@ -1 +0,0 @@ -export * from "dom-expressions/src/asyncSSR"; \ No newline at end of file diff --git a/packages/solid/web/server-async/index.ts b/packages/solid/web/server-async/index.ts deleted file mode 100644 index 521670a36..000000000 --- a/packages/solid/web/server-async/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ssr, ssrSpread } from "./asyncSSR"; -import { createMemo, untrack, splitProps, Component, JSX } from "solid-js"; - -export * from "./asyncSSR"; - -export { - For, - Show, - Suspense, - SuspenseList, - Switch, - Match, - Index, - ErrorBoundary, - assignProps -} from "solid-js"; - -export const isServer = true; - -export function spread() {} - -export function Dynamic( - props: T & { children?: any; component?: Component | string | keyof JSX.IntrinsicElements } -): () => JSX.Element { - const [p, others] = splitProps(props, ["component"]); - return createMemo(() => { - const comp = p.component, - t = typeof comp; - - if (comp) { - if (t === "function") return untrack(() => (comp as Function)(others as any)); - else if (t === "string") { - const [local, sOthers] = splitProps(others, ["children"]); - return ssr([`<${comp} `, ">", ``], ssrSpread(sOthers), local.children || ""); - } - } - }); -} - -export function Portal(props: { mount?: Node; useShadow?: boolean; children: JSX.Element }) { - return ""; -} diff --git a/packages/solid/web/server/index.ts b/packages/solid/web/server/index.ts index 77971f571..09093aa6d 100644 --- a/packages/solid/web/server/index.ts +++ b/packages/solid/web/server/index.ts @@ -1,7 +1,7 @@ -import { ssr, ssrSpread } from "./syncSSR"; +import { ssr, ssrSpread } from "./server"; import { splitProps, Component, JSX } from "solid-js"; -export * from "./syncSSR"; +export * from "./server"; export { For, diff --git a/packages/solid/web/server/server.js b/packages/solid/web/server/server.js new file mode 100644 index 000000000..4ccb67de8 --- /dev/null +++ b/packages/solid/web/server/server.js @@ -0,0 +1 @@ +export * from "dom-expressions/src/server"; \ No newline at end of file diff --git a/packages/solid/web/server/syncSSR.js b/packages/solid/web/server/syncSSR.js deleted file mode 100644 index 85fa81f3d..000000000 --- a/packages/solid/web/server/syncSSR.js +++ /dev/null @@ -1 +0,0 @@ -export * from "dom-expressions/src/syncSSR"; \ No newline at end of file diff --git a/packages/solid/web/src/client.js b/packages/solid/web/src/client.js new file mode 100644 index 000000000..0260ac464 --- /dev/null +++ b/packages/solid/web/src/client.js @@ -0,0 +1 @@ +export * from "dom-expressions/src/client"; \ No newline at end of file diff --git a/packages/solid/web/src/core.ts b/packages/solid/web/src/core.ts index 736e449dc..2eb825c37 100644 --- a/packages/solid/web/src/core.ts +++ b/packages/solid/web/src/core.ts @@ -4,10 +4,11 @@ import { createRenderEffect, createMemo, createComponent, - getContextOwner + getContextOwner, + sharedConfig } from "solid-js"; // reactive injection for dom-expressions function memo(fn: () => any, equal: boolean) { return createMemo(fn, undefined, equal); } -export { getContextOwner as currentContext, createComponent, createRoot as root, createRenderEffect as effect, memo } +export { getContextOwner as currentContext, createComponent, createRoot as root, createRenderEffect as effect, memo, sharedConfig } diff --git a/packages/solid/web/src/index.ts b/packages/solid/web/src/index.ts index 7695c6b8d..0a0ce503b 100644 --- a/packages/solid/web/src/index.ts +++ b/packages/solid/web/src/index.ts @@ -1,105 +1,105 @@ -import { insert, spread, SVGElements } from "./runtime"; -import { - createSignal, - createMemo, - onCleanup, - untrack, - splitProps, - Component, - JSX, - createRoot -} from "solid-js"; - -export * from "./runtime"; - -export { - For, - Show, - Suspense, - SuspenseList, - Switch, - Match, - Index, - ErrorBoundary, - assignProps -} from "solid-js"; - -export * from "./server-mock"; -export const isServer = false; -const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; - -function createElement(tagName: string, isSVG = false): HTMLElement|SVGElement { - return isSVG ? document.createElementNS(SVG_NAMESPACE, tagName) : - document.createElement(tagName); -} - -export function Portal(props: { - mount?: Node; - useShadow?: boolean; - isSVG?: boolean; - children: JSX.Element; -}) { - const hydration = globalThis._$HYDRATION; - const { useShadow } = props, - marker = document.createTextNode(""), - mount = props.mount || document.body; - - // don't render when hydrating - function renderPortal() { - if (hydration && hydration.context) { - const [s, set] = createSignal(false); - queueMicrotask(() => set(true)); - return () => s() && props.children; - } else return () => props.children; - } - - if (mount instanceof HTMLHeadElement) { - const [clean, setClean] = createSignal(false); - const cleanup = () => setClean(true); - createRoot(dispose => insert(mount, () => (!clean() ? renderPortal()() : dispose()), null)); - onCleanup(() => { - if (hydration && hydration.context) queueMicrotask(cleanup); - else cleanup(); - }); - } else { - const container = createElement(props.isSVG ? "g" : "div", props.isSVG), - renderRoot = - useShadow && container.attachShadow ? container.attachShadow({ mode: "open" }) : container; - - Object.defineProperty(container, "host", { - get() { - return marker.parentNode; - } - }); - insert(renderRoot, renderPortal()); - mount.appendChild(container); - (props as any).ref && (props as any).ref(container); - onCleanup(() => mount.removeChild(container)); - } - return marker; -} - -type DynamicProps = T&{ - children?: any; - component?: Component|string|keyof JSX.IntrinsicElements; -}; - -export function Dynamic(props: DynamicProps): () => JSX.Element { - const [p, others] = splitProps(props, ["component"]); - return createMemo(() => { - const component = p.component as Function|string; - switch (typeof component) { - case "function": - return untrack(() => component(others)); - - case "string": - const isSvg = SVGElements.has(component); - const el = createElement(component, isSvg); - spread(el, others, isSvg); - return el; - - default: - break; - } - }); -} +import { insert, spread, SVGElements } from "./client"; +import { + createSignal, + createMemo, + onCleanup, + untrack, + splitProps, + Component, + JSX, + createRoot, + sharedConfig +} from "solid-js"; + +export * from "./client"; + +export { + For, + Show, + Suspense, + SuspenseList, + Switch, + Match, + Index, + ErrorBoundary, + assignProps +} from "solid-js"; + +export * from "./server-mock"; +export const isServer = false; +const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; + +function createElement(tagName: string, isSVG = false): HTMLElement|SVGElement { + return isSVG ? document.createElementNS(SVG_NAMESPACE, tagName) : + document.createElement(tagName); +} + +export function Portal(props: { + mount?: Node; + useShadow?: boolean; + isSVG?: boolean; + children: JSX.Element; +}) { + const { useShadow } = props, + marker = document.createTextNode(""), + mount = props.mount || document.body; + + // don't render when hydrating + function renderPortal() { + if (sharedConfig.context) { + const [s, set] = createSignal(false); + queueMicrotask(() => set(true)); + return () => s() && props.children; + } else return () => props.children; + } + + if (mount instanceof HTMLHeadElement) { + const [clean, setClean] = createSignal(false); + const cleanup = () => setClean(true); + createRoot(dispose => insert(mount, () => (!clean() ? renderPortal()() : dispose()), null)); + onCleanup(() => { + if (sharedConfig.context) queueMicrotask(cleanup); + else cleanup(); + }); + } else { + const container = createElement(props.isSVG ? "g" : "div", props.isSVG), + renderRoot = + useShadow && container.attachShadow ? container.attachShadow({ mode: "open" }) : container; + + Object.defineProperty(container, "host", { + get() { + return marker.parentNode; + } + }); + insert(renderRoot, renderPortal()); + mount.appendChild(container); + (props as any).ref && (props as any).ref(container); + onCleanup(() => mount.removeChild(container)); + } + return marker; +} + +type DynamicProps = T&{ + children?: any; + component?: Component|string|keyof JSX.IntrinsicElements; +}; + +export function Dynamic(props: DynamicProps): () => JSX.Element { + const [p, others] = splitProps(props, ["component"]); + return createMemo(() => { + const component = p.component as Function|string; + switch (typeof component) { + case "function": + return untrack(() => component(others)); + + case "string": + const isSvg = SVGElements.has(component); + const el = createElement(component, isSvg); + spread(el, others, isSvg); + return el; + + default: + break; + } + }); +} diff --git a/packages/solid/web/src/runtime.js b/packages/solid/web/src/runtime.js deleted file mode 100644 index 65e55b7d8..000000000 --- a/packages/solid/web/src/runtime.js +++ /dev/null @@ -1 +0,0 @@ -export * from "dom-expressions/src/runtime"; \ No newline at end of file diff --git a/packages/solid/web/test/element.spec.tsx b/packages/solid/web/test/element.spec.tsx index 9ec4d0089..4fd663d6e 100644 --- a/packages/solid/web/test/element.spec.tsx +++ b/packages/solid/web/test/element.spec.tsx @@ -13,11 +13,11 @@ describe("Basic element attributes", () => { }, onClick: () => console.log("clicked") }, - d = createRoot(() =>

) as HTMLDivElement & { __click: any }; + d = createRoot(() =>
) as HTMLDivElement & { $$click: any }; expect(div!).toBe(d); expect(d.id).toBe("main"); expect(d.title).toBe("main"); - expect(d.__click).toBeDefined(); + expect(d.$$click).toBeDefined(); expect(d.innerHTML).toBe("

Hi

"); }); diff --git a/packages/solid/web/test/suspense.spec.tsx b/packages/solid/web/test/suspense.spec.tsx index daff62154..cd29ca1ce 100644 --- a/packages/solid/web/test/suspense.spec.tsx +++ b/packages/solid/web/test/suspense.spec.tsx @@ -4,7 +4,6 @@ import { createSignal, createComputed, createResource, - createResourceState, useTransition } from "../../src"; import { render, Suspense, SuspenseList } from "../src"; @@ -64,36 +63,6 @@ describe("Testing Suspense", () => { }); }); -describe("Testing Suspense with State", () => { - let div = document.createElement("div"), - disposer: () => void; - const ChildComponent = (props: { name: string }) => { - const [state, load] = createResourceState({ greeting: "" }); - load({ greeting: () => new Promise(r => setTimeout(() => r("Hey"), 300)) }); - return <>{`${state.greeting}, ${props.name}`}; - }, - Component = () => ( - - - - - ); - - test("Create Suspense control flow", done => { - disposer = render(Component, div); - expect(div.innerHTML).toBe("Loading"); - setTimeout(() => { - expect(div.innerHTML).toBe("Hey, Jo!Hey, Jacob!"); - done(); - }, 400); - }); - - test("dispose", () => { - div.innerHTML = ""; - disposer(); - }); -}); - describe("SuspenseList", () => { const promiseFactory = (time: number, v: string) => { return () => diff --git a/packages/solid/web/test/switch.spec.tsx b/packages/solid/web/test/switch.spec.tsx index 4c909e843..877d528c1 100644 --- a/packages/solid/web/test/switch.spec.tsx +++ b/packages/solid/web/test/switch.spec.tsx @@ -1,6 +1,6 @@ /* @jsxImportSource solid-js */ -import { render, Switch, Match } from "../src"; -import { createRoot, createSignal } from "../../src"; +import { render, Switch, Match, For } from "../src"; +import { createRoot, createSignal, createState } from "../../src"; describe("Testing a single match switch control flow", () => { let div: HTMLDivElement, disposer: () => void; @@ -72,7 +72,7 @@ describe("Testing an only child Switch control flow", () => { setCount(4); expect(div.innerHTML).toBe("2"); expect(div.firstChild).toBe(c); - }) + }); test("dispose", () => disposer()); }); @@ -115,6 +115,45 @@ describe("Testing function handler Switch control flow", () => { test("dispose", () => disposer()); }); +describe("Testing a For in a Switch control flow", () => { + let div: HTMLDivElement, disposer: () => void; + const [state, setState] = createState({ + users: [ + { firstName: "Jerry", certified: false }, + { firstName: "Janice", certified: false } + ] + }); + const Component = () => ( +
+ + + {user => {user.firstName}} + + +
+ ); + + test("Create Switch control flow", () => { + createRoot(dispose => { + disposer = dispose; + ; + }); + + expect(div.innerHTML).toBe("fallback"); + }); + + test("Toggle Switch control flow", () => { + setState("users", 1, "certified", true); + expect(div.innerHTML).toBe("Janice"); + setState("users", 0, "certified", true); + expect(div.innerHTML).toBe("Jerry"); + setState("users", u => [{ firstName: "Gordy", certified: true }, ...u]); + expect(div.innerHTML).toBe("Gordy"); + }); + + test("dispose", () => disposer()); +}); + describe("Test top level switch control flow", () => { let div = document.createElement("div"), disposer: () => void; @@ -125,7 +164,7 @@ describe("Test top level switch control flow", () => { ); - test("Create when control flow", () => { + test("Create switch control flow", () => { disposer = render(Component, div); expect(div.innerHTML).toBe("fallback"); From 091c08646ce72d39966e1bf8d90824e188f44b83 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Thu, 28 Jan 2021 02:10:22 -0800 Subject: [PATCH 02/14] update packages --- package-lock.json | 20 ++++++++++---------- package.json | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1657d0088..c2603366e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "solid-js", - "version": "0.23.0", + "version": "0.24.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5875,9 +5875,9 @@ } }, "dom-expressions": { - "version": "0.25.0-beta.2", - "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.25.0-beta.2.tgz", - "integrity": "sha512-Pr+TLahPyt3oT88/abJT4Gtt0HYGFQne2wl6vSItx8uhu4jYI8cjw5UITFIsyJqZG170HC5+sSgenyvLhivYDQ==", + "version": "0.25.0-beta.3", + "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.25.0-beta.3.tgz", + "integrity": "sha512-ilMld9UgyHhA/ewc9ywdC2AOScjbIC3cXHgkoeQwCAUALBHDKPr+kaTNiy33bq0D76MX1KkRS/oOE/8/300uXQ==", "dev": true, "requires": { "babel-plugin-transform-rename-import": "^2.3.0" @@ -7754,9 +7754,9 @@ } }, "hyper-dom-expressions": { - "version": "0.25.0-beta.2", - "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.25.0-beta.2.tgz", - "integrity": "sha512-W3ys3bzAyFplOUhd06ujeWglliuIYMwfqzLrpaK+jvI/SjDPfOm/u0NnYL7EtroVACphJuOqRiq+pUzgDirsUQ==", + "version": "0.25.0-beta.3", + "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.25.0-beta.3.tgz", + "integrity": "sha512-pvzzP+ldS+8RojHqy5G3By8NzczPZlFBHxXmpaz8+qnUrCzFrMfGx3/vLzYec8+7Kf67LisMhahgg7aT4nJZCg==", "dev": true }, "iconv-lite": { @@ -10917,9 +10917,9 @@ "dev": true }, "lit-dom-expressions": { - "version": "0.25.0-beta.2", - "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.25.0-beta.2.tgz", - "integrity": "sha512-+kYztDMXOKaZUOiVYCvgyBVtavkIZ9nFfbh8QolNcIui2u2VA0W0b04FkU5zwrR2MfoQYgLLtWNrQm0LTIMOEA==", + "version": "0.25.0-beta.3", + "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.25.0-beta.3.tgz", + "integrity": "sha512-C0k8S7kGL0HOOCZvNZa8OiFrjZ6bjdLjJkYcSDU1epyWLPh+tktiiy22SGFEoqIUs7a0F1ZgZbMq0kSFziNegA==", "dev": true }, "load-json-file": { diff --git a/package.json b/package.json index 9e94fe17e..9d618d960 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,12 @@ "babel-jest": "^26.6.3", "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.2", "coveralls": "^3.1.0", - "dom-expressions": "0.25.0-beta.2", - "hyper-dom-expressions": "0.25.0-beta.2", + "dom-expressions": "0.25.0-beta.3", + "hyper-dom-expressions": "0.25.0-beta.3", "jest": "~26.6.3", "jest-ts-webcompat-resolver": "^1.0.0", "lerna": "^3.22.1", - "lit-dom-expressions": "0.25.0-beta.2", + "lit-dom-expressions": "0.25.0-beta.3", "ncp": "2.0.0", "npm-run-all": "^4.1.5", "rimraf": "^3.0.2", From 2673b0e222dce74bfc83f06b7943a049b302e0b7 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Thu, 28 Jan 2021 02:12:43 -0800 Subject: [PATCH 03/14] v0.24.0-beta.0 --- lerna.json | 2 +- packages/babel-preset-solid/package.json | 2 +- packages/react-solid-state/package-lock.json | 2 +- packages/react-solid-state/package.json | 4 ++-- packages/solid-element/package.json | 4 ++-- packages/solid-meta/package.json | 4 ++-- packages/solid-rx/package.json | 4 ++-- packages/solid-ssr/package.json | 8 ++++---- packages/solid-styled-components/package.json | 4 ++-- packages/solid-styled-jsx/package.json | 4 ++-- packages/solid/package.json | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index da9201583..ab0453eb1 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": [ "packages/*" ], - "version": "0.23.11" + "version": "0.24.0-beta.0" } diff --git a/packages/babel-preset-solid/package.json b/packages/babel-preset-solid/package.json index 6c220f9bf..95acf094d 100644 --- a/packages/babel-preset-solid/package.json +++ b/packages/babel-preset-solid/package.json @@ -1,6 +1,6 @@ { "name": "babel-preset-solid", - "version": "0.23.8", + "version": "0.24.0-beta.0", "description": "Babel preset to transform JSX for Solid.js", "author": "Ryan Carniato ", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/babel-preset-solid#readme", diff --git a/packages/react-solid-state/package-lock.json b/packages/react-solid-state/package-lock.json index fff7308d0..c33f3adea 100644 --- a/packages/react-solid-state/package-lock.json +++ b/packages/react-solid-state/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-solid-state", - "version": "0.23.11", + "version": "0.24.0-beta.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/react-solid-state/package.json b/packages/react-solid-state/package.json index b5b942ab8..9979f6127 100644 --- a/packages/react-solid-state/package.json +++ b/packages/react-solid-state/package.json @@ -1,7 +1,7 @@ { "name": "react-solid-state", "description": "Auto tracking state management for modern React", - "version": "0.23.11", + "version": "0.24.0-beta.0", "author": "Ryan Carniato", "license": "MIT", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/react-solid-state#readme", @@ -17,7 +17,7 @@ "test:coverage": "jest --coverage" }, "dependencies": { - "solid-js": "^0.23.11" + "solid-js": "^0.24.0-beta.0" }, "peerDependencies": { "react": "*", diff --git a/packages/solid-element/package.json b/packages/solid-element/package.json index 82cf9ebf8..409813afe 100644 --- a/packages/solid-element/package.json +++ b/packages/solid-element/package.json @@ -3,7 +3,7 @@ "description": "Webcomponents wrapper for Solid", "author": "Ryan Carniato", "license": "MIT", - "version": "0.23.11", + "version": "0.24.0-beta.0", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/solid-element#readme", "type": "module", "main": "dist/index.js", @@ -23,6 +23,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.23.11" + "solid-js": "^0.24.0-beta.0" } } diff --git a/packages/solid-meta/package.json b/packages/solid-meta/package.json index 5cf8484e8..f6913a8da 100644 --- a/packages/solid-meta/package.json +++ b/packages/solid-meta/package.json @@ -1,7 +1,7 @@ { "name": "solid-meta", "description": "Solid wrapper for Styled JSX", - "version": "0.23.11", + "version": "0.24.0-beta.0", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -25,6 +25,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.23.11" + "solid-js": "^0.24.0-beta.0" } } diff --git a/packages/solid-rx/package.json b/packages/solid-rx/package.json index dbfa39c15..fc7608893 100644 --- a/packages/solid-rx/package.json +++ b/packages/solid-rx/package.json @@ -1,6 +1,6 @@ { "name": "solid-rx", - "version": "0.23.11", + "version": "0.24.0-beta.0", "description": "Functionally reactive extensions for Solid.js", "author": "Ryan Carniato ", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/solid-rx#readme", @@ -29,6 +29,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.23.11" + "solid-js": "^0.24.0-beta.0" } } diff --git a/packages/solid-ssr/package.json b/packages/solid-ssr/package.json index d5014b448..f35191b65 100644 --- a/packages/solid-ssr/package.json +++ b/packages/solid-ssr/package.json @@ -1,7 +1,7 @@ { "name": "solid-ssr", "description": "Patches node to work with Solid's SSR", - "version": "0.23.11", + "version": "0.24.0-beta.0", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -24,10 +24,10 @@ "start:example:stream": "node examples/stream/lib/index.js" }, "devDependencies": { - "babel-preset-solid": "^0.23.8", + "babel-preset-solid": "^0.24.0-beta.0", "express": "^4.17.1", - "solid-js": "^0.23.11", - "solid-styled-components": "^0.23.11" + "solid-js": "^0.24.0-beta.0", + "solid-styled-components": "^0.24.0-beta.0" }, "peerDependencies": { "solid-js": "*" diff --git a/packages/solid-styled-components/package.json b/packages/solid-styled-components/package.json index fa385b429..108e21527 100644 --- a/packages/solid-styled-components/package.json +++ b/packages/solid-styled-components/package.json @@ -1,7 +1,7 @@ { "name": "solid-styled-components", "description": "Styled Components for Solid", - "version": "0.23.11", + "version": "0.24.0-beta.0", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -28,6 +28,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.23.11" + "solid-js": "^0.24.0-beta.0" } } diff --git a/packages/solid-styled-jsx/package.json b/packages/solid-styled-jsx/package.json index 4b91c7b51..830e73a2a 100644 --- a/packages/solid-styled-jsx/package.json +++ b/packages/solid-styled-jsx/package.json @@ -1,7 +1,7 @@ { "name": "solid-styled-jsx", "description": "Solid wrapper for Styled JSX", - "version": "0.23.11", + "version": "0.24.0-beta.0", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -23,6 +23,6 @@ "styled-jsx": "^3.2.5" }, "devDependencies": { - "solid-js": "^0.23.11" + "solid-js": "^0.24.0-beta.0" } } diff --git a/packages/solid/package.json b/packages/solid/package.json index 37d53faae..1bf4b98f1 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -1,7 +1,7 @@ { "name": "solid-js", "description": "A declarative JavaScript library for building user interfaces.", - "version": "0.23.11", + "version": "0.24.0-beta.0", "author": "Ryan Carniato", "license": "MIT", "homepage": "https://github.com/ryansolid/solid#readme", From e2336b73911b4de635e7fd6742109980586b3e3b Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Sat, 30 Jan 2021 02:37:33 -0800 Subject: [PATCH 04/14] fix non-loading resources, new SSR API, jsx publish convention --- package-lock.json | 453 +++++++++++++++++++- package.json | 9 +- packages/babel-preset-solid/package.json | 2 +- packages/solid-meta/babel.config.cjs | 16 +- packages/solid-meta/package.json | 11 +- packages/solid-ssr/examples/async/index.js | 12 +- packages/solid-ssr/examples/ssg/index.js | 6 +- packages/solid-ssr/examples/ssr/index.js | 11 +- packages/solid-ssr/examples/stream/index.js | 3 +- packages/solid/src/reactive/signal.ts | 15 +- packages/solid/src/render/hydration.ts | 18 +- packages/solid/src/static/rendering.ts | 116 ++--- packages/solid/web/src/core.ts | 5 +- packages/solid/web/src/server-mock.ts | 33 +- 14 files changed, 594 insertions(+), 116 deletions(-) diff --git a/package-lock.json b/package-lock.json index c2603366e..05386dd2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,48 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/cli": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.12.10.tgz", + "integrity": "sha512-+y4ZnePpvWs1fc/LhZRTHkTesbXkyBYuOB+5CyodZqrEuETXi3zOVfpAQIdgC3lXbHLTDG9dQosxR9BhvLKDLQ==", + "dev": true, + "requires": { + "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", + "chokidar": "^3.4.0", + "commander": "^4.0.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "lodash": "^4.17.19", + "make-dir": "^2.1.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", @@ -3529,6 +3571,185 @@ "glob-to-regexp": "^0.3.0" } }, + "@nicolo-ribaudo/chokidar-2": { + "version": "2.1.8-no-fsevents", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz", + "integrity": "sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -4277,6 +4498,13 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true, + "optional": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4431,9 +4659,9 @@ } }, "babel-plugin-jsx-dom-expressions": { - "version": "0.25.0-beta.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.25.0-beta.2.tgz", - "integrity": "sha512-CUtzPKOqHBWXvy1kbgpWsLO2XQVlCBvJd5STgW0H8KUjHKuldIzysO7v8pMAp16D4qc522McTbz/unnQJmzEnw==", + "version": "0.25.0-beta.4", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.25.0-beta.4.tgz", + "integrity": "sha512-VZxruAeJP6d2I+hOj6Bus7Z05ROIAP+HaZInNFjks8mI1lUh5aFnbLs7tGurxhfi7j6ccRqzKyVFrNMCPi/uyg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.10.4", @@ -4562,6 +4790,13 @@ "is-windows": "^1.0.0" } }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -4801,6 +5036,59 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "dependencies": { + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "optional": true + }, + "fsevents": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", + "dev": true, + "optional": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + } + } + }, "chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -5875,9 +6163,9 @@ } }, "dom-expressions": { - "version": "0.25.0-beta.3", - "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.25.0-beta.3.tgz", - "integrity": "sha512-ilMld9UgyHhA/ewc9ywdC2AOScjbIC3cXHgkoeQwCAUALBHDKPr+kaTNiy33bq0D76MX1KkRS/oOE/8/300uXQ==", + "version": "0.25.0-beta.4", + "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.25.0-beta.4.tgz", + "integrity": "sha512-KK2dO7OIlv8xJQHwikqF4fjEYw5t7RAH1nk/9xzBFoK2adKumOVTVc//eFGdPJGcnYSjUe4DpTQwdQn/WO9q6Q==", "dev": true, "requires": { "babel-plugin-transform-rename-import": "^2.3.0" @@ -6682,6 +6970,12 @@ "minipass": "^2.6.0" } }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", @@ -7754,9 +8048,9 @@ } }, "hyper-dom-expressions": { - "version": "0.25.0-beta.3", - "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.25.0-beta.3.tgz", - "integrity": "sha512-pvzzP+ldS+8RojHqy5G3By8NzczPZlFBHxXmpaz8+qnUrCzFrMfGx3/vLzYec8+7Kf67LisMhahgg7aT4nJZCg==", + "version": "0.25.0-beta.4", + "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.25.0-beta.4.tgz", + "integrity": "sha512-7ePYuJRLeQe22IPKdXwU5/GJMsriVL12w568+GX+14mbWQ139yTXf7bc10xbyxOe69GRV9XB/IEQ8l8F8CXhmw==", "dev": true }, "iconv-lite": { @@ -7990,6 +8284,16 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -10917,9 +11221,9 @@ "dev": true }, "lit-dom-expressions": { - "version": "0.25.0-beta.3", - "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.25.0-beta.3.tgz", - "integrity": "sha512-C0k8S7kGL0HOOCZvNZa8OiFrjZ6bjdLjJkYcSDU1epyWLPh+tktiiy22SGFEoqIUs7a0F1ZgZbMq0kSFziNegA==", + "version": "0.25.0-beta.4", + "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.25.0-beta.4.tgz", + "integrity": "sha512-E/5D0hPCwnxLQtpr4YFbRROuXUBr7pDaU7YaKrNMRpexnm5/6vjpHJqOV7SCdhl4n3OEkSN5mDw52yVcJmTViQ==", "dev": true }, "load-json-file": { @@ -12485,6 +12789,131 @@ "once": "^1.3.0" } }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, "redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", diff --git a/package.json b/package.json index 9d618d960..4e1ce791b 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "report:coverage": "lerna run report:coverage --parallel" }, "devDependencies": { + "@babel/cli": "^7.12.10", "@babel/core": "^7.12.9", "@babel/preset-env": "^7.12.7", "@babel/preset-typescript": "^7.12.7", @@ -32,14 +33,14 @@ "@rollup/plugin-replace": "2.3.3", "@types/jest": "^26.0.14", "babel-jest": "^26.6.3", - "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.2", + "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.4", "coveralls": "^3.1.0", - "dom-expressions": "0.25.0-beta.3", - "hyper-dom-expressions": "0.25.0-beta.3", + "dom-expressions": "0.25.0-beta.4", + "hyper-dom-expressions": "0.25.0-beta.4", "jest": "~26.6.3", "jest-ts-webcompat-resolver": "^1.0.0", "lerna": "^3.22.1", - "lit-dom-expressions": "0.25.0-beta.3", + "lit-dom-expressions": "0.25.0-beta.4", "ncp": "2.0.0", "npm-run-all": "^4.1.5", "rimraf": "^3.0.2", diff --git a/packages/babel-preset-solid/package.json b/packages/babel-preset-solid/package.json index 95acf094d..d855586f8 100644 --- a/packages/babel-preset-solid/package.json +++ b/packages/babel-preset-solid/package.json @@ -14,6 +14,6 @@ "test": "node test.js" }, "dependencies": { - "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.2" + "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.4" } } diff --git a/packages/solid-meta/babel.config.cjs b/packages/solid-meta/babel.config.cjs index 9ae68fbb6..808f8b52e 100644 --- a/packages/solid-meta/babel.config.cjs +++ b/packages/solid-meta/babel.config.cjs @@ -17,5 +17,19 @@ module.exports = { ] ] } - } + }, + presets: [ + "@babel/preset-typescript" + ], + plugins: [ + [ + "babel-plugin-jsx-dom-expressions", + { + moduleName: "solid-js/web", + contextToCustomElements: true, + wrapConditionals: true, + builtIns: ["For", "Show", "Switch", "Match", "Suspense", "SuspenseList", "Portal"] + } + ] + ] }; diff --git a/packages/solid-meta/package.json b/packages/solid-meta/package.json index f6913a8da..9a3dd3f2a 100644 --- a/packages/solid-meta/package.json +++ b/packages/solid-meta/package.json @@ -9,15 +9,20 @@ "url": "https://github.com/ryansolid/solid/blob/master/packages/solid-meta" }, "type": "module", - "main": "dist/index.jsx", - "module": "dist/index.jsx", + "main": "dist/index.js", + "exports": { + ".": { + "solid": "./dist/index.jsx", + "default": "./dist/index.js" + } + }, "types": "dist/index.d.ts", "files": [ "dist" ], "sideEffects": false, "scripts": { - "build": "tsc", + "build": "tsc && babel src/index.tsx --out-file dist/index.js", "test": "jest && npm run test:types", "test:types": "tsc --project tsconfig.test.json" }, diff --git a/packages/solid-ssr/examples/async/index.js b/packages/solid-ssr/examples/async/index.js index 9ca296641..d257b2087 100644 --- a/packages/solid-ssr/examples/async/index.js +++ b/packages/solid-ssr/examples/async/index.js @@ -1,7 +1,6 @@ import express from "express"; import path from "path"; -import { awaitSuspense } from "solid-js"; import { renderToStringAsync } from "solid-js/web"; import { extractCss } from "solid-styled-components"; import App from "../shared/src/components/App"; @@ -13,25 +12,26 @@ const lang = "en"; app.use(express.static(path.join(__dirname, "../public"))); app.get("*", async (req, res) => { - let html; + let result; try { - const string = await renderToStringAsync(awaitSuspense(() => )); + const { html, script } = await renderToStringAsync(() => ); const style = extractCss(); - html = ` + result = ` 🔥 Solid SSR 🔥 + ${script} ${style ? `` : ""} -
${string}
+
${html}
`; } catch (err) { console.error(err); } finally { - res.send(html); + res.send(result); } }); diff --git a/packages/solid-ssr/examples/ssg/index.js b/packages/solid-ssr/examples/ssg/index.js index c34a22c33..c295cdfa0 100644 --- a/packages/solid-ssr/examples/ssg/index.js +++ b/packages/solid-ssr/examples/ssg/index.js @@ -1,4 +1,3 @@ -import { awaitSuspense } from "solid-js"; import { renderToStringAsync } from "solid-js/web"; import { extractCss } from "solid-styled-components"; import App from "../shared/src/components/App"; @@ -6,7 +5,7 @@ const lang = "en"; // entry point for server render export default async req => { - const string = await renderToStringAsync(awaitSuspense(() => )); + const { html, script } = await renderToStringAsync(() => ); const style = extractCss(); return ` @@ -14,9 +13,10 @@ export default async req => { + ${script} ${style ? `` : ""} -
${string}
+
${html}
`; }; diff --git a/packages/solid-ssr/examples/ssr/index.js b/packages/solid-ssr/examples/ssr/index.js index 83c65f966..ce08ac751 100644 --- a/packages/solid-ssr/examples/ssr/index.js +++ b/packages/solid-ssr/examples/ssr/index.js @@ -11,23 +11,24 @@ const lang = "en"; app.use(express.static(path.join(__dirname, "../public"))); app.get("*", (req, res) => { - let html; + let result; try { - const string = renderToString(() => ); - html = ` + const { html, script } = renderToString(() => ); + result = ` 🔥 Solid SSR 🔥 + ${script} -
${string}
+
${html}
`; } catch (err) { console.error(err); } finally { - res.send(html); + res.send(result); } }); diff --git a/packages/solid-ssr/examples/stream/index.js b/packages/solid-ssr/examples/stream/index.js index bde640d77..06c9f3a76 100644 --- a/packages/solid-ssr/examples/stream/index.js +++ b/packages/solid-ssr/examples/stream/index.js @@ -11,7 +11,7 @@ const lang = "en"; app.use(express.static(path.join(__dirname, "../public"))); app.get("*", (req, res) => { - const stream = renderToNodeStream(() => ); + const { stream, script } = renderToNodeStream(() => ); const htmlStart = ` @@ -20,6 +20,7 @@ app.get("*", (req, res) => { + ${script}
`; diff --git a/packages/solid/src/reactive/signal.ts b/packages/solid/src/reactive/signal.ts index 3c6d9c584..fb9c8bc5e 100644 --- a/packages/solid/src/reactive/signal.ts +++ b/packages/solid/src/reactive/signal.ts @@ -16,7 +16,7 @@ const UNOWNED: Owner = { context: null, owner: null }; -const [transPending, setTransPending] = createSignal(false, true); +const [transPending, setTransPending] = /*@__PURE__*/createSignal(false, true); export var Owner: Owner | null = null; export var Listener: Computation | null = null; let Pending: Signal[] | null = null; @@ -484,17 +484,14 @@ export function createResource( err = null; let p: Promise | T; if (sharedConfig.context) { - if (sharedConfig.loadResource && !options.notStreamed) { - fn = () => sharedConfig.loadResource!(id!); + if (sharedConfig.context.loadResource && !options.notStreamed) { + p = sharedConfig.context.loadResource!(id!); } else if (sharedConfig.resources && id && id in sharedConfig.resources) { - fn = () => { - const data = sharedConfig.resources![id!]; - delete sharedConfig.resources![id!]; - return data; - }; + p = sharedConfig.resources![id!]; + delete sharedConfig.resources![id!]; } } - p = fn(); + if (!p!) p = fn(); if (typeof p !== "object" || !("then" in p)) { loadEnd(pr, transform(p, untrack(s))); return Promise.resolve(p); diff --git a/packages/solid/src/render/hydration.ts b/packages/solid/src/render/hydration.ts index 6fdf3a3a5..117c8f963 100644 --- a/packages/solid/src/render/hydration.ts +++ b/packages/solid/src/render/hydration.ts @@ -1,11 +1,10 @@ -type HydrationContext = { id: string, count: number }; +type HydrationContext = { id: string; count: number; loadResource?: (id: string) => Promise }; type SharedConfig = { context?: HydrationContext; resources?: { [key: string]: any }; - loadResource?: (id: string) => Promise; registry?: Map; -} +}; export const sharedConfig: SharedConfig = {}; @@ -14,10 +13,9 @@ export function setHydrateContext(context?: HydrationContext): void { } export function nextHydrateContext(): HydrationContext | undefined { - return sharedConfig.context - ? { - id: `${sharedConfig.context.id}${sharedConfig.context.count++}.`, - count: 0, - } - : undefined; -} \ No newline at end of file + return { + ...sharedConfig.context, + id: `${sharedConfig.context!.id}${sharedConfig.context!.count++}.`, + count: 0 + }; +} diff --git a/packages/solid/src/static/rendering.ts b/packages/solid/src/static/rendering.ts index 03849a858..34837d012 100644 --- a/packages/solid/src/static/rendering.ts +++ b/packages/solid/src/static/rendering.ts @@ -213,8 +213,7 @@ export interface Resource { } type SuspenseContextType = { - completedCount: number; - resources: Set; + resources: Map; completed: () => void; }; @@ -229,7 +228,7 @@ export function createResource( if (sharedConfig.context!.async && !resolved) { const ctx = useContext(SuspenseContext); if (ctx) { - ctx.resources.add(id); + if (!ctx.resources.has(id)) ctx.resources.set(id, read); contexts.add(ctx); } } @@ -251,11 +250,9 @@ export function createResource( return p; } p.then(res => { + read.loading = false; ctx.resources[id] = res; - for (const c of contexts) { - if (++c.completedCount === c.resources.size) c.completed(); - } - contexts.clear(); + notifySuspense(contexts); return res; }); return p; @@ -274,20 +271,33 @@ export function lazy(fn: () => Promise<{ default: any }>): (props: any) => strin const id = sharedConfig.context!.id + sharedConfig.context!.count++; if (resolved) return resolved(props); const ctx = useContext(SuspenseContext); + const track = { loading: true }; if (ctx) { - ctx.resources.add(id); + ctx.resources.set(id, track); contexts.add(ctx); } p.then(() => { - for (const c of contexts) { - if (++c.completedCount === c.resources.size) c.completed(); - } - contexts.clear(); + track.loading = false; + notifySuspense(contexts); }); return ""; }; } +function suspenseComplete(c: SuspenseContextType) { + for (let r of c.resources.values()) { + if (r.loading) return false; + } + return true; +} + +function notifySuspense(contexts: Set) { + for (const c of contexts) { + if (suspenseComplete(c)) c.completed(); + } + contexts.clear(); +} + export function useTransition(): [() => boolean, (fn: () => any) => void] { return [ () => false, @@ -302,17 +312,11 @@ type HydrationContext = { count: number; writeResource?: (id: string, v: Promise) => void; resources: Record; - suspense: Record; + suspense: Record; async?: boolean; streaming?: boolean; }; -type SuspenseContextValue = { - completedCount: number; - resources: Set; - completed: () => void; -}; - export function SuspenseList(props: { children: string; revealOrder: "forwards" | "backwards" | "together"; @@ -332,16 +336,17 @@ export function Suspense(props: { fallback: string; children: string }) { const id = ctx.id + ctx.count; const done = ctx.async ? lookup(Owner, SUSPENSE_GLOBAL)(id) : () => {}; const o = Owner!; - const value: SuspenseContextValue = ctx.suspense[id] || (ctx.suspense[id] = { - completedCount: 0, - resources: new Set(), - completed: () => { - const res = runSuspense(); - if (value.completedCount === value.resources.size) { - done(resolveSSRNode(res)); + const value: SuspenseContextType = + ctx.suspense[id] || + (ctx.suspense[id] = { + resources: new Map(), + completed: () => { + const res = runSuspense(); + if (suspenseComplete(value)) { + done(resolveSSRNode(res)); + } } - } - }); + }); function runSuspense() { setHydrateContext({ ...ctx, count: 0 }); return runWithOwner(o, () => { @@ -354,7 +359,7 @@ export function Suspense(props: { fallback: string; children: string }) { }); } const res = runSuspense(); - if (value.completedCount === value.resources.size) { + if (suspenseComplete(value)) { done(); return res; } @@ -363,32 +368,31 @@ export function Suspense(props: { fallback: string; children: string }) { const SUSPENSE_REPLACE = /<#([0-9\.]+)\#>/; export function awaitSuspense(fn: () => any) { - return () => - new Promise(resolve => { - const registry = new Set(); - const cache: Record = Object.create(null); - const res = createMemo(() => { - Owner!.context = { [SUSPENSE_GLOBAL]: getCallback }; - return fn(); - }); - if (!registry.size) resolve(res()); - function getCallback(key: string) { - registry.add(key); - return (value: string) => { - if (value) cache[key] = value; - registry.delete(key); - if (!registry.size) - queueMicrotask(() => { - let source = resolveSSRNode(res()); - let final = ""; - let match: any; - while ((match = source.match(SUSPENSE_REPLACE))) { - final += source.substring(0, match.index); - source = cache[match[1]] + source.substring(match.index + match[0].length); - } - resolve(final + source); - }); - }; - } + return new Promise(resolve => { + const registry = new Set(); + const cache: Record = Object.create(null); + const res = createMemo(() => { + Owner!.context = { [SUSPENSE_GLOBAL]: getCallback }; + return fn(); }); + if (!registry.size) resolve(res()); + function getCallback(key: string) { + registry.add(key); + return (value: string) => { + if (value) cache[key] = value; + registry.delete(key); + if (!registry.size) + queueMicrotask(() => { + let source = resolveSSRNode(res()); + let final = ""; + let match: any; + while ((match = source.match(SUSPENSE_REPLACE))) { + final += source.substring(0, match.index); + source = cache[match[1]] + source.substring(match.index + match[0].length); + } + resolve(final + source); + }); + }; + } + }); } diff --git a/packages/solid/web/src/core.ts b/packages/solid/web/src/core.ts index 2eb825c37..8732b94a4 100644 --- a/packages/solid/web/src/core.ts +++ b/packages/solid/web/src/core.ts @@ -5,10 +5,11 @@ import { createMemo, createComponent, getContextOwner, - sharedConfig + sharedConfig, + awaitSuspense } from "solid-js"; // reactive injection for dom-expressions function memo(fn: () => any, equal: boolean) { return createMemo(fn, undefined, equal); } -export { getContextOwner as currentContext, createComponent, createRoot as root, createRenderEffect as effect, memo, sharedConfig } +export { getContextOwner as currentContext, createComponent, createRoot as root, createRenderEffect as effect, memo, sharedConfig, awaitSuspense as asyncWrap } diff --git a/packages/solid/web/src/server-mock.ts b/packages/solid/web/src/server-mock.ts index 3b51dde13..b07f36bb6 100644 --- a/packages/solid/web/src/server-mock.ts +++ b/packages/solid/web/src/server-mock.ts @@ -1,12 +1,39 @@ //@ts-nocheck +type RenderToStringResults = { + html: string; + script: string +} + +type RenderToStreamResults = { + stream: T; + script: string; +} + export function renderToString( fn: () => T, options?: { + eventNames?: string[]; + } +): RenderToStringResults; +export function renderToStringAsync( + fn: () => T, + options?: { + eventNames?: string[]; timeoutMs?: number; } -): T extends Promise ? Promise : string {} -export function renderToNodeStream(fn: () => T): NodeJS.ReadableStream {} -export function renderToWebStream(fn: () => T): ReadableStream {} +): Promise; +export function renderToNodeStream( + fn: () => T, + options?: { + eventNames?: string[]; + } +): RenderToStreamResults; +export function renderToWebStream( + fn: () => T, + options?: { + eventNames?: string[]; + } +): RenderToStreamResults; export function ssr(template: string[] | string, ...nodes: any[]): { t: string } {} export function resolveSSRNode(node: any): string {} export function ssrClassList(value: { [k: string]: boolean }): string {} From a6c563c7e3cb92671ea2d5b29a839e40cedb058a Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Sat, 30 Jan 2021 02:38:15 -0800 Subject: [PATCH 05/14] v0.24.0-beta.1 --- lerna.json | 2 +- packages/babel-preset-solid/package.json | 2 +- packages/react-solid-state/package-lock.json | 2 +- packages/react-solid-state/package.json | 4 ++-- packages/solid-element/package.json | 4 ++-- packages/solid-meta/package.json | 4 ++-- packages/solid-rx/package.json | 4 ++-- packages/solid-ssr/package.json | 8 ++++---- packages/solid-styled-components/package.json | 4 ++-- packages/solid-styled-jsx/package.json | 4 ++-- packages/solid/package.json | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index ab0453eb1..602d1b879 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": [ "packages/*" ], - "version": "0.24.0-beta.0" + "version": "0.24.0-beta.1" } diff --git a/packages/babel-preset-solid/package.json b/packages/babel-preset-solid/package.json index d855586f8..306f70f05 100644 --- a/packages/babel-preset-solid/package.json +++ b/packages/babel-preset-solid/package.json @@ -1,6 +1,6 @@ { "name": "babel-preset-solid", - "version": "0.24.0-beta.0", + "version": "0.24.0-beta.1", "description": "Babel preset to transform JSX for Solid.js", "author": "Ryan Carniato ", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/babel-preset-solid#readme", diff --git a/packages/react-solid-state/package-lock.json b/packages/react-solid-state/package-lock.json index c33f3adea..32855a6e4 100644 --- a/packages/react-solid-state/package-lock.json +++ b/packages/react-solid-state/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-solid-state", - "version": "0.24.0-beta.0", + "version": "0.24.0-beta.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/react-solid-state/package.json b/packages/react-solid-state/package.json index 9979f6127..f4db7ac99 100644 --- a/packages/react-solid-state/package.json +++ b/packages/react-solid-state/package.json @@ -1,7 +1,7 @@ { "name": "react-solid-state", "description": "Auto tracking state management for modern React", - "version": "0.24.0-beta.0", + "version": "0.24.0-beta.1", "author": "Ryan Carniato", "license": "MIT", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/react-solid-state#readme", @@ -17,7 +17,7 @@ "test:coverage": "jest --coverage" }, "dependencies": { - "solid-js": "^0.24.0-beta.0" + "solid-js": "^0.24.0-beta.1" }, "peerDependencies": { "react": "*", diff --git a/packages/solid-element/package.json b/packages/solid-element/package.json index 409813afe..c8f9900bd 100644 --- a/packages/solid-element/package.json +++ b/packages/solid-element/package.json @@ -3,7 +3,7 @@ "description": "Webcomponents wrapper for Solid", "author": "Ryan Carniato", "license": "MIT", - "version": "0.24.0-beta.0", + "version": "0.24.0-beta.1", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/solid-element#readme", "type": "module", "main": "dist/index.js", @@ -23,6 +23,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.0" + "solid-js": "^0.24.0-beta.1" } } diff --git a/packages/solid-meta/package.json b/packages/solid-meta/package.json index 9a3dd3f2a..600862a0b 100644 --- a/packages/solid-meta/package.json +++ b/packages/solid-meta/package.json @@ -1,7 +1,7 @@ { "name": "solid-meta", "description": "Solid wrapper for Styled JSX", - "version": "0.24.0-beta.0", + "version": "0.24.0-beta.1", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -30,6 +30,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.0" + "solid-js": "^0.24.0-beta.1" } } diff --git a/packages/solid-rx/package.json b/packages/solid-rx/package.json index fc7608893..af6d276fe 100644 --- a/packages/solid-rx/package.json +++ b/packages/solid-rx/package.json @@ -1,6 +1,6 @@ { "name": "solid-rx", - "version": "0.24.0-beta.0", + "version": "0.24.0-beta.1", "description": "Functionally reactive extensions for Solid.js", "author": "Ryan Carniato ", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/solid-rx#readme", @@ -29,6 +29,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.0" + "solid-js": "^0.24.0-beta.1" } } diff --git a/packages/solid-ssr/package.json b/packages/solid-ssr/package.json index f35191b65..77906bf69 100644 --- a/packages/solid-ssr/package.json +++ b/packages/solid-ssr/package.json @@ -1,7 +1,7 @@ { "name": "solid-ssr", "description": "Patches node to work with Solid's SSR", - "version": "0.24.0-beta.0", + "version": "0.24.0-beta.1", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -24,10 +24,10 @@ "start:example:stream": "node examples/stream/lib/index.js" }, "devDependencies": { - "babel-preset-solid": "^0.24.0-beta.0", + "babel-preset-solid": "^0.24.0-beta.1", "express": "^4.17.1", - "solid-js": "^0.24.0-beta.0", - "solid-styled-components": "^0.24.0-beta.0" + "solid-js": "^0.24.0-beta.1", + "solid-styled-components": "^0.24.0-beta.1" }, "peerDependencies": { "solid-js": "*" diff --git a/packages/solid-styled-components/package.json b/packages/solid-styled-components/package.json index 108e21527..6a2dd1d4c 100644 --- a/packages/solid-styled-components/package.json +++ b/packages/solid-styled-components/package.json @@ -1,7 +1,7 @@ { "name": "solid-styled-components", "description": "Styled Components for Solid", - "version": "0.24.0-beta.0", + "version": "0.24.0-beta.1", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -28,6 +28,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.0" + "solid-js": "^0.24.0-beta.1" } } diff --git a/packages/solid-styled-jsx/package.json b/packages/solid-styled-jsx/package.json index 830e73a2a..c3d1ec577 100644 --- a/packages/solid-styled-jsx/package.json +++ b/packages/solid-styled-jsx/package.json @@ -1,7 +1,7 @@ { "name": "solid-styled-jsx", "description": "Solid wrapper for Styled JSX", - "version": "0.24.0-beta.0", + "version": "0.24.0-beta.1", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -23,6 +23,6 @@ "styled-jsx": "^3.2.5" }, "devDependencies": { - "solid-js": "^0.24.0-beta.0" + "solid-js": "^0.24.0-beta.1" } } diff --git a/packages/solid/package.json b/packages/solid/package.json index 1bf4b98f1..94c4e1385 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -1,7 +1,7 @@ { "name": "solid-js", "description": "A declarative JavaScript library for building user interfaces.", - "version": "0.24.0-beta.0", + "version": "0.24.0-beta.1", "author": "Ryan Carniato", "license": "MIT", "homepage": "https://github.com/ryansolid/solid#readme", From 81be9ace4fd1dcac2dc8212c6654e91eab1dffd9 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Sat, 30 Jan 2021 21:55:32 -0800 Subject: [PATCH 06/14] fix #322 state spreads --- packages/solid/src/reactive/state.ts | 2 +- packages/solid/web/test/dynamic.spec.tsx | 46 +++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/solid/src/reactive/state.ts b/packages/solid/src/reactive/state.ts index 36c0dfadf..fdd7e8d32 100644 --- a/packages/solid/src/reactive/state.ts +++ b/packages/solid/src/reactive/state.ts @@ -114,7 +114,7 @@ export function proxyDescriptor(target: StateNode, property: string | number | s return desc; delete desc.value; delete desc.writable; - desc.get = () => target[property as string | number]; + desc.get = () => target[$PROXY][property as string | number]; return desc; } diff --git a/packages/solid/web/test/dynamic.spec.tsx b/packages/solid/web/test/dynamic.spec.tsx index 4256c4824..24d77a430 100644 --- a/packages/solid/web/test/dynamic.spec.tsx +++ b/packages/solid/web/test/dynamic.spec.tsx @@ -1,5 +1,5 @@ /* @jsxImportSource solid-js */ -import { createRoot, createSignal, Component, JSX } from "../../src"; +import { createRoot, createSignal, createState, Component, JSX } from "../../src"; import { Dynamic } from "../src"; describe("Testing Dynamic control flow", () => { @@ -49,3 +49,47 @@ describe("Testing Dynamic control flow", () => { expect(div.querySelector('path')).toBeInstanceOf(SVGElement); }); }); + + +describe("Testing Dynamic with state spread", () => { + let div: HTMLDivElement, disposer: () => void; + + interface DynamicProps { + title: string; + } + const [comp, setComp] = createSignal | keyof JSX.IntrinsicElements>(), + [state, setState] = createState({ + title: "Smith" + }); + const Component = () => ( +
+ +
+ ), + CompA: Component = props =>
Hi {props.title}
, + CompB: Component = props => Yo {props.title}; + + beforeEach(() => { + createRoot(dispose => { + disposer = dispose; + ; + }); + }) + + afterEach(() => disposer()); + + test("Toggle Dynamic control flow", () => { + expect(div.innerHTML).toBe(""); + setComp(CompA); + expect(div.innerHTML).toBe("
Hi Smith
"); + setState("title", "Smithers"); + expect(div.innerHTML).toBe("
Hi Smithers
"); + setComp(CompB); + expect(div.innerHTML).toBe("Yo Smithers"); + setComp("h1"); + expect(div.innerHTML).toBe(`

`); + setState("title", "Sunny") + expect(div.innerHTML).toBe(`

`); + expect(div.querySelector('h1')).toBeInstanceOf(HTMLElement); + }); +}); From 68cbe4f5c3c106de6ebd00bb4d001103a2e9ee8a Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Tue, 2 Feb 2021 00:17:55 -0800 Subject: [PATCH 07/14] resourceAPI refactor, better props --- documentation/api.md | 8 +- documentation/components.md | 6 +- documentation/suspense.md | 33 ++--- package-lock.json | 24 ++-- package.json | 8 +- packages/babel-preset-solid/package.json | 2 +- packages/solid-element/src/index.ts | 4 +- packages/solid-meta/src/index.tsx | 12 +- .../shared/src/components/Profile/index.js | 39 +++--- packages/solid-styled-components/src/index.js | 8 +- packages/solid/bench/s-mod.js | 2 +- packages/solid/dom/package.json | 8 -- packages/solid/package.json | 4 - packages/solid/src/index.ts | 2 +- packages/solid/src/reactive/mutable.ts | 2 - packages/solid/src/reactive/signal.ts | 52 +++++--- packages/solid/src/reactive/state.ts | 44 +++---- packages/solid/src/render/component.ts | 113 +++++++++++++----- packages/solid/src/static/index.ts | 4 +- packages/solid/src/static/reactive.ts | 2 +- packages/solid/src/static/rendering.ts | 68 +++++++---- packages/solid/test/component.spec.ts | 10 +- packages/solid/test/dev.spec.ts | 6 +- packages/solid/test/resource.spec.ts | 56 ++++----- packages/solid/web/server/index.ts | 2 +- packages/solid/web/src/core.ts | 4 +- packages/solid/web/src/index.ts | 2 +- packages/solid/web/test/suspense.spec.tsx | 28 ++--- tsconfig.test.json | 2 +- 29 files changed, 297 insertions(+), 258 deletions(-) delete mode 100644 packages/solid/dom/package.json diff --git a/documentation/api.md b/documentation/api.md index af041914d..c36e1ee8e 100644 --- a/documentation/api.md +++ b/documentation/api.md @@ -76,9 +76,9 @@ Creates a new computation that automatically tracks dependencies and runs during Creates a conditional signal that only notifies subscribers when entering or exiting their key matching the value. Useful for delegated selection state. -### `createResource(initialValue, options: { name }): [getValueFn, loadFn]` +### `createResource(key, fetcher, initialValue): getValueFn` -Creates a new resource signal that can hold an async resource. Resources when read while loading trigger Suspense. The `loadFn` takes a Promise whose resolved value is set in the resource. +Creates a new resource signal that can hold an async resource. Resources when read while loading trigger Suspense. The `fetcher` is a function that accepts key and returns a Promise whose resolved value is set in the resource. ### `lazy(() => ): Component` @@ -88,9 +88,9 @@ Used to lazy load components to allow for things like code splitting and Suspens Used to batch async updates deferring commit until all async processes are complete. -### `assignProps(target, ...sources): target` +### `mergeProps(...sources): target` -A reactive object `assign` method. Useful for setting default props for components in case caller doesn't provide them. Or cloning the props object including reactive properties. +A reactive object `merge` method. Useful for setting default props for components in case caller doesn't provide them. Or cloning the props object including reactive properties. ### `splitProps(props, ...keyArrays): [...splitProps]` diff --git a/documentation/components.md b/documentation/components.md index 91d7ca05f..05b763023 100644 --- a/documentation/components.md +++ b/documentation/components.md @@ -84,13 +84,13 @@ To help maintain reactivity Solid has a couple prop helpers: ```jsx // default props -props = assignProps({}, { name: "Smith" }, props); +props = mergeProps({ name: "Smith" }, props); // clone props -const newProps = assignProps({}, props); +const newProps = mergeProps(props); // merge props -assignProps(props, otherProps); +props = mergeProps(props, otherProps); // split props into multiple props objects const [local, others] = splitProps(props, ["className"]) diff --git a/documentation/suspense.md b/documentation/suspense.md index d5365cf1a..522be1430 100644 --- a/documentation/suspense.md +++ b/documentation/suspense.md @@ -170,12 +170,10 @@ Solid ships with a primitive to handle async data loading, `createResource`. It import { createResource } from "solid-js"; // notice returns a function that returns a promise -const fetchUser = id => - () => fetch(`https://swapi.co/api/people/${id}/`).then(r => r.json()); +const fetchJSON = query => fetch(query).then(r => r.json()); export default const UserPanel = props => { - let [user, load] = createResource(); - load(fetchUser(props.userId))); + let [user] = createResource(() => `https://swapi.co/api/people/${props.userId}/`, fetchJSON); return
@@ -192,32 +190,17 @@ export default const UserPanel = props => {
} ``` + This example handles the different loading states. However, you can expand this example to use Suspense instead by wrapping with the `Suspense` Component. -`load` also supports a second argument to transform the data before you store it. This can be useful for creating resource caches. +Resource also returns actions that can be performed, like `refetch` and `mutate`. And if absent using `fetch` to return JSON is the default fetcher. ```js -// fetch all the posts -loadPosts(fetchPosts); - -// fetch a single post and add it to the resource -loadPosts(fetchPost(id), (post, prev) => { ...prev, [id]: post }); -``` - -Or even if you store it as state be able to deep data diff: -```js -const [user, loadUser] = createResource(); -const [state] = createState({ - get user() { return user() } -}) - -// get the user initially -loadUser(fetchUser(id)) - -// refresh user info -loadUser(fetchUser(id), (user, prevUser) => reconcile(user)(prevUser)); +let [user, { refetch, mutate }] = createResource( + () => `https://swapi.co/api/people/${props.userId}/` +); ``` -Since it is in state and being reconciled only the properties that have updated on the server will notify change. +The first argument can be a unique string key or dynamically generated one that is automatically tracked in a function. > **For React Users:** At the time of writing this React has not completely settled how their Data Fetching API will look. Solid ships with this feature today, and it might differ from what React ultimately lands on. diff --git a/package-lock.json b/package-lock.json index 05386dd2e..eb16b9024 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4659,9 +4659,9 @@ } }, "babel-plugin-jsx-dom-expressions": { - "version": "0.25.0-beta.4", - "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.25.0-beta.4.tgz", - "integrity": "sha512-VZxruAeJP6d2I+hOj6Bus7Z05ROIAP+HaZInNFjks8mI1lUh5aFnbLs7tGurxhfi7j6ccRqzKyVFrNMCPi/uyg==", + "version": "0.25.0-beta.5", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.25.0-beta.5.tgz", + "integrity": "sha512-hlQFu28l0RhSbgN3GWZqdCal3J7JFGpmQcnmLUZmL0+biEhxUzverx/xtx9uQg7psVlK+XllgmKaqhZ1TJGWGA==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.10.4", @@ -6163,9 +6163,9 @@ } }, "dom-expressions": { - "version": "0.25.0-beta.4", - "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.25.0-beta.4.tgz", - "integrity": "sha512-KK2dO7OIlv8xJQHwikqF4fjEYw5t7RAH1nk/9xzBFoK2adKumOVTVc//eFGdPJGcnYSjUe4DpTQwdQn/WO9q6Q==", + "version": "0.25.0-beta.6", + "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.25.0-beta.6.tgz", + "integrity": "sha512-uFZcKJi5ga8ThL8HANs4okMJo4Y95UgTUmaGJWjWv2HpF3V7xGFkJzUwi0z6tgZY5SbuJwJ4uc8onOyYCzxCFQ==", "dev": true, "requires": { "babel-plugin-transform-rename-import": "^2.3.0" @@ -8048,9 +8048,9 @@ } }, "hyper-dom-expressions": { - "version": "0.25.0-beta.4", - "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.25.0-beta.4.tgz", - "integrity": "sha512-7ePYuJRLeQe22IPKdXwU5/GJMsriVL12w568+GX+14mbWQ139yTXf7bc10xbyxOe69GRV9XB/IEQ8l8F8CXhmw==", + "version": "0.25.0-beta.6", + "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.25.0-beta.6.tgz", + "integrity": "sha512-kUbzhoBs6MpUUdjTtTLciAMprp+9Dh9/qjlfYEidZxsE7OM59PkoS6cU1X2WvfXqCRObEzIj7nMzSRLnioRN6A==", "dev": true }, "iconv-lite": { @@ -11221,9 +11221,9 @@ "dev": true }, "lit-dom-expressions": { - "version": "0.25.0-beta.4", - "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.25.0-beta.4.tgz", - "integrity": "sha512-E/5D0hPCwnxLQtpr4YFbRROuXUBr7pDaU7YaKrNMRpexnm5/6vjpHJqOV7SCdhl4n3OEkSN5mDw52yVcJmTViQ==", + "version": "0.25.0-beta.6", + "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.25.0-beta.6.tgz", + "integrity": "sha512-BjkvW5ycpuXW7b6W0835YzomqJSXIiN5Xo5H4wz1E71GA0Ay9s6w4s4syKWUqQx2LXaAP/QvTYJSJv59dtPNnw==", "dev": true }, "load-json-file": { diff --git a/package.json b/package.json index 4e1ce791b..8a3a5b2be 100644 --- a/package.json +++ b/package.json @@ -33,14 +33,14 @@ "@rollup/plugin-replace": "2.3.3", "@types/jest": "^26.0.14", "babel-jest": "^26.6.3", - "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.4", + "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.5", "coveralls": "^3.1.0", - "dom-expressions": "0.25.0-beta.4", - "hyper-dom-expressions": "0.25.0-beta.4", + "dom-expressions": "0.25.0-beta.6", + "hyper-dom-expressions": "0.25.0-beta.6", "jest": "~26.6.3", "jest-ts-webcompat-resolver": "^1.0.0", "lerna": "^3.22.1", - "lit-dom-expressions": "0.25.0-beta.4", + "lit-dom-expressions": "0.25.0-beta.6", "ncp": "2.0.0", "npm-run-all": "^4.1.5", "rimraf": "^3.0.2", diff --git a/packages/babel-preset-solid/package.json b/packages/babel-preset-solid/package.json index 306f70f05..ce45f9624 100644 --- a/packages/babel-preset-solid/package.json +++ b/packages/babel-preset-solid/package.json @@ -14,6 +14,6 @@ "test": "node test.js" }, "dependencies": { - "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.4" + "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.5" } } diff --git a/packages/solid-element/src/index.ts b/packages/solid-element/src/index.ts index 4ad5b18cc..db8fd2828 100644 --- a/packages/solid-element/src/index.ts +++ b/packages/solid-element/src/index.ts @@ -27,7 +27,7 @@ function createProps(raw: T) { function withSolid(ComponentType: ComponentType): ComponentType { return (rawProps: T, options: ComponentOptions) => { const { element } = options as { - element: ICustomElement & { _context?: any }; + element: ICustomElement & { _$owner?: any }; }; return createRoot((dispose: Function) => { const props = createProps(rawProps); @@ -40,7 +40,7 @@ function withSolid(ComponentType: ComponentType): ComponentType { const comp = (ComponentType as FunctionComponent)(props as T, options); return insert(element.renderRoot, comp); - }, (element.assignedSlot && element.assignedSlot._context) || element._context); + }, (element.assignedSlot && element.assignedSlot._$owner) || element._$owner); }; } diff --git a/packages/solid-meta/src/index.tsx b/packages/solid-meta/src/index.tsx index ed4db0bc3..a7bd5d62d 100644 --- a/packages/solid-meta/src/index.tsx +++ b/packages/solid-meta/src/index.tsx @@ -8,7 +8,7 @@ import { useContext, Component, JSX, - assignProps + mergeProps } from "solid-js"; import { isServer, Show, Portal, Dynamic } from "solid-js/web"; @@ -136,16 +136,16 @@ export function renderTags(tags: Array) { } export const Title: Component> = props => - MetaTag(assignProps({ tag: "title" }, props)); + MetaTag(mergeProps({ tag: "title" }, props)); export const Style: Component> = props => - MetaTag(assignProps({ tag: "style" }, props)); + MetaTag(mergeProps({ tag: "style" }, props)); export const Meta: Component> = props => - MetaTag(assignProps({ tag: "meta" }, props)); + MetaTag(mergeProps({ tag: "meta" }, props)); export const Link: Component> = props => - MetaTag(assignProps({ tag: "link" }, props)); + MetaTag(mergeProps({ tag: "link" }, props)); export const Base: Component> = props => - MetaTag(assignProps({ tag: "base" }, props)); + MetaTag(mergeProps({ tag: "base" }, props)); diff --git a/packages/solid-ssr/examples/shared/src/components/Profile/index.js b/packages/solid-ssr/examples/shared/src/components/Profile/index.js index 5ecc9c480..e0bf97940 100644 --- a/packages/solid-ssr/examples/shared/src/components/Profile/index.js +++ b/packages/solid-ssr/examples/shared/src/components/Profile/index.js @@ -3,25 +3,28 @@ const Profile = lazy(() => import("./Profile")); // this component lazy loads data and code in parallel export default () => { - const [user, loadUser] = createResource(), - [info, loadInfo] = createResource([]); - loadUser( - () => + const [user] = createResource("user", () => { // simulate data loading - new Promise(res => { + console.log("LOAD USER"); + return new Promise(res => { setTimeout(() => res({ firstName: "Jon", lastName: "Snow" }), 400); - }) - ); - loadInfo( - () => - // simulate cascading data loading - new Promise(res => { - setTimeout( - () => - res(["Something Interesting", "Something else you might care about", "Or maybe not"]), - 800 - ); - }) - ); + }); + }), + [info] = createResource( + () => user() && "userinfo", + () => { + // simulate cascading data loading + console.log("LOAD INFO"); + return new Promise(res => { + setTimeout( + () => + res(["Something Interesting", "Something else you might care about", "Or maybe not"]), + 400 + ); + }); + }, + [] + ); + return ; }; diff --git a/packages/solid-styled-components/src/index.js b/packages/solid-styled-components/src/index.js index d7e583cfb..a24344e26 100644 --- a/packages/solid-styled-components/src/index.js +++ b/packages/solid-styled-components/src/index.js @@ -1,5 +1,5 @@ import { css, setup as gooberSetup } from "goober"; -import { assignProps, splitProps, createContext, useContext, createComponent } from "solid-js"; +import { mergeProps, splitProps, createContext, useContext, createComponent } from "solid-js"; import { spread, ssr, ssrSpread, isServer } from "solid-js/web"; export { css, glob, extractCss, keyframes } from "goober"; export function setup(prefixer) { @@ -21,16 +21,16 @@ export function styled(tag) { return (...args) => { return props => { const theme = useContext(ThemeContext); - const clone = assignProps({}, props, { + const clone = mergeProps(props, { get className() { const pClassName = props.className, append = "className" in props && /^go[0-9]+/.test(pClassName); // Call `css` with the append flag and pass the props let className = css.apply({ target: this.target, o: append, p: clone }, args); return [pClassName, className].filter(Boolean).join(" "); - } + }, + theme }); - theme && (clone.theme = theme); const [local, newProps] = splitProps(clone, ["as"]); const createTag = local.as || tag; let el; diff --git a/packages/solid/bench/s-mod.js b/packages/solid/bench/s-mod.js index 35ebe9a30..e908f94d1 100644 --- a/packages/solid/bench/s-mod.js +++ b/packages/solid/bench/s-mod.js @@ -138,7 +138,7 @@ function createContext(defaultValue) { function useContext(context) { return lookup(Owner, context.id) || context.defaultValue; } -function getContextOwner() { +function getOwner() { return Owner; } // Internal implementation diff --git a/packages/solid/dom/package.json b/packages/solid/dom/package.json deleted file mode 100644 index 646acd807..000000000 --- a/packages/solid/dom/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "solid-js/dom", - "main": "../web/dist/web.cjs", - "module": "../web/dist/web.js", - "types": "../web/types/index.d.ts", - "type": "module", - "sideEffects": false -} \ No newline at end of file diff --git a/packages/solid/package.json b/packages/solid/package.json index 94c4e1385..9e19f45fb 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -63,10 +63,6 @@ "./dev": { "import": "./dev/dist/dev.js", "require": "./dev/dist/dev.cjs" - }, - "./dom": { - "import": "./web/dist/web.js", - "require": "./web/dist/web.cjs" } }, "scripts": { diff --git a/packages/solid/src/index.ts b/packages/solid/src/index.ts index c2aa83bb1..96087c90a 100644 --- a/packages/solid/src/index.ts +++ b/packages/solid/src/index.ts @@ -19,7 +19,7 @@ export { createContext, useContext, children, - getContextOwner, + getOwner, equalFn, serializeGraph } from "./reactive/signal"; diff --git a/packages/solid/src/reactive/mutable.ts b/packages/solid/src/reactive/mutable.ts index 655c90735..61f825d2b 100644 --- a/packages/solid/src/reactive/mutable.ts +++ b/packages/solid/src/reactive/mutable.ts @@ -44,7 +44,6 @@ const proxyTraps: ProxyHandler = { ? wrap( value, "_SOLID_DEV_" && target[$NAME] && `${target[$NAME]}:${property as string}`, - false, proxyTraps ) : value; @@ -71,7 +70,6 @@ export function createMutable( const wrappedState = wrap( unwrappedState, "_SOLID_DEV_" && ((options && options.name) || hashValue(unwrappedState)), - true, proxyTraps ); if ("_SOLID_DEV_") { diff --git a/packages/solid/src/reactive/signal.ts b/packages/solid/src/reactive/signal.ts index fb9c8bc5e..36e18e0e4 100644 --- a/packages/solid/src/reactive/signal.ts +++ b/packages/solid/src/reactive/signal.ts @@ -16,7 +16,8 @@ const UNOWNED: Owner = { context: null, owner: null }; -const [transPending, setTransPending] = /*@__PURE__*/createSignal(false, true); +export const $LAZY = Symbol("lazy"); +const [transPending, setTransPending] = /*@__PURE__*/ createSignal(false, true); export var Owner: Owner | null = null; export var Listener: Computation | null = null; let Pending: Signal[] | null = null; @@ -345,7 +346,7 @@ export function getListener() { return Listener; } -export function getContextOwner() { +export function getOwner() { return Owner; } @@ -419,10 +420,17 @@ export interface Resource { (): T | undefined; loading: boolean; } -export function createResource( - init?: T, - options: { notStreamed?: boolean } = {} -): [Resource, (fn: () => Promise | T) => Promise] { +export function createResource( + key: U | false | (() => U | false), + fetcher: (k: U, getPrev: () => T | undefined) => T | Promise, + init?: T +): [ + Resource, + { + mutate: (v: T | undefined) => T | undefined; + refetch: () => void; + } +] { const contexts = new Set(), [s, set] = createSignal(init, true), [track, trigger] = createSignal(), @@ -430,7 +438,8 @@ export function createResource( let err: any = null, pr: Promise | null = null, - id: string | null = null; + id: string | null = null, + dynamic = typeof key === "function"; if (sharedConfig.context) { id = `${sharedConfig.context!.id}${sharedConfig.context!.count++}`; @@ -480,29 +489,34 @@ export function createResource( } return v; } - function load(fn: () => Promise | T, transform = (v: T, prev: T | undefined) => v) { + function load() { err = null; - let p: Promise | T; + let p: Promise | T, + lookup = dynamic ? (key as () => U)() : (key as U); + if (!lookup) { + loadEnd(pr, untrack(s)!); + return; + } if (sharedConfig.context) { - if (sharedConfig.context.loadResource && !options.notStreamed) { + if (sharedConfig.context.loadResource && lookup !== ($LAZY as any)) { p = sharedConfig.context.loadResource!(id!); } else if (sharedConfig.resources && id && id in sharedConfig.resources) { - p = sharedConfig.resources![id!]; - delete sharedConfig.resources![id!]; + p = sharedConfig.resources![id]; + delete sharedConfig.resources![id]; } } - if (!p!) p = fn(); + if (!p!) p = fetcher(lookup, s); if (typeof p !== "object" || !("then" in p)) { - loadEnd(pr, transform(p, untrack(s))); - return Promise.resolve(p); + loadEnd(pr, p); + return; } pr = p; batch(() => { setLoading(true); trigger(); }); - return p.then( - v => loadEnd(p as Promise, transform(v, untrack(s))), + p.then( + v => loadEnd(p as Promise, v), e => loadEnd(p as Promise, undefined as any, e) ); } @@ -511,7 +525,9 @@ export function createResource( return loading(); } }); - return [read as Resource, load]; + if (dynamic) createComputed(load); + else load(); + return [read as Resource, { refetch: load, mutate: set }]; } function readSignal(this: Signal | Memo) { diff --git a/packages/solid/src/reactive/state.ts b/packages/solid/src/reactive/state.ts index fdd7e8d32..13c43417d 100644 --- a/packages/solid/src/reactive/state.ts +++ b/packages/solid/src/reactive/state.ts @@ -38,30 +38,27 @@ export type State = { export function wrap( value: T, name?: string, - processProps?: boolean, traps?: ProxyHandler ): State { let p = value[$PROXY]; if (!p) { Object.defineProperty(value, $PROXY, { value: (p = new Proxy(value, traps || proxyTraps)) }); - if (processProps) { - let keys = Object.keys(value), - desc = Object.getOwnPropertyDescriptors(value); - for (let i = 0, l = keys.length; i < l; i++) { - const prop = keys[i]; - if (desc[prop].get) { - const get = createMemo(desc[prop].get!.bind(p), undefined, true); - Object.defineProperty(value, prop, { - get - }); - } - if (desc[prop].set) { - const og = desc[prop].set!, - set = (v: T[keyof T]) => batch(() => og.call(p, v)); - Object.defineProperty(value, prop, { - set - }); - } + let keys = Object.keys(value), + desc = Object.getOwnPropertyDescriptors(value); + for (let i = 0, l = keys.length; i < l; i++) { + const prop = keys[i]; + if (desc[prop].get) { + const get = createMemo(desc[prop].get!.bind(p), undefined, true); + Object.defineProperty(value, prop, { + get + }); + } + if (desc[prop].set) { + const og = desc[prop].set!, + set = (v: T[keyof T]) => batch(() => og.call(p, v)); + Object.defineProperty(value, prop, { + set + }); } } if ("_SOLID_DEV_" && name) Object.defineProperty(value, $NAME, { value: name }); @@ -160,11 +157,7 @@ const proxyTraps: ProxyHandler = { getOwnPropertyDescriptor: proxyDescriptor }; -export function setProperty( - state: StateNode, - property: string | number, - value: any -) { +export function setProperty(state: StateNode, property: string | number, value: any) { if (state[property] === value) return; const notify = Array.isArray(state) || !(property in state); if (value === undefined) { @@ -340,8 +333,7 @@ export function createState( const unwrappedState = unwrap(state || {}, true); const wrappedState = wrap( unwrappedState, - "_SOLID_DEV_" && ((options && options.name) || hashValue(unwrappedState)), - true + "_SOLID_DEV_" && ((options && options.name) || hashValue(unwrappedState)) ); if ("_SOLID_DEV_") { const name = (options && options.name) || hashValue(unwrappedState); diff --git a/packages/solid/src/render/component.ts b/packages/solid/src/render/component.ts index 39e9534c4..3e9536c7e 100644 --- a/packages/solid/src/render/component.ts +++ b/packages/solid/src/render/component.ts @@ -1,6 +1,6 @@ -import { untrack, createResource, createMemo } from "../reactive/signal"; +import { untrack, createSignal, createResource, createMemo, $LAZY } from "../reactive/signal"; import { sharedConfig, nextHydrateContext, setHydrateContext } from "./hydration"; -import type { JSX } from "../jsx" +import type { JSX } from "../jsx"; type PropsWithChildren

= P & { children?: JSX.Element }; export type Component

= (props: PropsWithChildren

) => JSX.Element; @@ -29,20 +29,53 @@ export function createComponent(Comp: (props: T) => JSX.Element, props: T): J return untrack(() => Comp(props as T)); } -export function assignProps(target: T, source: U): T & U; -export function assignProps(target: T, source1: U, source2: V): T & U & V; -export function assignProps( - target: T, +const propTraps: ProxyHandler<{ get: (k: string | number | symbol) => any; keys: () => any }> = { + get(_, property) { + return _.get(property); + }, + has(_, property) { + return _.get(property) !== undefined; + }, + getOwnPropertyDescriptor(_, property) { + return { + configurable: true, + enumerable: true, + get() { + return _.get(property); + } + }; + }, + ownKeys(_) { + return _.keys(); + } +}; + +export function mergeProps(source: T): T; +export function mergeProps(source: T, source1: U): T & U; +export function mergeProps(source: T, source1: U, source2: V): T & U & V; +export function mergeProps( + source: T, source1: U, source2: V, source3: W ): T & U & V & W; -export function assignProps(target: any, ...sources: any): any { - for (let i = 0; i < sources.length; i++) { - const descriptors = Object.getOwnPropertyDescriptors(sources[i]); - Object.defineProperties(target, descriptors); - } - return target; +export function mergeProps(...sources: any): any { + return new Proxy( + { + get(property: string | number | symbol) { + for (let i = sources.length - 1; i >= 0; i--) { + const v = sources[i][property]; + if (v !== undefined) return v; + } + }, + keys() { + const keys = []; + for (let i = 0; i < sources.length; i++) keys.push(...Object.keys(sources[i])); + return [...new Set(keys)]; + } + }, + propTraps + ); } export function splitProps( @@ -90,39 +123,57 @@ export function splitProps< Pick, Omit ]; -export function splitProps(props: T, ...keys: [(keyof T)[]]) { - const descriptors = Object.getOwnPropertyDescriptors(props), - split = (k: (keyof T)[]) => { - const clone: Partial = {}; - for (let i = 0; i < k.length; i++) { - const key = k[i]; - if (descriptors[key]) { - Object.defineProperty(clone, key, descriptors[key]); - delete descriptors[key]; +export function splitProps(props: T, ...keys: Array<(keyof T)[]>) { + const blocked = new Set(keys.flat()); + const descriptors = Object.getOwnPropertyDescriptors(props); + const res = keys.map(k => { + const clone = {}; + for (let i = 0; i < k.length; i++) { + const key = k[i]; + if (descriptors[key]) Object.defineProperty(clone, key, descriptors[key]); + } + return clone; + }); + res.push( + new Proxy( + { + get(property: string | number | symbol) { + if (blocked.has(property as keyof T)) return; + return props[property as keyof T]; + }, + keys() { + return Object.keys(props).filter(k => !blocked.has(k as keyof T)); } - } - return clone; - }; - return keys.map(split).concat(split(Object.keys(descriptors) as (keyof T)[])); + }, + propTraps + ) + ); + return res; } // lazy load a function component asynchronously export function lazy>(fn: () => Promise<{ default: T }>): T { let p: Promise<{ default: T }>; return ((props: any) => { - const ctx = sharedConfig.context, - [s, l] = createResource(undefined, { notStreamed: true }); - if (sharedConfig.context && sharedConfig.resources) { + const ctx = sharedConfig.context; + let comp: () => T | undefined; + if (ctx && sharedConfig.resources) { + ctx.count++; // increment counter for hydration + const [s, set] = createSignal(); (p || (p = fn())).then(mod => { setHydrateContext(ctx); - l(() => mod.default); + set(mod.default); setHydrateContext(undefined); }); - } else l(() => (p || (p = fn())).then(mod => mod.default)); + comp = s; + } else { + const [s] = createResource($LAZY, () => (p || (p = fn())).then(mod => mod.default)); + comp = s; + } let Comp: T | undefined; return createMemo( () => - (Comp = s()) && + (Comp = comp()) && untrack(() => { if (!ctx) return Comp!(props); const c = sharedConfig.context; diff --git a/packages/solid/src/static/index.ts b/packages/solid/src/static/index.ts index e0fe2cda2..116e79469 100644 --- a/packages/solid/src/static/index.ts +++ b/packages/solid/src/static/index.ts @@ -16,7 +16,7 @@ export { on, createContext, useContext, - getContextOwner, + getOwner, equalFn, requestCallback, createState, @@ -29,7 +29,7 @@ export { export { awaitSuspense, - assignProps, + mergeProps, splitProps, createComponent, For, diff --git a/packages/solid/src/static/reactive.ts b/packages/solid/src/static/reactive.ts index 72b9194d5..1de169aff 100644 --- a/packages/solid/src/static/reactive.ts +++ b/packages/solid/src/static/reactive.ts @@ -154,7 +154,7 @@ export function useContext(context: Context): T { return lookup(Owner, context.id) || context.defaultValue; } -export function getContextOwner() { +export function getOwner() { return Owner; } diff --git a/packages/solid/src/static/rendering.ts b/packages/solid/src/static/rendering.ts index 34837d012..fa1d878a4 100644 --- a/packages/solid/src/static/rendering.ts +++ b/packages/solid/src/static/rendering.ts @@ -51,15 +51,16 @@ export function createComponent( return Comp(props as T); } -export function assignProps(target: T, source: U): T & U; -export function assignProps(target: T, source1: U, source2: V): T & U & V; -export function assignProps( - target: T, +export function mergeProps(source: T, source1: U): T & U; +export function mergeProps(source: T, source1: U, source2: V): T & U & V; +export function mergeProps( + source: T, source1: U, source2: V, source3: W ): T & U & V & W; -export function assignProps(target: any, ...sources: any): any { +export function mergeProps(...sources: any): any { + const target = {}; for (let i = 0; i < sources.length; i++) { const descriptors = Object.getOwnPropertyDescriptors(sources[i]); Object.defineProperties(target, descriptors); @@ -218,48 +219,73 @@ type SuspenseContextType = { }; const SuspenseContext = createContext(); -export function createResource( +export function createResource( + key: U | false | (() => U | false), + fetcher: (k: U, getPrev: () => T | undefined) => T | Promise, value?: T -): [Resource, (fn: () => Promise | T) => { then: Function }] { +): [ + Resource, + { + mutate: (v: T | undefined) => T | undefined; + refetch: () => void; + } +] { const contexts = new Set(); const id = sharedConfig.context!.id + sharedConfig.context!.count++; + let resource: { ref?: any; data?: T } = {}; + if (sharedConfig.context!.async) { + resource = sharedConfig.context!.resources[id] || (sharedConfig.context!.resources[id] = {}); + if (resource.ref) { + if (!resource.data && !resource.ref[0].loading) resource.ref[1].refetch(); + return resource.ref; + } + } const read = () => { - const resolved = sharedConfig.context!.async && id in sharedConfig.context!.resources; + const resolved = sharedConfig.context!.async && sharedConfig.context!.resources[id].data; if (sharedConfig.context!.async && !resolved) { const ctx = useContext(SuspenseContext); if (ctx) { - if (!ctx.resources.has(id)) ctx.resources.set(id, read); + ctx.resources.set(id, read); contexts.add(ctx); } } - return resolved ? sharedConfig.context!.resources[id] : value; + return resolved ? sharedConfig.context!.resources[id].data : value; }; read.loading = false; - function load(fn: () => Promise | T) { + function load() { const ctx = sharedConfig.context!; - if (!ctx.async && !ctx.streaming) return { then() {} }; - if (ctx.resources && id in ctx.resources) { - value = ctx.resources[id]; - return { then() {} }; + if (!ctx.async && !ctx.streaming) return; + if (ctx.resources && id in ctx.resources && ctx.resources[id].data) { + value = ctx.resources[id].data; + return; } + const lookup = typeof key === "function" ? (key as () => U)() : key; + if (!lookup) return; read.loading = true; - const p = fn(); + const p = fetcher(lookup, () => value); if ("then" in p) { if (ctx.writeResource) { ctx.writeResource(id, p); - return p; + return; } p.then(res => { read.loading = false; - ctx.resources[id] = res; + ctx.resources[id].data = res; notifySuspense(contexts); return res; }); - return p; + return; } - return Promise.resolve((value = p)); + ctx.resources[id].data = p; } - return [read, load]; + load(); + return (resource.ref = [read, { refetch: load, mutate: v => (value = v) }] as [ + Resource, + { + mutate: (v: T | undefined) => T | undefined; + refetch: () => void; + } + ]); } export function lazy(fn: () => Promise<{ default: any }>): (props: any) => string { diff --git a/packages/solid/test/component.spec.ts b/packages/solid/test/component.spec.ts index 6d5169b21..2f5e4abed 100644 --- a/packages/solid/test/component.spec.ts +++ b/packages/solid/test/component.spec.ts @@ -1,4 +1,4 @@ -import { createComponent, assignProps, splitProps, createState } from "../src"; +import { createComponent, mergeProps, splitProps, createState } from "../src"; type SimplePropTypes = { a?: string | null; @@ -31,7 +31,7 @@ describe("Set Default Props", () => { c: "j" }, defaults: SimplePropTypes = { a: "yy", b: "ggg", d: "DD" }; - props = assignProps(defaults, props); + props = mergeProps(defaults, props); expect(props.a).toBe("ji"); expect(props.b).toBe(null); expect(props.c).toBe("j"); @@ -50,7 +50,7 @@ describe("Clone Props", () => { b: null, c: "j" }; - const newProps = assignProps({}, props); + const newProps = mergeProps({}, props); expect(reactive).toBe(false); expect(newProps.a).toBe("ji"); expect(reactive).toBe(true); @@ -62,13 +62,13 @@ describe("Clone Props", () => { describe("Clone State", () => { const [state, setState] = createState<{a: string, b: string, c?: string}>({ a: "Hi", b: "Jo" }); - const clone = assignProps({}, state); + const clone = mergeProps(state); expect(clone.a).toBe("Hi"); expect(clone.b).toBe("Jo"); setState({ a: "Greetings", c: "John" }); expect(clone.a).toBe("Greetings"); expect(clone.b).toBe("Jo"); - expect(clone.c).toBe(undefined); + expect(clone.c).toBe("John"); }) describe("SplitProps Props", () => { diff --git a/packages/solid/test/dev.spec.ts b/packages/solid/test/dev.spec.ts index c14c7619f..f26525aa3 100644 --- a/packages/solid/test/dev.spec.ts +++ b/packages/solid/test/dev.spec.ts @@ -1,6 +1,6 @@ import { createRoot, - getContextOwner, + getOwner, createSignal, createState, createEffect, @@ -9,14 +9,14 @@ import { describe("Dev features", () => { test("Reactive graph serialization", () => { - let owner: ReturnType, set1: (v: number) => number, setState1: any; + let owner: ReturnType, set1: (v: number) => number, setState1: any; const SNAPSHOTS = [ `{"s1773325850":5,"s1773325850-1":5,"s533736025":{"firstName":"John","lastName":"Smith"},"c-1":{"explicit":6}}`, `{"s1773325850":7,"s1773325850-1":5,"s533736025":{"firstName":"Matt","lastName":"Smith","middleInitial":"R."},"c-1":{"explicit":6}}` ]; createRoot(() => { - owner = getContextOwner(); + owner = getOwner(); const [s, set] = createSignal(5); const [s2] = createSignal(5); createEffect(() => { diff --git a/packages/solid/test/resource.spec.ts b/packages/solid/test/resource.spec.ts index fdf92abf2..6c1563d6d 100644 --- a/packages/solid/test/resource.spec.ts +++ b/packages/solid/test/resource.spec.ts @@ -6,35 +6,30 @@ import { createState, createRenderEffect, onError, - SetStateFunction, Resource, - State, - reconcile + reconcile, + State } from "../src"; describe("Simulate a dynamic fetch", () => { let resolve: (v: string) => void, reject: (r: string) => void, - trigger: (v: number) => void, - load: (v: () => Promise) => void, - i: number, + trigger: (v: string) => void, value: Resource, error: string; - function fetcher(id: number) { - return () => - new Promise((r, f) => { - resolve = r; - reject = f; - }); + function fetcher(id: string) { + return new Promise((r, f) => { + resolve = r; + reject = f; + }); } test("initial async resource", async done => { createRoot(() => { - const [id, setId] = createSignal(1); - [value, load] = createResource(); + const [id, setId] = createSignal("1"); trigger = setId; onError(e => (error = e)); - createComputed(() => (i = id()) && load(fetcher(i))); + [value] = createResource(id, fetcher); createRenderEffect(value); }); expect(value()).toBeUndefined(); @@ -47,10 +42,10 @@ describe("Simulate a dynamic fetch", () => { }); test("test out of order", async done => { - trigger(2); + trigger("2"); expect(value.loading).toBe(true); const resolve1 = resolve; - trigger(3); + trigger("3"); const resolve2 = resolve; resolve2("Jake"); resolve1("Jo"); @@ -61,7 +56,7 @@ describe("Simulate a dynamic fetch", () => { }); test("promise rejection", async done => { - trigger(4); + trigger("4"); expect(value.loading).toBe(true); reject("Because I said so"); await Promise.resolve(); @@ -80,26 +75,23 @@ describe("Simulate a dynamic fetch with state and reconcile", () => { }; } let resolve: (v: User) => void, + refetch: () => void, user: Resource, - load: ( - v: () => Promise | User , - r?: (v: User, p: User) => User - ) => void, - state: { user?: User, userLoading: boolean }, + state: { user?: User; userLoading: boolean }, count = 0; - function fetcher() { + function fetcher(_: string, getPrev: () => User | undefined) { return new Promise(r => { resolve = r; - }); + }).then(value => reconcile(value)(getPrev() as State)); } const data: User[] = []; - data.push({ firstName: "John", address: { streetNumber: 4, streetName: "Grindel Rd" } }) - data.push({ ...data[0], firstName: "Joseph" }) + data.push({ firstName: "John", address: { streetNumber: 4, streetName: "Grindel Rd" } }); + data.push({ ...data[0], firstName: "Joseph" }); test("initial async resource", async done => { createRoot(() => { - [user, load] = createResource(); - [state] = createState<{ user?: User, userLoading: boolean }>({ + [user, { refetch }] = createResource("user", fetcher); + [state] = createState<{ user?: User; userLoading: boolean }>({ get user() { return user(); }, @@ -109,7 +101,6 @@ describe("Simulate a dynamic fetch with state and reconcile", () => { }); createComputed(() => (state.user, count++)); }); - load(fetcher); expect(state.user).toBeUndefined(); expect(state.userLoading).toBe(true); resolve(data[0]); @@ -119,7 +110,7 @@ describe("Simulate a dynamic fetch with state and reconcile", () => { expect(state.userLoading).toBe(false); expect(count).toBe(2); - load(fetcher, (value, prev) => reconcile(value)(prev)); + refetch(); expect(state.userLoading).toBe(true); resolve(data[1]); await Promise.resolve(); @@ -136,9 +127,8 @@ describe("Simulate a dynamic fetch with state and reconcile", () => { describe("using Resource with no root", () => { test("loads default value", () => { expect(() => { - const [, load] = createResource(); let resolve: (v: string) => void; - load(() => new Promise(r => (resolve = r))); + createResource("error", () => new Promise(r => (resolve = r))); resolve!("Hi"); }).not.toThrow(); }); diff --git a/packages/solid/web/server/index.ts b/packages/solid/web/server/index.ts index 09093aa6d..195034616 100644 --- a/packages/solid/web/server/index.ts +++ b/packages/solid/web/server/index.ts @@ -12,7 +12,7 @@ export { Match, Index, ErrorBoundary, - assignProps + mergeProps } from "solid-js"; export const isServer = true; diff --git a/packages/solid/web/src/core.ts b/packages/solid/web/src/core.ts index 8732b94a4..e55204975 100644 --- a/packages/solid/web/src/core.ts +++ b/packages/solid/web/src/core.ts @@ -4,7 +4,7 @@ import { createRenderEffect, createMemo, createComponent, - getContextOwner, + getOwner, sharedConfig, awaitSuspense } from "solid-js"; @@ -12,4 +12,4 @@ import { // reactive injection for dom-expressions function memo(fn: () => any, equal: boolean) { return createMemo(fn, undefined, equal); } -export { getContextOwner as currentContext, createComponent, createRoot as root, createRenderEffect as effect, memo, sharedConfig, awaitSuspense as asyncWrap } +export { getOwner, createComponent, createRoot as root, createRenderEffect as effect, memo, sharedConfig, awaitSuspense as asyncWrap } diff --git a/packages/solid/web/src/index.ts b/packages/solid/web/src/index.ts index 0a0ce503b..8bc690acb 100644 --- a/packages/solid/web/src/index.ts +++ b/packages/solid/web/src/index.ts @@ -22,7 +22,7 @@ export { Match, Index, ErrorBoundary, - assignProps + mergeProps } from "solid-js"; export * from "./server-mock"; diff --git a/packages/solid/web/test/suspense.spec.tsx b/packages/solid/web/test/suspense.spec.tsx index cd29ca1ce..42d4f4098 100644 --- a/packages/solid/web/test/suspense.spec.tsx +++ b/packages/solid/web/test/suspense.spec.tsx @@ -1,11 +1,5 @@ /* @jsxImportSource solid-js */ -import { - lazy, - createSignal, - createComputed, - createResource, - useTransition -} from "../../src"; +import { lazy, createSignal, createResource, useTransition } from "../../src"; import { render, Suspense, SuspenseList } from "../src"; describe("Testing Suspense", () => { @@ -15,9 +9,10 @@ describe("Testing Suspense", () => { [triggered, trigger] = createSignal(false); const LazyComponent = lazy(() => new Promise(r => resolvers.push(r))), ChildComponent = (props: { greeting: string }) => { - let [value, load] = createResource(""); - createComputed( - () => triggered() && load(() => new Promise(r => setTimeout(() => r("Jo"), 300))) + let [value] = createResource( + () => triggered() ? "child" : null, + () => new Promise(r => setTimeout(() => r("Jo"), 300)), + "" ); return () => `${props.greeting} ${value()}`; }, @@ -64,8 +59,8 @@ describe("Testing Suspense", () => { }); describe("SuspenseList", () => { - const promiseFactory = (time: number, v: string) => { - return () => + const promiseFactory = (time: number) => { + return (v: string) => new Promise(r => { setTimeout(() => { r(v); @@ -73,18 +68,15 @@ describe("SuspenseList", () => { }); }, A = () => { - const [value, load] = createResource(); - load(promiseFactory(200, "A")); + const [value] = createResource("A", promiseFactory(200)); return

{value()}
; }, B = () => { - const [value, load] = createResource(); - load(promiseFactory(100, "B")); + const [value] = createResource("B", promiseFactory(100)); return
{value()}
; }, C = () => { - const [value, load] = createResource(); - load(promiseFactory(300, "C")); + const [value] = createResource("C", promiseFactory(300)); return
{value()}
; }; diff --git a/tsconfig.test.json b/tsconfig.test.json index 0ed2f882f..d465441cb 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -4,7 +4,7 @@ "target": "esnext", "moduleResolution": "node", "strict": true, - "lib": ["dom", "es2015", "dom.iterable"], + "lib": ["dom", "esnext", "dom.iterable"], "jsx": "preserve" }, "exclude": ["node_modules"] From fe25130c3f903a83843af94a586b0a6b11d9434c Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Tue, 2 Feb 2021 01:00:46 -0800 Subject: [PATCH 08/14] small tweaks, update docs --- CHANGELOG.md | 37 +++++++++++++++++++++++++++ packages/solid/src/reactive/signal.ts | 16 ++++++------ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7153a6ad1..248b1c6c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## 0.24.0 - 2021-2-x + +This release is the start of the rework of the SSR solution. Consolidating them under a single method. Unfortunately this one comes with several breaking changes. + +### Breaking Changes + +#### Removed `solid-js/dom` + +It's been a few versions deprecated. It's gone. + +#### Updated Resource API + +Changed to more resemble SWR and React Query. Needed to remove `createResourceState`so now need to use a getter over `createResource` to get same effect. See updated documentation. + +#### Change SSR render call signatures + +They now return results objects that include the generated hydration script. No more need to generate it separately. Also comes autowrapped in the `script` tag now. + +#### `assignProps` to `mergeProps` + +While you use them the same way mostly it no longer has `Object.assign` semantics and always returns a new object. This is important as in many cases we need to upgrade to a Proxy. + +#### Renamed `getContextOwner` to `getOwner` + +Removes confusion around context and consistent with new helper `runWithOwner`. + +### Non-break Changes + +#### Event Delegation + +Solid is now being more strict on what events it delegates. Limiting to standard pointer/touch/mouse/keyboard events. Custom events will no longer be delegated. +### New Features + +#### `children` helper + +Resolves children and returns a memo. This makes it much easier to deal with children. Using same mechanism `` can now have dynamic children like `` inside. + ## 0.23.0 - 2020-12-05 This release is mostly bug fixes. Breaking change for TS users. JSX types no longer pollutes global namespace. This means you need to update your projects to import it. diff --git a/packages/solid/src/reactive/signal.ts b/packages/solid/src/reactive/signal.ts index 49d81641e..241eb0da7 100644 --- a/packages/solid/src/reactive/signal.ts +++ b/packages/solid/src/reactive/signal.ts @@ -334,14 +334,14 @@ export function getOwner() { return Owner; } -export function runWithOwner(owner: Owner | null, callback: () => T) { - const currentOwner = getContextOwner(); - - Owner = owner; - const result = callback(); - Owner = currentOwner; - - return result; +export function runWithOwner(o: Owner, fn: () => any) { + const prev = Owner; + Owner = o; + try { + return fn(); + } finally { + Owner = prev; + } } export function hashValue(v: any) { From 5cb06dd381a3f7b276cd15c4ff220d8b0cf3bccc Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Tue, 2 Feb 2021 01:05:46 -0800 Subject: [PATCH 09/14] v0.24.0-beta.2 --- lerna.json | 2 +- packages/babel-preset-solid/package.json | 2 +- packages/react-solid-state/package-lock.json | 2 +- packages/react-solid-state/package.json | 4 ++-- packages/solid-element/package.json | 4 ++-- packages/solid-meta/package.json | 4 ++-- packages/solid-rx/package.json | 4 ++-- packages/solid-ssr/package.json | 8 ++++---- packages/solid-styled-components/package.json | 4 ++-- packages/solid-styled-jsx/package.json | 4 ++-- packages/solid/package.json | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lerna.json b/lerna.json index 602d1b879..6fb25f3c5 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": [ "packages/*" ], - "version": "0.24.0-beta.1" + "version": "0.24.0-beta.2" } diff --git a/packages/babel-preset-solid/package.json b/packages/babel-preset-solid/package.json index ce45f9624..41c620142 100644 --- a/packages/babel-preset-solid/package.json +++ b/packages/babel-preset-solid/package.json @@ -1,6 +1,6 @@ { "name": "babel-preset-solid", - "version": "0.24.0-beta.1", + "version": "0.24.0-beta.2", "description": "Babel preset to transform JSX for Solid.js", "author": "Ryan Carniato ", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/babel-preset-solid#readme", diff --git a/packages/react-solid-state/package-lock.json b/packages/react-solid-state/package-lock.json index 32855a6e4..cfe36d773 100644 --- a/packages/react-solid-state/package-lock.json +++ b/packages/react-solid-state/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-solid-state", - "version": "0.24.0-beta.1", + "version": "0.24.0-beta.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/react-solid-state/package.json b/packages/react-solid-state/package.json index f4db7ac99..114bc58e7 100644 --- a/packages/react-solid-state/package.json +++ b/packages/react-solid-state/package.json @@ -1,7 +1,7 @@ { "name": "react-solid-state", "description": "Auto tracking state management for modern React", - "version": "0.24.0-beta.1", + "version": "0.24.0-beta.2", "author": "Ryan Carniato", "license": "MIT", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/react-solid-state#readme", @@ -17,7 +17,7 @@ "test:coverage": "jest --coverage" }, "dependencies": { - "solid-js": "^0.24.0-beta.1" + "solid-js": "^0.24.0-beta.2" }, "peerDependencies": { "react": "*", diff --git a/packages/solid-element/package.json b/packages/solid-element/package.json index c8f9900bd..678232ad2 100644 --- a/packages/solid-element/package.json +++ b/packages/solid-element/package.json @@ -3,7 +3,7 @@ "description": "Webcomponents wrapper for Solid", "author": "Ryan Carniato", "license": "MIT", - "version": "0.24.0-beta.1", + "version": "0.24.0-beta.2", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/solid-element#readme", "type": "module", "main": "dist/index.js", @@ -23,6 +23,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.1" + "solid-js": "^0.24.0-beta.2" } } diff --git a/packages/solid-meta/package.json b/packages/solid-meta/package.json index 600862a0b..25b5f5388 100644 --- a/packages/solid-meta/package.json +++ b/packages/solid-meta/package.json @@ -1,7 +1,7 @@ { "name": "solid-meta", "description": "Solid wrapper for Styled JSX", - "version": "0.24.0-beta.1", + "version": "0.24.0-beta.2", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -30,6 +30,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.1" + "solid-js": "^0.24.0-beta.2" } } diff --git a/packages/solid-rx/package.json b/packages/solid-rx/package.json index af6d276fe..5e8fe4c1a 100644 --- a/packages/solid-rx/package.json +++ b/packages/solid-rx/package.json @@ -1,6 +1,6 @@ { "name": "solid-rx", - "version": "0.24.0-beta.1", + "version": "0.24.0-beta.2", "description": "Functionally reactive extensions for Solid.js", "author": "Ryan Carniato ", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/solid-rx#readme", @@ -29,6 +29,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.1" + "solid-js": "^0.24.0-beta.2" } } diff --git a/packages/solid-ssr/package.json b/packages/solid-ssr/package.json index 77906bf69..38f3aa7d0 100644 --- a/packages/solid-ssr/package.json +++ b/packages/solid-ssr/package.json @@ -1,7 +1,7 @@ { "name": "solid-ssr", "description": "Patches node to work with Solid's SSR", - "version": "0.24.0-beta.1", + "version": "0.24.0-beta.2", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -24,10 +24,10 @@ "start:example:stream": "node examples/stream/lib/index.js" }, "devDependencies": { - "babel-preset-solid": "^0.24.0-beta.1", + "babel-preset-solid": "^0.24.0-beta.2", "express": "^4.17.1", - "solid-js": "^0.24.0-beta.1", - "solid-styled-components": "^0.24.0-beta.1" + "solid-js": "^0.24.0-beta.2", + "solid-styled-components": "^0.24.0-beta.2" }, "peerDependencies": { "solid-js": "*" diff --git a/packages/solid-styled-components/package.json b/packages/solid-styled-components/package.json index 6a2dd1d4c..524ed1c0c 100644 --- a/packages/solid-styled-components/package.json +++ b/packages/solid-styled-components/package.json @@ -1,7 +1,7 @@ { "name": "solid-styled-components", "description": "Styled Components for Solid", - "version": "0.24.0-beta.1", + "version": "0.24.0-beta.2", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -28,6 +28,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.1" + "solid-js": "^0.24.0-beta.2" } } diff --git a/packages/solid-styled-jsx/package.json b/packages/solid-styled-jsx/package.json index c3d1ec577..98c8d8f1f 100644 --- a/packages/solid-styled-jsx/package.json +++ b/packages/solid-styled-jsx/package.json @@ -1,7 +1,7 @@ { "name": "solid-styled-jsx", "description": "Solid wrapper for Styled JSX", - "version": "0.24.0-beta.1", + "version": "0.24.0-beta.2", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -23,6 +23,6 @@ "styled-jsx": "^3.2.5" }, "devDependencies": { - "solid-js": "^0.24.0-beta.1" + "solid-js": "^0.24.0-beta.2" } } diff --git a/packages/solid/package.json b/packages/solid/package.json index 9e19f45fb..162b527da 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -1,7 +1,7 @@ { "name": "solid-js", "description": "A declarative JavaScript library for building user interfaces.", - "version": "0.24.0-beta.1", + "version": "0.24.0-beta.2", "author": "Ryan Carniato", "license": "MIT", "homepage": "https://github.com/ryansolid/solid#readme", From d2430c8625bac90b3719e7dc91e86f84e3b0a74d Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Wed, 3 Feb 2021 00:59:35 -0800 Subject: [PATCH 10/14] update docs --- CHANGELOG.md | 28 +++++++++++++++-- documentation/suspense.md | 6 ++-- package-lock.json | 40 ++++++++++++++---------- package.json | 8 ++--- packages/babel-preset-solid/package.json | 2 +- 5 files changed, 56 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 248b1c6c2..8204237b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.24.0 - 2021-2-x +## 0.24.0 - 2021-02-03 This release is the start of the rework of the SSR solution. Consolidating them under a single method. Unfortunately this one comes with several breaking changes. @@ -26,17 +26,39 @@ While you use them the same way mostly it no longer has `Object.assign` semantic Removes confusion around context and consistent with new helper `runWithOwner`. -### Non-break Changes +#### Solid Element no longer uses State for props +This reduces the size of the library especially for those not using state. It also should slightly increase performance as no need for deep nesting of proxies. It also makes things behave more consistently avoided unintended deep wrapping. + +### Non-breaking Changes + +#### New non-reactive Async SSR + +I have now combined sync/streaming/async SSR into the same compiler output. To do so I have developed a new non-reactive Async SSR approach. After realizing how fast Solid renders, it occurred to me on the server we could do a much simpler approach if we were willing to re-render all content in Suspense boundaries. While that is some wasted work, compared to including the reactive system it's a killing. + +#### Increase SSR Performance + +Through reusing static strings in the template we reduce repeated creation costs. This small improvement can make 5-8% improvements where you have many rows. #### Event Delegation -Solid is now being more strict on what events it delegates. Limiting to standard pointer/touch/mouse/keyboard events. Custom events will no longer be delegated. +Solid is now being more strict on what events it delegates. Limiting to standard pointer/touch/mouse/keyboard events. Custom events will no longer be delegated automatically. This increases compatibility for Web Component users who don't compose their events. Non-delegated events will still work and binding array syntax with them. ### New Features #### `children` helper Resolves children and returns a memo. This makes it much easier to deal with children. Using same mechanism `` can now have dynamic children like `` inside. +#### "solid" Export Conidition +This is the way to package the JSX components to be compiled to work on server or client. By putting the "solid" condition the source JSX will be prioritized over normal browser builds. + +### Bug Fixes + +* Top level primitive values not working with `reconcile` +* Fix Dynamic Components to handle SVG +* Rename potentially conflicting properties for event delegtion +* Fixed State spreads to not loose reactiviy. Added support for dynamically created properties to track in spreads and helpers +* TypeScript, always TypeScript + ## 0.23.0 - 2020-12-05 This release is mostly bug fixes. Breaking change for TS users. JSX types no longer pollutes global namespace. This means you need to update your projects to import it. diff --git a/documentation/suspense.md b/documentation/suspense.md index 522be1430..1547919cb 100644 --- a/documentation/suspense.md +++ b/documentation/suspense.md @@ -210,10 +210,8 @@ It is important to note that Suspense is tracked based on data requirements of t ```jsx // start loading data before any part of the page is executed. -const [user, loadUser] = createResource(); -const [posts, loadPosts] = createResource(); -loadUser(fetchUser); -loadPosts(fetchPosts); +const [user] = createResource("user", fetchUser); +const [posts] = createResource("posts", fetchPost); function ProfilePage() { return ( diff --git a/package-lock.json b/package-lock.json index eb16b9024..084bc1ada 100644 --- a/package-lock.json +++ b/package-lock.json @@ -834,12 +834,20 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", - "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz", + "integrity": "sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", + "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "dev": true + } } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -4659,9 +4667,9 @@ } }, "babel-plugin-jsx-dom-expressions": { - "version": "0.25.0-beta.5", - "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.25.0-beta.5.tgz", - "integrity": "sha512-hlQFu28l0RhSbgN3GWZqdCal3J7JFGpmQcnmLUZmL0+biEhxUzverx/xtx9uQg7psVlK+XllgmKaqhZ1TJGWGA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.25.0.tgz", + "integrity": "sha512-U6ET1eUlQyNYJKKUcNgjEGM/H5xkMKscBdFGFBTW++vy5xq0oxILPuxrMtYmS6orA1/Imd6nV5zR7g95+uVCEg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.10.4", @@ -6163,9 +6171,9 @@ } }, "dom-expressions": { - "version": "0.25.0-beta.6", - "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.25.0-beta.6.tgz", - "integrity": "sha512-uFZcKJi5ga8ThL8HANs4okMJo4Y95UgTUmaGJWjWv2HpF3V7xGFkJzUwi0z6tgZY5SbuJwJ4uc8onOyYCzxCFQ==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.25.0.tgz", + "integrity": "sha512-nZRYqrqLVcc9mKyWK4URZakw+L7zI/HWOwvcXlZQwVNoRVXQYD16rBd5IsnpK1fUPA5WECxU5pFa3G6W10D4yA==", "dev": true, "requires": { "babel-plugin-transform-rename-import": "^2.3.0" @@ -8048,9 +8056,9 @@ } }, "hyper-dom-expressions": { - "version": "0.25.0-beta.6", - "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.25.0-beta.6.tgz", - "integrity": "sha512-kUbzhoBs6MpUUdjTtTLciAMprp+9Dh9/qjlfYEidZxsE7OM59PkoS6cU1X2WvfXqCRObEzIj7nMzSRLnioRN6A==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.25.0.tgz", + "integrity": "sha512-cVQ0DsMW5oE03/2gxgsEUT2vYblRSOEdAKxnVMFwAG3xMc3HLWlBQN4BgOA97Br037+M8ktMsYVUwDBUrr+RVg==", "dev": true }, "iconv-lite": { @@ -11221,9 +11229,9 @@ "dev": true }, "lit-dom-expressions": { - "version": "0.25.0-beta.6", - "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.25.0-beta.6.tgz", - "integrity": "sha512-BjkvW5ycpuXW7b6W0835YzomqJSXIiN5Xo5H4wz1E71GA0Ay9s6w4s4syKWUqQx2LXaAP/QvTYJSJv59dtPNnw==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.25.0.tgz", + "integrity": "sha512-v6KsKyD1HW01GV6sWsyn0ilod2eSf25uZ6UfB27Wr1Vwcc2SPw4ITQYXql0O9BsSK63MRDJLX1AiDoHY+18lOA==", "dev": true }, "load-json-file": { diff --git a/package.json b/package.json index 8a3a5b2be..a531cd770 100644 --- a/package.json +++ b/package.json @@ -33,14 +33,14 @@ "@rollup/plugin-replace": "2.3.3", "@types/jest": "^26.0.14", "babel-jest": "^26.6.3", - "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.5", + "babel-plugin-jsx-dom-expressions": "~0.25.0", "coveralls": "^3.1.0", - "dom-expressions": "0.25.0-beta.6", - "hyper-dom-expressions": "0.25.0-beta.6", + "dom-expressions": "0.25.0", + "hyper-dom-expressions": "0.25.0", "jest": "~26.6.3", "jest-ts-webcompat-resolver": "^1.0.0", "lerna": "^3.22.1", - "lit-dom-expressions": "0.25.0-beta.6", + "lit-dom-expressions": "0.25.0", "ncp": "2.0.0", "npm-run-all": "^4.1.5", "rimraf": "^3.0.2", diff --git a/packages/babel-preset-solid/package.json b/packages/babel-preset-solid/package.json index 41c620142..593aca51e 100644 --- a/packages/babel-preset-solid/package.json +++ b/packages/babel-preset-solid/package.json @@ -14,6 +14,6 @@ "test": "node test.js" }, "dependencies": { - "babel-plugin-jsx-dom-expressions": "~0.25.0-beta.5" + "babel-plugin-jsx-dom-expressions": "~0.25.0" } } From 2347ac238efc1924eff19a4debdc460d935b510c Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Wed, 3 Feb 2021 01:06:04 -0800 Subject: [PATCH 11/14] v0.24.0-beta.3 --- lerna.json | 2 +- packages/babel-preset-solid/package.json | 2 +- packages/solid-ssr/package.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lerna.json b/lerna.json index 6fb25f3c5..46f82639a 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": [ "packages/*" ], - "version": "0.24.0-beta.2" + "version": "0.24.0-beta.3" } diff --git a/packages/babel-preset-solid/package.json b/packages/babel-preset-solid/package.json index 593aca51e..94122fae9 100644 --- a/packages/babel-preset-solid/package.json +++ b/packages/babel-preset-solid/package.json @@ -1,6 +1,6 @@ { "name": "babel-preset-solid", - "version": "0.24.0-beta.2", + "version": "0.24.0-beta.3", "description": "Babel preset to transform JSX for Solid.js", "author": "Ryan Carniato ", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/babel-preset-solid#readme", diff --git a/packages/solid-ssr/package.json b/packages/solid-ssr/package.json index 38f3aa7d0..580f2ed9f 100644 --- a/packages/solid-ssr/package.json +++ b/packages/solid-ssr/package.json @@ -1,7 +1,7 @@ { "name": "solid-ssr", "description": "Patches node to work with Solid's SSR", - "version": "0.24.0-beta.2", + "version": "0.24.0-beta.3", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -24,7 +24,7 @@ "start:example:stream": "node examples/stream/lib/index.js" }, "devDependencies": { - "babel-preset-solid": "^0.24.0-beta.2", + "babel-preset-solid": "^0.24.0-beta.3", "express": "^4.17.1", "solid-js": "^0.24.0-beta.2", "solid-styled-components": "^0.24.0-beta.2" From 2722157f798ce451b68e074723a241388b209c20 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Wed, 3 Feb 2021 23:44:33 -0800 Subject: [PATCH 12/14] Update docs, remove auto state getter memoization --- CHANGELOG.md | 5 ++ documentation/api.md | 24 +++--- documentation/reactivity.md | 74 +++++-------------- documentation/state.md | 38 +++++++--- packages/solid-ssr/README.md | 2 +- .../solid-ssr/examples/async/rollup.config.js | 6 +- .../solid-ssr/examples/ssg/rollup.config.js | 6 +- .../solid-ssr/examples/ssr/rollup.config.js | 4 +- .../examples/stream/rollup.config.js | 4 +- packages/solid/src/reactive/mutable.ts | 37 +++++++--- packages/solid/src/reactive/state.ts | 40 +++------- 11 files changed, 111 insertions(+), 129 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8204237b9..2ca2309d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,9 +39,14 @@ I have now combined sync/streaming/async SSR into the same compiler output. To d #### Increase SSR Performance Through reusing static strings in the template we reduce repeated creation costs. This small improvement can make 5-8% improvements where you have many rows. + #### Event Delegation Solid is now being more strict on what events it delegates. Limiting to standard pointer/touch/mouse/keyboard events. Custom events will no longer be delegated automatically. This increases compatibility for Web Component users who don't compose their events. Non-delegated events will still work and binding array syntax with them. + +#### State getters no longer memos + +Automatic memos put some constraints on the disposal system that get in the way of making the approach flexible to hold all manner of reactive primitives. Some previous limitations included not being able to have nested getters. You can still manually create a memo and put it in a getter but the default will not be memoized. ### New Features #### `children` helper diff --git a/documentation/api.md b/documentation/api.md index c36e1ee8e..20d57a43b 100644 --- a/documentation/api.md +++ b/documentation/api.md @@ -4,9 +4,13 @@ This is the smallest and most primitive reactive atom used to track a single value. By default signals always notify on setting a value. You can have it only notify on changes if you pass true to the second parameter. Or a custom comparator can be passed in to indicate whether the values should be considered equal and listeners not notified. -### `createState(initValue): [state, setState]` +### `createMemo(prev => , initialValue, boolean | comparatorFn): getValueFn` -Creates a new State proxy object and setState pair. State only triggers update on values changing. Tracking is done by intercepting property access and automatically tracks deep nesting via proxy. +Creates a readonly derived signal that recalculates it's value whenever the executed codes dependencies update. By default memos always notify on updating a value. You can have it only notify on changes if you pass true to the second parameter. Or a custom comparator can be passed in to indicate whether the values should be considered equal and listeners not notified. + +### `createEffect(prev => , initialValue): void` + +Creates a new computation that automatically tracks dependencies and runs after each render where a dependency has changed. Ideal for using `ref`s and managing other side effects. 2nd argument is the initial value. ### `onMount(() => )` @@ -16,17 +20,9 @@ Registers a method that runs after initial render and elements have been mounted Registers a cleanup method that executes on disposal or recalculation of the current context. Can be used in components or computations. -### `createMemo(prev => , initialValue, boolean | comparatorFn): getValueFn` - -Creates a readonly signal that recalculates it's value whenever the executed codes dependencies update. By default memos always notify on updating a value. You can have it only notify on changes if you pass true to the second parameter. Or a custom comparator can be passed in to indicate whether the values should be considered equal and listeners not notified. - -### `createComputed(prev => , initialValue): void` - -Creates a new computation that automatically tracks dependencies and runs immediately. Use this to write to other reactive primitives or to reactively trigger async data loading. 2nd argument is the initial value. - -### `createEffect(prev => , initialValue): void` +### `createState(initValue): [state, setState]` -Creates a new computation that automatically tracks dependencies and runs after each render where a dependency has changed. Ideal for using `ref`s and managing other side effects. 2nd argument is the initial value. +Creates a new State proxy object and setState pair. State only triggers update on values changing. Tracking is done by intercepting property access and automatically tracks deep nesting via proxy. ### `createContext(defaultContext): Context` @@ -68,6 +64,10 @@ Creates a new mutable State proxy object. State only triggers update on values c Creates memo that only notifies downstream changes when the browser is idle. `timeoutMs` is the maximum time to wait before forcing the update. +### `createComputed(prev => , initialValue): void` + +Creates a new computation that automatically tracks dependencies and runs immediately. Use this to write to other reactive primitives or to reactively trigger async data loading before render. 2nd argument is the initial value. + ### `createRenderEffect(prev => , initialValue): void` Creates a new computation that automatically tracks dependencies and runs during the render phase as DOM elements are created and updated but not necessarily connected. All internal DOM updates happen at this time. diff --git a/documentation/reactivity.md b/documentation/reactivity.md index f0d2f5b26..3e542919e 100644 --- a/documentation/reactivity.md +++ b/documentation/reactivity.md @@ -2,9 +2,9 @@ Solid's data management is built off a set of flexible reactive primitives which are responsible for all the updates. It takes a very similar approach to MobX or Vue except it never trades its granularity for a VDOM. Dependencies are automatically tracked when you access your reactive values in your Effects and JSX View code. -Solid has a number of reactive primitives but the main 2 are Signals, and State. Ultimately you will need to understand both to write effective Solid code. +Solid's primitives come in the form of `create` calls that often return tuples, where generally the first element is a readable primitive and the second is a setter. It is common to refer to only the readable part by the primitive name. -Signals hold simple values that you view as atomic immutable cells that consist of a getter and setter. These are ideal for simple local component values. They are called signals as they act as tiny streams that wire your application together. +Here is a basic auto incrementing counter that is updating based on setting the `count` signal. ```jsx import { createSignal, onCleanup } from "solid-js"; @@ -21,49 +21,16 @@ const App = () => { render(() => , document.getElementById("app")); ``` -> **For React Users:** This looks like React Hooks, but it is very different. There are no Hook rules, or concern about stale closures because your Component only runs once. It is only the "Hooks" that re-execute. So they always have the latest. - -Solid's state object are deeply nested reactive data trees useful for global stores, model caches, and 3rd party immutable data interopt. They have a much more powerful setter that allows to specify nested changes and use value and function forms for updates. - -They can be used in Components as well and is the go to choice when data gets more complicated (nested). - -```jsx -import { createState, onCleanup } from "solid-js"; -import { render } from "solid-js/web"; - -const App = () => { - const [state, setState] = createState({ - user: { - firstName: "John", - lastName: "Smith", - get fullName() { - return `${this.firstName} ${this.lastName}`; - } - } - }); - - return ( -
setState("user", "lastName", value => value + "!")}> - {state.user.fullName} -
- ); -}; - -render(() => , document.getElementById("app")); -``` - -Remember if you destructure or spread a state object reactivity is lost. However, unlike Vue we don't separate our `setup` from our view code so there is little concern about transforming or transfering these reactive atoms around. Just access the properties where you need them. - -With Solid State and Context API you really don't need 3rd party global stores. These proxies are optimized part of the reactive system and lend to creating controlled unidirectional patterns. +> **For React Users:** This looks like React Hooks, but it is very different. There are no Hook rules, or concern about stale closures because your Component only runs once. It is only the "Hooks" that re-execute. So they always have the latest references. ## Signals -Signals are the glue that hold the library together. They are a simple primitive that contain values that change over time. With Signals you can track all sorts of changes from various sources in your applications. You can update a Signal manually or from any Async source. +Signals are the glue that hold the library together. They are a simple primitive that contain values that change over time. With Signals you can track all sorts of changes from various sources in your applications. They are not tied to any specific component and can be used wherever whenever. ```js import { createSignal, onCleanup } from "solid-js"; -function useTick(delay) { +function createTick(delay) { const [getCount, setCount] = createSignal(0), handle = setInterval(() => setCount(getCount() + 1), delay); onCleanup(() => clearInterval(handle)); @@ -71,26 +38,28 @@ function useTick(delay) { } ``` -## Accessors Reactive Scope +## Reactive Scope and Tracking Signals are special functions that when executed return their value. In addition they are trackable when executed under a reactive scope. This means that when their value is read (executed) the currently executing reactive scope is now subscribed to the Signal and will re-execute whenever the Signal is updated. -This mechanism is based on the executing function's scope so Signals reads can be composed and nested as many levels as desired. By wrapping a Signal read in a thunk `() => signal()` you have effectively created a derived signal that can be tracked as well. The same holds true for accessing state. Want to use state as a signal just wrap it in a function: +This method of tracking wraps the execution stack so Signals can be accessed any number of levels deep. In so, by wrapping a Signal read in a thunk `() => signal()` you have effectively created a derived signal that can be tracked as well. The same holds true for accessing props or Solid's reactive State proxies. Want to use state as a signal just wrap it in a function: ```js -// I can be tracked +// I can be tracked later const firstName = () => state.user.firstName; return
{firstName()}
; ``` -These accessors are just functions that can be tracked and return a value. No additional primitive or method is needed for them to work as Signals in their own right. However, you need another primitive to create that reactive scope: +These are just functions that can be tracked and return a value. No additional primitive or method is needed for them to work as Signals in their own right. This is because Signals are readonly. Any pure function that wraps a signal is also a Signal. + +However, you need another primitive to actually execute the work and track the these signals. ## Computations A computation is calculation over a function execution that automatically and dynamically tracks any child signals that are accessed during that execution. A computation goes through a cycle on execution where it releases its previous execution's dependencies, then executes grabbing the current dependencies. -There are 3 main computations used by Solid: Memos which are pure and designed to cache values until their reactivity forces re-evaluation, Computeds which are designed to write to other signals, and Effects which are intended to produce side effects after rendering. +There are 2 main types of computations. Those that are pure and meant to derive a value called Memos and those that update the outside world and produce side effects, aptly called Effects. ```js import { createSignal, createEffect, createMemo } from "solid-js"; @@ -106,7 +75,7 @@ setCount(count() + 1); Effects are what allow the DOM to stay up to date. While you don't see them, everytime you write an expression in the JSX(code between the parenthesis `{}`), the compiler is wrapping it in a function and passing it to a `createEffect` call. -Memos allow us to store and access values without re-evaluating them until their dependencies change. +Memos allow us to store and access values without re-evaluating them until their dependencies change. They are very similar to derived Signals mentioned above except they only re-evaluate when their dependencies change and return the last cached value on read. Keep in mind memos are only necessary if you wish to prevent re-evaluation when the value is read. Useful for expensive operations like DOM Node creation. Any example with a memo could also just be a function and effectively be the same without caching as it's just another signal. @@ -126,6 +95,7 @@ setCount(count() + 1); Memos also pass the previous value on each execution. This is useful for reducing operations (obligatory Redux in a couple lines example): ```js +// reducer const reducer = (state, action = {}) => { switch (action.type) { case "LIST/ADD": @@ -135,9 +105,12 @@ const reducer = (state, action = {}) => { } }; +// initial state +const state = { list: [] }; + // redux const [getAction, dispatch] = createSignal(), - getStore = createMemo(state => reducer(state, getAction()), { list: [] }); + getStore = createMemo(state => reducer(state, getAction()), state); // subscribe and dispatch createEffect(() => console.log(getStore().list)); @@ -171,13 +144,6 @@ For convenience Solid provides an `on` operator to set up explict dependencies f createEffect(on(a, v => console.log(v, b()))); ``` -Another situation is maybe you want something to run only on mount: - -```js -// does not update when props update -createEffect(() => untrack(() => console.log("Mounted with", props.a, props.b))); -``` - Solid executes synchronously but sometimes you want to apply multiple changes at once. `batch` allows us to do that without triggering updates multiple times. ```js @@ -197,10 +163,10 @@ _Note: Solid's graph is synchronously executed so any starting point that isn't ## Composition -State and Signals combine wonderfully as wrapping a state selector in a function instantly makes it reactive accessor. They encourage composing more sophisticated patterns to fit developer need. +Solid's primitives combine wonderfully. They encourage composing more sophisticated patterns to fit developer need. ```js -// deep reconciled immutable reducer +// Solid's fine-grained exivalent to React's `useReducer` Hook const useReducer = (reducer, init) => { const [state, setState] = createState(init), [getAction, dispatch] = createSignal(); diff --git a/documentation/state.md b/documentation/state.md index b4e335f74..9d94f7f3b 100644 --- a/documentation/state.md +++ b/documentation/state.md @@ -10,20 +10,38 @@ Through the use of proxies and explicit setters it gives the control of an immut ### `createState(object)` -Initializes with object value and returns an array where the first index is the state object and the second is the setState method. +Solid's state object are deeply nested reactive data trees useful for global stores, model caches, and 3rd party immutable data interopt. They have a much more powerful setter that allows to specify nested changes and use value and function forms for updates. -Initial state consists of a tree of values, including getters that are automatically wrapped in a Memo that can define derived values: +They can be used in Components as well and is the go to choice when data gets more complicated (nested). ```jsx -const [state, setState] = createState({ - firstName: "John", - lastName: "Miller", - get fullName() { - return `${this.firstName} ${this.lastName}`; - } -}); +import { createState } from "solid-js"; +import { render } from "solid-js/web"; + +const App = () => { + const [state, setState] = createState({ + user: { + firstName: "John", + lastName: "Smith", + get fullName() { + return `${this.firstName} ${this.lastName}`; + } + } + }); + + return ( +
setState("user", "lastName", value => value + "!")}> + {state.user.fullName} +
+ ); +}; + +render(() => , document.getElementById("app")); ``` -> Note: getters are only currently supported top level + +Remember if you destructure or spread a state object outside of a computation or JSX reactivity is lost. However, unlike Vue we don't separate our `setup` from our view code so there is little concern about transforming or transfering these reactive atoms around. Just access the properties where you need them. + +With Solid State and Context API you really don't need 3rd party global stores. These proxies are optimized part of the reactive system and lend to creating controlled unidirectional patterns. ### `setState(changes)` diff --git a/packages/solid-ssr/README.md b/packages/solid-ssr/README.md index cb3991ec8..feef2a49a 100644 --- a/packages/solid-ssr/README.md +++ b/packages/solid-ssr/README.md @@ -2,7 +2,7 @@ This library provides tools to help with SSR. So far it's a simple Static Generator. This project is still in progress. -Look at the examples to best understand how to use it. Important I make use of conditional export maps here in node. You need the latest version of Node 14 (works in latest Node 12 too but this example doesn't, sorry). +Look at the examples to best understand how to use it. Important I make use of conditional export maps here in node. You need the latest version of Node 14 (or latest Node 12 but this example doesn't work in Node 12, sorry). ### Examples diff --git a/packages/solid-ssr/examples/async/rollup.config.js b/packages/solid-ssr/examples/async/rollup.config.js index 128daa207..1e45ab8d1 100644 --- a/packages/solid-ssr/examples/async/rollup.config.js +++ b/packages/solid-ssr/examples/async/rollup.config.js @@ -15,10 +15,10 @@ export default [ preserveEntrySignatures: false, external: ["solid-js", "solid-js/web", "path", "express"], plugins: [ - nodeResolve({ preferBuiltins: true }), + nodeResolve({ preferBuiltins: true, exportConditions: ["solid", "node"] }), babel({ babelHelpers: "bundled", - presets: [["solid", { generate: "ssr", hydratable: true, async: true }]] + presets: [["solid", { generate: "ssr", hydratable: true }]] }), common() ] @@ -33,7 +33,7 @@ export default [ ], preserveEntrySignatures: false, plugins: [ - nodeResolve(), + nodeResolve({ exportConditions: ["solid"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "dom", hydratable: true }]] diff --git a/packages/solid-ssr/examples/ssg/rollup.config.js b/packages/solid-ssr/examples/ssg/rollup.config.js index acfba00d4..49d2dcace 100644 --- a/packages/solid-ssr/examples/ssg/rollup.config.js +++ b/packages/solid-ssr/examples/ssg/rollup.config.js @@ -15,10 +15,10 @@ export default [ ], external: ["solid-js", "solid-js/web"], plugins: [ - nodeResolve({ preferBuiltins: true }), + nodeResolve({ preferBuiltins: true, exportConditions: ["solid", "node"] }), babel({ babelHelpers: "bundled", - presets: [["solid", { generate: "ssr", hydratable: true, async: true }]] + presets: [["solid", { generate: "ssr", hydratable: true }]] }), common() ] @@ -33,7 +33,7 @@ export default [ ], preserveEntrySignatures: false, plugins: [ - nodeResolve(), + nodeResolve({ exportConditions: ["solid"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "dom", hydratable: true }]] diff --git a/packages/solid-ssr/examples/ssr/rollup.config.js b/packages/solid-ssr/examples/ssr/rollup.config.js index f72591207..887e3dc15 100644 --- a/packages/solid-ssr/examples/ssr/rollup.config.js +++ b/packages/solid-ssr/examples/ssr/rollup.config.js @@ -14,7 +14,7 @@ export default [ ], external: ["solid-js", "solid-js/web", "path", "express", "stream"], plugins: [ - nodeResolve({ preferBuiltins: true }), + nodeResolve({ preferBuiltins: true, exportConditions: ["solid", "node"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "ssr", hydratable: true }]] @@ -33,7 +33,7 @@ export default [ ], preserveEntrySignatures: false, plugins: [ - nodeResolve(), + nodeResolve({ exportConditions: ["solid"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "dom", hydratable: true }]] diff --git a/packages/solid-ssr/examples/stream/rollup.config.js b/packages/solid-ssr/examples/stream/rollup.config.js index df126b9a7..fed3900b7 100644 --- a/packages/solid-ssr/examples/stream/rollup.config.js +++ b/packages/solid-ssr/examples/stream/rollup.config.js @@ -15,7 +15,7 @@ export default [ ], external: ["solid-js", "solid-js/web", "path", "express", "stream"], plugins: [ - nodeResolve({ preferBuiltins: true }), + nodeResolve({ preferBuiltins: true, exportConditions: ["solid", "node"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "ssr", hydratable: true }]] @@ -33,7 +33,7 @@ export default [ ], preserveEntrySignatures: false, plugins: [ - nodeResolve(), + nodeResolve({ exportConditions: ["solid"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "dom", hydratable: true }]] diff --git a/packages/solid/src/reactive/mutable.ts b/packages/solid/src/reactive/mutable.ts index 61f825d2b..05ad3388b 100644 --- a/packages/solid/src/reactive/mutable.ts +++ b/packages/solid/src/reactive/mutable.ts @@ -1,6 +1,5 @@ -import { Listener, createSignal, hashValue, registerGraph } from "./signal"; +import { Listener, createSignal, hashValue, registerGraph, batch } from "./signal"; import { - wrap, unwrap, isWrappable, getDataNodes, @@ -41,11 +40,7 @@ const proxyTraps: ProxyHandler = { node[0](); } return wrappable - ? wrap( - value, - "_SOLID_DEV_" && target[$NAME] && `${target[$NAME]}:${property as string}`, - proxyTraps - ) + ? wrap(value, "_SOLID_DEV_" && target[$NAME] && `${target[$NAME]}:${property as string}`) : value; }, @@ -62,18 +57,38 @@ const proxyTraps: ProxyHandler = { getOwnPropertyDescriptor: proxyDescriptor }; +function wrap(value: T, name?: string): State { + let p = value[$PROXY]; + if (!p) { + Object.defineProperty(value, $PROXY, { value: (p = new Proxy(value, proxyTraps)) }); + let keys = Object.keys(value), + desc = Object.getOwnPropertyDescriptors(value); + for (let i = 0, l = keys.length; i < l; i++) { + const prop = keys[i]; + if (desc[prop].set) { + const og = desc[prop].set!, + set = (v: T[keyof T]) => batch(() => og.call(p, v)); + Object.defineProperty(value, prop, { + set + }); + } + } + if ("_SOLID_DEV_" && name) Object.defineProperty(value, $NAME, { value: name }); + } + return p; +} + export function createMutable( state: T | State, options?: { name?: string } ): State { - const unwrappedState = unwrap(state || {}, true); + const unwrappedState = unwrap(state || {}); const wrappedState = wrap( unwrappedState, - "_SOLID_DEV_" && ((options && options.name) || hashValue(unwrappedState)), - proxyTraps + "_SOLID_DEV_" && ((options && options.name) || hashValue(unwrappedState)) ); if ("_SOLID_DEV_") { - const name = options && options.name || hashValue(unwrappedState); + const name = (options && options.name) || hashValue(unwrappedState); registerGraph(name, { value: unwrappedState }); } return wrappedState as State; diff --git a/packages/solid/src/reactive/state.ts b/packages/solid/src/reactive/state.ts index 13c43417d..9f8a26da5 100644 --- a/packages/solid/src/reactive/state.ts +++ b/packages/solid/src/reactive/state.ts @@ -1,4 +1,4 @@ -import { Listener, createSignal, createMemo, batch, hashValue, registerGraph } from "./signal"; +import { Listener, createSignal, batch, hashValue, registerGraph } from "./signal"; export const $RAW = Symbol("state-raw"), $NODE = Symbol("state-node"), $PROXY = Symbol("state-proxy"), @@ -35,32 +35,10 @@ export type State = { AddSymbolToStringTag & AddCallable; -export function wrap( - value: T, - name?: string, - traps?: ProxyHandler -): State { +function wrap(value: T, name?: string): State { let p = value[$PROXY]; if (!p) { - Object.defineProperty(value, $PROXY, { value: (p = new Proxy(value, traps || proxyTraps)) }); - let keys = Object.keys(value), - desc = Object.getOwnPropertyDescriptors(value); - for (let i = 0, l = keys.length; i < l; i++) { - const prop = keys[i]; - if (desc[prop].get) { - const get = createMemo(desc[prop].get!.bind(p), undefined, true); - Object.defineProperty(value, prop, { - get - }); - } - if (desc[prop].set) { - const og = desc[prop].set!, - set = (v: T[keyof T]) => batch(() => og.call(p, v)); - Object.defineProperty(value, prop, { - set - }); - } - } + Object.defineProperty(value, $PROXY, { value: (p = new Proxy(value, proxyTraps)) }); if ("_SOLID_DEV_" && name) Object.defineProperty(value, $NAME, { value: name }); } return p; @@ -74,7 +52,7 @@ export function isWrappable(obj: any) { ); } -export function unwrap(item: any, skipGetters?: boolean): T { +export function unwrap(item: any): T { let result, unwrapped, v, prop; if ((result = item != null && item[$RAW])) return result; if (!isWrappable(item)) return item; @@ -83,17 +61,17 @@ export function unwrap(item: any, skipGetters?: boolean): T if (Object.isFrozen(item)) item = item.slice(0); for (let i = 0, l = item.length; i < l; i++) { v = item[i]; - if ((unwrapped = unwrap(v, skipGetters)) !== v) item[i] = unwrapped; + if ((unwrapped = unwrap(v)) !== v) item[i] = unwrapped; } } else { if (Object.isFrozen(item)) item = Object.assign({}, item); let keys = Object.keys(item), - desc = skipGetters && Object.getOwnPropertyDescriptors(item); + desc = Object.getOwnPropertyDescriptors(item); for (let i = 0, l = keys.length; i < l; i++) { prop = keys[i]; - if (skipGetters && (desc as any)[prop].get) continue; + if ((desc as any)[prop].get) continue; v = item[prop]; - if ((unwrapped = unwrap(v, skipGetters)) !== v) item[prop] = unwrapped; + if ((unwrapped = unwrap(v)) !== v) item[prop] = unwrapped; } } return item; @@ -330,7 +308,7 @@ export function createState( state: T | State, options?: { name?: string } ): [State, SetStateFunction] { - const unwrappedState = unwrap(state || {}, true); + const unwrappedState = unwrap(state || {}); const wrappedState = wrap( unwrappedState, "_SOLID_DEV_" && ((options && options.name) || hashValue(unwrappedState)) From ed6c23d8f9ceca8480b7911b59d2a89059dac95f Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Wed, 3 Feb 2021 23:45:27 -0800 Subject: [PATCH 13/14] v0.24.0-beta.4 --- lerna.json | 2 +- packages/react-solid-state/package-lock.json | 2 +- packages/react-solid-state/package.json | 4 ++-- packages/solid-element/package.json | 4 ++-- packages/solid-meta/package.json | 4 ++-- packages/solid-rx/package.json | 4 ++-- packages/solid-ssr/package.json | 6 +++--- packages/solid-styled-components/package.json | 4 ++-- packages/solid-styled-jsx/package.json | 4 ++-- packages/solid/package.json | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lerna.json b/lerna.json index 46f82639a..97b155cc3 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": [ "packages/*" ], - "version": "0.24.0-beta.3" + "version": "0.24.0-beta.4" } diff --git a/packages/react-solid-state/package-lock.json b/packages/react-solid-state/package-lock.json index cfe36d773..4e49290b4 100644 --- a/packages/react-solid-state/package-lock.json +++ b/packages/react-solid-state/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-solid-state", - "version": "0.24.0-beta.2", + "version": "0.24.0-beta.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/react-solid-state/package.json b/packages/react-solid-state/package.json index 114bc58e7..f1594eaa8 100644 --- a/packages/react-solid-state/package.json +++ b/packages/react-solid-state/package.json @@ -1,7 +1,7 @@ { "name": "react-solid-state", "description": "Auto tracking state management for modern React", - "version": "0.24.0-beta.2", + "version": "0.24.0-beta.4", "author": "Ryan Carniato", "license": "MIT", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/react-solid-state#readme", @@ -17,7 +17,7 @@ "test:coverage": "jest --coverage" }, "dependencies": { - "solid-js": "^0.24.0-beta.2" + "solid-js": "^0.24.0-beta.4" }, "peerDependencies": { "react": "*", diff --git a/packages/solid-element/package.json b/packages/solid-element/package.json index 678232ad2..64a2de167 100644 --- a/packages/solid-element/package.json +++ b/packages/solid-element/package.json @@ -3,7 +3,7 @@ "description": "Webcomponents wrapper for Solid", "author": "Ryan Carniato", "license": "MIT", - "version": "0.24.0-beta.2", + "version": "0.24.0-beta.4", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/solid-element#readme", "type": "module", "main": "dist/index.js", @@ -23,6 +23,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.2" + "solid-js": "^0.24.0-beta.4" } } diff --git a/packages/solid-meta/package.json b/packages/solid-meta/package.json index 25b5f5388..cdbd57eb8 100644 --- a/packages/solid-meta/package.json +++ b/packages/solid-meta/package.json @@ -1,7 +1,7 @@ { "name": "solid-meta", "description": "Solid wrapper for Styled JSX", - "version": "0.24.0-beta.2", + "version": "0.24.0-beta.4", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -30,6 +30,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.2" + "solid-js": "^0.24.0-beta.4" } } diff --git a/packages/solid-rx/package.json b/packages/solid-rx/package.json index 5e8fe4c1a..1785f6513 100644 --- a/packages/solid-rx/package.json +++ b/packages/solid-rx/package.json @@ -1,6 +1,6 @@ { "name": "solid-rx", - "version": "0.24.0-beta.2", + "version": "0.24.0-beta.4", "description": "Functionally reactive extensions for Solid.js", "author": "Ryan Carniato ", "homepage": "https://github.com/ryansolid/solid/blob/master/packages/solid-rx#readme", @@ -29,6 +29,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.2" + "solid-js": "^0.24.0-beta.4" } } diff --git a/packages/solid-ssr/package.json b/packages/solid-ssr/package.json index 580f2ed9f..157561158 100644 --- a/packages/solid-ssr/package.json +++ b/packages/solid-ssr/package.json @@ -1,7 +1,7 @@ { "name": "solid-ssr", "description": "Patches node to work with Solid's SSR", - "version": "0.24.0-beta.3", + "version": "0.24.0-beta.4", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -26,8 +26,8 @@ "devDependencies": { "babel-preset-solid": "^0.24.0-beta.3", "express": "^4.17.1", - "solid-js": "^0.24.0-beta.2", - "solid-styled-components": "^0.24.0-beta.2" + "solid-js": "^0.24.0-beta.4", + "solid-styled-components": "^0.24.0-beta.4" }, "peerDependencies": { "solid-js": "*" diff --git a/packages/solid-styled-components/package.json b/packages/solid-styled-components/package.json index 524ed1c0c..cc532e5dd 100644 --- a/packages/solid-styled-components/package.json +++ b/packages/solid-styled-components/package.json @@ -1,7 +1,7 @@ { "name": "solid-styled-components", "description": "Styled Components for Solid", - "version": "0.24.0-beta.2", + "version": "0.24.0-beta.4", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -28,6 +28,6 @@ "solid-js": "^0.23.0" }, "devDependencies": { - "solid-js": "^0.24.0-beta.2" + "solid-js": "^0.24.0-beta.4" } } diff --git a/packages/solid-styled-jsx/package.json b/packages/solid-styled-jsx/package.json index 98c8d8f1f..1f2e5e67c 100644 --- a/packages/solid-styled-jsx/package.json +++ b/packages/solid-styled-jsx/package.json @@ -1,7 +1,7 @@ { "name": "solid-styled-jsx", "description": "Solid wrapper for Styled JSX", - "version": "0.24.0-beta.2", + "version": "0.24.0-beta.4", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -23,6 +23,6 @@ "styled-jsx": "^3.2.5" }, "devDependencies": { - "solid-js": "^0.24.0-beta.2" + "solid-js": "^0.24.0-beta.4" } } diff --git a/packages/solid/package.json b/packages/solid/package.json index 162b527da..59ede1252 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -1,7 +1,7 @@ { "name": "solid-js", "description": "A declarative JavaScript library for building user interfaces.", - "version": "0.24.0-beta.2", + "version": "0.24.0-beta.4", "author": "Ryan Carniato", "license": "MIT", "homepage": "https://github.com/ryansolid/solid#readme", From 65c31b36f1686a920d81e689ab42baceb6827ba9 Mon Sep 17 00:00:00 2001 From: Ryan Carniato Date: Wed, 3 Feb 2021 23:58:42 -0800 Subject: [PATCH 14/14] prettier cleanup --- packages/solid/src/reactive/signal.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/solid/src/reactive/signal.ts b/packages/solid/src/reactive/signal.ts index 241eb0da7..48b3560ea 100644 --- a/packages/solid/src/reactive/signal.ts +++ b/packages/solid/src/reactive/signal.ts @@ -276,9 +276,9 @@ export function untrack(fn: () => T): T { return result; } -type ReturnTypeArray = { [P in keyof T]: T[P] extends (() => infer U) ? U : never }; +type ReturnTypeArray = { [P in keyof T]: T[P] extends () => infer U ? U : never }; export function on T>, U>( - ...args: X['length'] extends 1 + ...args: X["length"] extends 1 ? [w: () => T, fn: (v: T, prev: T | undefined, prevResults?: U) => U] : [...w: X, fn: (v: ReturnTypeArray, prev: ReturnTypeArray | [], prevResults?: U) => U] ): (prev?: U) => U { @@ -377,8 +377,8 @@ export interface Context { defaultValue: T; } -export function createContext(): Context -export function createContext(defaultValue: T): Context +export function createContext(): Context; +export function createContext(defaultValue: T): Context; export function createContext(defaultValue?: T): Context { const id = Symbol("context"); return { id, Provider: createProvider(id), defaultValue };