Repository: solidjs/solid Branch: main Commit: a0524c066d8f Files: 178 Total size: 822.6 KB Directory structure: gitextract_kiwcvw3q/ ├── .changeset/ │ ├── README.md │ └── config.json ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ └── main-ci.yml ├── .gitignore ├── .gitpod.yml ├── .nvmrc ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── documentation/ │ └── resources/ │ └── examples.md ├── package.json ├── packages/ │ ├── babel-preset-solid/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── index.js │ │ ├── package.json │ │ └── test.js │ ├── solid/ │ │ ├── .npmignore │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── babel.config.cjs │ │ ├── bench/ │ │ │ ├── bench.cjs │ │ │ ├── libraries/ │ │ │ │ ├── kairo.cjs │ │ │ │ ├── preact.cjs │ │ │ │ ├── rval-mod.cjs │ │ │ │ ├── s-mod.cjs │ │ │ │ ├── s.cjs │ │ │ │ ├── sinuous-mod.cjs │ │ │ │ ├── sinuous.cjs │ │ │ │ ├── usignal.cjs │ │ │ │ └── vuerx.cjs │ │ │ ├── prototypes/ │ │ │ │ ├── message-noarray.cjs │ │ │ │ ├── message.cjs │ │ │ │ ├── queue-noarray.cjs │ │ │ │ └── queue.cjs │ │ │ └── results.md │ │ ├── h/ │ │ │ ├── README.md │ │ │ ├── jsx-dev-runtime/ │ │ │ │ └── package.json │ │ │ ├── jsx-runtime/ │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ └── index.ts │ │ │ │ └── tsconfig.json │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── hyperscript.ts │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── html/ │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── index.ts │ │ │ │ └── lit.ts │ │ │ └── tsconfig.json │ │ ├── jsx-runtime.d.ts │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── reactive/ │ │ │ │ ├── array.ts │ │ │ │ ├── observable.ts │ │ │ │ ├── scheduler.ts │ │ │ │ └── signal.ts │ │ │ ├── render/ │ │ │ │ ├── Suspense.ts │ │ │ │ ├── component.ts │ │ │ │ ├── flow.ts │ │ │ │ ├── hydration.ts │ │ │ │ └── index.ts │ │ │ └── server/ │ │ │ ├── index.ts │ │ │ ├── reactive.ts │ │ │ └── rendering.ts │ │ ├── store/ │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── index.ts │ │ │ │ ├── modifiers.ts │ │ │ │ ├── mutable.ts │ │ │ │ ├── server.ts │ │ │ │ └── store.ts │ │ │ ├── test/ │ │ │ │ ├── modifiers.spec.ts │ │ │ │ ├── mutable.spec.ts │ │ │ │ ├── mutableWithClass.spec.tsx │ │ │ │ └── store.spec.ts │ │ │ ├── tsconfig.build.json │ │ │ └── tsconfig.json │ │ ├── test/ │ │ │ ├── MessageChannel.ts │ │ │ ├── array.spec.ts │ │ │ ├── component.bench.ts │ │ │ ├── component.spec.ts │ │ │ ├── component.type-tests.ts │ │ │ ├── dev.spec.ts │ │ │ ├── external-source.spec.ts │ │ │ ├── observable.spec.ts │ │ │ ├── rendering.spec.ts │ │ │ ├── resource.spec.ts │ │ │ ├── resource.type-tests.ts │ │ │ ├── scheduler.spec.ts │ │ │ ├── signals.memo.spec.ts │ │ │ ├── signals.spec.ts │ │ │ └── signals.type-tests.ts │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── tsconfig.test.json │ │ ├── universal/ │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── index.ts │ │ │ │ └── universal.ts │ │ │ └── tsconfig.json │ │ ├── vite.config.mjs │ │ └── web/ │ │ ├── README.md │ │ ├── package.json │ │ ├── server/ │ │ │ ├── index.ts │ │ │ └── server.ts │ │ ├── src/ │ │ │ ├── client.ts │ │ │ ├── core.ts │ │ │ ├── index.ts │ │ │ ├── jsx.ts │ │ │ └── server-mock.ts │ │ ├── storage/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ ├── tsconfig.build.json │ │ │ └── tsconfig.json │ │ ├── test/ │ │ │ ├── context.spec.tsx │ │ │ ├── dynamic.spec.tsx │ │ │ ├── element.spec.tsx │ │ │ ├── errorboundary.spec.tsx │ │ │ ├── for.spec.tsx │ │ │ ├── index.spec.tsx │ │ │ ├── portal.spec.tsx │ │ │ ├── server-mock.spec.tsx │ │ │ ├── show.spec.tsx │ │ │ ├── suspense.spec.tsx │ │ │ └── switch.spec.tsx │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── solid-element/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── sample.jsx │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── solid-ssr/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── examples/ │ │ │ ├── .gitignore │ │ │ ├── async/ │ │ │ │ ├── index.js │ │ │ │ └── rollup.config.js │ │ │ ├── shared/ │ │ │ │ ├── src/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── App.js │ │ │ │ │ │ ├── Home.js │ │ │ │ │ │ ├── Profile/ │ │ │ │ │ │ │ ├── Profile.js │ │ │ │ │ │ │ └── index.js │ │ │ │ │ │ └── Settings.js │ │ │ │ │ ├── index.js │ │ │ │ │ └── router.js │ │ │ │ └── static/ │ │ │ │ └── styles.css │ │ │ ├── ssg/ │ │ │ │ ├── export.js │ │ │ │ ├── index.js │ │ │ │ └── rollup.config.js │ │ │ ├── ssr/ │ │ │ │ ├── index.js │ │ │ │ └── rollup.config.js │ │ │ └── stream/ │ │ │ ├── index.js │ │ │ └── rollup.config.js │ │ ├── package.json │ │ └── static/ │ │ ├── index.cjs │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── writeToDisk.cjs │ │ └── writeToDisk.js │ └── test-integration/ │ ├── CHANGELOG.md │ ├── babel.config.cjs │ ├── package.json │ ├── test-imports.mjs │ ├── tests/ │ │ └── downloaded.spec.ts │ └── tsconfig.json ├── pnpm-workspace.yaml ├── tsconfig.json ├── tsconfig.test.json └── turbo.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .changeset/README.md ================================================ # Changesets Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) ================================================ FILE: .changeset/config.json ================================================ { "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], "linked": [["solid-js", "babel-preset-solid"]], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": [] } ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = LF charset = utf-8 insert_final_newline = true ================================================ FILE: .github/FUNDING.yml ================================================ open_collective: solid ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: '🐛 Bug report' description: Create a report to help us improve body: - type: markdown attributes: value: | Thank you for reporting an issue :pray:. This issue tracker is for reporting bugs found in `solid` (https://github.com/solidjs/solid). If you have a question about how to achieve something and are struggling, please post a question inside of `solid` Discussions tab: https://github.com/solidjs/solid/discussions Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already: - `solid` Issues tab: https://github.com/solidjs/solid/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc - `solid` closed issues tab: https://github.com/solidjs/solid/issues?q=is%3Aissue+sort%3Aupdated-desc+is%3Aclosed - `solid` Discussions tab: https://github.com/solidjs/solid/discussions The more information you fill in, the better the community can help you. - type: textarea id: description attributes: label: Describe the bug description: Provide a clear and concise description of the challenge you are running into. validations: required: true - type: input id: link attributes: label: Your Example Website or App description: | Which website or app were you using when the bug happened? Note: - Your bug will may get fixed much faster if we can run your code and it doesn't have dependencies other than the solid-js npm package. - To create a shareable code example you can use Stackblitz (https://stackblitz.com/). Please no localhost URLs. - Please read these tips for providing a minimal example: https://stackoverflow.com/help/mcve. placeholder: | e.g. https://stackblitz.com/edit/...... OR Github Repo validations: required: true - type: textarea id: steps attributes: label: Steps to Reproduce the Bug or Issue description: Describe the steps we have to take to reproduce the behavior. placeholder: | 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error validations: required: true - type: textarea id: expected attributes: label: Expected behavior description: Provide a clear and concise description of what you expected to happen. placeholder: | As a user, I expected ___ behavior but i am seeing ___ validations: required: true - type: textarea id: screenshots_or_videos attributes: label: Screenshots or Videos description: | If applicable, add screenshots or a video to help explain your problem. For more information on the supported file image/file types and the file size limits, please refer to the following link: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/attaching-files placeholder: | You can drag your video or image files inside of this editor ↓ - type: textarea id: platform attributes: label: Platform value: | - OS: [e.g. macOS, Windows, Linux] - Browser: [e.g. Chrome, Safari, Firefox] - Version: [e.g. 91.1] validations: required: true - type: textarea id: additional attributes: label: Additional context description: Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: 🤔 Long question or idea url: https://github.com/solidjs/solid/discussions about: Ask long-form questions and discuss ideas. - name: 💬 Community Chat url: https://discord.com/invite/solidjs about: Ask quick questions or simply chat on the SolidJS Discord server. - name: 💬 New Updates (Twitter) url: https://twitter.com/solid_js about: Link to our twitter account if you want to follow us and stay up to date with news - name: 💬 New Updates (Reddit) url: https://www.reddit.com/r/solidjs/ about: Link to our twitter account if you want to follow us and stay up to date with news - name: 🍿 YouTube Channel url: https://www.youtube.com/c/RyanCarniato9 about: Are you a techlead or wanting to learn more about SolidJS? Watch Ryan Carniato (creator of solidJS) share detailed technical deep dives into topics like how SolidJS, Server Side Rendering (SSR), ReactJS, Svelte, MarkoJS & Partial Hydration works under the hood and so much more ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Summary ## How did you test this change? ================================================ FILE: .github/workflows/main-ci.yml ================================================ name: 'Solid CI' on: pull_request: branches: - '*' push: branches: - main jobs: job: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: node-version-file: ".nvmrc" registry-url: "https://registry.npmjs.org" cache: "pnpm" - name: Installing deps run: pnpm install - name: Building run: pnpm run build - name: Testing & Coverage run: | pnpm run test pnpm run coverage - name: Coveralls uses: coverallsapp/github-action@v2 with: base-path: packages/solid path-to-lcov: "./packages/solid/coverage/lcov.info" github-token: ${{ secrets.GITHUB_TOKEN }} - name: Archive production artifacts uses: actions/upload-artifact@v4 with: name: dist-folder path: | '*/dist' '*/types' ================================================ FILE: .gitignore ================================================ node_modules/ dist/ lib/ .vscode/ .idea/ coverage/ types/ .DS_Store .turbo/ packages/solid/src/jsx.d.ts packages/solid/h/jsx-runtime/src/jsx.d.ts packages/solid/web/server/server.d.ts packages/solid/web/src/client.d.ts packages/solid/universal/src/universal.d.ts # vscode devcontainers see issues #419 and #421 for context /.devcontainer.json /devcontainer ================================================ FILE: .gitpod.yml ================================================ tasks: - init: pnpm install && pnpm run build vscode: extensions: ["esbenp.prettier-vscode"] ================================================ FILE: .nvmrc ================================================ 22.13.1 ================================================ FILE: .prettierrc ================================================ { "trailingComma": "none", "tabWidth": 2, "semi": true, "singleQuote": false, "arrowParens": "avoid", "printWidth": 100 } ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## 1.8.0 - 2023-10-09 I admit this is not the most exciting release from a feature standpoint. We are in that holding pattern between the end of 1.x and the start of 2.0. We recently made our new reactive experiments public and continue to build those out in public with [@solidjs/signals](https://github.com/solidjs/signals). This version is more about addressing some of the fundamentals that will help us in other projects like SolidStart while we do the transition. A big part of this is applying what we have learned when doing performance benchmarks for the work that has been funded by [Google Chrome Aurora](https://www.solidjs.com/blog/chrome-supports-solidjs). Async and Resources need work and are too all in. It is great to have a solution but now that we have a better understanding we need to start breaking things apart into their fundamental pieces. ### De-duping Streaming Serialization This is the marquee feature of this release and is largely the work of @lxsmnsyc. Solid has been able to serialize promises and do streaming for a couple of years now, but it was very special-cased. Now it is a generic mechanism. This matters because it means that we have decoupled the promise serialization from Resources, and in so decoupled the whole when the stream is done from them. This opens up things like nested promises. More so we have a mechanism now that deeply de-dupes data serialized across flushes. This is important for features like Islands where you might pass the same props to multiple Islands across different Suspense boundaries and don't want to send the data more than once. And even examples where that data can be accessed at varying depths (recursive comments in say a Hackernews site). ### Hydration Improvements Fragments for Hydration have been a bit of a pain and we keep seeming to have different issues reported around element duplication. Most commonly this has been around where there are `lazy` component siblings or where the fragment is top-level. After looking into and fixing an [issue for Astro](https://github.com/withastro/astro/pull/8365) I decided to look at some of the oldest bugs in Solid and found it was a similar bug. In many cases, the DOM can change throughout Hydration while doing things like streaming but we need to pause and resume hydration because code isn't available yet. While we don't create elements during hydration, getting an accurate snapshot of the DOM for the current state for future list reconciliation is a process we've had a few tries at but in 1.8 we update this in a way that makes sure it doesn't get out of date. Also in 1.8, we have added some performance improvements to hydration in the form of not redundantly setting attributes or props as the page hydrates similar to how we don't update text. This is all migration towards a future where we don't need to do as much hydration, but it is important to note that values will be kept as they were on the server rather than how they may compute at runtime during hydration. ### Smaller Templates In 1.7 we removed unnecessary closing tags from template strings. It was a bit painful because we were a bit overzealous at first. While I believe in the end we got to a good place, ultimately all but the simplest reductions have been hidden behind a compiler flag(`omitNestedClosingTags`). Thanks to work from @intrnl we are implementing another template size reduction technique of removing unnecessary quotes. Quotes are actually not required by HTML in some cases and it can add up. ### Other #### Fix NGINX Server Side Includes Comments led with `#` are treated as special directives for a few different servers so we've needed to change our open hydration markers to `$`. As usual, your version of Solid and the Babel Plugin should be the same to ensure this matches up. #### Better Guards on Global Scripts Solid uses an inline HydrationScript as a way to do processing before the framework and code have loaded. To handle things like event capture and streaming. However, we didn't do a good job of guarding the right thing when multiple were added to the same page, a situation that can happen in Micro-frontends or 3rd party Islands solutions. Now the script guards against duplicate inclusion. ## 1.7.0 - 2023-03-30 Solid has experienced incredible growth in usage the last 6 months. Companies are using it to power production applications and SolidStart Beta has been a big part of that. As a natural part of this growth and increased use at scale we are continuing to learn what works well and what the rough edges in Solid are today. This v1.7 release marks the beginning of the migration roadmap to v2.0. We are beginning to re-evaluate core APIs and will begin introducing new ones while reasonably deprecating older ones in a manner that eases breaking changes. Our intention is to ease the broader ecosystem into preparing for improvements that a major 2.0 will unlock for the whole community. ### Improved TypeScript #### Null-Asserted Control Flow One of the pains of using Solid with TypeScript has been that JSX control flows can't really type narrow. This is true, but starting with the migration to explicit `keyed` in v1.5 we now complete this story by introducing callback forms for `` and `` that work when non-keyed. The main difference is the callback form instead of passing in the value as it does when `keyed`, passes in a function that is type narrowed. ```js // keyed w/ callback - reruns full callback on change {nonNullUser =>
{nonNullUser.name}
}
// non-keyed w/o callback... - only updates the one expression, needs ! assertion
{user()!.name}
// NEW! // non-keyed w/ callback - only updates the one expression {nonNullUser =>
{nonNullUser().name}
}
``` Keep in mind because we are non-null asserting the input signal so it won't expect null in closures that execute when the condition is no longer satisfied. For this reason the accessor from the callback is special and will throw when attempted to be accessed when the condition is no longer true. This may be unexpected but it is our best attempt to keep TypeScript strict and not present inconsistency in reactivity. Luckily this only applies to things like timers which you should be cleaning up anyway and not things like event handlers. We recommend using the original conditions source in those closures if you must. #### Better Event Types for Input Elements This has irked people for a while but we come by it honestly, `target` is gives you a type of `Element` rather than the specific element that is the target. That means no access to `.value` or `.checked`. The reason is there is no way to know at compile time what the target of an event will be. The `currentTarget` will be the element you attach the event to but the target can be anything. There is a way to work around this though, in that if we know the `currentTarget` is of type that generates the event and that the `currentTarget` is the the type of this element we can assume it is the `target` as well. Not perfect logic but it is what React does and we do too. Now `onInput`, `onChange`, `onBlur`, `onFocus`, `onFocusIn`, and `onFocusOut` all support more detailed `target` when applied to `HTMLInputElement`, `HTMLTextAreaElement`, and `HTMLSelectElement`. #### Stricter JSX Elements Strict JSX elements have been tricky because we have to acknowledge at a certain point that TypeScript is to serve our purposes rather than to represent all possible values that could work. For us the ambiguity lies in functions. Solid's JSX needs to accept functions to handle dynamic insertion. However, in authoring it leads to awkward situations. The first you hit the first time use Solid. You create that counter and don't call `count` as a function and it works. ```js function Counter() { const [count, setCount] = createSignal(1); return ; } ``` This example works in some places and not others which might lead to the wrong conclusions. The second place you might hit this is when you get a little further on your journey and decide you need a component to re-render and decide that you can just wrap the whole thing in a function: ```js function MyComp(props) { return () => { // look working early returns if (props.count > 5) { return
Maximum Tries
; } return
Attempt {props.count}
; }; } ``` Again this seems fine, except the fact that every time `count` changes you are recreating all the DOM Elements even when it resolves to the same conditional. Eventually you might even not think twice about passing functions into children of arbitrary components: ```js {() =>
{resource()}
}
``` But what does this do? When is the function called? As it turns out removing functions from `JSX.Element` type makes all of these scenarios error. Components only expect the values dictated by their types. ```js function MyLayout(props: { children: JSX.Element }): JSX.Element; function MyFor(props: { each: T[], children: (item: T) => U }): JSX.Element; // valid Hello

Hello

{name()} {name() &&

Hello

}
{(() => { return })()} {untrack(() => { return

{name()}

})}
{(user) =>
{user.name}
}
// invalid {name} {() =>

Hello

}
{() => "Hello"} {() => name() &&

Hello

}
{(user) =>
{user.name}
}
Not a Function
``` The tradeoff here is that authoring components you can no longer just return a Signal or Memo without casting. If using JSX you can always return a Fragment. If not you will need to cast to `unknown as JSX.Element`. ### Better Errors and Cleanup #### `catchError` replaces `onError` Error Handling is complicated enough without having to try to guess how they propagate. `onError` admittedly is a lower level primitive but fundamentally had this flaw. It worked by registering an error handler on the parent scope, but left it ambiguous how to handle siblings. Is it a queue? Are they independent? As a result we are introducing `catchError` in this release which introduces its own scope to catch any errors below it. The first argument in the primitive is similar to the try and the second argument is the catch. ```js catchError( () => { // do stuff throw new Error("I've Errored"); }, err => console.log(err) ); ``` `onError` will still be present until it can be removed in a future major version. #### Standardized Errors Error Handling has had many weird edge cases introduced by applications throwing unusual values. In v1.7 we wrap all thrown values that aren't of type `Error` in a `new Error` and attach the original thrown value as `.cause`. ### More Performant Dev Tools Now that [Solid Dev Tools](https://github.com/thetarnav/solid-devtools) have been stabilizing, we have a much better idea what support we need for them. In so we were able to remove the very costly serialization we were doing for generating unique identifiers. Conventions around naming and exports were streamlined and standardized as well. ### Others - Smaller compiled output, remove auxilary closing tags - Support for `prop:` and `attr:` in Spreads - Don't apply special props (like `readonly`) to custom elements - Introduced improved serializer, [seroval](https://github.com/lxsmnsyc/seroval) - Fixed quirks in Solid's treeshaking in Rollup - Minify inline class and style attributes - Update `solid-ssr` to type `"module"` ## 1.6.0 - 2022-10-20 Solid v1.6 doesn't bring a ton of new features but brings some big improvements in existing ones. ### Highlights #### Official Partial Hydration Support Solid has worked for quite some time in partial hydrated ("Islands") frameworks like Astro, Iles, Solitude, etc.. but now we have added core features to support this effort better. These features are mostly designed for metaframework authors rather than the end user they are exposed through a couple APIs. `` joins `` as being a way to resume hydration and hydration ids during server rendering. Now we can stop and start hydratable sections. This is important because it opens up a new optimization. `createResource` calls under non-hydrating sections do not serialize. That means that resources that are server only stay on the server. The intention is that hydrating Islands can then serialize their `props` coming in. Essentially only shipping the JSON for data actually used on the client. The power here is static markup can interview dynamic components. ```js

Server Rendered Header

Server Rendered Sub Header

{serverOnlyResource().text}

More server-renderd content

``` Keep in mind Server rendered content like this can only be rendered on the server so to maintain a client navigation with this paradigm requires a special router that handles HTML partials. Similarly we want the trees to talk to each other so `hydrate` calls now have been expanded to accept a parent `Owner` this will allow Islands to communicate through Contex without shipping the whole tree to browser. ```js

Server Rendered Header

Server Rendered Sub Header

``` These improvements make it easier to create Partial Hydration solutions on top of Solid, and serve to improve the capabilities of the ones we already have. #### Native Spread Improvements Native spreads are something we started at very naively. Simply just iterating an object that has some reactive properties and updating the DOM element. However, this didn't take into consideration two problems. First properties on objects can change, they can be added or removed, and more so the object itself can be swapped. Since Solid doesn't re-render it needs to keep a fixed reference to the merged properties. Secondly, these are merged. Properties override others. What this means is we need to consider the element holistically to know that the right things are applied. For Components this was a never a problem since they are just function calls. Unfortunately for native elements this means all those compiler optimizations we do for specific bindings now need to get pulled into this. Which is why we avoided it in the past. But the behavior was too unpredictable. In 1.6 we have smartened spread to merge properly using similar approach to how process Components. We've also found new ways to optimize the experience. (See below). ### Other Improvements #### Deproxification Working on new Spread behavior we realized that while we can't tell from compilation which spreads can change. We can tell at runtime which are proxies. And in so if we only need to merge things which don't swap, and aren't proxies we can avoid making a Proxy. What is great about this is it has a cascading effect. If component props aren't a proxy, then `splitProps` and `mergeProps` don't need to create them, and so on. While this requires a little extra code it is a real win. We get a lot request for low end IoT devices because of Solid's incredible performance. In tests Solid outperforms many of the Virtual DOM solutions in this space. However most of them don't support proxies. So now if you don't use a `Store` or swap out the props object: ```js // this is fine
// these could swap out the object so they make proxies
// or
``` We don't need to introduce any proxy the user didn't create. This makes Solid a viable option for these low-end devices. ## 1.5.0 - 2022-08-26 ### Key Highlights #### New Batching Behavior Solid 1.4 patched a long time hole in Solid's behavior. Until that point Stores did not obey batching. However, it shone a light on something that should maybe have been obvious before. Batching behavior which stays in the past is basically broken for mutable data, No Solid only has `createMutable` and `produce` but with these sort of primitives the sole purpose is that you perform a sequence of actions, and batching not making this properly was basically broken. Adding an element to an array then removing another item shouldn't just skip the first operation. ```js const store = createMutable(["a", "b", "c"]); const move = store.splice(1, 1); store.splice(0, 0, ...move); // solid 1.4 // ["b", "a", "b", "c"]; // solid 1.5 // ["b", "a", "c"]; ``` After a bunch of careful thought and auditting we decided that Solid's `batch` function should behave the same as how reactivity propagates in the system once a signal is set. As in we just add observers to a queue to run, but if we read from a derived value that is stale it will evaluate eagerly. In so signals will update immediately in a batch now and any derived value will be on read. The only purpose of it is to group writes that begin outside of the reactive system, like in event handlers. #### More Powerful Resources Resources continue to get improvements. A common pattern in Islands frameworks like Astro is to fetch the data from the out side and pass it in. In this case you wouldn't want Solid to do the fetching on initial render or the serialization, but you still may want to pass it to a resource so it updates on any change. For that to work reactivity needs to run in the browser. The whole thing has been awkward to wire up but no longer. `ssrLoadFrom` field lets you specify where the value comes from during ssr. The default is `server` which fetches on the server and serializes it for client hydration. But `initial` will use the `initialValue` instead and not do any fetching or addtional serialization. ```js const [user] = createResource(fetchUser, { initialValue: globalThis.DATA.user, ssrLoadFrom: "initial" }); ``` We've improved TypeScript by adding a new `state` field which covers a more detailed view of the Resource state beyond `loading` and `error`. You can now check whether a Resource is `"unresolved"`, `"pending"`, `"ready"`, `"refreshing"`, or `"error"`. | state | value resolved | loading | has error | | ---------- | -------------- | ------- | --------- | | unresolved | No | No | No | | pending | No | Yes | No | | ready | Yes | No | No | | refreshing | Yes | Yes | No | | errored | No | No | Yes | A widely requested feature has been allowing them to be stores. While higher level APIs are still being determined we now have a way to plugin the internal storage by passing something with the signature of a signal to the new _Experimental_ `storage` option. ```js function createDeepSignal(value: T): Signal { const [store, setStore] = createStore({ value }); return [ () => store.value, (v: T) => { const unwrapped = unwrap(store.value); typeof v === "function" && (v = v(unwrapped)); setStore("value", reconcile(v)); return store.value; } ] as Signal; } const [resource] = createResource(fetcher, { storage: createDeepSignal }); ``` #### Consolidated SSR This release marks the end of years long effort to merge async and streaming mechanism. Since pre 1.0 these were seperate. Solid's original SSR efforts used reactivity on the server with different compilation. It was easiest to migrate synchronous and streaming rendering and for a time async had a different compilation. We got them on the same compilation 2 years ago but runtimes were different. Piece by piece things have progressed until finally async is now just streaming if flushed at the end. This means some things have improved across the board. Async triggered Error Boundaries previously were only ever client rendered (throwing an error across the network), but now if they happen any time before sending to the browser they are server rendered. `onCleanup` now runs on the server if a branch changes. Keep in mind this is for rendering effects (like setting a status code) and not true side effects as not all rendering cleans up. Finally we've had a chance to do a bunch of SSR rendering performance improvements. Including replacing our data serializer with an early copy of Dylan Piercey from [Marko](https://markojs.com)'s upcoming serializer for Marko 6. Which boasts performance improvements of up to 6x `devalue` which we used previously. #### Keyed Control Flow Solid's `` and `` control flow originally re-rendered based on value change rather than truthy-ness changing. This allowed the children to be "keyed" to the value but lead to over rendering in common cases. Pre 1.0 it was decided to make these only re-render when statement changed from `true` to `false` or vice versa, except for the callback form that was still keyed. This worked pretty well except it was not obvious that a callback was keyed. So in 1.5 we are making this behavior explicit. If you want keyed you should specify it via attribute: ```js // re-render whenever user changes // normal
{user().name}
// callback {user =>
{user.name}
}
``` However, to not be breaking if a callback is present we will assume it's keyed. We still recommend you start adding these attributes (and TS will fail without them). In the future we will introduce a non-keyed callback form as well so users can benefit from type narrowing in that case as well. ### Other Improvements ### `children.toArray` Children helper now has the ability to be coerced to an array: ```js const resolved = children(() => props.children); resolved.toArray(); // definitely an array ``` #### Better SSR Spreads Finally fixed spread merging with non-spread properties during SSR, including the ability to merge children. #### Better Error Handling We weren't handling falsey errors previously. Now when Solid receives an error that isn't an `Error` object or a string it will coerce it into an `Unknown Error`. ## 1.4.0 - 2022-05-12 ### New Features #### Resource Deferred Streaming Streaming brings a lot of performance benefits but it also comes with the tradeoff we need to respond with the headers before we can send any content. This means we must set the Response headers early if we want to benefit from streaming. While it's always possible to fetch first and delay rendering that slows down everything. Even our async server rendering doesn't block rendering but instead just waits to respond to the end. But what if you want to stream but also want to wait on some key data loading so you still have an opportunity to handle the response on the server before sending it to the browser? We now have the ability to tell Solid's stream renderer to wait for a resource before flushing the stream. That you can opt in by setting `deferStream` option. ```js // fetches a user and streams content as soon as possible const [user] = createResource(() => params.id, fetchUser); // fetches a user but only streams content after this resource has loaded const [user] = createResource(() => params.id, fetchUser, { deferStream: true }); ``` #### Top Level Arrays in Stores Since Stores were first introduced it has always bugged me that the most common case, creating a list required nesting it under a property to track properly. Thanks to some exploration into proxy traps and iteration we now support top level arrays. In addition to its other modes, the Store setter will accept an array which allows for common operations. ```js const [todos, setTodos] = createStore([ { id: 1, title: "Thing I have to do", done: false }, { id: 2, title: "Learn a New Framework", done: false } ]); // set at an index setTodos(1, done, true); // use an array setTodos([...todos, { id: 3, title: "New Todo", done: false }]) // iterate over it with {todo => }; ``` Through this change we also stopped over execution when listening to specific properties. To support iteration Solid previously would notify the owning object of any array when an was index added/removed or object new property created or deleted on any object. The one caveat is downstream optimized control flow that untrack index reads on arrays will now need to track the iterated object explicity. Solid exports a `$TRACK` symbol used to subscribe to the object and all its properties. #### Stale Resource Reads Suspense and Transitions are amazingly powerful feature but occasionally you want to opt out of the consistency and show things out of date because it will show up faster and some of things you are waiting for are not as high priority. In so you want the Transition to end sooner, but not necessarily stop showing the stale data for part of the screen. It is still preferable to receding back to loading spinner state. Solid's Resources now support being able to read the value without triggering Suspense. As long as it has loaded previously `latest` property won't cause fallback appear or Transitions to hold. This will always return the `latest` value regardless whether it is stale (ie.. a new value is being fetched) and will reactively update. This is super powerful in Transitions as you can use the Resources own `loading` state to know if it is stale. Since the Transition will hold while the critical data is loading, the loading state will not be applied to the in view screen until that Transition has ended. If the resource is still loading now you can show that it is stale. ```js const [resource] = createResource(source, fetcher); // read it as usual resource(); // read the latest (don't suspend if loaded at least once) resource.latest; ``` Example: https://codesandbox.io/s/solid-stale-resource-y3fy4l #### Combining multiple Custom Renderers The Babel plugin now allows configuring multiple custom renderers at the same time. The primary case it is so a developer can still lever Solid's optimized DOM compilation while using their custom renderer. To make this work specify the tags each renderer is reponsible for. It will try to resolve them in order. ```js import { HTMLElements, SVGElements } from "solid-js/web"; let solidConfig = { moduleName: "solid-js/web", // @ts-ignore generate: "dynamic", renderers: [ { name: "dom", moduleName: "solid-js/web", elements: [...HTMLElements, ...SVGElements] }, { name: "universal", moduleName: "solid-three", elements: [] } ] }; ``` ### Improvements/Fixes #### Synchronous Top Level `createEffect` These were originally deferred to a microtask to resemble how effects are queued under a listener. However it is more correct to run immediate like everything else top level. #### Better Types around Components This one took the effort of many resident TypeScript experts, but we've now landed on some better types for components. The biggest change is `Component` no longer has an opinion on whether it should have `children` or not. We've added supplementary types `ParentComponent` and `FlowComponent` to denote Components that may have `children` or always have `children`. And we've added `VoidComponent` for those which may never have children. #### Sources in `createResource` are now Memos A small change but it was unusual to have refetching trigger a reactive expression outside of a reactive context. Now on refetch it grabs the last source value rather than re-running it. #### `createMutable` batches array methods like push, pop, etc.. Now these built-ins are batched and more performant. We've also add `modifyMutable` that applies modifiers batched to stores created with `createMutable`. ```js modifyMutable(state.data.user, reconcile({ firstName: "Jake", middleName: "R" })); ``` #### Stores and mutables now respect batch Writing to a store or mutable within `batch` (including effects) no longer immediately updates the value, so reading within the same batch gives the old value. This guarantees consistency with memos and other computations, just like signals. #### Better Support for React JSX transform We have added support to `solid-js/h` to support the new React JSX transform. You can use it directly in TypeScript by using: ```json { "jsx": "react-jsx", "jsxImportSource": "solid-js/h" } ``` Keep in mind this has all the consequences of not using the custom transform. It means larger library code, slower performance, and worse ergonomics. Remember to wrap your reactive expressions in functions. #### HyperScript now returns functions This one is a potentially breaking change, but the current behavior was broken. It was possible(and common) for children to be created before the parents the way JSX worked. This was an oversight on my original design that needs to be fixed, as it breaks context, and disposal logic. So now when you get your results back from `h` you need to call it. Solid's `render` function will handle this automatically. ```js const getDiv = h("div", "Hello"); document.body.appendChild(getDiv()); // call as a function to have it create the element. ``` ### Removals and Deprecations #### `className`, `htmlFor` deprecated While they still work for now, Solid will remove support for these React-isms in a future version. They leave us with multiple ways to set the same attribute. This is problematic for trying to merge them. Solid updates independently so it is too easy for these things to trample on each other. Also when optimizing for compilation since with things like Spreads you can't know if the property is present, Solid has to err on the side of caution. This means more code and less performance. #### Experimental `refetchResources` removed This primitive ended up being too general to be useful. There are enough cases we can't rely on the refetch everything by default mentality. For that reason we are dropping support of this experimental feature. ## 1.3.0 - 2022-01-05 ### New Features #### HTML Streaming This release adds support for HTML streaming. Now we not only stream data after the initial shell but the HTML as it finishes. The big benefit is that now for cached results, or times when the network are slow we no longer have to show the placeholder while waiting for JavaScript bundle to load. As soon as the HTML is available it will be streamed and inserted. With it comes new streaming API `renderToStream`. This is a universal API designed to handle both Node and Web writable streams. It returns an object that mirrors a Readable stream on both platforms that has both `pipe` (node) and `pipeTo` (web). The benefit of this `pipe` API is the user can choose when to insert the content in the output stream whether soon as possible, or `onCompleteShell`, or `onCompleteAll`. This decouples Solid's rendering a from the stream a bit but leaves things open to performance improvements in the future. ```js // node const stream = renderToStream(() => ).pipe(res); // web const stream = renderToStream(() => ).pipeTo(writable); ``` #### Error Boundaries on the Server We've added support for Error Boundaries on the Server for all rendering methods(`renderToString`, `renderToStringAsync`, `renderToStream`). Errors can be caught both from synchronous rendering and from errors that happen in Resource resolution. However, Our approach doesn't guarentee all errors are handled on the server as with streaming it is possible that the Error Boundary has already made it to the browser while a nested Suspense component hasn't settled. If an Error is hit it will propagate up to the top most Suspense Boundary that hasn't been flushed yet. If it is not handled by an Error Boundary before that it will abort rendering, and send the Error to the browser to propagate up to the nearest Error Boundary. This works now but there is more to explore here in improving Error handling in general with SSR. So look forward to feedback on the feature. #### Isolated Server Render/Hydration Contexts Sometimes you want to server render and hydrate multiple Solid apps on the same page. Maybe you are using the Islands architecture with something like [Astro](https://astro.build). We now have the ability to pass a unique `renderId` on all our server rendering methods and to the `hydrate` function. This will isolate all hydration and resource resolution. This means we can use things like server side Suspense in these solutions. Also now you only need to include the Hydration Script once on the page. Each Island will be responsible for initializing it's own resources. ```js // on the server const html = renderToString(() => , { renderId: "island1" }); // for the browser hydrate(() => , mountEl, { renderId: "island1" }); ``` #### `createReaction` This new primitive is mostly for more advanced use cases and is very helpful for interopt with purely pull based systems (like integrating with React's render cycle). It registers an untracked side effect and returns a tracking function. The tracking function is used to track code block, and the side effect is not fired until the first time any of the dependencies in the tracking code is updated. `track` must be called to track again. ```js const [s, set] = createSignal("start"); const track = createReaction(() => console.log("something")); // next time s changes run the reaction track(() => s()); set("end"); // "something" set("final"); // no-op as reaction only runs on first update, need to call track again. ``` This primitive is niche for certain use cases but where it is useful it is indispensible (like the next feature which uses a similar API). #### External Sources (experimental) Ever wanted to use a third party reactive library directly in Solid, like MobX, Vue Reactivity, or Kairo. We are experimenting with adding native support so reactive atoms from these libraries can be used directly in Solid's primitives and JSX without a wrapper. This feature is still experimental since supporting Transitions and Concurrent Rendering will take some more effort. But we have added `enableExternalSource` enable this feature. Thanks @3Shain for designing this solution. ```js import { Reaction, makeAutoObservable } from "mobx"; import { enableExternalSource } from "solid-js"; import { render } from "solid-js/web"; let id = 0; enableExternalSource((fn, trigger) => { const reaction = new Reaction(`externalSource@${++id}`, trigger); return { track: x => { let next; reaction.track(() => (next = fn(x))); return next; }, dispose: () => { reaction.dispose(); } }; }); class Timer { secondsPassed = 0; constructor() { makeAutoObservable(this); } increase() { this.secondsPassed += 1; } reset() { this.secondsPassed = 0; } } // component driven directly off MobX function App() { const timer = new Timer(); setInterval(() => { timer.increase(); }, 1000); return ; } render(() => , document.getElementById("app")); ``` #### `refetchResources` (experimental) In efforts to allow for scaling from simple resources up to cached solutions we are adding some experimental features to `createResource` to work with library writers to develop the best patterns. Caching is always a tricky problem and with SSR and streaming being part of the equation the core framework needs at minimum to provide some hooks into orchestrating them. Sometimes it's valuable to trigger `refetch` across many resources. Now you can. ```js import { createResource, refetchResources } from "solid-js"; const userCache = {}; function MyComponent(props) { const [data] = createResource( () => props.id, (userId, { refetching }) => { const cached = userCache[userId]; // return cached value if available and not refetching if (cached && !refetching) return cached; return fetchUser(userId); } ); } // somewhere else refetchResources(); ``` You can also pass a parameter to `refetchResources` to provide additional information to the `refetching` info of the fetcher. This could be used for conditional cache invalidation. Like only refetch resources related to `users`. This mechanism requires a bit of wiring but the idea is you'd wrap `createResource` in maybe a `createQuery` and implement your own conventions around resource cache management. Still working out how this should work best, but the goal is to provide the mechanisms to support resource caches without being responsible for their implementation. To opt-out being part of the global refetch createResource now takes a `globalRefetch` option that can be set to false. In addition to a new option to disable `refetchResources` there is no an `onHydrated` callback that takes the same arguments as the fetcher. When a resource is restored from the server the fetcher is not called. However, this callback will be. This is useful for populating caches. ### Improvements #### Better TypeScript Support Thanks to the tireless efforts of several contributors we now have significantly better types in Solid. This was a huge effort and involved pulling in maintainers of TypeScript to help us work through it. Thank you @trusktr for spearheading the effort. #### Better SourceMaps Work has been done to improve sourcemaps by updating `babel-plugin-dom-expressions` to better preserve identifiers from the JSX. Thanks to @LXSMNSYC for exploring and implementing this. ### Breaking Changes/Deprecations #### `startTransition` no longer takes callback as a second argument Instead it returns a promise you can await. This works better for chaining sequences of actions. ```js const [start, isPending] = useTransition(); start(() => doSomething()).then(() => allDone()); ``` #### Resource fetcher info object replaces `getPrev` To streamline API for refetch we are slightly updating the `createResource`: ```js const [data] = createResource(sourceSignal, (source, { value, refetching }) => {}); ``` For those using existing 2nd argument: ```js const [data] = createResource(sourceSignal, (source, getPrev) => { const value = getPrev(); }); // becomes const [data] = createResource(sourceSignal, (source, { value }) => {}); ``` #### Deprecating Legacy Streaming APIs `pipeToNodeWritable` and `pipeToWritable` are deprecated. They will still work for now with basic usage but some of the more advanced options didn't map over to the new APIs directly and have been removed. Move to using `renderToStream`. ### Bug Fixes - Fixed browser extensions modifying the head breaking hydration. - Fixed reinserting `` on hydration from document. - Fixed over-executing on multi-select with `createSelector`. - Fixed event delegation conflicting with document event listeners. - Fixed self owning source infinite recursion. - Fixed faulty treesplitting for hydration in client only render. - Fixed return type of `preload` on lazy components to always be a promise. - Fixed compile error with leading white space after opening tags when generating ssr. ## 1.2.0 - 2021-10-25 ### New Features #### Custom Renderers This release adds support custom renderers through a new "universal" transform. Solid now provides a sub module `solid-js/universal` that exports a `createRenderer` method that allows you to create your own runtimes. This will enable things like native mobile and desktop, canvas and webgl, or even rendering to the terminal. This is still new so very much looking for feedback. #### Spreads Added to Solid's `html` It's been a long time coming but Solid's Tagged Template Literals now support element and component spreads using htm inspired syntax. ```js html`
`; ``` ### Fixes #### Dynamic Spreads now work on Components Previously spreads on components would only track property changes on bound objects and not when the whole object changed. This now works: ```js ``` #### ClassList properly merges multiple classnames in the key It is common in libraries like Tailwind to apply multiple classes at the same time. There was an issue where true and false resolutions were cancelling each other out. This would only set `text-sm`. ```js
``` #### Consistent handling of HTMLEntities Things like ` ` used to render differently depending if in elements or components(or fragments). This has been made consistent across all three. #### Various improvements to Types and Transitions A lot of bugs from the last minor release were around Transitions that have been addressed. And as always Types have been gradually improving. ## 1.1.0 - 2021-08-09 Expanding Solid's concurrency to include scheduling. Bug fixes around Types and around reactive execution order guarantees. ### New Features #### `createUniqueId` A universal id generator that works across server/browser. ```js const id = createUniqueId(); ``` > **Note** on the server this only works under hydratable components #### `from` A simple helper to make it easier to interopt with external producers like RxJS observables or with Svelte Stores. This basically turns any subscribable (object with a `subscribe` method) into a Signal and manages subscription and disposal. ```js const signal = from(obsv$); ``` It can also take a custom producer function where the function is passed a setter function returns a unsubscribe function: ```js const clock = from(set => { const t = setInterval(() => set(1), 1000); return () => clearInterval(t); }); ``` > Note: Signals created by `from` have equality checks turned off to interface better with external streams and sources. #### `enableScheduling` (experimental) By default Solid's concurrent rendering/Transitions doesn't schedule work differently and just runs synchronously. Its purpose is to smooth out IO situations like Navigation. However now you can opt into interruptible scheduling similar to React's behavior by calling this once at your programs entry. I've yet to see a realworld scenario where this makes a big difference but now we can do cool demos too and start testing it. #### `startTransition` Works like its counterpart in `useTransition`, this useful when you don't need pending state. ```js import { createSignal, startTransition } from "solid-js"; function App() { const [signal, setSignal] = createSignal("Howdy"); function clickHandler(e) { startTransition(() => setSignal("Holla")); } /* ...stuff */ } ``` ## 1.0.0 - 2021-06-27 ### Breaking Changes ### setSignal now supports function form While that in itself is a great new feature as you can do: ```js const [count, setCount] = createSignal(0); setCount(c => c + 1); ``` This promotes immutable patterns, let's you access the previous value without it being tracked, and makes Signals consistent with State. It means that when functions are stored in signals you need to use this form to remove ambiguity ```js const [count, setCount] = createSignal(ComponentA); // Do this: setCount(() => ComponentB); // Don't do this as it will call the function immediately: setCount(ComponentB); ``` #### `createState` moved and renamed `createState` has been renamed to `createStore` and moved to `solid-js/store`. Also moved to `solid-js/store`: `createMutable`, `produce`, `reconcile` #### SSR Entry points `renderToString` and `renderToStringAsync` now only return their stringified markup. To insert scripts you need to call `generateHydrationScript` or use the new `` component. `renderToNodeStream` and `renderToWebStream` have been replaced with `pipeToNodeWritable` and `pipeToWritable`, respectively. #### Options Objects Most non-essential arguments on reactive primitives are now living on an options object. This was done to homogenize the API and make it easier to make future additions while remaining backwards compatible. #### on No longer uses rest parameters for multiple dependencies. Instead pass an array. This facilitates new option to defer execution until dependencies change. #### Actions renamed to Directives To remove future confusion with other uses of actions the `JSX.Actions` interace is now the `JSX.Directives` interface. ## 0.26.0 - 2021-04-09 This release is about finalizing some API changes on the road to 1.0. This one has one breaking change and not much else. #### Signals no longer always notify by default Solid's original behavior has been to always notify on signal change even if the value hasn't changed. The idea was to simulate stream behavior. However, this has some downsides: 1. Inconsistent with State.. I made the decision to make state equality check by default, it is weird signals and memo's do not. 2. More likely to hit infinite loops. Equality check naturally stops infinite loops in some cases. While infinite loops aren't good and code that produces them suspect, it is nice to keep things clean. 3. It is consistent with other modern reactive libraries like MobX and Vue. The API has not changed. You can opt out of the default behavior by passing in your own comparator or false to the 2nd parameter of `createSignal` and the 3rd parameter of `createMemo`. My hope this is the last release before I start making 1.0 RC's. This one has big enough impact I want to get this out first. I imagine the remaining changes will be just syntax. ## 0.25.0 - 2021-03-28 This release is about refining the APIs as we approach the our release candidate for 1.0. ### Breaking Changes #### Resource API Minor difference to allow the first argument to be optional and support more features in the future. New full signature is: ```ts export function createResource( fn: U | false | (() => U | false), fetcher: (k: U, getPrev: () => T | undefined) => T | Promise, options?: { initialValue?: T } ): ResourceReturn; ``` 3rd argument is now an options object instead of just the initial value. This breaking. But this also allows the first argument to be optional for the non-tracking case. Need a promise that only loads once? Don't have need to re-use the fetcher. Do this: ```js const [data] = createResource(async () => (await fetch(`https://someapi.com/info`)).json()); ``` #### on/onCapture These are an escape hatch for unusual events. Previously these were custom attributes but now they are namespaced like: ```jsx
console.log(e.target)} /> ``` #### change `main` field to be node Now that we are supporting SSR for legacy(non-ESM) systems I need to use the main field to indicate a node env. We will be using the "browser" field for the client build in Solid. This straight up breaks Jest which doesn't respect that. I've created `solid-jest` to handle this. https://github.com/solidjs/solid-jest ### New Features #### Namespace Types Types added for Namespace attributes. You probably won't need most of these because they are for more advanced usage. However to use them you need to extend the JSX Namespace: ```ts declare module "solid-js" { namespace JSX { interface Directives { // use:____ } interface ExplicitProperties { // prop:____ } interface ExplicitAttributes { // attr:____ } interface CustomEvents { // on:____ } interface CustomCaptureEvents { // oncapture:____ } } } ``` #### Lazy component preload Lazy components now have a preload function so you can pre-emptively load them. ```js const LazyComp = lazy(() => import("./some-comp")); // load ahead of time LazyComp.preload(); ``` #### Error Boundary reset Error boundaries now have the ability to reset themselves and try again. It is the second argument to the fallback. ```js { if (count++ < 3) return reset(); return "Failure"; }} > ``` ## 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. ### 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`. #### 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 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 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. For users TS 4.1 or above add to your tsconfig to have JSX types in all your TSX files: ```js "compilerOptions" { "jsx": "preserve", "jsxImportSource": "solid-js", } ``` Or mixing and matching? You can set JSX types per file using the pragma at the top of each file: ```js /* @jsxImportSource solid-js */ ``` You can now import `JSX` types directly from Solid as neccessary: ```js import { JSX } from "solid-js"; ``` ## 0.22.0 - 2020-11-14 ### Unified Exports (Deprecation `solid-js/dom`) Solid now has streamlined exports for isomorphic development. This means from now on using `solid-js/web` instead of `solid-js/dom`. Based on compiler options it will swap out the appropriate packages for web. You should only ever import `solid-js`, `solid-js/h`, `solid-js/html`, and `solid-js/web` directly in your code. `solid-js/web` now exports an `isServer` field which indicates whether the code is executed for server rendering. This is constant in the respective packages meaning it can allow for powerful treeshaking/dead code elimination in final bundles even when used directly in end user code or 3rd party libraries. ### Dev Mode Aliasing `solid-js` to `solid-js/dev` in your bundler links in a Dev mode of Solid. It's still a WIP process but it introduces some new APIs. First signals and state (and resources) have the ability to set a name for debug purposes as an options argument. We also export a `serializeGraph` method which will serialize all the signals below the executing context in the reactive graph. Finally there is a new `globalThis._$afterUpdate` hook that can be assigned that will be called after every render that can be used for tracking purposes. This is just the start but it is my intention to develop these features to allow for better HMR and DevTools. > Note: If the libraries are not being pulled into your bundle and are treated as external you may need to alias `solid-js` to `solid-js/dev` in your bundler in order to use dev mode. ### Self contained HyperScript/Lit Modules We now ship the respective DOM expressions code. This makes it much easier to use directly from a CDN like Skypack. You literally can develop with Solid in the old school write it in notepad before npm was a thing sort of way. ```html ``` Save this in a text file called "site.html" and double click it and instant Solid in your browser. ### renderToWebStream New `renderToWebStream` for synchronous SSR mode. This allows us to stream from things like Cloudflare Workers. ### createMutable New mutable state primitive. Useful for interopt with other libraries. We can use this potentially for things like Vue/MobX compat. Or when we need to interact with libraries that can't be aware of Solid's reactive system, yet we want to capture updates. It supports getters and setters. Use with caution as it can promote difficult to reason about code, anti-patterns, and unexpected performance cliffs. Keep in mind Vue and MobX care less about these inefficient patterns since they have a VDOM safety net. We do not. For advanced users only. ```js const user = createMutable({ firstName: "John", lastName: "Smith", get fullName() { return `${this.firstName} ${this.lastName}`; }, set fullName(value) { const parts = value.split(" "); batch(() => { this.firstName = parts[0]; this.lastName = parts[1]; }); } }); console.log(user.fullName); // John Smith user.fullName = "Jake Murray"; console.log(user.firstName); // Jake ``` ### State Getter/Setters are now Wrapped Getters are now wrapped in `createMemo` and setters in `batch`. However, this introduces a new limitation that they can only be top level to have this behavior. ### State compatible with Prop Helpers You can now use state with `assignProps` and `splitProps` helpers. ### Removed DOM SSR No longer supporting hydratable DOM SSR in patched(ie... JSDOM) node environments. Use the standard SSR methods instead. Can still run Solid in JSDOM for things like Jest, but can't be used for isomorphic development. ## 0.21.0 - 2020-10-17 ### Attribute and Prop changes We will now default to using Attributes where possible to be consistent. Solid is aiming to generally reflect the case insensitiveness of HTML. Custom Elements remain the one place that defaults to property setters on Dynamic elements. While TypeScript 4.2 is yet to be released, we are introduce `attr`, `prop`, `use` and `style` namespace directives. To allow more expressiveness in binding syntax. ### Other Changes - New `on` and `onMount` helpers - More performant SSR escaping - Lazy eval SSR Component props (fix SSR Context API) - Add support for SSR with Solid Styled Components - Fix Lit Dom Expressions style in Template tags - Fix JSX Types ## 0.20.0 - 2020-09-24 ### Re-scheduling Reactivity. This release makes large changes to the Reactive System. Key changes are deferring `createEffect` to be after rendering and introducing `createComputed` do reactive graph updates like loading async data. ### Concurrency In addition the reactive model brings updates to Suspense and Transitions. Solid now has true concurrent rendering at a granular level. This mechanism does differ from React as it currently only supports a single future. ### Removed APIs `afterEffects`, `createDependentEffect`, and `suspend` have been removed as they no longer make sense with the new reactive system timing. ## 0.19.0 - 2020-08-23 API Changes to support better SSR ### Breaking Changes: #### Set State Mutable form is no longer a default. It was strangely inconsistent as you could accidentally mutate in immutable forms. No indicator why it should behave differently and work. Increased the size of `state` for everyone and added performance overhead with additional proxy wrapping. Also it was based on returning undefined meaning function forms could never return undefined to blank a vlue. Solid has changed it into a state setter modifier `produce` after ImmerJS naming. ```js // top level setState(produce(s => (s.name = "John"))); // nested setState( "user", produce(s => (s.name = "John")) ); ``` #### Prop APIs After writing `setDefaults`, `cloneProps`, and about to introduce `mergeProps` it became clear we can do this all with a single `assignProps` helper. So the former has been removed and now we have: ```js // default props props = assignProps({}, { name: "Smith" }, props); // clone props newProps = assignProps({}, props); // merge props assignProps(props, otherProps); ``` It follows the same pattern as ES `Object.assign` adding properties to the first argument and returning it. Except this method copies property descriptors without accessing them to preserve reactivity. #### `freeze` & `sample` have been renamed These APIs never had the most obvious naming, borrowing from SRP and digital circuit concepts rather than common english. They are now `batch` and `untrack` respectively which better reflect their purpose. These are now deprecated and will be removed in next minor version. #### Resource API For better automatic hydration support it is prudent to change resource signatures to take functions that return promises rather than promises themselves. This factory function has a lot advantages. This allows the library to decide whether to execute it or not. In certain cases we can choose skipping creating the promise altogether. It also leaves the door open for things like retry. We use this mechanism to wire up streamed data from the server and automatic data hydration for resources rendered into the page in async SSR. #### SSR Improvements New experimental support for Suspense aware synchronous, asynchronous, and streaming SSR with hydration, progressive hydration, and automatic isomorphic data serialization. Completely removed what was there before with a simple static generator and more examples, so all existing projects using `solid-ssr` package will break with this release. This is a much better foundation, and I hope to build better things on top. ### New #### State Getters For convenience of passing derived values or external reactive expressions through Solid's state initializer you can now add `getter`'s. ```jsx const [state, setState] = createState({ firstName: "Jon", lastName: "Snow", get greeting() { return `You know nothing ${state.firstName} ${state.lastName}`; } }); return
{state.greeting}
; ``` #### Control Flow Dynamic allows swapping Component dynamically. ```jsx // element tag name const [comp, setComp] = createSignal("h1"); ; // Component setComp(MyComp); ``` ErrorBoundary catches uncaught downstream errors and shows a fallback. ```jsx Something went terribly wrong
}> ``` #### Portals render in the Head You can now render portals in the head with no additional div element. #### Multi-version detection Common hard to track issue with Solid is when multiple versions of the library are running on the same page. It breaks reactivity, and is sometimes difficult to notice. Solid now detects if a version has already been loaded at runtime and complains. ### Bug Fixes & Updates Arguably a new feature but Solid now detects computation owners with pending dependency changes when trying to resolve nested computations. In so it will resolve those dependencies first. This fixes a long time issue with conditional processing with not directly related reactive atoms. Improved TypeScript Types. ## 0.18.0 - 2020-05-01 A lot of bug fixes, and introduction of string based SSR. Breaking Changes: - Removal of `forwardRef`. Value and function handled by just `ref`. - Change to how TypeScript is managed. Brought all JSX types inside the repo, and improved Component typing. - Changed default renderer in `solid-ssr` to string renderer. ## 0.17.0 - 2020-03-24 A lot of consolidation in preparation for release candidate - Big refactor of core reactive system and render list reconciler - Significantly smaller reducing core by atleast 3kb minified - Better handling of nested reactive nodes in Fragments - Update SSR mechanisms, added progressive event hydration, created repo for SSR environment (`solid-ssr`) - `@once` compiler hint to statically bind values - Better wrapping hueristics for booleans and ternaries in JSX Breaking Changes - Removed `transform` prop from control flow. Idiomatic approach is to make a HOC for transformations of this nature. - Removed selectWhen/selectEach control flow transforms. - Changed event system - `on____` prop to stop differentiating on case. Super confusing.Instead will try to delegate unless unable. Made TypeScript all CamelCase (although technically both forms behave identically) - Removed `model` event delegation approach. Instead to create bound event use array: `onClick={[handler, row.id]}`. Inspired by Inferno's `linkEvent` helper. - Renamed `events` prop to `on` prop - Added `onCapture` prop for capture events ## 0.16.0 - 2020-01-14 Big changes to experimental features: - New resource API `createResource` and `createResourceState` to replace `loadResource`. These are built to prioritize read capabilities and simplify implementation. - Support for Async SSR `renderToString` now returns a promise. Uses Suspense to know when it is done. - Progressive Hydration with code splitting support. Ability to track events and replay as hydration completes to reduce "uncanny valley". Components can be lazily loaded even during hydration. **No support for async data on hydration yet**, so render it from server and load into state synchronously. - New error boundary api with `onError`. If an error occurs in context or child context the nearest handler/s will be called. - Deprecating the `force` `setState` modifier as it is confusing. ## 0.15.0 - 2019-12-16 A lot fixes and new features: - Suspense improvements: `SuspenseList`, `useTransition`, trigger on read. Update API, and added `reload` and retry capability. Removed need for `awaitSuspense` by making `Show` and `Switch` control flows `Suspense` aware. - Deprecate `selectWhen` and `selectEach`. - Untrack all Components. No more fear of nesting Components in JSX expressions. Top level in a Component will always be inert now. - Support for safe boolean and logical operators. This allows for the same optimization as the `Show` control flow for simple inline JSX conditionals like `
{state.count > 5 && }
`. - Support for non-curried operator forms. All operators now support an accessor first form as well as the functional curried form. Ex `map(() => state.list, item => item)` - Fix issues with spreading over `children` props. - Better Type Definitions. ## 0.14.0 - 2019-11-16 v0.14.0 brings changes to the render runtime and `setState` API - Adds diffing to batched computations to improve update performance - Supports support for mutable(TypeScript safe) `setState` API inspired by Immer. Function setters in Solid now pass a mutable version of state. Modifying will schedule updates. This form must not return a value. It can still be used immutably simply by returning the new value. - Changes how `force` and `reconcile` helpers work. They can now be used on nested paths. - Removes support for multi-path `setState`. ## 0.13.0 - 2019-10-27 v0.13.0 contains large changes to the reactive system and compiler. The main update is to simplify reactivity by removing computation recycling. While this was a useful feature to avoid unnecessary computation nodes, Solid now uses batching as a different approach to get similar results. Most templating libraries can offer breakneck update speeds without fine grained updates. The real cost of these top down approaches is the need to redo structural reconciliation. The current approach is that different computations will be created for each: - Dynamic insert expression (any expression between tags) - Spread operator - JSX template entry point(Top level tag, Fragment, or Component Children) To aid in performance simple text inserts the `textContent` binding is now optimized so they can be batched. In addition there are some improvements to template cloning and SVG handing in SSR. ## 0.12.0 - 2019-10-18 v0.12.0 contains a breaking change to the reactive rendering system - Removal of explicit dynamic binding, bindings will default to reactive unless impossible to be so (literal, function declaration, simple variable) - SVG Camelcase attribute Support - Prettier now supported! ## 0.11.0 - 2019-09-27 v0.11.0 continues to add updates to the reactive system as well as some new features: - Fix reactivity resolution ordering on downstream conditionals - Add basic (non-namespaced) SVG support - Add experimental Server Side Rendering and Client Side Hydration capabilities - Add Suspense aware control flow transformation (`awaitSuspense`) - Allow state objects to track functions - More TypeScript definition improvments and fixes ## 0.10.0 - 2019-08-11 v0.10.0 makes significant changes to the reactive system. Key updates: - Fixed synchronicity on all hooks/control flows. - Adds the ability to use comparators on `createMemo`. - Fixes bugs with nested control flows. - Fixes bugs with Suspense. - Update Suspense `delayMs` to `maxDuration` to match React. (Usage of `maxDuration` still experimental) ## 0.9.0 - 2019-07-20 v0.9.0 makes signifigant changes to underlying reconciler. - New Control Flow - Removes Custom Directives - New Functional Operators ## 0.8.0 - 2019-06-14 v0.8.0 brings further improvements in reducing bundle size and optimizations in reactivity. New Features: - Universal loadResource API - afterEffects hook - Switch Control Flow ## 0.7.0 - 2019-05-25 v0.7.0 brings further improvements in tree shaking, Context API including Provide control flow, and suspense helpers for loading Async Components and Data. This is a breaking change as in order to support this version, Solid has forked S.js the underlying library and now ships with it built in. This means Solid will no longer be compatible other S.js libraries. It is a turning point but enables the powerful new features. ## 0.6.0 - 2019-05-07 v0.6.0 brings a Tree Shakeable runtime. This means when Solid used with JSX the compiler can intelligently only include the code that is being used. This is a breaking change in that: - No longer need to import 'r' and selectWhen and selectEach directives have been moved to solid-js from solid-js/dom. You should not need to import from 'solid-js/dom' directly anymore as your compiled code will do it automatically. - HyperScript and Lit imports have been made the default import now.. ex: ```js import html from "solid-js/html"; ``` - Tidied up the compiled template code. This should make it much nicer to debug when not minified. ## 0.5.0 - 2019-04-14 - Add support for multiple renderers (JSX, Tagged Template Literals, HyperScript). Added direct imports or 'solid-js/dom' alternatives 'solid-js/html' and 'solid-js/h'. - Reorganized dependencies work. ## 0.4.2 - 2019-03-18 - Add fallbacks for control flow - Add new Portal Control Flow - This allows nodes to be rendered outside of the component tree with support for satelite ShadowRoots. - Add new Suspend Control Flow - This renders content to a isolated document and display fallback content in its place until ready. Good for nested Async Data Fetching. - Default node placeholders to comments (improved text interpolation) - Added events binding for irregular event names ## 0.4.0 - 2019-02-16 - Rename API to create\_\_ to be semantically correct - Added implicit event delegation ## 0.3.8 - 2019-01-31 - Add support for HyperScript ## 0.3.7 - 2019-01-16 - Improved data reconciler performance - Added data reconciler options ## 0.3.4 - 2019-01-04 - Added optional comparator for signals. - Removed redundant type checks and extra function calls. - Changed S.js to a dependency instead of a peer dependency. ## 0.3.2 - 2018-12-30 - Separated useSignal getter/setters for clearer more consistent API ## 0.3.1 - 2018-12-29 - Remove operators from core package since are auxilliary with new API. - Updated JSX Dom Expressions to use new control flow JSX and JSX Fragment support. ## 0.3.0 - 2018-12-25 - New setState API inspired by Falcor paths to handle ranges. - Reduction in API to remove State object functions and change to explicit methods. - Expose reconcile method to do deep differences against immutable data sources (previously automatically done for selectors). - Removed 'from' operators as limited usefulness with new patterns. ## 0.2.0 - 2018-11-13 - Large simplifications to remove inconsistency around wrapping and unwrapping values. State values are always wrapped get, and fully unwrapped on set. - Updated binding syntax. Dynamic expressions are now bound with an inner parenthesis `{( )}`js - Removed Immutable State object. May attempt something similar in the future but at this time it wasn't worth the effort trying to attempt both. There are better approaches to Proxy Immutable data structures. ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at community@solidjs.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the project community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. [homepage]: http://contributor-covenant.org ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to SolidJS Thank you for investing your time in contributing to our project! ✨. Read our [Code of Conduct](https://github.com/solidjs/solid/blob/main/CODE_OF_CONDUCT.md) to keep our community approachable and respectable. Solid accepts a number of contributions from the broader community. More hands indeed make lighter work. We're however selective of the types of contributions we receive. This usually involves vetting code quality, current focus, alignment with team philosophies etc. It's typically a good idea to submit a proposal for a change before spending time implementing it. This is to ensure that your efforts align with the current needs or more practically that work isn't completed by multiple contributors. Note: If you would like your project listed here please submit a PR or contact a core/ecosystem member on Discord. ## Team Structure & Organization There are a lot of opportunities to get involved. We organize Solid community efforts via Discord and typically onboard dedicated contributors into focused teams: - Docs (headed by [@LadyBluenotes](https://github.com/ladybluenotes)) - Infrastructure (headed by [@davedbase](https://github.com/davedbase)) - Advocacy (headed by [@hindsight](https://github.com/eslachance)) - Growth (headed by [@davedbase](https://github.com/davedbase)) - Translators (headed by [@davedbase](https://github.com/davedbase)) Most team members are part of the Ecosystem Team or Core Team. Entry into these groups is selected by Core Members only. We do not accept applications or requests for entry. Selections are made ad-hoc according to internal needs. Selections are typically announced at Community Meetings which occur quarterly. ## Meetings and Schedules SolidJS team members organize via Discord chat and audio channels. Channels exist to manage these conversations and threads within channels are used to focus on specific topics. A number of meetings occur weekly between each group however there is no set cadence or recurring schedule. Typically attendance for team members is requested to maintain membership, however we respect and recognize OSS contributions are typically ad hoc and as can be given by our members and generous donors. ## Official Opportunities As a growing community, Solid has an on-going need for developers, writers, designers and general thought leaders. The following is a list of openings and tasks that Core attempts to maintain often. ### Docs Team To get involved, check out our [Contributing Guide](https://github.com/solidjs/solid-docs-next/blob/main/CONTRIBUTING.md) in the new docs repository! - General/Core Docs - Write new drafts for the new docs repo - Work on the infrastructure for the new docs site - Create videos, diagrams, and other multimedia content - Solid Start 1.0 API - Draft an initial, comprehensive set of docs for Solid Start - This currently takes place in [this subfolder](https://github.com/solidjs/solid-docs-next/tree/main/content/start) on `solid-docs-next` ### Infrastructure Team - Solid Site - Help maintain the current Solid website by implementing bugs, testing and reporting issues - Port the current website from being an SPA to Solid Start - Website redevelopment project for 2.0 - Solid Service API - Help implement our API service that powers solid REPL - Test, validate and implement security and bug fixes - Add new missing features - Develop new Solid Docs platform and website - Help coordinate creating MDX components - Developer infrastructure for delivering future community documentation platform - Solidex (our ecosystem directory) - How maintain a list of ecosystem projects and resources (articles, podcasts etc.) - Vet incoming PR from submissions and merge + deploy updated the directory - Improve workflow and systems for managing Solidex - Implement an API (via Solid Service API) to search and filter resources - Solid Dev Tools - We're actively looking for individuals to prototype and experiment on a set of developer tools. ### Solid Start Team Solid Start is our new meta framework that focuses on enhancing Solid's DX story and general usability. Similar to SvelteKit, Next and other meta frameworks, this project is considered a primary core supported effort. Solid Start is approaching its beta release and we're looking for developers to test, validate and build on top of it. Join the #solid-start channel on Discord or the [solid-start](https://github.com/solidjs/solid-start) to learn more. ## Ecosystem Opportunities SolidJS core members maintain a separate project called [SolidJS Community](https://github.com/solidjs-community). This is a large and lush ecosystem community project that encompasses a number of critical core tooling such as Solid Primitives, Solid Aria (similar to React Aria) etc. The following are projects looking for leaders or support: - [**Solid Aria**](https://github.com/solidjs-community/solid-aria) (lead by [@fabien-ml](https://github.com/fabien-ml)): A port of React Aria. - [**Solid Examples**](https://github.com/solidjs-community/solid-examples) (lead by [@foolswisdom](https://github.com/mosheduminer)): A list of examples, patterns and app implementations. - [**Solid Codemod**](https://github.com/solidjs-community/solid-codemod) (lead by [@trivikr](https://github.com/trivikr)): Convert React or other libraries to Solid automatically. - [**Solid Snippets**](https://github.com/solidjs-community/solid-snippets) (lead by [@thetarnav](https://github.com/thetarnav)): VSCode snippet library. - [**Solid DSL**](https://github.com/solidjs-community/solid-dsl) (lead by [@davedbase](https://github.com/davedbase)): A project to explore enhancing JSX or other DSL options. - [**Solid Primitives**](https://github.com/solidjs-community/solid-primitives) (lead by [@davedbase](https://github.com/davedbase)): A large primitives (hooks) library. Contributing to ecosystem projects is just as important as contributing to Solid core projects. As Solid grows a lush, well supported and high-quality set of packages and learning materials will benefit it's users and future viability. ## Where do I start? If you haven't found any interesting information on this page then we encourage you to start hacking at a Solid related utility or package that does. Building useful tools for fellow OSS ecosystem and Solid users enhances the whole platform. We can't wait to see what you build! ## Building Solid This repository uses [pnpm](https://pnpm.io/) and [Turborepo](https://turborepo.org/). If you want to build Solid from scratch, use the following steps: 1. `corepack enable` (use the correct version of PNPM, https://nodejs.org/api/corepack.html#enabling-the-feature) 2. `pnpm install` (install all dependencies) 3. `pnpm build` You can then run all tests via `pnpm test`. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016-2025 Ryan Carniato Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

SolidJS

[![Build Status](https://img.shields.io/github/actions/workflow/status/solidjs/solid/main-ci.yml?branch=main&logo=github&style=for-the-badge)](https://github.com/solidjs/solid/actions/workflows/main-ci.yml) [![Coverage Status](https://img.shields.io/coveralls/github/solidjs/solid.svg?style=for-the-badge)](https://coveralls.io/github/solidjs/solid?branch=main) [![NPM Version](https://img.shields.io/npm/v/solid-js.svg?style=for-the-badge)](https://www.npmjs.com/package/solid-js) [![](https://img.shields.io/npm/dm/solid-js.svg?style=for-the-badge)](https://www.npmjs.com/package/solid-js) [![Discord](https://img.shields.io/discord/722131463138705510?style=for-the-badge)](https://discord.com/invite/solidjs) [![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/solidjs?style=for-the-badge)](https://www.reddit.com/r/solidjs/) **[Website](https://www.solidjs.com/) • [API Docs](https://docs.solidjs.com/) • [Features Tutorial](https://www.solidjs.com/tutorial/introduction_basics) • [Playground](https://playground.solidjs.com/?version=1.3.13#NobwRAdghgtgpmAXGGUCWEwBowBcCeADgsrgM4Ae2YZA9gK4BOAxiWGjIbY7gAQi9GcCABM4jXgF9eAM0a0YvADo1aAGzQiAtACsyAegDucAEYqA3EogcuPfr2ZCouOAGU0Ac2hqps+YpU6DW09CysrGXoIZlw0WgheAGEGCBdGAAoASn4rXgd4sj5gZhTcLF4yOFxkqNwAXV4AXgcnF3cvKDV0gAZMywT8iELeDEc4eFSm3iymgD4KqprU9JLamYBqXgBGPvCBoVwmBPTcvN4AHhN6XFx43gJiRpUrm-iVXnjEjWYAa0aQUZCCa4SSzU5nfirZaZSTgi76F63CBgga7CCwiBWISicTpGaNebnJZpXj6WblES0Zj0YEAOg8VQAompxsJcAAhfAASREJzAUEIhBUmTRYEkdSAA) • [Discord](https://discord.com/invite/solidjs)** Solid is a declarative JavaScript library for creating user interfaces. Instead of using a Virtual DOM, it compiles its templates to real DOM nodes and updates them with fine-grained reactions. Declare your state and use it throughout your app, and when a piece of state changes, only the code that depends on it will rerun. ## At a Glance ```tsx import { createSignal } from "solid-js"; import { render } from "solid-js/web"; function Counter() { const [count, setCount] = createSignal(0); const doubleCount = () => count() * 2; console.log("The body of the function runs once..."); return ( <> ); } render(Counter, document.getElementById("app")!); ``` Try this code in our [playground](https://playground.solidjs.com/anonymous/0c88df54-91b0-4c88-bd20-e962bde49725)!
Explain this! ```tsx import { createSignal } from "solid-js"; import { render } from "solid-js/web"; // A component is just a function that returns a DOM node function Counter() { // Create a piece of reactive state, giving us an accessor, count(), and a setter, setCount() const [count, setCount] = createSignal(0); //To create derived state, just wrap an expression in a function const doubleCount = () => count() * 2; console.log("The body of the function runs once..."); // JSX allows you to write HTML within your JavaScript function and include dynamic expressions using the { } syntax // The only part of this that will ever rerender is the doubleCount() text. return ( <> ); } // The render function mounts a component onto your page render(Counter, document.getElementById("app")!); ``` Solid compiles your JSX down to efficient real DOM updates. It uses the same reactive primitives (`createSignal`) at runtime but making sure there's as little rerendering as possible. Here's what that looks like in this example: ```js import { template as _$template } from "solid-js/web"; import { delegateEvents as _$delegateEvents } from "solid-js/web"; import { insert as _$insert } from "solid-js/web"; //The compiler pulls out any static HTML const _tmpl$ = /*#__PURE__*/_$template(`
## Key Features - Fine-grained updates to the real DOM - Declarative data: model your state as a system with reactive primitives - Render-once mental model: your components are regular JavaScript functions that run once to set up your view - Automatic dependency tracking: accessing your reactive state subscribes to it - [Small](https://dev.to/this-is-learning/javascript-framework-todomvc-size-comparison-504f) and [fast](https://krausest.github.io/js-framework-benchmark/current.html) - Simple: learn a few powerful concepts that can be reused, combined, and built on top of - Provides modern framework features like JSX, fragments, Context, Portals, Suspense, streaming SSR, progressive hydration, Error Boundaries and concurrent rendering. - Naturally debuggable: A `
` is a real div, so you can use your browser's devtools to inspect the rendering - [Web component friendly](https://github.com/solidjs/solid/tree/main/packages/solid-element#readme) and can author custom elements - Isomorphic: render your components on the client and the server - Universal: write [custom renderers](https://github.com/solidjs/solid/releases/tag/v1.2.0) to use Solid anywhere - A growing community and ecosystem with active core team support
Quick Start You can get started with a simple app by running the following in your terminal: ```sh > npx degit solidjs/templates/js my-app > cd my-app > npm i # or yarn or pnpm > npm run dev # or yarn or pnpm ``` Or for TypeScript: ```sh > npx degit solidjs/templates/ts my-app > cd my-app > npm i # or yarn or pnpm > npm run dev # or yarn or pnpm ``` This will create a minimal, client-rendered application powered by [Vite](https://vitejs.dev/). Or you can install the dependencies in your own setup. To use Solid with JSX (_recommended_), run: ```sh > npm i -D babel-preset-solid > npm i solid-js ``` The easiest way to get set up is to add `babel-preset-solid` to your `.babelrc`, babel config for webpack, or rollup configuration: ```js "presets": ["solid"] ``` For TypeScript to work, remember to set your `.tsconfig` to handle Solid's JSX: ```js "compilerOptions": { "jsx": "preserve", "jsxImportSource": "solid-js", } ```
## Why Solid? ### Performant Meticulously engineered for performance and with half a decade of research behind it, Solid's performance is almost indistinguishable from optimized vanilla JavaScript (See Solid on the [JS Framework Benchmark](https://krausest.github.io/js-framework-benchmark/current.html)). Solid is [small](https://bundlephobia.com/package/solid-js@1.3.15) and completely tree-shakable, and [fast](https://levelup.gitconnected.com/how-we-wrote-the-fastest-javascript-ui-framework-again-db097ddd99b6) when rendering on the server, too. Whether you're writing a fully client-rendered SPA or a server-rendered app, your users see it faster than ever. ([Read more about Solid's performance](https://dev.to/ryansolid/thinking-granular-how-is-solidjs-so-performant-4g37) from the library's creator.) ### Powerful Solid is fully-featured with everything you can expect from a modern framework. Performant state management is built-in with Context and Stores: you don't have to reach for a third party library to manage global state (if you don't want to). With Resources, you can use data loaded from the server like any other piece of state and build a responsive UI for it thanks to Suspense and concurrent rendering. And when you're ready to move to the server, Solid has full SSR and serverless support, with streaming and progressive hydration to get to interactive as quickly as possible. (Check out our full [interactive features walkthrough](https://www.solidjs.com/tutorial/introduction_basics).) ### Pragmatic Do more with less: use simple, composable primitives without hidden rules and gotchas. In Solid, components are just functions - rendering is determined purely by how your state is used - so you're free to organize your code how you like and you don't have to learn a new rendering system. Solid encourages patterns like declarative code and read-write segregation that help keep your project maintainable, but isn't opinionated enough to get in your way. ### Productive Solid is built on established tools like JSX and TypeScript and integrates with the Vite ecosystem. Solid's bare-metal, minimal abstractions give you direct access to the DOM, making it easy to use your favorite native JavaScript libraries like D3. And the Solid ecosystem is growing fast, with [custom primitives](https://github.com/solidjs-community/solid-primitives), [component libraries](https://kobalte.dev), and build-time utilities that let you [write Solid code in new ways](https://github.com/LXSMNSYC/solid-labels). ## More Check out our official [documentation](https://docs.solidjs.com) or browse some [examples](https://github.com/solidjs/solid/blob/main/documentation/resources/examples.md) ## Browser Support SolidJS Core is committed to supporting the last 2 years of modern browsers including Firefox, Safari, Chrome and Edge (for desktop and mobile devices). We do not support IE or similar sunset browsers. For server environments, we support Node LTS and the latest Deno and Cloudflare Worker runtimes. Testing Powered By SauceLabs ## Community Come chat with us on [Discord](https://discord.com/invite/solidjs)! Solid's creator and the rest of the core team are active there, and we're always looking for contributions. ### Contributors ### Open Collective Support us with a donation and help us continue our activities. [[Contribute](https://opencollective.com/solid)] ### Sponsors Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/solid#sponsor)] ================================================ FILE: documentation/resources/examples.md ================================================ # Examples ## Online - [Counter](https://codesandbox.io/s/8no2n9k94l) Simple Counter - [Simple Todos](https://codesandbox.io/s/lrm786ojqz) Todos with LocalStorage persistence - [Simple Routing](https://codesandbox.io/s/jjp8m8nlz5) Use 'switch' control flow for simple routing - [Scoreboard](https://codesandbox.io/s/solid-scoreboard-sjpje) Make use of hooks to do some simple transitions - [Tic Tac Toe](https://playground.solidjs.com/anonymous/335adbcd-289e-42f8-9a9c-152a96277747) Simple Example of the classic game - [Form Validation](https://codesandbox.io/s/solid-form-validation-2cdti) HTML 5 validators with custom async validation - [CSS Animations](https://codesandbox.io/s/basic-css-transition-36rln?file=/index.js) Using Solid Transition Group - [Styled Components](https://codesandbox.io/s/solid-styled-components-yv2t1) A simple example of creating Styled Components. - [Styled JSX](https://codesandbox.io/s/solid-styled-jsx-xgx6b) A simple example of using Styled JSX with Solid. - [Counter Context](https://codesandbox.io/s/counter-context-gur76) Implement a global store with Context API - [Async Resource](https://codesandbox.io/s/2o4wmxj9zy) Ajax requests to SWAPI with Promise cancellation - [Async Resource GraphQL](https://codesandbox.io/s/async-resource-graphql-r4rcx?file=/index.js) Simple resource for handling graphql request. - [Suspense](https://codesandbox.io/s/5v67oym224) Various Async loading with Solid's Suspend control flow - [Suspense Tabs](https://codesandbox.io/s/solid-suspense-tabs-vkgpj) Deferred loading spinners for smooth UX. - [SuspenseList](https://codesandbox.io/s/solid-suspenselist-eorvk) Orchestrating multiple Suspense Components. - [Redux Undoable Todos](https://codesandbox.io/s/pkjw38r8mj) Example from Redux site done with Solid. - [Simple Todos Template Literals](https://codesandbox.io/s/jpm68z1q33) Simple Todos using Lit DOM Expressions - [Simple Todos HyperScript](https://codesandbox.io/s/0vmjlmq94v) Simple Todos using Hyper DOM Expressions ## Demos - [TodoMVC](https://github.com/solidjs/solid-todomvc) Classic TodoMVC example - [Real World Demo](https://github.com/solidjs/solid-realworld) Real World Demo for Solid - [Hacker News](https://github.com/solidjs/solid-hackernews) Hacker News Clone for Solid - [Storybook](https://github.com/rturnq/storybook-solid) Solid with Storybook ## Benchmarks - [JS Framework Benchmark](https://github.com/krausest/js-framework-benchmark/tree/master/frameworks/keyed/solid) The one and only - [Sierpinski's Triangle Demo](https://github.com/ryansolid/solid-sierpinski-triangle-demo) Solid implementation of the React Fiber demo. - [WebComponent Todos](https://github.com/shprink/web-components-todo/tree/main/solid) Showing off Solid Element - [UIBench Benchmark](https://github.com/ryansolid/solid-uibench) a benchmark tests a variety of UI scenarios. - [DBMon Benchmark](https://github.com/ryansolid/solid-dbmon) A benchmark testing ability of libraries to render unoptimized data. ================================================ FILE: package.json ================================================ { "name": "solid-js", "description": "A declarative JavaScript library for building user interfaces.", "version": "1.0.0", "author": "Ryan Carniato", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/solidjs/solid" }, "private": true, "scripts": { "preinstall": "npx only-allow pnpm", "postinstall": "simple-git-hooks", "test": "turbo run test test-types", "coverage": "turbo run coverage", "build": "turbo run build", "types": "turbo run types", "publish": "pnpm run build && pnpm run types && pnpm run release:only", "bump": "changeset add", "release:only": "changeset publish", "format": "prettier --write --cache \"**/*.[tj]s?(x)\"" }, "devDependencies": { "@babel/cli": "^7.18.9", "@babel/core": "^7.20.12", "@babel/preset-env": "^7.18.9", "@babel/preset-typescript": "^7.18.6", "@changesets/cli": "^2.25.2", "@rollup/plugin-babel": "^6.0.3", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", "@types/node": "^22.7.5", "@vitest/coverage-v8": "^2.1.2", "babel-plugin-jsx-dom-expressions": "^0.40.3", "coveralls": "^3.1.1", "csstype": "^3.1.0", "dom-expressions": "0.40.4", "hyper-dom-expressions": "0.40.4", "jsdom": "^25.0.1", "lit-dom-expressions": "0.40.4", "ncp": "^2.0.0", "npm-run-all": "^4.1.5", "prettier": "^3.6.2", "rimraf": "^3.0.2", "rollup": "^4.24.0", "rollup-plugin-cleanup": "^3.2.1", "rollup-plugin-copy": "^3.4.0", "seroval": "~1.5.0", "simple-git-hooks": "^2.8.1", "symlink-dir": "^5.0.1", "tsconfig-replace-paths": "^0.0.11", "turbo": "^1.3.1", "typescript": "~5.7.2", "vite-plugin-solid": "^2.6.1", "vitest": "^2.1.2" }, "simple-git-hooks": { "pre-commit": "pnpm run format" }, "pnpm": { "overrides": { "babel-preset-solid": "workspace:*" } }, "engines": { "pnpm": "^9.15.0" }, "packageManager": "pnpm@9.15.0" } ================================================ FILE: packages/babel-preset-solid/CHANGELOG.md ================================================ # babel-preset-solid ## 1.9.10 ### Patch Changes - 6c92555: Update dom-expressions, seroval plugins, optional chaining ref, style optimization - Updated dependencies [2270ae9] - Updated dependencies [94d87f1] - Updated dependencies [3114302] - Updated dependencies [6c92555] - solid-js@1.9.10 ## 1.9.9 ### Patch Changes - c07887c: fix #2524 closedby types, fix regression inlining style/classList - Updated dependencies [f59ee48] - Updated dependencies [62c5a98] - Updated dependencies [62c5a98] - Updated dependencies [c07887c] - solid-js@1.9.9 ## 1.9.8 ### Patch Changes - 2cd810f: compiler and jsx type updates - fix: ssr style undefined - fix: ssr double escaped array - fix: skip jsxImportSource skipping transform - fix: @once on style, classlist - JSX type updates - Update Universal Renderer Types ## 1.9.6 ### Patch Changes - 8356213: update compiler config, fix boolean attribute regression, update JSX types ## 1.9.5 ### Patch Changes - 35266c1: JSX type updates, preliminary MathML support, fix spread overescaping ## 1.9.3 ### Patch Changes - 9b70a15: validation fixes, type updates, ssr attribute fix ## 1.9.2 ### Patch Changes - 22aff14: update validation: smaller lib, opt out, better table handling add `on:` event types for native events ## 1.9.0 ### Minor Changes - 2a3a1980: update dom-expressions - Improved Custom Element/Shadow DOM traversal - @olivercoad - Better heuristic to determine when to importNode - @titoBouzout - handleEvent syntax to allow custom event properties when not delegated - @titoBouzout - support for bool: attribute namespace - @titoBouzout - add "is" as detection for custom element - @titoBouzout - fix missing exports in different envs - @trusktr - better hydration mismatch errors - @ryansolid - improved HTML validation of JSX partials - @titoBouzout ## 1.8.22 ### Patch Changes - 26128ec0: fix #2259 attr: in ssr, updates some types ## 1.8.19 ### Patch Changes - 816a5c61: fix #2209 processing parent before child value binding in select - 424a31a3: optimize hydration keys ## 1.8.18 ### Patch Changes - 6693b56f: update TS, custom elements, and a lot compiler fixes fixes #2144, #2145, #2178, #2192 ## 1.8.17 ### Patch Changes - 72c5381d: fix #2134, merge dom expressions fix #2136, fix #2137, fix #2110 ## 1.8.16 ### Patch Changes - 071cd42f: fix #2100, fix #2102 - hydration errors due to over optimization ## 1.8.15 ### Patch Changes - 4ee461dc: improve template escaping, fragment hydration, SVG use types ## 1.8.12 ### Patch Changes - 85b26c36: fix #2041, fix #2043 - async renderer timing, numeric prop literals ## 1.8.9 ### Patch Changes - 80d4830f: fix #2016 value spread, smaller build output ## 1.8.8 ### Patch Changes - 968e2cc9: update seroval, fix #1972, fix #1980, fix #2002, support partial ALS ## 1.8.6 ### Patch Changes - 54e1aecf: update seroval, fix this, optimize star imports, fix #1952 hydration race condition ## 1.8.4 ### Patch Changes - cf0542a4: fix #1927, fix #1929, fix #1931, update storage API ## 1.8.2 ### Patch Changes - dd492c5e: fix #1917, fix #1918 error handling with serialization ## 1.8.0 ### Minor Changes - 2c087cbb: update to seroval streaming serializer, change ssr markers - 2c087cbb: hydration perf improvement, fix #1849 ### Patch Changes - 2c087cbb: remove attribute quotes in template, batch serialization - 2c087cbb: improved serialization/guards, fix #1413, fix #1796 hydration with lazy ## 1.8.0-beta.2 ### Minor Changes - e3a97d28: hydration perf improvement, fix #1849 ## 1.8.0-beta.1 ### Patch Changes - f6d511db: remove attribute quotes in template, batch serialization ## 1.8.0-beta.0 ### Minor Changes - d8e0e8e8: update to seroval streaming serializer, change ssr markers ### Patch Changes - bf09b838: improved serialization/guards, fix #1413, fix #1796 hydration with lazy ## 1.7.12 ### Patch Changes - 10ac07af: update jsx types, iife compiler optimization ## 1.7.7 ### Patch Changes - e660e5a3: add prettier code format in git-commit-hook ## 1.7.4 ### Patch Changes - 91110701: fix element/test mismatch issues #1684, #1697, #1707 fix solid-ssr types add missing JSX types #1690 fix firefox iframe #1688 ## 1.7.3 ### Patch Changes - 655f0b7e: fix attr in ssr spread, fix static undefined classList values, fix #1666 directives in TTLs ## 1.7.2 ### Patch Changes - 699d88eb: More thorough close tag ommission fix ## 1.7.1 ### Patch Changes - d4087fe7: fix 1663: template element closing errors ## 1.7.0 ### Minor Changes - f7dc355f: Remove FunctionElement from JSX.Element types - 940e5745: change to seroval serializer, better ssr fragment fixes - 2b80f706: Reduce DOM compiler output size Remove auxilary closing tags and lazy evaluate templates - 74f00e15: Support prop/attr directives in spreads, apply prop aliases only to specific elements ### Patch Changes - 41ca6522: fixes around templates and hydration - 3de9432c: Better Input Event Types, Template Pruning, Universal Renderer Fixes - a382c0c5: minify inline style, class - 6a4fe46c: fix #1553 improper html entity encoding in literal expressions ## 1.7.0-beta.5 ### Patch Changes - a382c0c5: minify inline style, class ## 1.7.0-beta.4 ### Patch Changes - 3de9432c: Better Input Event Types, Template Pruning, Universal Renderer Fixes ## 1.7.0-beta.3 ### Patch Changes - 41ca6522: fixes around templates and hydration ## 1.7.0-beta.2 ### Minor Changes - 940e5745: change to seroval serializer, better ssr fragment fixes ## 1.7.0-beta.1 ### Minor Changes - 2b80f706: Reduce DOM compiler output size Remove auxilary closing tags and lazy evaluate templates - 74f00e15: Support prop/attr directives in spreads, apply prop aliases only to specific elements ## 1.7.0-beta.0 ### Minor Changes - f7dc355: Remove FunctionElement from JSX.Element types ### Patch Changes - 6a4fe46: fix #1553 improper html entity encoding in literal expressions ## 1.6.16 ### Patch Changes - d10da016: Fix #1651 hydration markers introduced too early ## 1.6.13 ### Patch Changes - 60f8624d: fix #1596 ssr fragment text merge, fix #1599 ssr onCleanup ## 1.6.12 ### Patch Changes - 676ed331: docs: fix typos - 081ca06c: fix #1553 html encoding for native strings on components - 4fdec4f9: fix #1564, fix #1567 template literal bugs ## 1.6.10 ### Patch Changes - 7ab43a4: fix #1492 SSR Spread Breaks Hydration fix #1495 runWithOwner not clearing listener fix #1498 unrecoverable error in async batch ## 1.6.9 ### Patch Changes - a572c12: Streaming without a wrapper and compile time JSX validation ## 1.6.7 ### Patch Changes - 89baf12: fix boolean escaping, improve ssr performance ## 1.6.6 ### Patch Changes - 2119211: fix #1423 - inlined arrow functions in SSR and update rollup ## 1.6.3 ### Patch Changes - e95e95f: Bug fixes and testing changelog ================================================ FILE: packages/babel-preset-solid/README.md ================================================ # babel-preset-solid Babel preset to transform JSX into Solid runtime calls. ### Install Via NPM ```javascript npm install babel-preset-solid --save-dev ``` or Yarn ```javascript yarn add babel-preset-solid --dev ``` ### Usage Make or update your .babelrc config file with the preset: ```javascript { "presets": [ "solid" ] } ``` Via package.json ```javascript ... "babel": { "presets": [ "es2015", "solid" ], "plugins": [ ] }, ... ``` ### Usage for SSR code When need to transform JSX code to be used on the server, pass the following options ```javascript { "presets": [ ["solid", { "generate": "ssr", "hydratable": true }] ] } ``` And for the browser build pass the hydratable option as well: ```javascript { "presets": [ ["solid", { "generate": "dom", "hydratable": true }] ] } ``` ================================================ FILE: packages/babel-preset-solid/index.js ================================================ const jsxTransform = require("babel-plugin-jsx-dom-expressions"); module.exports = function (context, options = {}) { const plugins = [ [ jsxTransform, Object.assign( { moduleName: "solid-js/web", builtIns: [ "For", "Show", "Switch", "Match", "Suspense", "SuspenseList", "Portal", "Index", "Dynamic", "ErrorBoundary" ], contextToCustomElements: true, wrapConditionals: true, generate: "dom" }, options ) ] ]; return { plugins }; }; ================================================ FILE: packages/babel-preset-solid/package.json ================================================ { "name": "babel-preset-solid", "version": "1.9.10", "description": "Babel preset to transform JSX for Solid.js", "author": "Ryan Carniato ", "homepage": "https://github.com/solidjs/solid/blob/main/packages/babel-preset-solid#readme", "license": "MIT", "repository": "https://github.com/solidjs/solid/blob/main/packages/babel-preset-solid", "main": "index.js", "files": [ "index.js" ], "scripts": { "test": "node test.js" }, "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.10" }, "peerDependenciesMeta": { "solid-js": { "optional": true } } } ================================================ FILE: packages/babel-preset-solid/test.js ================================================ const babel = require("@babel/core"); const preset = require("."); const assert = require("assert"); const { code } = babel.transformSync("const v =
;", { presets: [preset], babelrc: false, compact: true }); assert.equal( code, 'import{template as _$template}from"solid-js/web";var _tmpl$=/*#__PURE__*/_$template(`
`);const v=_tmpl$();' ); console.log("passed"); ================================================ FILE: packages/solid/.npmignore ================================================ types/tsconfig.tsbuildinfo ================================================ FILE: packages/solid/CHANGELOG.md ================================================ # solid-js ## 1.9.11 ### Patch Changes - 6628d9f: Update dom-expressions/seroval to latest ## 1.9.10 ### Patch Changes - 2270ae9: Fix: Collision during SSR in createResource due to `loading` property. - 94d87f1: Update `build:clean` and `types:clean` script to include missing paths - 3114302: Improve `splitProps` performance - 6c92555: Update dom-expressions, seroval plugins, optional chaining ref, style optimization ## 1.9.9 ### Patch Changes - f59ee48: fix dynamic overtracking - 62c5a98: Update `SuspenseList` to handle hydration context - 62c5a98: Add unit tests for `resolveSSRNode` and `createResource` functions - c07887c: fix #2524 closedby types, fix regression inlining style/classList ## 1.9.8 ### Patch Changes - 09a9c1d: Export RendererOptions and Renderer types from solid-js/universal - 472c007: fix(scheduler): adjust yield timing logic to improve task scheduling … - 3d3207d: fix #2491 no key on merge false - 2cd810f: compiler and jsx type updates - fix: ssr style undefined - fix: ssr double escaped array - fix: skip jsxImportSource skipping transform - fix: @once on style, classlist - JSX type updates - Update Universal Renderer Types - cbff564: feat: createMutable support for class inheritance - e056eab: add support for `is` in `Dynamic`, closes #2413 - bdba4dc: Fix resource instances always getting cached on SSR - Updated dependencies [2cd810f] - babel-preset-solid@1.9.8 ## 1.9.7 ### Patch Changes - 84ca952: Fix hydration issues caused by seroval update. - 4cd7eb1: Catch synchronous errors in `createResource`. ## 1.9.6 ### Patch Changes - 362e99f: fix #2444 prev value in memo messing with reactive rendering - 8356213: update compiler config, fix boolean attribute regression, update JSX types - c65faec: fix #2428 - owner always present in resource fetcher - 6380b01: fix #2399: novalidate, #2460 spellcheck types ## 1.9.5 ### Patch Changes - 86ae8a9: add optional initalValue argument to `from` helper - 89e016d: dev: Add `internal` flag to signal - 9431b88: Mirror createDynamic for SSR - 35266c1: JSX type updates, preliminary MathML support, fix spread overescaping - 0eab77d: Removed unnecessary evaluations of and conditions. - fff8aed: Update typescript to 5.7 - f9ef621: dev: Add afterRegisterGraph hook replacing afterCreateSignal ## 1.9.4 ### Patch Changes - b93956f: fix escaping in resolution done outside of DOM Expressions - 199dd69: fix reconcile null guard - 7f9cd3d: lazy image, tagged template detection, security fixes - 32aa744: Improve resolving arguments in createResource ## 1.9.3 ### Patch Changes - bb6ce8b: Reordering setter overloads - 9b70a15: validation fixes, type updates, ssr attribute fix ## 1.9.2 ### Patch Changes - 22aff14: update validation: smaller lib, opt out, better table handling add `on:` event types for native events - e2e2a03: Fix setter type compatibility with kobalte select and add tests ## 1.9.1 ### Patch Changes - fb67b687: fix anchor host interfering with event delegation - 7ecf92d3: fix #2304 component props can be string, explicit imports in tests ## 1.9.0 ### Minor Changes - 4f8597dc: better handling of exports client/server - 120bf06d: fix!: Remove browser field from package.json - 2a3a1980: update dom-expressions - Improved Custom Element/Shadow DOM traversal - @olivercoad - Better heuristic to determine when to importNode - @titoBouzout - handleEvent syntax to allow custom event properties when not delegated - @titoBouzout - support for bool: attribute namespace - @titoBouzout - add "is" as detection for custom element - @titoBouzout - fix missing exports in different envs - @trusktr - better hydration mismatch errors - @ryansolid - improved HTML validation of JSX partials - @titoBouzout ### Patch Changes - 80b09589: Improve signal setter type for code completion of string literal unions. - 51bec61a: update TS to NodeNext ## 1.8.23 ### Patch Changes - bc20a4ce: update types, fix hydration cancel timing error, sync ssr script appending - 9697c94b: jsdoc: Fix incorrect links of reactive utility `on` - 9e192d7e: fix #2282: Add Check for Proxy support - 379293d9: use correct hydration id in server lazy - 73c00927: Fix missing code block end in `useTransition`'s jsdoc comment - e4b2c668: fix missing disposal of nested transition nodes - 94929afa: fix wrapping of object with `null` prototype ## 1.8.22 ### Patch Changes - f8ae663c: Fix broken links in Readme - 19d0295f: fix stranded effects during hydration cancelation - 26128ec0: fix #2259 attr: in ssr, updates some types ## 1.8.21 ### Patch Changes - a036a63a: shortcut hydrate call when hydration is done ## 1.8.20 ### Patch Changes - c8fe58e9: fix #2250 hydration error, fix lazy component loading, better hydration cancelation - 80dd2769: fix #2236 improper shortcircuit in resource hydration ## 1.8.19 ### Patch Changes - 3fc015c2: track length in array helpers, fix mobx external source - f909c1c1: fix #2228 - chained resources with initial values - 816a5c61: fix #2209 processing parent before child value binding in select - 424a31a3: optimize hydration keys ## 1.8.18 ### Patch Changes - 6693b56f: update TS, custom elements, and a lot compiler fixes fixes #2144, #2145, #2178, #2192 - a8c2a8f3: remove weird server resource hack, fix hydrated resource state ## 1.8.17 ### Patch Changes - 72c5381d: fix #2134, merge dom expressions fix #2136, fix #2137, fix #2110 - e065e475: fix #2135 ssr of top level fragments under Suspense ## 1.8.16 ### Patch Changes - 8de75a47: fix #2065 forward initial value to `on` - 071cd42f: fix #2100, fix #2102 - hydration errors due to over optimization - 3212f74d: Adjust some JSDocs ## 1.8.15 ### Patch Changes - 829af663: fix #2047 early interaction/multiple resources - 4ee461dc: improve template escaping, fragment hydration, SVG use types ## 1.8.14 ### Patch Changes - 4b76be80: fix storage export in top-level package.json ## 1.8.13 ### Patch Changes - 3ac8210c: fix storage export ## 1.8.12 ### Patch Changes - aba5de08: fix #1746 class properties not working getters in createMutable - 85b26c36: fix #2041, fix #2043 - async renderer timing, numeric prop literals ## 1.8.11 ### Patch Changes - 1ec67f15: fix #2028, fix #2029 revert spread value bypass, and guard multi-text ## 1.8.10 ### Patch Changes - 169d23b4: fix disposal timing when streaming ## 1.8.9 ### Patch Changes - 80d4830f: fix #2016 value spread, smaller build output - 918586fb: fix #2017 object replacing array in `reconcile` - 71bea784: fix #1971 order of merged properties - b0862d39: fix #2014 html not replaced when resource resolves next `tick` - cbc8d3ee: remove seroval plugins from build output ## 1.8.8 ### Patch Changes - 40b5d78d: chore(types): return mapped type for splitProps excluded `other` value - 968e2cc9: update seroval, fix #1972, fix #1980, fix #2002, support partial ALS - 292aba41: fix #1982 ErrorBoundary with ExternalSource - 7e5667ab: fix #1998 Switch relying on order - 8d2de12f: fix #1850 untrack in external source - b887587a: fix #1973 array over object reconcile ## 1.8.7 ### Patch Changes - 22667bbc: fix: createSignal not found when bundled - e09a3cc3: fix timing issue with deferStream ## 1.8.6 ### Patch Changes - 2b320376: Add types directory export for each package - fb7f4bc1: fix #1950 leaking error tracking - b092368c: feat(DEV): Add afterCreateSignal hook to DevHooks - 54e1aecf: update seroval, fix this, optimize star imports, fix #1952 hydration race condition ## 1.8.5 ### Patch Changes - 80ca972f: fix `onHydrate` call being skipped ## 1.8.4 ### Patch Changes - cf0542a4: fix #1927, fix #1929, fix #1931, update storage API - 3f3a3396: serialization error handling, experimental async storage ## 1.8.3 ### Patch Changes - 1f0226e1: fix #1917 for real this time ## 1.8.2 ### Patch Changes - b632dfd5: Add missing `indexArray` to server-side runtime. - dd492c5e: fix #1917, fix #1918 error handling with serialization - 4968fe26: Add `.js` extension to import ## 1.8.1 ### Patch Changes - 0b9b71aa: better errors for hydration ## 1.8.0 ### Minor Changes - 2c087cbb: update to seroval streaming serializer, change ssr markers - 2c087cbb: hydration perf improvement, fix #1849 ### Patch Changes - 2c087cbb: remove attribute quotes in template, batch serialization - 2c087cbb: improved serialization/guards, fix #1413, fix #1796 hydration with lazy - 2c087cbb: fix: missing `has` property in `SharedConfig` - 2c087cbb: fix #1905, fix #1908 JSX type ommissions ## 1.8.0-beta.2 ### Minor Changes - e3a97d28: hydration perf improvement, fix #1849 ### Patch Changes - d797a143: fix #1905, fix #1908 JSX type ommissions ## 1.8.0-beta.1 ### Patch Changes - f6d511db: remove attribute quotes in template, batch serialization - af625dd3: fix: missing `has` property in `SharedConfig` ## 1.8.0-beta.0 ### Minor Changes - d8e0e8e8: update to seroval streaming serializer, change ssr markers ### Patch Changes - bf09b838: improved serialization/guards, fix #1413, fix #1796 hydration with lazy ## 1.7.12 ### Patch Changes - 12eb1552: fix #1875 - mergeProps not handling undefined on SSR - 13b1fa6e: fix #1883 initialize createDeferred with transition value - 10ac07af: update jsx types, iife compiler optimization - 8b49110b: Allow passing defer:boolean to `on` ## 1.7.11 ### Patch Changes - 26740b88: fix #1848 Suspense Default Context Non-Null ## 1.7.10 ### Patch Changes - 5ed448ae: Export `ContextProviderComponent`, `ResolvedChildren` and `ResolvedJSXElement` types - 7dd1f413: fix .pipeTo signature to return promise - c2008f02: Fix underscore property - 792e7dea: fix #1821 improve context performance ## 1.7.9 ### Patch Changes - 44a2bf0b: fix #1814 incorrect typing embedding for h and html - 6cd10c73: Changes how the Setter type was declared without actually functionally changing it, fixing the Setter type being assignable to any other Setter type; fixes #1818. Generically typed Setters must now non-null assert their parameter, i.e. ```diff function myCustomSignal(v: T) { const [get, set] = createSignal(); - const mySetter: Setter = (v?) => set(v); + const mySetter: Setter = (v?) => set(v!); const [get, set] = createSignal(v); - const mySetter: Setter = (v?) => set(v); + const mySetter: Setter = (v?) => set(v!); } ``` - 6c9879c9: fix in introspection in stores - 039cf60d: update universal runtime readme - 852f4c76: add missing link jsx types ## 1.7.8 ### Patch Changes - efd23186: fix #1780 invalid HTML comments - 51074fab: remove optional chaining, reduce bundle size - fe6f03f9: fix #1795 early effects running during async hydration ## 1.7.7 ### Patch Changes - c4cbfd3c: fix(Portal): reactive in children when pass signal directly - 0100bd12: Propagate errors to parents when throwing errors in nested catchError - 46e5e787: Improve type inference of `createSelector`. - 8ba0e80a: Fix `mergeProps`. - e660e5a3: add prettier code format in git-commit-hook - 93d44d45: fix #1787 missing CJS types ## 1.7.6 ### Patch Changes - 83c99d51: fix #1739 resolved state of disabled resources - f99dd044: Solid-Element: Add clarification on 'props' parameter in customElement function - 88493691: apply reference optimization to mergeProps - 514ef679: test: add tests to `splitProps` - 20261537: fix #1735 web component instantiation before constructor - 194f93c7: Improve performance in `splitProps` and `mergeProps` ## 1.7.5 ### Patch Changes - 5288cfa8: fix #1713, fix non-option jsx types - 8852c199: test: add tests to `splitProps` and `mergeProps` ## 1.7.4 ### Patch Changes - 1b5ea076: perf: avoid unnecessary flat - 91110701: fix element/test mismatch issues #1684, #1697, #1707 fix solid-ssr types add missing JSX types #1690 fix firefox iframe #1688 ## 1.7.3 ### Patch Changes - 655f0b7e: fix attr in ssr spread, fix static undefined classList values, fix #1666 directives in TTLs - 8ce2c47b: Portal fixes #1676, #1677 ## 1.7.2 ### Patch Changes - 27994dc9: Another attempt at fixing skypack - dfec6883: fix #1668 proto methods on store data nodes ## 1.7.1 ### Patch Changes - ba024813: fix ref timing in portals ## 1.7.0 ### Minor Changes - 503b6328: Add type narrowing non-keyed control flow - 86c32279: always cast to errors when handled - f7dc355f: Remove FunctionElement from JSX.Element types - 940e5745: change to seroval serializer, better ssr fragment fixes - 608b3c3a: Add catchError/deprecate onError - 2b80f706: Reduce DOM compiler output size Remove auxilary closing tags and lazy evaluate templates - 8d0877e4: fix #1562 cleanup order - 74f00e15: Support prop/attr directives in spreads, apply prop aliases only to specific elements ### Patch Changes - 6b77d9ed: Better types on function callback control flow - 41ca6522: fixes around templates and hydration - 840933b8: fix #1653 portal bypasses Suspense - cb6a383d: ensure narrowed values are non-null - 3de9432c: Better Input Event Types, Template Pruning, Universal Renderer Fixes - 2cb6f3d6: fix treeshaking in rollup 3 - 24469762: Add a reference to the component funciton to DevComponent owner. Rename DevComponent's property from `componentName` to `name`. - 5545d3ee: Type narrowed flow on the server, add stale warning - 0dc8e365: Make non-null control flow assertion stricter by throwing - 4929530b: Remove name generation of owners and signals - 71c40af6: DEV: Minor additions and change the API of dev hooks - 6a4fe46c: fix #1553 improper html entity encoding in literal expressions - 5d671b89: Fix external source tests - 23c157ac: fix backward compatibility of template, fix #1639 loading on iframe ## 1.7.0-beta.5 ### Patch Changes - 0dc8e365: Make non-null control flow assertion stricter by throwing ## 1.7.0-beta.4 ### Patch Changes - cb6a383d: ensure narrowed values are non-null - 3de9432c: Better Input Event Types, Template Pruning, Universal Renderer Fixes - 2cb6f3d6: fix treeshaking in rollup 3 - 23c157ac: fix backward compatibility of template, fix #1639 loading on iframe ## 1.7.0-beta.3 ### Patch Changes - 41ca6522: fixes around templates and hydration ## 1.7.0-beta.2 ### Minor Changes - 940e5745: change to seroval serializer, better ssr fragment fixes ## 1.7.0-beta.1 ### Minor Changes - 608b3c3a: Add catchError/deprecate onError - 2b80f706: Reduce DOM compiler output size Remove auxilary closing tags and lazy evaluate templates - 8d0877e4: fix #1562 cleanup order - 74f00e15: Support prop/attr directives in spreads, apply prop aliases only to specific elements ### Patch Changes - 6b77d9ed: Better types on function callback control flow - 24469762: Add a reference to the component funciton to DevComponent owner. Rename DevComponent's property from `componentName` to `name`. - 5545d3ee: Type narrowed flow on the server, add stale warning ## 1.7.0-beta.0 ### Minor Changes - 503b632: Add type narrowing non-keyed control flow - 86c3227: always cast to errors when handled - f7dc355: Remove FunctionElement from JSX.Element types ### Patch Changes - 4929530: Remove name generation of owners and signals - 71c40af: DEV: Minor additions and change the API of dev hooks - e245736: Fixed test case for setStore 7 parameter overload by fixing KeyOf giving number for KeyOf - 6a4fe46: fix #1553 improper html entity encoding in literal expressions ## 1.6.16 ### Patch Changes - d10da016: Fix #1651 hydration markers introduced too early - 620c7636: Switch test runner from Jest to Vitest ## 1.6.15 ### Patch Changes - e8448ebd: fix #1624 early fallback removal, add missing svg pathLength type - da83ebda: defer ssr cleanup to next macrotask ## 1.6.14 ### Patch Changes - 6cceab2f: fix #1613 broken renderToString ## 1.6.13 ### Patch Changes - af20f00b: fix #1602 wrong resource state during SSR - 60f8624d: fix #1596 ssr fragment text merge, fix #1599 ssr onCleanup ## 1.6.12 ### Patch Changes - e2888c77: Correct the type of `isServer` const to `boolean` from `false`. - 676ed331: docs: fix typos - b8a3ff13: fix #1586 error boundary called twice - 1aff80c6: fix #1573 top level reconcile not merging - 53db3f0f: fix fallback hydration - 47d574a8: fix #1588: dynamic mount elements in Portals without recreation - e245736f: Fixed test case for setStore 7 parameter overload by fixing KeyOf giving number for KeyOf - 61d1fe25: Export `isDev` const from solid-js/web for differentiating between dev/prod env. - 4fdec4f9: fix #1564, fix #1567 template literal bugs ## 1.6.11 ### Patch Changes - bfbd002: Fixed the store setter's recursive fallback overload not terminating with non-numbers - 1ecdea4: chore: export package.json - 91d518a: fix: createResource should not ignores empty string throw - 18e734d: Support null for detachedOwner in createRoot - 12d458d: fix #1547, missing SVGPattern type - 4aaa94b: Fix: swap KeyOf for MutableKeyOf in one of the SetStoreFunction overload - c26f933: Add fast track for `untrack` in case of `null` listener - 6fb3cd8: fix #1541: process errors at the end of synchronous execution - c5b208c: fix #1522, errors stop future effects from running ## 1.6.10 ### Patch Changes - 1b32e63: Fix broken comments description link to solid docs - dd879da: fix #1493 export DynamicProps - d89e791: Add generic to onCleanup - 695d99b: Export `EffectOptions` and `OnOptions` from main module - d35a1ca: Fixed the return type of the `Symbol.observable` method of the `observable` in the generated `.d.ts` - 7ab43a4: fix #1492 SSR Spread Breaks Hydration fix #1495 runWithOwner not clearing listener fix #1498 unrecoverable error in async batch ## 1.6.9 ### Patch Changes - a572c12: Streaming without a wrapper and compile time JSX validation - 0ad9859: fix #1478 error infinite loop - 12629a3: DEV: registerGraph `graph` property added to values ## 1.6.8 ### Patch Changes - 6db2d89: Fix #1461 - streaming broken due to reusing same resources for lazy dedupe ## 1.6.7 ### Patch Changes - c4ac14c: Format/Cleanup Types and code style - 1384496: Fix unowned roots having owner in dev - 1dbd5a9: stub out render and hydrate on server - 368e508: make splitProps with dynamic source return proxies - 54f3068: fix #1452 runWithOwner responsible for errors in its scope - c8edacd: Fix lazy defined in components during SSR - 89baf12: fix boolean escaping, improve ssr performance ## 1.6.6 ### Patch Changes - a603850: Export SignalOptions - 2119211: fix #1423 - inlined arrow functions in SSR and update rollup - 5a5a72d: Fix #1436 incorrectly missing proxy detection - 5eb575a: fix: delete lazy contexts one by one as they are completed ## 1.6.5 ### Patch Changes - 50d1304: fix #1416 nulls in array reconcile - ee71b16: fix #1410 - node 14 compatibility. Remove `||=` operator that isn't available on some legacy platforms. ## 1.6.4 ### Patch Changes - a42a5f6: memoize merging functions ## 1.6.3 ### Patch Changes - e95e95f: Bug fixes and testing changelog ================================================ FILE: packages/solid/README.md ================================================

SolidJS

[![Build Status](https://img.shields.io/github/actions/workflow/status/solidjs/solid/main-ci.yml?branch=main&logo=github&style=for-the-badge)](https://github.com/solidjs/solid/actions/workflows/main-ci.yml) [![Coverage Status](https://img.shields.io/coveralls/github/solidjs/solid.svg?style=for-the-badge)](https://coveralls.io/github/solidjs/solid?branch=main) [![NPM Version](https://img.shields.io/npm/v/solid-js.svg?style=for-the-badge)](https://www.npmjs.com/package/solid-js) [![](https://img.shields.io/npm/dm/solid-js.svg?style=for-the-badge)](https://www.npmjs.com/package/solid-js) [![Discord](https://img.shields.io/discord/722131463138705510?style=for-the-badge)](https://discord.com/invite/solidjs) [![Subreddit subscribers](https://img.shields.io/reddit/subreddit-subscribers/solidjs?style=for-the-badge)](https://www.reddit.com/r/solidjs/) **[Website](https://www.solidjs.com/) • [API Docs](https://docs.solidjs.com/) • [Features Tutorial](https://www.solidjs.com/tutorial/introduction_basics) • [Playground](https://playground.solidjs.com/?version=1.3.13#NobwRAdghgtgpmAXGGUCWEwBowBcCeADgsrgM4Ae2YZA9gK4BOAxiWGjIbY7gAQi9GcCABM4jXgF9eAM0a0YvADo1aAGzQiAtACsyAegDucAEYqA3EogcuPfr2ZCouOAGU0Ac2hqps+YpU6DW09CysrGXoIZlw0WgheAGEGCBdGAAoASn4rXgd4sj5gZhTcLF4yOFxkqNwAXV4AXgcnF3cvKDV0gAZMywT8iELeDEc4eFSm3iymgD4KqprU9JLamYBqXgBGPvCBoVwmBPTcvN4AHhN6XFx43gJiRpUrm-iVXnjEjWYAa0aQUZCCa4SSzU5nfirZaZSTgi76F63CBgga7CCwiBWISicTpGaNebnJZpXj6WblES0Zj0YEAOg8VQAompxsJcAAhfAASREJzAUEIhBUmTRYEkdSAA) • [Discord](https://discord.com/invite/solidjs)** Solid is a declarative JavaScript library for creating user interfaces. Instead of using a Virtual DOM, it compiles its templates to real DOM nodes and updates them with fine-grained reactions. Declare your state and use it throughout your app, and when a piece of state changes, only the code that depends on it will rerun. Check out our [intro video](https://www.youtube.com/watch?v=cELFZQAMdhQ) or read on! ## Key Features - Fine-grained updates to the real DOM - Declarative data: model your state as a system with reactive primitives - Render-once mental model: your components are regular JavaScript functions that run once to set up your view - Automatic dependency tracking: accessing your reactive state subscribes to it - [Small](https://dev.to/this-is-learning/javascript-framework-todomvc-size-comparison-504f) and [fast](https://krausest.github.io/js-framework-benchmark/current.html) - Simple: learn a few powerful concepts that can be reused, combined, and built on top of - Provides modern framework features like JSX, fragments, Context, Portals, Suspense, streaming SSR, progressive hydration, Error Boundaries and concurrent rendering. - Naturally debuggable: A `
` is a real div, so you can use your browser's devtools to inspect the rendering - [Web component friendly](https://github.com/solidjs/solid/tree/main/packages/solid-element#readme) and can author custom elements - Isomorphic: render your components on the client and the server - Universal: write [custom renderers](https://github.com/solidjs/solid/releases/tag/v1.2.0) to use Solid anywhere - A growing community and ecosystem with active core team support
Quick Start You can get started with a simple app by running the following in your terminal: ```sh > npx degit solidjs/templates/js my-app > cd my-app > npm i # or yarn or pnpm > npm run dev # or yarn or pnpm ``` Or for TypeScript: ```sh > npx degit solidjs/templates/ts my-app > cd my-app > npm i # or yarn or pnpm > npm run dev # or yarn or pnpm ``` This will create a minimal, client-rendered application powered by [Vite](https://vitejs.dev/). Or you can install the dependencies in your own setup. To use Solid with JSX (_recommended_), run: ```sh > npm i -D babel-preset-solid > npm i solid-js ``` The easiest way to get set up is to add `babel-preset-solid` to your `.babelrc`, babel config for webpack, or rollup configuration: ```js "presets": ["solid"] ``` For TypeScript to work, remember to set your `.tsconfig` to handle Solid's JSX: ```js "compilerOptions": { "jsx": "preserve", "jsxImportSource": "solid-js", } ```
## Why Solid? ### Performant Meticulously engineered for performance and with half a decade of research behind it, Solid's performance is almost indistinguishable from optimized vanilla JavaScript (See Solid on the [JS Framework Benchmark](https://krausest.github.io/js-framework-benchmark/current.html)). Solid is [small](https://bundlephobia.com/package/solid-js@1.3.15) and completely tree-shakable, and [fast](https://levelup.gitconnected.com/how-we-wrote-the-fastest-javascript-ui-framework-again-db097ddd99b6) when rendering on the server, too. Whether you're writing a fully client-rendered SPA or a server-rendered app, your users see it faster than ever. ([Read more about Solid's performance](https://dev.to/ryansolid/thinking-granular-how-is-solidjs-so-performant-4g37) from the library's creator.) ### Powerful Solid is fully-featured with everything you can expect from a modern framework. Performant state management is built-in with Context and Stores: you don't have to reach for a third party library to manage global state (if you don't want to). With Resources, you can use data loaded from the server like any other piece of state and build a responsive UI for it thanks to Suspense and concurrent rendering. And when you're ready to move to the server, Solid has full SSR and serverless support, with streaming and progressive hydration to get to interactive as quickly as possible. (Check out our full [interactive features walkthrough](https://www.solidjs.com/tutorial/introduction_basics).) ### Pragmatic Do more with less: use simple, composable primitives without hidden rules and gotchas. In Solid, components are just functions - rendering is determined purely by how your state is used - so you're free to organize your code how you like and you don't have to learn a new rendering system. Solid encourages patterns like declarative code and read-write segregation that help keep your project maintainable, but isn't opinionated enough to get in your way. ### Productive Solid is built on established tools like JSX and TypeScript and integrates with the Vite ecosystem. Solid's bare-metal, minimal abstractions give you direct access to the DOM, making it easy to use your favorite native JavaScript libraries like D3. And the Solid ecosystem is growing fast, with [custom primitives](https://github.com/solidjs-community/solid-primitives), [component libraries](https://github.com/hope-ui/hope-ui), and build-time utilities that let you [write Solid code in new ways](https://github.com/LXSMNSYC/solid-labels).
Show Me! ```jsx import { render } from "solid-js/web"; import { createSignal } from "solid-js"; // A component is just a function that (optionally) accepts properties and returns a DOM node const Counter = props => { // Create a piece of reactive state, giving us a accessor, count(), and a setter, setCount() const [count, setCount] = createSignal(props.startingCount || 1); // The increment function calls the setter const increment = () => setCount(count() + 1); console.log( "The body of the function runs once, like you'd expect from calling any other function, so you only ever see this console log once." ); // JSX allows us to write HTML within our JavaScript function and include dynamic expressions using the { } syntax // The only part of this that will ever rerender is the count() text. return ( ); }; // The render function mounts a component onto your page render(() => , document.getElementById("app")); ``` See it in action in our interactive [Playground](https://playground.solidjs.com/?hash=-894962706&version=1.3.13)! Solid compiles our JSX down to efficient real DOM expressions updates, still using the same reactive primitives (`createSignal`) at runtime but making sure there's as little rerendering as possible. Here's what that looks like in this example: ```js import { render, createComponent, delegateEvents, insert, template } from "solid-js/web"; import { createSignal } from "solid-js"; const _tmpl$ = /*#__PURE__*/ template(``, 2); const Counter = props => { const [count, setCount] = createSignal(props.startingCount || 1); const increment = () => setCount(count() + 1); console.log("The body of the function runs once . . ."); return (() => { //_el$ is a real DOM node! const _el$ = _tmpl$.cloneNode(true); _el$.firstChild; _el$.$$click = increment; //This inserts the count as a child of the button in a way that allows count to update without rerendering the whole button insert(_el$, count, null); return _el$; })(); }; render( () => createComponent(Counter, { startingCount: 2 }), document.getElementById("app") ); delegateEvents(["click"]); ```
## More Check out our official [documentation](https://www.solidjs.com/guide) or browse some [examples](https://github.com/solidjs/solid/blob/main/documentation/resources/examples.md) ## Browser Support SolidJS Core is committed to supporting the last 2 years of modern browsers including Firefox, Safari, Chrome and Edge (for desktop and mobile devices). We do not support IE or similar sunset browsers. For server environments, we support Node LTS and the latest Deno and Cloudflare Worker runtimes. Testing Powered By SauceLabs ## Community Come chat with us on [Discord](https://discord.com/invite/solidjs)! Solid's creator and the rest of the core team are active there, and we're always looking for contributions. ### Contributors ### Open Collective Support us with a donation and help us continue our activities. [[Contribute](https://opencollective.com/solid)] ### Sponsors Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/solid#sponsor)] ================================================ FILE: packages/solid/babel.config.cjs ================================================ const path = require('path'); module.exports = { env: { test: { presets: [ ["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript" ], plugins: [ [ "babel-plugin-transform-rename-import", { replacements: [ { original: "rxcore", replacement: path.join(__dirname, "../../packages/solid/web/src/core") }, { original: "^solid-js$", replacement: path.join(__dirname, "src"), } ] } ], [ "babel-plugin-jsx-dom-expressions", { moduleName: path.join(__dirname, "web/src/index"), contextToCustomElements: true, wrapConditionals: true } ] ] } } }; ================================================ FILE: packages/solid/bench/bench.cjs ================================================ const { createRoot, createSignal, createComputed } = require("../dist/solid.cjs"); var now = typeof process === 'undefined' ? browserNow : nodeNow; var COUNT = 1e5; main(); function main() { var createTotal = 0; createTotal += bench(createDataSignals, COUNT, COUNT); createTotal += bench(createComputations0to1, COUNT, 0); createTotal += bench(createComputations1to1, COUNT, COUNT); createTotal += bench(createComputations2to1, COUNT / 2, COUNT); createTotal += bench(createComputations4to1, COUNT / 4, COUNT); createTotal += bench(createComputations1000to1, COUNT / 1000, COUNT); //total += bench1(createComputations8, COUNT, 8 * COUNT); createTotal += bench(createComputations1to2, COUNT, COUNT / 2); createTotal += bench(createComputations1to4, COUNT, COUNT / 4); createTotal += bench(createComputations1to8, COUNT, COUNT / 8); createTotal += bench(createComputations1to1000, COUNT, COUNT / 1000); console.log(`create total: ${createTotal.toFixed(0)}`); console.log('---'); var updateTotal = 0; updateTotal += bench(updateComputations1to1, COUNT * 4, 1); updateTotal += bench(updateComputations2to1, COUNT * 2, 2); updateTotal += bench(updateComputations4to1, COUNT, 4); updateTotal += bench(updateComputations1000to1, COUNT / 100, 1000); updateTotal += bench(updateComputations1to2, COUNT * 4, 1); updateTotal += bench(updateComputations1to4, COUNT * 4, 1); updateTotal += bench(updateComputations1to1000, COUNT * 4, 1); console.log(`update total: ${updateTotal.toFixed(0)}`); console.log(`total: ${(createTotal + updateTotal).toFixed(0)}`); } function bench(fn, count, scount) { var time = run(fn, count, scount); console.log(`${fn.name}: ${time.toFixed(0)}`); return time; } function run(fn, n, scount) { // prep n * arity sources var start, end; createRoot(function () { // run 3 times to warm up var sources = createDataSignals(scount, []); fn(n / 100, sources); sources = createDataSignals(scount, []); fn(n / 100, sources); sources = createDataSignals(scount, []); % OptimizeFunctionOnNextCall(fn); fn(n / 100, sources); sources = createDataSignals(scount, []); for (var i = 0; i < scount; i++) { sources[i][0](); sources[i][0](); //%OptimizeFunctionOnNextCall(sources[i]); sources[i][0](); } // start GC clean % CollectGarbage(null); start = now(); fn(n, sources); // end GC clean sources = null; % CollectGarbage(null); end = now(); }); return end - start; } function createDataSignals(n, sources) { for (var i = 0; i < n; i++) { sources[i] = createSignal(i); } return sources; } function createComputations0to1(n, sources) { for (var i = 0; i < n; i++) { createComputation0(i); } } function createComputations1to1000(n, sources) { for (var i = 0; i < n / 1000; i++) { const [get] = sources[i]; for (var j = 0; j < 1000; j++) { createComputation1(get); } //sources[i] = null; } } function createComputations1to8(n, sources) { for (var i = 0; i < n / 8; i++) { const [get] = sources[i]; createComputation1(get); createComputation1(get); createComputation1(get); createComputation1(get); createComputation1(get); createComputation1(get); createComputation1(get); createComputation1(get); //sources[i] = null; } } function createComputations1to4(n, sources) { for (var i = 0; i < n / 4; i++) { const [get] = sources[i]; createComputation1(get); createComputation1(get); createComputation1(get); createComputation1(get); //sources[i] = null; } } function createComputations1to2(n, sources) { for (var i = 0; i < n / 2; i++) { const [get] = sources[i]; createComputation1(get); createComputation1(get); //sources[i] = null; } } function createComputations1to1(n, sources) { for (var i = 0; i < n; i++) { const [get] = sources[i] createComputation1(get); //sources[i] = null; } } function createComputations2to1(n, sources) { for (var i = 0; i < n; i++) { createComputation2( sources[i * 2][0], sources[i * 2 + 1][0] ); //sources[i * 2] = null; //sources[i * 2 + 1] = null; } } function createComputations4to1(n, sources) { for (var i = 0; i < n; i++) { createComputation4( sources[i * 4][0], sources[i * 4 + 1][0], sources[i * 4 + 2][0], sources[i * 4 + 3][0] ); //sources[i * 4] = null; //sources[i * 4 + 1] = null; //sources[i * 4 + 2] = null; //sources[i * 4 + 3] = null; } } function createComputations8(n, sources) { for (var i = 0; i < n; i++) { createComputation8( sources[i * 8][0], sources[i * 8 + 1][0], sources[i * 8 + 2][0], sources[i * 8 + 3][0], sources[i * 8 + 4][0], sources[i * 8 + 5][0], sources[i * 8 + 6][0], sources[i * 8 + 7][0] ); sources[i * 8] = null; sources[i * 8 + 1] = null; sources[i * 8 + 2] = null; sources[i * 8 + 3] = null; sources[i * 8 + 4] = null; sources[i * 8 + 5] = null; sources[i * 8 + 6] = null; sources[i * 8 + 7] = null; } } // only create n / 100 computations, as otherwise takes too long function createComputations1000to1(n, sources) { for (var i = 0; i < n; i++) { createComputation1000(sources, i * 1000); } } function createComputation0(i) { createComputed(function () { return i; }); } function createComputation1(s1) { createComputed(function () { return s1(); }); } function createComputation2(s1, s2) { createComputed(function () { return s1() + s2(); }); } function createComputation4(s1, s2, s3, s4) { createComputed(function () { return s1() + s2() + s3() + s4(); }); } function createComputation8(s1, s2, s3, s4, s5, s6, s7, s8) { createComputed(function () { return s1() + s2() + s3() + s4() + s5() + s6() + s7() + s8(); }); } function createComputation1000(ss, offset) { createComputed(function () { var sum = 0; for (var i = 0; i < 1000; i++) { sum += ss[offset + i][0](); } return sum; }); } function updateComputations1to1(n, sources) { var [get1, set1] = sources[0]; createComputed(function () { return get1(); }); for (var i = 0; i < n; i++) { set1(i); } } function updateComputations2to1(n, sources) { var [get1, set1] = sources[0], [get2] = sources[1]; createComputed(function () { return get1() + get2(); }); for (var i = 0; i < n; i++) { set1(i); } } function updateComputations4to1(n, sources) { var [get1, set1] = sources[0], [get2] = sources[1], [get3] = sources[2], [get4] = sources[3]; createComputed(function () { return get1() + get2() + get3() + get4(); }); for (var i = 0; i < n; i++) { set1(i); } } function updateComputations1000to1(n, sources) { var [get1, set1] = sources[0]; createComputed(function () { var sum = 0; for (var i = 0; i < 1000; i++) { sum += sources[i][0](); } return sum; }); for (var i = 0; i < n; i++) { set1(i); } } function updateComputations1to2(n, sources) { var [get1, set1] = sources[0]; createComputed(function () { return get1(); }); createComputed(function () { return get1(); }); for (var i = 0; i < n / 2; i++) { set1(i); } } function updateComputations1to4(n, sources) { var [get1, set1] = sources[0]; createComputed(function () { return get1(); }); createComputed(function () { return get1(); }); createComputed(function () { return get1(); }); createComputed(function () { return get1(); }); for (var i = 0; i < n / 4; i++) { set1(i); } } function updateComputations1to1000(n, sources) { var [get1, set1] = sources[0]; for (var i = 0; i < 1000; i++) { createComputed(function () { return get1(); }); } for (var i = 0; i < n / 1000; i++) { set1(i); } } function browserNow() { return performance.now(); } function nodeNow() { var hrt = process.hrtime(); return hrt[0] * 1000 + hrt[1] / 1e6; } ================================================ FILE: packages/solid/bench/libraries/kairo.cjs ================================================ let currentCollecting = null; function setData(data, value) { if (!inTransaction) { return runInTransaction(() => setData(data, value)); } if (data.value !== value) { data.value = value; data.flags |= 1024 /* Changed */; dirtyDataQueue.push(data); // TODO: redunant? markObserversMaybeStale(data); } } const dirtyDataQueue = []; const effects = []; function markObserversMaybeStale(computation) { if (computation.flags & 64 /* SingleObserver */) { const observer = computation.observer; if (observer !== null) { if ((observer.flags & 2048) /* Zombie */ === 0) { observer.depsReadyBits |= 1 << computation.observerSlot; // maximum 52 dependencies } if (observer.flags & 8 /* MaybeStale */) { return; } observer.flags |= 8 /* MaybeStale */; markObserversMaybeStale(observer); } } else { for (let i = 0; i < computation.observers.length; i++) { const observer = computation.observers[i]; if ((observer.flags & 2048) /* Zombie */ === 0) { observer.depsReadyBits |= 1 << computation.observerSlots[i]; } if (observer.flags & 8 /* MaybeStale */) { continue; } observer.flags |= 8 /* MaybeStale */; markObserversMaybeStale(observer); } } } function accessData(data) { if (currentCollecting !== null) { if (currentCollecting.flags & 256 /* Dynamic */) { insertNewSource(currentCollecting, data); } else if (currentCollecting.flags & 512 /* MaybeStable */) { logUnstable(currentCollecting, data); } if (data.flags & 2048 /* Zombie */) { if ((currentCollecting.flags & 2048) /* Zombie */ === 0) { data.flags -= 2048 /* Zombie */; // dezombie naturally } } } return data.value; } function accessComputation(data) { if (data.flags & 32 /* Computing */) { throw new Error("Circular dependency"); } if (currentCollecting !== null) { if (currentCollecting.flags & 256 /* Dynamic */) { insertNewSource(currentCollecting, data); } else if (currentCollecting.flags & 512 /* MaybeStable */) { logUnstable(currentCollecting, data); } if (data.flags & 2048 /* Zombie */) { if ((currentCollecting.flags & 2048) /* Zombie */ === 0) { data.flags -= 2048 /* Zombie */; // dezombie naturally } } } if (data.flags & 8 /* MaybeStale */) { updateComputation(data); data.flags -= 8 /* MaybeStale */; } return data.value; } function logUnstable(accessor, data) { if (accessor.flags & 128 /* SingleSource */) { if (accessor.source !== data) { // currentCollecting.source is definitely not null? why? // deps changed accessor.flags -= 512 /* MaybeStable */; accessor.flags |= 256 /* Dynamic */; // clean observers from here if (accessor.checkIndex === 0) { // checkIndex == 0 means this is the first source // first source doesn't match? cleanupComputationOfSingleSource(accessor); // otherwise it changes from single source to multi source. } insertNewSource(accessor, data); // still need to log. } else { accessor.checkIndex++; } } else { const checkIndex = accessor.checkIndex; if (checkIndex >= accessor.sources.length || data !== accessor.sources[checkIndex]) { // deps changed. accessor.flags -= 512 /* MaybeStable */; accessor.flags |= 256 /* Dynamic */; // clean observers from here cleanupComputation(accessor, checkIndex); insertNewSource(accessor, data); } else { accessor.checkIndex++; } } } function collectSourceAndRecomputeComputation(computation) { const stored = currentCollecting; currentCollecting = computation; computation.flags |= 32 /* Computing */; const currentValue = computation.collect(); computation.flags -= 32 /* Computing */; if (computation.flags & 512 /* MaybeStable */) { // check the real used deps is lesser than assumed. if (computation.flags & 128 /* SingleSource */) { if (computation.checkIndex === 0 && computation.source !== null) { computation.flags -= 512 /* MaybeStable */; computation.flags |= 256 /* Dynamic */; cleanupComputationOfSingleSource(computation); } } else if (computation.checkIndex != computation.sources.length) { computation.flags -= 512 /* MaybeStable */; computation.flags |= 256 /* Dynamic */; cleanupComputation(computation, computation.checkIndex); } currentCollecting.checkIndex = 0; } currentCollecting = stored; return currentValue; } function untrack(fn, ...args) { const stored = currentCollecting; currentCollecting = null; const ret = fn(...args); currentCollecting = stored; return ret; } function updateComputation(computation) { if (computation.flags & 256 /* Dynamic */) { cleanupComputation(computation, 0); } const currentValue = collectSourceAndRecomputeComputation(computation); if (computation.flags & 4096 /* NotReady */) { computation.flags -= 4096 /* NotReady */ | 256 /* Dynamic */; if (computation.flags & 16384 /* Stable */) { computation.flags |= 16384 /* Stable */; } else { computation.flags |= 512 /* MaybeStable */; } } // compare value , if changed, mark as changed if (currentValue !== computation.value) { computation.value = currentValue; computation.flags |= 1024 /* Changed */; // maybe problematic? } } function cleanupComputationOfSingleSource(cell) { if (cell.source === null) { return; } let theSource = cell.source; let observerSlotOfLastSourceOfComputation = cell.sourceSlot; cell.source = null; cell.sourceSlot = -1; if (theSource.flags & 64 /* SingleObserver */ && theSource.observer !== null) { theSource.observer = null; theSource.observerSlot = -1; } else { // here observers is definitely not empty: let lastObserverOfSource = theSource.observers.pop(); let sourceSlotOfLastObserverOfSource = theSource.observerSlots.pop(); //我原来在哪儿,要找回去。 if (observerSlotOfLastSourceOfComputation == theSource.observers.length) { // lucky, you are just the last observer return; } // replace you with last observer theSource.observers[observerSlotOfLastSourceOfComputation] = lastObserverOfSource; theSource.observerSlots[ observerSlotOfLastSourceOfComputation ] = sourceSlotOfLastObserverOfSource; // notify the change of position if (lastObserverOfSource.flags & 128 /* SingleSource */) { lastObserverOfSource.sourceSlot = observerSlotOfLastSourceOfComputation; } else { lastObserverOfSource.sourceSlots[ sourceSlotOfLastObserverOfSource ] = observerSlotOfLastSourceOfComputation; } } } function cleanupComputation(cell, remain) { if (cell.flags & 128 /* SingleSource */) { return cleanupComputationOfSingleSource(cell); } while (cell.sources.length > remain) { let theSource = cell.sources.pop(); let observerSlotOfLastSourceOfComputation = cell.sourceSlots.pop(); if (theSource.flags & 64 /* SingleObserver */ && theSource.observer !== null) { theSource.observer = null; theSource.observerSlot = -1; } else { let lastObserverOfSource = theSource.observers.pop(); let sourceSlotOfLastObserverOfSource = theSource.observerSlots.pop(); if (observerSlotOfLastSourceOfComputation == theSource.observers.length) { continue; } theSource.observers[observerSlotOfLastSourceOfComputation] = lastObserverOfSource; theSource.observerSlots[ observerSlotOfLastSourceOfComputation ] = sourceSlotOfLastObserverOfSource; if (lastObserverOfSource.flags & 128 /* SingleSource */) { lastObserverOfSource.sourceSlot = observerSlotOfLastSourceOfComputation; } else { lastObserverOfSource.sourceSlots[ sourceSlotOfLastObserverOfSource ] = observerSlotOfLastSourceOfComputation; } } } } function propagate(computation) { let notZombie = false; // if maybe stale if (computation.flags & 8 /* MaybeStale */) { if (computation.depsReadyBits !== 0) { throw "this should never happen."; } if (computation.flags & 4 /* Stale */) { updateComputation(computation); computation.flags -= 4 /* Stale */; } computation.flags -= 8 /* MaybeStale */; // now it is definitely not stale! } // if changed if (computation.flags & 1024 /* Changed */) { let hasObserver = false; if (computation.flags & 64 /* SingleObserver */) { const observer = computation.observer; if (observer !== null) { if ((observer.flags & 2048) /* Zombie */ === 0) { observer.flags |= 4 /* Stale */; observer.depsReadyBits -= 1 << computation.observerSlot; if (observer.depsReadyBits === 0 && propagate(observer)) { notZombie = true; } hasObserver = true; } } } else { for (let i = 0; i < computation.observers.length; ) { let current = computation.observers[i]; if (current.flags & 2048 /* Zombie */) { i++; continue; } if ((current.flags & 8) /* MaybeStale */ === 0) { i++; hasObserver = true; // ??? continue; } current.flags |= 4 /* Stale */; current.depsReadyBits -= 1 << computation.observerSlots[i]; if (current.depsReadyBits === 0 && propagate(current)) { notZombie = true; } if (current === computation.observers[i]) { i++; } hasObserver = true; } } // now remove changed mark. computation.flags -= 1024 /* Changed */; if (computation.last_effect) { let wnode = computation.last_effect; while (wnode !== null) { effects.push(wnode.fn); wnode = wnode.prev; } } else if (!hasObserver) { computation.flags |= 2048 /* Zombie */; } } else { if (computation.flags & 64 /* SingleObserver */) { if (computation.observer !== null) { // TODO: make inline cache? if (!((computation.observer.flags & 2048) /* Zombie */)) { computation.observer.depsReadyBits -= 1 << computation.observerSlot; if (computation.observer.depsReadyBits === 0 && propagate(computation.observer)) { notZombie = true; } } } } else { for (let i = 0; i < computation.observers.length; i++) { let current = computation.observers[i]; if (current.flags & 2048 /* Zombie */) { continue; } current.depsReadyBits -= 1 << computation.observerSlots[i]; if (current.depsReadyBits === 0 && propagate(current)) { notZombie = true; } } } } return notZombie; } function watch(data, sideEffect) { if (data.flags & 2048 /* Zombie */) { data.flags -= 2048 /* Zombie */; } if (data.flags & 1 /* Data */) { accessData(data); // TODO: is it necessary? } else { accessComputation(data); // because it maybe stale? } const node = { fn: sideEffect, prev: data.last_effect, next: null, disposed: false, data: data }; if (data.last_effect) { data.last_effect.next = node; } data.last_effect = node; return node; } function disposeWatcher(watcher) { if (watcher.disposed) { return; } watcher.disposed = true; if (watcher.next === null) { // it is the last. watcher.data.last_effect = watcher.prev; } else { watcher.next.prev = watcher.prev; } if (watcher.prev) { watcher.prev.next = watcher.next; } } let inTransaction = false; function runInTransaction(fn) { if (inTransaction) { // already inside a transaction return fn(); } inTransaction = true; const retValue = fn(); inTransaction = false; while (dirtyDataQueue.length) { const data = dirtyDataQueue.pop(); propagate(data); } while (effects.length) { effects.pop()(); } return retValue; } function insertNewSource(accessing, source) { if (accessing.flags & 128 /* SingleSource */) { if (accessing.source === null) { accessing.source = source; accessing.sourceSlot = insertNewObserver(source, accessing, -1); } else { accessing.flags -= 128 /* SingleSource */; // notify relocation if (accessing.source.flags & 64 /* SingleObserver */) { accessing.source.observerSlot = 0; } else { accessing.source.observerSlots[accessing.sourceSlot] = 0; } accessing.sources = [accessing.source]; accessing.sourceSlots = [accessing.sourceSlot]; accessing.source = null; accessing.sourceSlot = -1; return insertNewSource(accessing, source); } } else { accessing.sources.push(source); accessing.sourceSlots.push(insertNewObserver(source, accessing, accessing.sourceSlots.length)); } } function insertNewObserver(accesed, observer, atWhichSlotOfObserver) { if (accesed.flags & 64 /* SingleObserver */) { if (accesed.observer === null) { accesed.observer = observer; accesed.observerSlot = atWhichSlotOfObserver; return -1; } else { accesed.flags -= 64 /* SingleObserver */; if (accesed.observer.flags & 128 /* SingleSource */) { accesed.observer.sourceSlot = 0; } else { accesed.observer.sourceSlots[accesed.observerSlot] = 0; } accesed.observers = [accesed.observer]; accesed.observerSlots = [accesed.observerSlot]; accesed.observer = null; accesed.observerSlot = -1; return insertNewObserver(accesed, observer, atWhichSlotOfObserver); } } else { accesed.observers.push(observer); accesed.observerSlots.push(atWhichSlotOfObserver); return accesed.observerSlots.length - 1; } } function createData(value) { return { flags: 1 /* Data */ | 64 /* SingleObserver */ | 2048 /* Zombie */, last_effect: null, observer: null, observerSlot: -1, observers: null, observerSlots: null, value }; } function createComputation(fn, options) { const ret = { flags: 2 /* Computation */ | 128 /* SingleSource */ | 64 /* SingleObserver */ | 2048 /* Zombie */ | 8 /* MaybeStale */ | 256 /* Dynamic */ | 4096 /* NotReady */, last_effect: null, observer: null, observerSlot: -1, observers: null, observerSlots: null, value: null, source: null, sourceSlot: -1, sources: null, sourceSlots: null, collect: fn, depsReadyBits: 0, checkIndex: 0 }; // ret.value = collectSourceAndRecomputeComputation(ret); if (options === null || options === void 0 ? void 0 : options.static) { // if source is ready: give it ready. ret.flags |= 16384 /* Stable */; } return ret; } module.exports = { createRoot: fn => fn(), createSignal: value => { const node = createData(value); return [() => accessData(node), v => setData(node, v)]; }, createComputed: fn => { const g = createComputation(fn); watch(g, () => {}); } }; ================================================ FILE: packages/solid/bench/libraries/preact.cjs ================================================ // preact signals 1.1.0 (c) 2018 Jason Miller // https://github.com/preactjs/signals function cycleDetected() { throw new Error("Cycle detected"); } // Flags for Computed and Effect. const RUNNING = 1 << 0; const NOTIFIED = 1 << 1; const OUTDATED = 1 << 2; const DISPOSED = 1 << 3; const HAS_ERROR = 1 << 4; const TRACKING = 1 << 5; // Flags for Nodes. const NODE_FREE = 1 << 0; const NODE_SUBSCRIBED = 1 << 1; function startBatch() { batchDepth++; } function endBatch() { if (batchDepth > 1) { batchDepth--; return; } let error; let hasError = false; while (batchedEffect !== undefined) { let effect = batchedEffect; batchedEffect = undefined; batchIteration++; while (effect !== undefined) { const next = effect._nextBatchedEffect; effect._nextBatchedEffect = undefined; effect._flags &= ~NOTIFIED; if (!(effect._flags & DISPOSED) && effect._flags & OUTDATED) { try { effect._callback(); } catch (err) { if (!hasError) { error = err; hasError = true; } } } effect = next; } } batchIteration = 0; batchDepth--; if (hasError) { throw error; } } function batch(callback) { if (batchDepth > 0) { return callback(); } /*@__INLINE__**/ startBatch(); try { return callback(); } finally { endBatch(); } } // Currently evaluated computed or effect. let evalContext = undefined; // Effects collected into a batch. let batchedEffect = undefined; let batchDepth = 0; let batchIteration = 0; // A global version number for signals, used for fast-pathing repeated // computed.peek()/computed.value calls when nothing has changed globally. let globalVersion = 0; function addDependency(signal) { if (evalContext === undefined) { return undefined; } let node = signal._node; if (node === undefined || node._target !== evalContext) { // `signal` is a new dependency. Create a new node dependency node, move it // to the front of the current context's dependency list. node = { _flags: 0, _version: 0, _source: signal, _prevSource: undefined, _nextSource: evalContext._sources, _target: evalContext, _prevTarget: undefined, _nextTarget: undefined, _rollbackNode: node, }; evalContext._sources = node; signal._node = node; // Subscribe to change notifications from this dependency if we're in an effect // OR evaluating a computed signal that in turn has subscribers. if (evalContext._flags & TRACKING) { signal._subscribe(node); } return node; } else if (node._flags & NODE_FREE) { // `signal` is an existing dependency from a previous evaluation. Reuse the dependency // node and move it to the front of the evaluation context's dependency list. node._flags &= ~NODE_FREE; const head = evalContext._sources; if (node !== head) { const prev = node._prevSource; const next = node._nextSource; if (prev !== undefined) { prev._nextSource = next; } if (next !== undefined) { next._prevSource = prev; } if (head !== undefined) { head._prevSource = node; } node._prevSource = undefined; node._nextSource = head; evalContext._sources = node; } // We can assume that the currently evaluated effect / computed signal is already // subscribed to change notifications from `signal` if needed. return node; } return undefined; } /** @internal */ function Signal(value) { this._value = value; this._version = 0; this._node = undefined; this._targets = undefined; } Signal.prototype._refresh = function () { return true; }; Signal.prototype._subscribe = function (node) { if (!(node._flags & NODE_SUBSCRIBED)) { node._flags |= NODE_SUBSCRIBED; node._nextTarget = this._targets; if (this._targets !== undefined) { this._targets._prevTarget = node; } this._targets = node; } }; Signal.prototype._unsubscribe = function (node) { if (node._flags & NODE_SUBSCRIBED) { node._flags &= ~NODE_SUBSCRIBED; const prev = node._prevTarget; const next = node._nextTarget; if (prev !== undefined) { prev._nextTarget = next; node._prevTarget = undefined; } if (next !== undefined) { next._prevTarget = prev; node._nextTarget = undefined; } if (node === this._targets) { this._targets = next; } } }; Signal.prototype.subscribe = function (fn) { const signal = this; return effect(function () { const value = signal.value; const flag = this._flags & TRACKING; this._flags &= ~TRACKING; try { fn(value); } finally { this._flags |= flag; } }); }; Signal.prototype.valueOf = function () { return this.value; }; Signal.prototype.toString = function () { return this.value + ""; }; Signal.prototype.peek = function () { return this._value; }; Object.defineProperty(Signal.prototype, "value", { get() { const node = addDependency(this); if (node !== undefined) { node._version = this._version; } return this._value; }, set(value) { if (value !== this._value) { if (batchIteration > 100) { cycleDetected(); } this._value = value; this._version++; globalVersion++; /**@__INLINE__*/ startBatch(); try { for ( let node = this._targets; node !== undefined; node = node._nextTarget ) { node._target._notify(); } } finally { endBatch(); } } }, }); function signal(value) { return new Signal(value); } function prepareSources(target) { for ( let node = target._sources; node !== undefined; node = node._nextSource ) { const rollbackNode = node._source._node; if (rollbackNode !== undefined) { node._rollbackNode = rollbackNode; } node._source._node = node; node._flags |= NODE_FREE; } } function cleanupSources(target) { // At this point target._sources is a mishmash of current & former dependencies. // The current dependencies are also in a reverse order of use. // Therefore build a new, reverted list of dependencies containing only the current // dependencies in a proper order of use. // Drop former dependencies from the list and unsubscribe from their change notifications. let node = target._sources; let sources = undefined; while (node !== undefined) { const next = node._nextSource; if (node._flags & NODE_FREE) { node._source._unsubscribe(node); node._nextSource = undefined; } else { if (sources !== undefined) { sources._prevSource = node; } node._prevSource = undefined; node._nextSource = sources; sources = node; } node._source._node = node._rollbackNode; if (node._rollbackNode !== undefined) { node._rollbackNode = undefined; } node = next; } target._sources = sources; } function Computed(compute) { Signal.call(this, undefined); this._compute = compute; this._sources = undefined; this._globalVersion = globalVersion - 1; this._flags = OUTDATED; } Computed.prototype = new Signal(); Computed.prototype._refresh = function () { this._flags &= ~NOTIFIED; if (this._flags & RUNNING) { return false; } // If this computed signal has subscribed to updates from its dependencies // (TRACKING flag set) and none of them have notified about changes (OUTDATED // flag not set), then the computed value can't have changed. if ((this._flags & (OUTDATED | TRACKING)) === TRACKING) { return true; } this._flags &= ~OUTDATED; if (this._globalVersion === globalVersion) { return true; } this._globalVersion = globalVersion; // Mark this computed signal running before checking the dependencies for value // changes, so that the RUNNIN flag can be used to notice cyclical dependencies. this._flags |= RUNNING; if (this._version > 0) { // Check the dependencies for changed values. The dependency list is already // in order of use. Therefore if multiple dependencies have changed values, only // the first used dependency is re-evaluated at this point. let node = this._sources; while (node !== undefined) { // If a dependency has something blocking it from refreshing (e.g. a dependency // cycle) or there's a new version of the dependency, then we need to recompute. if (!node._source._refresh() || node._source._version !== node._version) { break; } node = node._nextSource; } // If none of the dependencies have changed values since last recompute then the // computed value can't have changed. if (node === undefined) { this._flags &= ~RUNNING; return true; } } const prevContext = evalContext; try { prepareSources(this); evalContext = this; const value = this._compute(); if ( this._flags & HAS_ERROR || this._value !== value || this._version === 0 ) { this._value = value; this._flags &= ~HAS_ERROR; this._version++; } } catch (err) { this._value = err; this._flags |= HAS_ERROR; this._version++; } evalContext = prevContext; cleanupSources(this); this._flags &= ~RUNNING; return true; }; Computed.prototype._subscribe = function (node) { if (this._targets === undefined) { this._flags |= OUTDATED | TRACKING; // A computed signal subscribes lazily to its dependencies when the it // gets its first subscriber. for ( let node = this._sources; node !== undefined; node = node._nextSource ) { node._source._subscribe(node); } } Signal.prototype._subscribe.call(this, node); }; Computed.prototype._unsubscribe = function (node) { Signal.prototype._unsubscribe.call(this, node); // Computed signal unsubscribes from its dependencies from it loses its last subscriber. if (this._targets === undefined) { this._flags &= ~TRACKING; for ( let node = this._sources; node !== undefined; node = node._nextSource ) { node._source._unsubscribe(node); } } }; Computed.prototype._notify = function () { if (!(this._flags & NOTIFIED)) { this._flags |= OUTDATED | NOTIFIED; for ( let node = this._targets; node !== undefined; node = node._nextTarget ) { node._target._notify(); } } }; Computed.prototype.peek = function () { if (!this._refresh()) { cycleDetected(); } if (this._flags & HAS_ERROR) { throw this._value; } return this._value; }; Object.defineProperty(Computed.prototype, "value", { get() { if (this._flags & RUNNING) { cycleDetected(); } const node = addDependency(this); this._refresh(); if (node !== undefined) { node._version = this._version; } if (this._flags & HAS_ERROR) { throw this._value; } return this._value; }, }); function computed(compute) { return new Computed(compute); } function cleanupEffect(effect) { const cleanup = effect._cleanup; effect._cleanup = undefined; if (typeof cleanup === "function") { /*@__INLINE__**/ startBatch(); // Run cleanup functions always outside of any context. const prevContext = evalContext; evalContext = undefined; try { cleanup(); } catch (err) { effect._flags &= ~RUNNING; throw err; } finally { evalContext = prevContext; endBatch(); } } } function disposeEffect(effect) { for ( let node = effect._sources; node !== undefined; node = node._nextSource ) { node._source._unsubscribe(node); } effect._sources = undefined; cleanupEffect(effect); } function endEffect(prevContext) { if (evalContext !== this) { throw new Error("Out-of-order effect"); } cleanupSources(this); evalContext = prevContext; this._flags &= ~RUNNING; if (this._flags & DISPOSED) { disposeEffect(this); } endBatch(); } function Effect(compute) { this._compute = compute; this._cleanup = undefined; this._sources = undefined; this._nextBatchedEffect = undefined; this._flags = OUTDATED | TRACKING; } Effect.prototype._callback = function () { const finish = this._start(); try { if (!(this._flags & DISPOSED)) { this._cleanup = this._compute(); } } finally { finish(); } }; Effect.prototype._start = function () { if (this._flags & RUNNING) { cycleDetected(); } this._flags |= RUNNING; this._flags &= ~DISPOSED; prepareSources(this); cleanupEffect(this); /*@__INLINE__**/ startBatch(); this._flags &= ~OUTDATED; const prevContext = evalContext; evalContext = this; return endEffect.bind(this, prevContext); }; Effect.prototype._notify = function () { if (!(this._flags & NOTIFIED)) { this._flags |= NOTIFIED | OUTDATED; this._nextBatchedEffect = batchedEffect; batchedEffect = this; } }; Effect.prototype._dispose = function () { this._flags |= DISPOSED; if (!(this._flags & RUNNING)) { disposeEffect(this); } }; function effect(compute) { const effect = new Effect(compute); effect._callback(); // Return a bound function instead of a wrapper like `() => effect._dispose()`, // because bound functions seem to be just as fast and take up a lot less memory. return effect._dispose.bind(effect); } function createRoot(fn) { return fn(); } function createSignal(value) { const r = new Signal(value); return [() => r.value, v => r.value = v]; } module.exports = { createSignal, createRoot, createComputed: effect }; ================================================ FILE: packages/solid/bench/libraries/rval-mod.cjs ================================================ const NOT_TRACKING = 0; const STALE = 1; const UP_TO_DATE = 2; const context = { isUpdating: false, pending: [], currentlyComputingStack: [], get currentlyComputing() { return this.currentlyComputingStack[this.currentlyComputingStack.length - 1]; }, isRunningReactions: false, runPendingObservers }; function batch(fn) { if (context.isUpdating) return fn(); try { context.isUpdating = true; return fn(); } finally { context.isUpdating = false; runPendingObservers(); } } function runPendingObservers() { if (!context.isUpdating && !context.isRunningReactions) { context.isRunningReactions = true; while (context.pending.length) { // N.B. errors here cause other pending subscriptions to be aborted! const fns = context.pending.splice(0); for (let i = 0, len = fns.length; i < len; i += 1) fns[i](); } context.isRunningReactions = false; } } class Signal { constructor(state) { this.listeners = new Set(); this.value = state; } addListener(listener) { this.listeners.add(listener); } removeListener(listener) { this.listeners.delete(listener); } get() { return registerRead(this); } set(newValue) { if (newValue !== this.value) { this.value = newValue; batch(() => runAll(this.listeners)); } } } class Computed { constructor(derivation) { this.derivation = derivation; this.listeners = new Set(); this.inputValues = undefined; this.observing = new Set(); this.state = NOT_TRACKING; this.dirtyCount = 0; this.value = undefined; this.markDirty = () => { if (++this.dirtyCount === 1) { this.state = STALE; runAll(this.listeners); } }; } addListener(observer) { this.listeners.add(observer); } removeListener(observer) { this.listeners.delete(observer) } registerDependency(sub) { this.observing.add(sub); } someDependencyHasChanged() { switch (this.state) { case NOT_TRACKING: return true; case UP_TO_DATE: return false; case STALE: if (!inputSetHasChanged(this.observing, this.inputValues)) { this.dirtyCount = 0; this.state = UP_TO_DATE; return false; } } return true; } track() { if (!this.someDependencyHasChanged()) return; const oldObserving = this.observing; const [newValue, newObserving] = track(this.derivation); this.value = newValue; this.observing = newObserving; registerDependencies(this.markDirty, oldObserving, newObserving); this.inputValues = recordInputSet(newObserving); this.dirtyCount = 0; this.state = UP_TO_DATE; } get() { registerRead(this); // yay, we are up to date! if (this.state === UP_TO_DATE) return this.value; // nope, we are not, and no one is observing either if (!context.currentlyComputing && !this.listeners.size) { // This won't actively remove any listener, but will transition the drv to // untracked, if no other listener arrived // TODO: optimize: have one handler for this! // TODO: should there be an option to disable this optimization to prevent mem leaking? setTimeout(() => this.removeListener(null), 0); } // maybe scheduled, definitely tracking, value is needed, track now! this.track(); return this.value; } } function track(fn) { const observing = new Set(); context.currentlyComputingStack.push(observing); const res = fn(); context.currentlyComputingStack.pop(); return [res, observing]; } function registerDependencies(listener, oldDeps, newDeps) { // Optimize: if (!oldDeps) { for (const d of newDeps.values()) d.addListener(listener); } else { for (const o of newDeps.values()) { if (!oldDeps.has(o)) o.addListener(listener); } for (const o of oldDeps) { if (!newDeps.has(o)) o.removeListener(listener); } } } function registerRead(observable) { if (context.currentlyComputing) context.currentlyComputing.add(observable); return observable.value; } function recordInputSet(deps) { // optimize: write more efficiently return [...deps].map(currentValue); } function inputSetHasChanged(deps, inputs) { return !deps || !inputs || ![...deps.values()].every((o, idx) => o.get() === inputs[idx]); } function currentValue(dep) { // Returns the current, last known (computed) value of a dep // Regardless whether that is stale or not return dep.value; } function runAll(fns) { for (const fn of fns.values()) fn(); } function createSignal(value) { const v = new Signal(value); return [v.get.bind(v), v.set.bind(v)]; } function createMemo(derivation) { const c = new Computed(derivation); return c.get.bind(c); } function createRoot(fn) { fn(); } function createEffect(fn) { const computed = new Computed(fn); let scheduled = true; let disposed = false; function onDirty() { if (scheduled || disposed) return; scheduled = true; context.pending.push(onInvalidate); } function onInvalidate() { scheduled = false; computed.someDependencyHasChanged() && computed.get(); } computed.addListener(onDirty); onInvalidate(); } module.exports = { createSignal, createRoot, createComputed: createEffect, createMemo, batch }; ================================================ FILE: packages/solid/bench/libraries/s-mod.cjs ================================================ // Modified version of S.js[https://github.com/adamhaile/S] by Adam Haile // Comparator memos from VSJolund fork https://github.com/VSjolund/vs-bind const equalFn = (a, b) => a === b; const ERROR = Symbol("error"); // Public interface function createRoot(fn, detachedOwner) { detachedOwner && (Owner = detachedOwner); let owner = Owner, listener = Listener, root = fn.length === 0 ? UNOWNED : createComputationNode(null, null), result = undefined, disposer = function _dispose() { if (RunningClock !== null) { RootClock.disposes.add(root); } else { dispose(root); } }; Owner = root; Listener = null; try { result = fn(disposer); } catch (err) { const fns = lookup(Owner, ERROR); if (!fns) throw err; fns.forEach(f => f(err)); } finally { RootClock.afters.run(f => f()); Listener = listener; Owner = owner; } return result; } function createSignal(value, areEqual) { const d = new DataNode(value); let setter; if (areEqual) { let age = -1; setter = v => { if (!areEqual(v, value)) { const time = RootClock.time; if (time === age) { throw new Error(`Conflicting value update: ${v} is not the same as ${value}`); } age = time; value = v; d.next(v); } }; } else setter = d.next.bind(d); return [d.current.bind(d), setter]; } function createEffect(fn, value) { createComputationNode(fn, value); } function createDependentEffect(fn, deps, defer) { const resolved = Array.isArray(deps) ? callAll(deps) : deps; defer = !!defer; createComputationNode(value => { const listener = Listener; resolved(); if (defer) defer = false; else { Listener = null; value = fn(value); Listener = listener; } return value; }); } function createMemo(fn, value, areEqual) { var node = createComputationNode(fn, value); node.comparator = areEqual || null; return () => { if (Listener !== null) { const state = node.state; if ((state & 7) !== 0) { liftComputation(node); } if (node.age === RootClock.time && state === 8) { throw new Error("Circular dependency."); } if ((state & 16) === 0) { if (node.log === null) node.log = createLog(); logRead(node.log); } } return node.value; }; } function batch(fn) { let result = undefined; if (RunningClock !== null) result = fn(); else { RunningClock = RootClock; RunningClock.changes.reset(); try { result = fn(); event(); } finally { RunningClock = null; } } return result; } function sample(fn) { let result, listener = Listener; Listener = null; result = fn(); Listener = listener; return result; } function afterEffects(fn) { if (RunningClock !== null) RunningClock.afters.add(fn); else RootClock.afters.add(fn); } function onCleanup(fn) { if (Owner === null) console.warn("cleanups created outside a `createRoot` or `render` will never be run"); else if (Owner.cleanups === null) Owner.cleanups = [fn]; else Owner.cleanups.push(fn); } function onError(fn) { if (Owner === null) console.warn("error handlers created outside a `createRoot` or `render` will never be run"); else if (Owner.context === null) Owner.context = { [ERROR]: [fn] }; else if (!Owner.context[ERROR]) Owner.context[ERROR] = [fn]; else Owner.context[ERROR].push(fn); } function isListening() { return Listener !== null; } function createContext(defaultValue) { const id = Symbol("context"); return { id, Provider: createProvider(id), defaultValue }; } function useContext(context) { return lookup(Owner, context.id) || context.defaultValue; } function getOwner() { return Owner; } // Internal implementation /// Graph classes and operations class DataNode { constructor(value) { this.value = value; this.pending = NOTPENDING; this.log = null; } current() { if (Listener !== null) { if (this.log === null) this.log = createLog(); logRead(this.log); } return this.value; } next(value) { if (RunningClock !== null) { if (this.pending !== NOTPENDING) { // value has already been set once, check for conflicts if (value !== this.pending) { throw new Error("conflicting changes: " + value + " !== " + this.pending); } } else { // add to list of changes this.pending = value; RootClock.changes.add(this); } } else { // not batching, respond to change now if (this.log !== null) { this.pending = value; RootClock.changes.add(this); event(); } else { this.value = value; } } return value; } } function createComputationNode(fn, value) { const node = { fn, value, age: RootClock.time, state: 0, comparator: null, source1: null, source1slot: 0, sources: null, sourceslots: null, dependents: null, dependentslot: 0, dependentcount: 0, owner: Owner, owned: null, log: null, context: null, cleanups: null }; if (fn === null) return node; let owner = Owner, listener = Listener; if (owner === null) console.warn("computations created outside a `createRoot` or `render` will never be disposed"); Owner = Listener = node; if (RunningClock === null) { toplevelComputation(node); } else node.value = node.fn(node.value); if (owner && owner !== UNOWNED) { if (owner.owned === null) owner.owned = [node]; else owner.owned.push(node); } Owner = owner; Listener = listener; return node; } function createClock() { return { time: 0, changes: new Queue(), updates: new Queue(), disposes: new Queue(), afters: new Queue() }; } function createLog() { return { node1: null, node1slot: 0, nodes: null, nodeslots: null }; } class Queue { constructor() { this.items = []; this.count = 0; } reset() { this.count = 0; } add(item) { this.items[this.count++] = item; } run(fn) { let items = this.items; for (let i = 0; i < this.count; i++) { try { const item = items[i]; items[i] = null; fn(item); } catch (err) { const fns = lookup(Owner, ERROR); if (!fns) throw err; fns.forEach(f => f(err)); } } this.count = 0; } } // "Globals" used to keep track of current system state let RootClock = createClock(), RunningClock = null, // currently running clock Listener = null, // currently listening computation Owner = null, // owner for new computations Pending = null; // pending node // Constants let NOTPENDING = {}, UNOWNED = createComputationNode(null, null); // State // 1 - Stale, 2 - Pending, 4 - Pending Disposal, 8 - Running, 16 - Disposed // Functions function callAll(ss) { return function all() { for (let i = 0; i < ss.length; i++) ss[i](); }; } function lookup(owner, key) { return ( owner && ((owner.context && owner.context[key]) || (owner.owner && lookup(owner.owner, key))) ); } function resolveChildren(children) { if (typeof children === "function") return createMemo(() => resolveChildren(children())); if (Array.isArray(children)) { const results = []; for (let i = 0; i < children.length; i++) { let result = resolveChildren(children[i]); Array.isArray(result) ? results.push.apply(results, result) : results.push(result); } return results; } return children; } function createProvider(id) { return function provider(props) { let rendered; createComputationNode(() => { Owner.context = { [id]: props.value }; rendered = sample(() => resolveChildren(props.children)); }); return rendered; }; } function logRead(from) { let to = Listener, fromslot, toslot = to.source1 === null ? -1 : to.sources === null ? 0 : to.sources.length; if (from.node1 === null) { from.node1 = to; from.node1slot = toslot; fromslot = -1; } else if (from.nodes === null) { if (from.node1 === to) return; from.nodes = [to]; from.nodeslots = [toslot]; fromslot = 0; } else { fromslot = from.nodes.length; if (from.nodes[fromslot - 1] === to) return; from.nodes.push(to); from.nodeslots.push(toslot); } if (to.source1 === null) { to.source1 = from; to.source1slot = fromslot; } else if (to.sources === null) { to.sources = [from]; to.sourceslots = [fromslot]; } else { to.sources.push(from); to.sourceslots.push(fromslot); } } function liftComputation(node) { if ((node.state & 6) !== 0) { applyUpstreamUpdates(node); } if ((node.state & 1) !== 0) { updateNode(node); } resetComputation(node, 31); } function event() { // b/c we might be under a top level S.root(), have to preserve current root let owner = Owner; RootClock.updates.reset(); RootClock.time++; try { run(RootClock); } finally { RunningClock = Listener = null; Owner = owner; } } function toplevelComputation(node) { RunningClock = RootClock; RootClock.changes.reset(); RootClock.updates.reset(); try { node.value = node.fn(node.value); if (RootClock.changes.count > 0 || RootClock.updates.count > 0) { RootClock.time++; run(RootClock); } } catch (err) { const fns = lookup(Owner, ERROR); if (!fns) throw err; fns.forEach(f => f(err)); } finally { RunningClock = Owner = Listener = null; } } function run(clock) { let running = RunningClock, count = 0; RunningClock = clock; clock.disposes.reset(); // for each batch ... while (clock.changes.count !== 0 || clock.updates.count !== 0 || clock.disposes.count !== 0) { if (count > 0) // don't tick on first run, or else we expire already scheduled updates clock.time++; clock.changes.run(applyDataChange); clock.updates.run(updateNode); clock.disposes.run(dispose); // if there are still changes after excessive batches, assume runaway if (count++ > 1e5) { throw new Error("Runaway clock detected"); } } clock.afters.run(f => f()); RunningClock = running; } function applyDataChange(data) { data.value = data.pending; data.pending = NOTPENDING; if (data.log) setComputationState(data.log, stateStale); } function updateNode(node) { const state = node.state; if ((state & 16) === 0) { if ((state & 2) !== 0) { node.dependents[node.dependentslot++] = null; if (node.dependentslot === node.dependentcount) { resetComputation(node, 14); } } else if ((state & 1) !== 0) { if ((state & 4) !== 0) { liftComputation(node); } else if (node.comparator) { const current = updateComputation(node); const comparator = node.comparator; if (!comparator(current, node.value)) { markDownstreamComputations(node, false, true); } } else { updateComputation(node); } } } } function updateComputation(node) { const value = node.value, owner = Owner, listener = Listener; Owner = Listener = node; node.state = 8; cleanupNode(node, false); node.value = node.fn(node.value); resetComputation(node, 31); Owner = owner; Listener = listener; return value; } function stateStale(node) { const time = RootClock.time; if (node.age < time) { node.state |= 1; node.age = time; setDownstreamState(node, !!node.comparator); } } function statePending(node) { const time = RootClock.time; if (node.age < time) { node.state |= 2; let dependents = node.dependents || (node.dependents = []); dependents[node.dependentcount++] = Pending; setDownstreamState(node, true); } } function pendingStateStale(node) { if ((node.state & 2) !== 0) { node.state = 1; const time = RootClock.time; if (node.age < time) { node.age = time; if (!node.comparator) { markDownstreamComputations(node, false, true); } } } } function setDownstreamState(node, pending) { RootClock.updates.add(node); if (node.comparator) { const pending = Pending; Pending = node; markDownstreamComputations(node, true, false); Pending = pending; } else { markDownstreamComputations(node, pending, false); } } function markDownstreamComputations(node, onchange, dirty) { const owned = node.owned; if (owned !== null) { const pending = onchange && !dirty; markForDisposal(owned, pending, RootClock.time); } const log = node.log; if (log !== null) { setComputationState(log, dirty ? pendingStateStale : onchange ? statePending : stateStale); } } function setComputationState(log, stateFn) { const node1 = log.node1, nodes = log.nodes; if (node1 !== null) stateFn(node1); if (nodes !== null) { for (let i = 0, ln = nodes.length; i < ln; i++) { stateFn(nodes[i]); } } } function markForDisposal(children, pending, time) { for (let i = 0, ln = children.length; i < ln; i++) { const child = children[i]; if (child !== null) { if (pending) { if ((child.state & 16) === 0) { child.state |= 4; } } else { child.age = time; child.state = 16; } const owned = child.owned; if (owned !== null) markForDisposal(owned, pending, time); } } } function applyUpstreamUpdates(node) { if ((node.state & 4) !== 0) { const owner = node.owner; if ((owner.state & 7) !== 0) liftComputation(owner); node.state &= ~4; } if ((node.state & 2) !== 0) { const slots = node.dependents; for (let i = node.dependentslot, ln = node.dependentcount; i < ln; i++) { const slot = slots[i]; if (slot != null) liftComputation(slot); slots[i] = null; } node.state &= ~2; } } function cleanupNode(node, final) { let source1 = node.source1, sources = node.sources, sourceslots = node.sourceslots, cleanups = node.cleanups, owned = node.owned, i, len; if (cleanups !== null) { for (i = 0; i < cleanups.length; i++) { cleanups[i](final); } node.cleanups = null; } node.context = null; if (owned !== null) { for (i = 0; i < owned.length; i++) { dispose(owned[i]); } node.owned = null; } if (source1 !== null) { cleanupSource(source1, node.source1slot); node.source1 = null; } if (sources !== null) { for (i = 0, len = sources.length; i < len; i++) { cleanupSource(sources.pop(), sourceslots.pop()); } } } function cleanupSource(source, slot) { let nodes = source.nodes, nodeslots = source.nodeslots, last, lastslot; if (slot === -1) { source.node1 = null; } else { last = nodes.pop(); lastslot = nodeslots.pop(); if (slot !== nodes.length) { nodes[slot] = last; nodeslots[slot] = lastslot; if (lastslot === -1) { last.source1slot = slot; } else { last.sourceslots[lastslot] = slot; } } } } function resetComputation(node, flags) { node.state &= ~flags; node.dependentslot = 0; node.dependentcount = 0; } function dispose(node) { node.fn = null; node.log = null; node.dependents = null; cleanupNode(node, true); resetComputation(node, 31); } module.exports = { createRoot, createComputed: createEffect, createSignal } ================================================ FILE: packages/solid/bench/libraries/s.cjs ================================================ "use strict"; // Public interface var S = function S(fn, value) { if (Owner === null) console.warn("computations created without a root or parent will never be disposed"); var _a = makeComputationNode(fn, value, false, false), node = _a.node, _value = _a.value; if (node === null) { return function computation() { return _value; }; } else { return function computation() { return node.current(); }; } }; // compatibility with commonjs systems that expect default export to be at require('s.js').default rather than just require('s-js') Object.defineProperty(S, "default", { value: S }); S.root = function root(fn) { var owner = Owner, disposer = fn.length === 0 ? null : function _dispose() { if (root === null); else if (RunningClock !== null) { RootClock.disposes.add(root); } else { dispose(root); } }, root = disposer === null ? UNOWNED : getCandidateNode(), result; Owner = root; try { result = disposer === null ? fn() : fn(disposer); } finally { Owner = owner; } if (disposer !== null && recycleOrClaimNode(root, null, undefined, true)) { root = null; } return result; }; S.on = function on(ev, fn, seed, onchanges) { if (Array.isArray(ev)) ev = callAll(ev); onchanges = !!onchanges; return S(on, seed); function on(value) { var listener = Listener; ev(); if (onchanges) onchanges = false; else { Listener = null; value = fn(value); Listener = listener; } return value; } }; function callAll(ss) { return function all() { for (var i = 0; i < ss.length; i++) ss[i](); }; } S.effect = function effect(fn, value) { makeComputationNode(fn, value, false, false); }; S.data = function data(value) { var node = new DataNode(value); return function data(value) { if (arguments.length === 0) { return node.current(); } else { return node.next(value); } }; }; S.value = function value(current, eq) { var node = new DataNode(current), age = -1; return function value(update) { if (arguments.length === 0) { return node.current(); } else { var same = eq ? eq(current, update) : current === update; if (!same) { var time = RootClock.time; if (age === time) throw new Error("conflicting values: " + update + " is not the same as " + current); age = time; current = update; node.next(update); } return update; } }; }; S.freeze = function freeze(fn) { var result = undefined; if (RunningClock !== null) { result = fn(); } else { RunningClock = RootClock; RunningClock.changes.reset(); try { result = fn(); event(); } finally { RunningClock = null; } } return result; }; S.sample = function sample(fn) { var result, listener = Listener; Listener = null; result = fn(); Listener = listener; return result; }; S.cleanup = function cleanup(fn) { if (Owner === null) console.warn("cleanups created without a root or parent will never be run"); else if (Owner.cleanups === null) Owner.cleanups = [fn]; else Owner.cleanups.push(fn); }; // experimental : exposing node constructors and some state S.makeDataNode = function makeDataNode(value) { return new DataNode(value); }; S.makeComputationNode = makeComputationNode; S.disposeNode = function disposeNode(node) { if (RunningClock !== null) { RootClock.disposes.add(node); } else { dispose(node); } }; S.isFrozen = function isFrozen() { return RunningClock !== null; }; S.isListening = function isListening() { return Listener !== null; }; // Internal implementation /// Graph classes and operations var Clock = /** @class */ (function () { function Clock() { this.time = 0; this.changes = new Queue(); // batched changes to data nodes this.updates = new Queue(); // computations to update this.disposes = new Queue(); // disposals to run after current batch of updates finishes } return Clock; })(); var RootClockProxy = { time: function () { return RootClock.time; } }; var DataNode = /** @class */ (function () { function DataNode(value) { this.value = value; this.pending = NOTPENDING; this.log = null; } DataNode.prototype.current = function () { if (Listener !== null) { logDataRead(this); } return this.value; }; DataNode.prototype.next = function (value) { if (RunningClock !== null) { if (this.pending !== NOTPENDING) { // value has already been set once, check for conflicts if (value !== this.pending) { throw new Error("conflicting changes: " + value + " !== " + this.pending); } } else { // add to list of changes this.pending = value; RootClock.changes.add(this); } } else { // not batching, respond to change now if (this.log !== null) { this.pending = value; RootClock.changes.add(this); event(); } else { this.value = value; } } return value; }; DataNode.prototype.clock = function () { return RootClockProxy; }; return DataNode; })(); var ComputationNode = /** @class */ (function () { function ComputationNode() { this.fn = null; this.value = undefined; this.age = -1; this.state = CURRENT; this.source1 = null; this.source1slot = 0; this.sources = null; this.sourceslots = null; this.log = null; this.owned = null; this.cleanups = null; } ComputationNode.prototype.current = function () { if (Listener !== null) { if (this.age === RootClock.time) { if (this.state === RUNNING) throw new Error("circular dependency"); else updateNode(this); // checks for state === STALE internally, so don't need to check here } logComputationRead(this); } return this.value; }; ComputationNode.prototype.clock = function () { return RootClockProxy; }; return ComputationNode; })(); var Log = /** @class */ (function () { function Log() { this.node1 = null; this.node1slot = 0; this.nodes = null; this.nodeslots = null; } return Log; })(); var Queue = /** @class */ (function () { function Queue() { this.items = []; this.count = 0; } Queue.prototype.reset = function () { this.count = 0; }; Queue.prototype.add = function (item) { this.items[this.count++] = item; }; Queue.prototype.run = function (fn) { var items = this.items; for (var i = 0; i < this.count; i++) { fn(items[i]); items[i] = null; } this.count = 0; }; return Queue; })(); // Constants var NOTPENDING = {}, CURRENT = 0, STALE = 1, RUNNING = 2, UNOWNED = new ComputationNode(); // "Globals" used to keep track of current system state var RootClock = new Clock(), RunningClock = null, // currently running clock Listener = null, // currently listening computation Owner = null, // owner for new computations LastNode = null; // cached unused node, for re-use // Functions var makeComputationNodeResult = { node: null, value: undefined }; function makeComputationNode(fn, value, orphan, sample) { var node = getCandidateNode(), owner = Owner, listener = Listener, toplevel = RunningClock === null; Owner = node; Listener = sample ? null : node; if (toplevel) { value = execToplevelComputation(fn, value); } else { value = fn(value); } Owner = owner; Listener = listener; var recycled = recycleOrClaimNode(node, fn, value, orphan); if (toplevel) finishToplevelComputation(owner, listener); makeComputationNodeResult.node = recycled ? null : node; makeComputationNodeResult.value = value; return makeComputationNodeResult; } function execToplevelComputation(fn, value) { RunningClock = RootClock; RootClock.changes.reset(); RootClock.updates.reset(); try { return fn(value); } finally { Owner = Listener = RunningClock = null; } } function finishToplevelComputation(owner, listener) { if (RootClock.changes.count > 0 || RootClock.updates.count > 0) { RootClock.time++; try { run(RootClock); } finally { RunningClock = null; Owner = owner; Listener = listener; } } } function getCandidateNode() { var node = LastNode; if (node === null) node = new ComputationNode(); else LastNode = null; return node; } function recycleOrClaimNode(node, fn, value, orphan) { var _owner = orphan || Owner === null || Owner === UNOWNED ? null : Owner, recycle = node.source1 === null && ((node.owned === null && node.cleanups === null) || _owner !== null), i; if (recycle) { LastNode = node; if (_owner !== null) { if (node.owned !== null) { if (_owner.owned === null) _owner.owned = node.owned; else for (i = 0; i < node.owned.length; i++) { _owner.owned.push(node.owned[i]); } node.owned = null; } if (node.cleanups !== null) { if (_owner.cleanups === null) _owner.cleanups = node.cleanups; else for (i = 0; i < node.cleanups.length; i++) { _owner.cleanups.push(node.cleanups[i]); } node.cleanups = null; } } } else { node.fn = fn; node.value = value; node.age = RootClock.time; if (_owner !== null) { if (_owner.owned === null) _owner.owned = [node]; else _owner.owned.push(node); } } return recycle; } function logRead(from) { var to = Listener, fromslot, toslot = to.source1 === null ? -1 : to.sources === null ? 0 : to.sources.length; if (from.node1 === null) { from.node1 = to; from.node1slot = toslot; fromslot = -1; } else if (from.nodes === null) { from.nodes = [to]; from.nodeslots = [toslot]; fromslot = 0; } else { fromslot = from.nodes.length; from.nodes.push(to); from.nodeslots.push(toslot); } if (to.source1 === null) { to.source1 = from; to.source1slot = fromslot; } else if (to.sources === null) { to.sources = [from]; to.sourceslots = [fromslot]; } else { to.sources.push(from); to.sourceslots.push(fromslot); } } function logDataRead(data) { if (data.log === null) data.log = new Log(); logRead(data.log); } function logComputationRead(node) { if (node.log === null) node.log = new Log(); logRead(node.log); } function event() { // b/c we might be under a top level S.root(), have to preserve current root var owner = Owner; RootClock.updates.reset(); RootClock.time++; try { run(RootClock); } finally { RunningClock = Listener = null; Owner = owner; } } function run(clock) { var running = RunningClock, count = 0; RunningClock = clock; clock.disposes.reset(); // for each batch ... while (clock.changes.count !== 0 || clock.updates.count !== 0 || clock.disposes.count !== 0) { if (count > 0) // don't tick on first run, or else we expire already scheduled updates clock.time++; clock.changes.run(applyDataChange); clock.updates.run(updateNode); clock.disposes.run(dispose); // if there are still changes after excessive batches, assume runaway if (count++ > 1e5) { throw new Error("Runaway clock detected"); } } RunningClock = running; } function applyDataChange(data) { data.value = data.pending; data.pending = NOTPENDING; if (data.log) markComputationsStale(data.log); } function markComputationsStale(log) { var node1 = log.node1, nodes = log.nodes; // mark all downstream nodes stale which haven't been already if (node1 !== null) markNodeStale(node1); if (nodes !== null) { for (var i = 0, len = nodes.length; i < len; i++) { markNodeStale(nodes[i]); } } } function markNodeStale(node) { var time = RootClock.time; if (node.age < time) { node.age = time; node.state = STALE; RootClock.updates.add(node); if (node.owned !== null) markOwnedNodesForDisposal(node.owned); if (node.log !== null) markComputationsStale(node.log); } } function markOwnedNodesForDisposal(owned) { for (var i = 0; i < owned.length; i++) { var child = owned[i]; child.age = RootClock.time; child.state = CURRENT; if (child.owned !== null) markOwnedNodesForDisposal(child.owned); } } function updateNode(node) { if (node.state === STALE) { var owner = Owner, listener = Listener; Owner = Listener = node; node.state = RUNNING; cleanup(node, false); node.value = node.fn(node.value); node.state = CURRENT; Owner = owner; Listener = listener; } } function cleanup(node, final) { var source1 = node.source1, sources = node.sources, sourceslots = node.sourceslots, cleanups = node.cleanups, owned = node.owned, i, len; if (cleanups !== null) { for (i = 0; i < cleanups.length; i++) { cleanups[i](final); } node.cleanups = null; } if (owned !== null) { for (i = 0; i < owned.length; i++) { dispose(owned[i]); } node.owned = null; } if (source1 !== null) { cleanupSource(source1, node.source1slot); node.source1 = null; } if (sources !== null) { for (i = 0, len = sources.length; i < len; i++) { cleanupSource(sources.pop(), sourceslots.pop()); } } } function cleanupSource(source, slot) { var nodes = source.nodes, nodeslots = source.nodeslots, last, lastslot; if (slot === -1) { source.node1 = null; } else { last = nodes.pop(); lastslot = nodeslots.pop(); if (slot !== nodes.length) { nodes[slot] = last; nodeslots[slot] = lastslot; if (lastslot === -1) { last.source1slot = slot; } else { last.sourceslots[lastslot] = slot; } } } } function dispose(node) { node.fn = null; node.log = null; cleanup(node, true); } module.exports = { createRoot: S.root, createComputed: S, createSignal(init) { const s = S.data(init); return [s, s]; } } ================================================ FILE: packages/solid/bench/libraries/sinuous-mod.cjs ================================================ function getChildrenDeep(children, res) { for (let i = 0; i < children.length; i += 1) { res.push(children[i]); getChildrenDeep(children[0]._children, res); } } const EMPTY_ARR = []; let tracking; let queue; /** * Returns true if there is an active observer. * @return {boolean} */ function isListening() { return !!tracking; } /** * Creates a root and executes the passed function that can contain computations. * The executed function receives an `unsubscribe` argument which can be called to * unsubscribe all inner computations. * * @param {Function} fn * @return {*} */ function createRoot(fn) { const prevTracking = tracking, rootUpdate = { _children: [] }; tracking = rootUpdate; const result = fn(() => { _unsubscribe(rootUpdate); tracking = undefined; }); tracking = prevTracking; return result; } function sample(fn) { const prevTracking = tracking; tracking = undefined; const value = fn(); tracking = prevTracking; return value; } function batch(fn) { let prevQueue = queue; queue = []; const result = fn(); let q = queue; queue = prevQueue; for (let i = 0; i < q.length; i += 1) { const data = q[i]; if (data._pending !== EMPTY_ARR) { const pending = data._pending; data._pending = EMPTY_ARR; data(pending); } } return result; } class DataNode { constructor(value) { this._observers = new Set(); this._pending = EMPTY_ARR; this.value = value; this._runObservers = null; } current() { if (tracking && tracking._observables && !this._observers.has(tracking)) { this._observers.add(tracking); tracking._observables.push(this); } return this.value; } next(nextValue) { if (queue) { if (this._pending === EMPTY_ARR) { queue.push(this); } this._pending = nextValue; return nextValue; } this.value = nextValue; // Clear `tracking` otherwise a computed triggered by a set // in another computed is seen as a child of that other computed. const clearedUpdate = tracking; tracking = undefined; // Update can alter data._observers, make a copy before running. this._runObservers = new Set(this._observers); for (const v of this._runObservers.values()) v._fresh = false; for (const v of this._runObservers.values()) !v._fresh && updateComputation(v); tracking = clearedUpdate; return this.value; } } function createComputationNode(observer, value) { const c = { observer, value, _fresh: false, _observables: [], _children: [], _cleanups: [] }; updateComputation(c); return c; } function removeFreshChildren(u) { if (u._fresh) { for (let i = 0, len = u._observables.length; i < len; i += 1) { const o = u._observables[i]; o._runObservers && o._runObservers.delete(u); } } } function onCleanup(fn) { if (tracking) { tracking._cleanups.push(fn); } return fn; } function _unsubscribe(node) { let i; for (i = 0; i < node._children.length; i += 1) _unsubscribe(node._children[i]); for (i = 0; i < node._observables.length; i += 1) { const o = node._observables[i]; o._observers.delete(node); o._runObservers && o._runObservers.delete(node); } for (i = 0; i < node._cleanups.length; i += 1) node._cleanups[i](); resetUpdate(node); } function updateComputation(node) { if (!node.observer) return; const prevTracking = tracking; if (tracking) { tracking._children.push(node); } const prevChildren = node._children; _unsubscribe(node); node._fresh = true; tracking = node; node.value = node.observer(node.value); // If any children computations were removed mark them as fresh. // Check the diff of the children list between pre and post update. const pK = Object.keys(prevChildren); for (let i = 0, len = pK.length; i < len; i += 1) { const u = prevChildren[i]; if (node._children.indexOf(u) === -1) { u._fresh = true; } } // If any children were marked as fresh remove them from the run lists. const allChildren = []; getChildrenDeep(node._children, allChildren); for (let i = 0; i < allChildren.length; i += 1) removeFreshChildren(allChildren[i]); tracking = prevTracking; return node.value; } function currentValue() { if (this._fresh) { for (let i = 0; i < this._observables.length; i += 1) this._observables[i].current(); } else { this.value = updateComputation(this); } return this.value; } function resetUpdate(node) { // Keep track of which observables trigger nodes. Needed for unsubscribe. node._observables = []; node._children = []; node._cleanups = []; } module.exports = { createSignal: value => { const o = new DataNode(value); return [o.current.bind(o), o.next.bind(o)]; }, createRoot, createComputed: (observer, seed) => createComputationNode(observer, seed), createMemo: (observer, seed) => { const c = createComputationNode(observer, seed); return currentValue.bind(c); } }; ================================================ FILE: packages/solid/bench/libraries/sinuous.cjs ================================================ function getChildrenDeep(children) { return children.reduce( (res, curr) => res.concat(curr, getChildrenDeep(curr._children)), [] ); } const EMPTY_ARR = []; let tracking; let queue; /** * Returns true if there is an active observer. * @return {boolean} */ function isListening() { return !!tracking; } /** * Creates a root and executes the passed function that can contain computations. * The executed function receives an `unsubscribe` argument which can be called to * unsubscribe all inner computations. * * @param {Function} fn * @return {*} */ function root(fn) { const prevTracking = tracking; const rootUpdate = () => {}; tracking = rootUpdate; resetUpdate(rootUpdate); const result = fn(() => { _unsubscribe(rootUpdate); tracking = undefined; }); tracking = prevTracking; return result; } /** * Sample the current value of an observable but don't create a dependency on it. * * @example * S(() => { if (foo()) bar(sample(bar) + 1); }); * * @param {Function} fn * @return {*} */ function sample(fn) { const prevTracking = tracking; tracking = undefined; const value = fn(); tracking = prevTracking; return value; } /** * Creates a transaction in which an observable can be set multiple times * but only trigger a computation once. * @param {Function} fn * @return {*} */ function transaction(fn) { let prevQueue = queue; queue = []; const result = fn(); let q = queue; queue = prevQueue; q.forEach(data => { if (data._pending !== EMPTY_ARR) { const pending = data._pending; data._pending = EMPTY_ARR; data(pending); } }); return result; } /** * Creates a new observable, returns a function which can be used to get * the observable's value by calling the function without any arguments * and set the value by passing one argument of any type. * * @param {*} value - Initial value. * @return {Function} */ function observable(value) { function data(nextValue) { if (arguments.length === 0) { if (tracking && !data._observers.has(tracking)) { data._observers.add(tracking); tracking._observables.push(data); } return value; } if (queue) { if (data._pending === EMPTY_ARR) { queue.push(data); } data._pending = nextValue; return nextValue; } value = nextValue; // Clear `tracking` otherwise a computed triggered by a set // in another computed is seen as a child of that other computed. const clearedUpdate = tracking; tracking = undefined; // Update can alter data._observers, make a copy before running. data._runObservers = new Set(data._observers); data._runObservers.forEach(observer => (observer._fresh = false)); data._runObservers.forEach(observer => { if (!observer._fresh) observer(); }); tracking = clearedUpdate; return value; } // Tiny indicator that this is an observable function. data._observers = new Set(); // The 'not set' value must be unique, so `nullish` can be set in a transaction. data._pending = EMPTY_ARR; return [data, data]; } /** * @namespace * @borrows observable as o */ /** * Creates a new computation which runs when defined and automatically re-runs * when any of the used observable's values are set. * * @param {Function} observer * @param {*} value - Seed value. * @return {Function} Computation which can be used in other computations. */ function computed(observer, value) { observer._update = update; // if (tracking == null) { // console.warn("computations created without a root or parent will never be disposed"); // } resetUpdate(update); update(); function update() { const prevTracking = tracking; if (tracking) { tracking._children.push(update); } const prevChildren = update._children; _unsubscribe(update); update._fresh = true; tracking = update; value = observer(value); // If any children computations were removed mark them as fresh. // Check the diff of the children list between pre and post update. prevChildren.forEach(u => { if (update._children.indexOf(u) === -1) { u._fresh = true; } }); // If any children were marked as fresh remove them from the run lists. const allChildren = getChildrenDeep(update._children); allChildren.forEach(removeFreshChildren); tracking = prevTracking; return value; } function data() { if (update._fresh) { update._observables.forEach(o => o()); } else { value = update(); } return value; } return data; } function removeFreshChildren(u) { if (u._fresh) { u._observables.forEach(o => { if (o._runObservers) { o._runObservers.delete(u); } }); } } /** * Run the given function just before the enclosing computation updates * or is disposed. * @param {Function} fn * @return {Function} */ function cleanup(fn) { if (tracking) { tracking._cleanups.push(fn); } return fn; } function _unsubscribe(update) { update._children.forEach(_unsubscribe); update._observables.forEach(o => { o._observers.delete(update); if (o._runObservers) { o._runObservers.delete(update); } }); update._cleanups.forEach(c => c()); resetUpdate(update); } function resetUpdate(update) { // Keep track of which observables trigger updates. Needed for unsubscribe. update._observables = []; update._children = []; update._cleanups = []; } module.exports = { createSignal: observable, createRoot: root, createComputed: computed, createMemo: computed, batch: transaction }; ================================================ FILE: packages/solid/bench/libraries/usignal.cjs ================================================ // uSignal 0.7.0 https://github.com/WebReflection/usignal 'use strict'; /*! (c) Andrea Giammarchi */ const {is} = Object; let batches; /** * Execute a callback that will not side-effect until its top-most batch is * completed. * @param {() => void} callback a function that batches changes to be notified * through signals. */ const batch = callback => { const prev = batches; batches = prev || []; try { callback(); if (!prev) for (const {value} of batches); } finally { batches = prev } }; /** * A signal with a value property also exposed via toJSON, toString and valueOf. * When created via computed, the `value` property is **readonly**. * @template T */ class Signal { /** @param {T} value the value carried along the signal. */ constructor(value) { this._ = value; } /** @returns {T} */ then() { return this.value } /** @returns {T} */ toJSON() { return this.value } /** @returns {T} */ toString() { return this.value } /** @returns {T} */ valueOf() { return this.value } } let computedSignal; class Computed extends Signal { constructor(_, v, o, f) { super(_); this.f = f; // is effect? this.$ = true; // should update ("value for money") this.r = new Set; // related signals this.s = new Reactive(v, o); // signal } /** @readonly */ get value() { if (this.$) { const prev = computedSignal; computedSignal = this; try { this.s.value = this._(this.s._) } finally { this.$ = false; computedSignal = prev; } } return this.s.value; } } const defaults = {async: false, equals: true}; /** * Returns a read-only Signal that is invoked only when any of the internally * used signals, as in within the callback, is unknown or updated. * @template T * @type {(fn: (v: T) => T, value?: T, options?: { equals?: boolean | ((prev: T, next: T) => boolean) }) => Signal} */ const computed = (fn, value, options = defaults) => new Computed(fn, value, options, false); let outerEffect; const noop = () => {}; class Effect extends Computed { constructor(_, v, o) { super(_, v, o, true); this.i = 0; // index this.a = !!o.async; // async this.m = true; // microtask this.e = []; // effects // "I am effects" ^_^;; } get value() { this.a ? this.async() : this.sync(); } async() { if (this.m) { this.m = false; queueMicrotask(() => { this.m = true; this.sync(); }); } } sync() { const prev = outerEffect; const {e} = (outerEffect = this); this.i = 0; super.value; // if effects are present in loops, these can grow or shrink. // when these grow, there's nothing to do, as well as when these are // still part of the loop, as the callback gets updated anyway. // however, if there were more effects before but none now, those can // just stop being referenced and go with the GC. if (this.i < e.length) for (const effect of e.splice(this.i)) effect.stop(); for (const {value} of e); outerEffect = prev; } stop() { this._ = noop; this.r.clear(); this.s.c.clear(); for (const effect of this.e.splice(0)) effect.stop(); } } /** * Invokes a function when any of its internal signals or computed values change. * * Returns a dispose callback. * @template T * @type {(fn: (v: T) => T, value?: T, options?: { async?: boolean }) => () => void} */ const effect = (callback, value, options = defaults) => { let unique; if (outerEffect) { const {i, e} = outerEffect; // bottleneck: // there's literally no way to optimize this path *unless* the callback is // already a known one. however, latter case is not really common code so // the question is: should I optimize this more than this? 'cause I don't // think the amount of code needed to understand if a callback is *likely* // the same as before makes any sense + correctness would be trashed. if (i === e.length || e[i]._ !== callback) e[i] = new Effect(callback, value, options); unique = e[i]; outerEffect.i++; } else (unique = new Effect(callback, value, options)).value; return () => { unique.stop() }; }; const skip = () => false; class Reactive extends Signal { constructor(_, {equals}) { super(_) this.c = new Set; // computeds this.s = equals === true ? is : (equals || skip); // (don't) skip updates } peek() { return this._ } get value() { if (computedSignal) { this.c.add(computedSignal); computedSignal.r.add(this); } return this._; } set value(_) { if (!this.s(this._, _)) { this._ = _; if (this.c.size) { const effects = []; const stack = [this]; for (const signal of stack) { for (const computed of signal.c) { if (!computed.$ && computed.r.has(signal)) { computed.r.clear(); computed.$ = true; if (computed.f) { effects.push(computed); const stack = [computed]; for (const c of stack) { for (const effect of c.e) { effect.r.clear(); effect.$ = true; stack.push(effect); } } } else stack.push(computed.s); } } } for (const effect of effects) batches ? batches.push(effect) : effect.value; } } } } /** * Returns a writable Signal that side-effects whenever its value gets updated. * @template T * @type {(initialValue: T, options?: { equals?: boolean | ((prev: T, next: T) => boolean) }) => Signal} */ const signal = (value, options = defaults) => new Reactive(value, options); // adapter function createRoot(fn) { return fn(); } function createSignal(value) { const r = new Reactive(value, {}); return [() => r.value, v => r.value = v]; } module.exports = { createSignal, createRoot, createComputed: effect }; ================================================ FILE: packages/solid/bench/libraries/vuerx.cjs ================================================ //shared // Make a map and return a function for checking if a key // is in that map. // // IMPORTANT: all calls of this function must be prefixed with /*#__PURE__*/ // So that rollup can tree-shake them if necessary. function makeMap(str, expectsLowerCase) { const map = Object.create(null); const list = str.split(','); for (let i = 0; i < list.length; i++) { map[list[i]] = true; } return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]; } const EMPTY_OBJ = Object.freeze({}); const hasOwnProperty = Object.prototype.hasOwnProperty; const hasOwn = (val, key) => hasOwnProperty.call(val, key); const isArray = Array.isArray; const isFunction = (val) => typeof val === 'function'; const isSymbol = (val) => typeof val === 'symbol'; const isObject = (val) => val !== null && typeof val === 'object'; const objectToString = Object.prototype.toString; const toTypeString = (value) => objectToString.call(value); const toRawType = (value) => { return toTypeString(value).slice(8, -1); }; const cacheStringFunction = (fn) => { const cache = Object.create(null); return ((str) => { const hit = cache[str]; return hit || (cache[str] = fn(str)); }); }; const capitalize = cacheStringFunction((str) => { return str.charAt(0).toUpperCase() + str.slice(1); }); // compare whether a value has changed, accounting for NaN. const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue); const def = (obj, key, value) => { Object.defineProperty(obj, key, { configurable: true, value }); }; //vue-rx const targetMap = new WeakMap(); const effectStack = []; let activeEffect; const ITERATE_KEY = Symbol( 'iterate' ); const MAP_KEY_ITERATE_KEY = Symbol( 'Map key iterate' ); function isEffect(fn) { return fn && fn._isEffect === true; } function effect(fn, options = EMPTY_OBJ) { if (isEffect(fn)) { fn = fn.raw; } const effect = createReactiveEffect(fn, options); if (!options.lazy) { effect(); } return effect; } function stop(effect) { if (effect.active) { cleanup(effect); if (effect.options.onStop) { effect.options.onStop(); } effect.active = false; } } let uid = 0; function createReactiveEffect(fn, options) { const effect = function reactiveEffect(...args) { if (!effect.active) { return options.scheduler ? undefined : fn(...args); } if (!effectStack.includes(effect)) { cleanup(effect); try { enableTracking(); effectStack.push(effect); activeEffect = effect; return fn(...args); } finally { effectStack.pop(); resetTracking(); activeEffect = effectStack[effectStack.length - 1]; } } }; effect.id = uid++; effect._isEffect = true; effect.active = true; effect.raw = fn; effect.deps = []; effect.options = options; return effect; } function cleanup(effect) { const { deps } = effect; if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect); } deps.length = 0; } } let shouldTrack = true; const trackStack = []; function pauseTracking() { trackStack.push(shouldTrack); shouldTrack = false; } function enableTracking() { trackStack.push(shouldTrack); shouldTrack = true; } function resetTracking() { const last = trackStack.pop(); shouldTrack = last === undefined ? true : last; } function track(target, type, key) { if (!shouldTrack || activeEffect === undefined) { return; } let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set())); } if (!dep.has(activeEffect)) { dep.add(activeEffect); activeEffect.deps.push(dep); if ( activeEffect.options.onTrack) { activeEffect.options.onTrack({ effect: activeEffect, target, type, key }); } } } function trigger(target, type, key, newValue, oldValue, oldTarget) { const depsMap = targetMap.get(target); if (!depsMap) { // never been tracked return; } const effects = new Set(); const computedRunners = new Set(); const add = (effectsToAdd) => { if (effectsToAdd) { effectsToAdd.forEach(effect => { if (effect !== activeEffect || !shouldTrack) { if (effect.options.computed) { computedRunners.add(effect); } else { effects.add(effect); } } }); } }; if (type === "clear" /* CLEAR */) { // collection being cleared // trigger all effects for target depsMap.forEach(add); } else if (key === 'length' && isArray(target)) { depsMap.forEach((dep, key) => { if (key === 'length' || key >= newValue) { add(dep); } }); } else { // schedule runs for SET | ADD | DELETE if (key !== void 0) { add(depsMap.get(key)); } // also run for iteration key on ADD | DELETE | Map.SET const isAddOrDelete = type === "add" /* ADD */ || (type === "delete" /* DELETE */ && !isArray(target)); if (isAddOrDelete || (type === "set" /* SET */ && target instanceof Map)) { add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY)); } if (isAddOrDelete && target instanceof Map) { add(depsMap.get(MAP_KEY_ITERATE_KEY)); } } const run = (effect) => { if ( effect.options.onTrigger) { effect.options.onTrigger({ effect, target, key, type, newValue, oldValue, oldTarget }); } if (effect.options.scheduler) { effect.options.scheduler(effect); } else { effect(); } }; // Important: computed effects must be run first so that computed getters // can be invalidated before any normal effects that depend on them are run. computedRunners.forEach(run); effects.forEach(run); } const builtInSymbols = new Set(Object.getOwnPropertyNames(Symbol) .map(key => Symbol[key]) .filter(isSymbol)); const get = /*#__PURE__*/ createGetter(); const shallowGet = /*#__PURE__*/ createGetter(false, true); const readonlyGet = /*#__PURE__*/ createGetter(true); const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true); const arrayInstrumentations = {}; ['includes', 'indexOf', 'lastIndexOf'].forEach(key => { arrayInstrumentations[key] = function (...args) { const arr = toRaw(this); for (let i = 0, l = this.length; i < l; i++) { track(arr, "get" /* GET */, i + ''); } // we run the method using the original args first (which may be reactive) const res = arr[key](...args); if (res === -1 || res === false) { // if that didn't work, run it again using raw values. return arr[key](...args.map(toRaw)); } else { return res; } }; }); function createGetter(isReadonly = false, shallow = false) { return function get(target, key, receiver) { if (key === "__v_isReactive" /* isReactive */) { return !isReadonly; } else if (key === "__v_isReadonly" /* isReadonly */) { return isReadonly; } else if (key === "__v_raw" /* raw */) { return target; } const targetIsArray = isArray(target); if (targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect.get(arrayInstrumentations, key, receiver); } const res = Reflect.get(target, key, receiver); if (isSymbol(key) && builtInSymbols.has(key) || key === '__proto__') { return res; } if (shallow) { !isReadonly && track(target, "get" /* GET */, key); return res; } if (isRef(res)) { if (targetIsArray) { !isReadonly && track(target, "get" /* GET */, key); return res; } else { // ref unwrapping, only for Objects, not for Arrays. return res.value; } } !isReadonly && track(target, "get" /* GET */, key); return isObject(res) ? isReadonly ? // need to lazy access readonly and reactive here to avoid // circular dependency readonly(res) : reactive(res) : res; }; } const set = /*#__PURE__*/ createSetter(); const shallowSet = /*#__PURE__*/ createSetter(true); function createSetter(shallow = false) { return function set(target, key, value, receiver) { const oldValue = target[key]; if (!shallow) { value = toRaw(value); if (!isArray(target) && isRef(oldValue) && !isRef(value)) { oldValue.value = value; return true; } } const hadKey = hasOwn(target, key); const result = Reflect.set(target, key, value, receiver); // don't trigger if target is something up in the prototype chain of original if (target === toRaw(receiver)) { if (!hadKey) { trigger(target, "add" /* ADD */, key, value); } else if (hasChanged(value, oldValue)) { trigger(target, "set" /* SET */, key, value, oldValue); } } return result; }; } function deleteProperty(target, key) { const hadKey = hasOwn(target, key); const oldValue = target[key]; const result = Reflect.deleteProperty(target, key); if (result && hadKey) { trigger(target, "delete" /* DELETE */, key, undefined, oldValue); } return result; } function has(target, key) { const result = Reflect.has(target, key); track(target, "has" /* HAS */, key); return result; } function ownKeys(target) { track(target, "iterate" /* ITERATE */, ITERATE_KEY); return Reflect.ownKeys(target); } const mutableHandlers = { get, set, deleteProperty, has, ownKeys }; const readonlyHandlers = { get: readonlyGet, has, ownKeys, set(target, key) { { console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target); } return true; }, deleteProperty(target, key) { { console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target); } return true; } }; const shallowReactiveHandlers = { ...mutableHandlers, get: shallowGet, set: shallowSet }; // Props handlers are special in the sense that it should not unwrap top-level // refs (in order to allow refs to be explicitly passed down), but should // retain the reactivity of the normal readonly object. const shallowReadonlyHandlers = { ...readonlyHandlers, get: shallowReadonlyGet }; const toReactive = (value) => isObject(value) ? reactive(value) : value; const toReadonly = (value) => isObject(value) ? readonly(value) : value; const getProto = (v) => Reflect.getPrototypeOf(v); function get$1(target, key, wrap) { target = toRaw(target); const rawKey = toRaw(key); if (key !== rawKey) { track(target, "get" /* GET */, key); } track(target, "get" /* GET */, rawKey); const { has, get } = getProto(target); if (has.call(target, key)) { return wrap(get.call(target, key)); } else if (has.call(target, rawKey)) { return wrap(get.call(target, rawKey)); } } function has$1(key) { const target = toRaw(this); const rawKey = toRaw(key); if (key !== rawKey) { track(target, "has" /* HAS */, key); } track(target, "has" /* HAS */, rawKey); const has = getProto(target).has; return has.call(target, key) || has.call(target, rawKey); } function size(target) { target = toRaw(target); track(target, "iterate" /* ITERATE */, ITERATE_KEY); return Reflect.get(getProto(target), 'size', target); } function add(value) { value = toRaw(value); const target = toRaw(this); const proto = getProto(target); const hadKey = proto.has.call(target, value); const result = proto.add.call(target, value); if (!hadKey) { trigger(target, "add" /* ADD */, value, value); } return result; } function set$1(key, value) { value = toRaw(value); const target = toRaw(this); const { has, get, set } = getProto(target); let hadKey = has.call(target, key); if (!hadKey) { key = toRaw(key); hadKey = has.call(target, key); } else { checkIdentityKeys(target, has, key); } const oldValue = get.call(target, key); const result = set.call(target, key, value); if (!hadKey) { trigger(target, "add" /* ADD */, key, value); } else if (hasChanged(value, oldValue)) { trigger(target, "set" /* SET */, key, value, oldValue); } return result; } function deleteEntry(key) { const target = toRaw(this); const { has, get, delete: del } = getProto(target); let hadKey = has.call(target, key); if (!hadKey) { key = toRaw(key); hadKey = has.call(target, key); } else { checkIdentityKeys(target, has, key); } const oldValue = get ? get.call(target, key) : undefined; // forward the operation before queueing reactions const result = del.call(target, key); if (hadKey) { trigger(target, "delete" /* DELETE */, key, undefined, oldValue); } return result; } function clear() { const target = toRaw(this); const hadItems = target.size !== 0; const oldTarget = target instanceof Map ? new Map(target) : new Set(target) ; // forward the operation before queueing reactions const result = getProto(target).clear.call(target); if (hadItems) { trigger(target, "clear" /* CLEAR */, undefined, undefined, oldTarget); } return result; } function createForEach(isReadonly) { return function forEach(callback, thisArg) { const observed = this; const target = toRaw(observed); const wrap = isReadonly ? toReadonly : toReactive; !isReadonly && track(target, "iterate" /* ITERATE */, ITERATE_KEY); // important: create sure the callback is // 1. invoked with the reactive map as `this` and 3rd arg // 2. the value received should be a corresponding reactive/readonly. function wrappedCallback(value, key) { return callback.call(thisArg, wrap(value), wrap(key), observed); } return getProto(target).forEach.call(target, wrappedCallback); }; } function createIterableMethod(method, isReadonly) { return function (...args) { const target = toRaw(this); const isMap = target instanceof Map; const isPair = method === 'entries' || (method === Symbol.iterator && isMap); const isKeyOnly = method === 'keys' && isMap; const innerIterator = getProto(target)[method].apply(target, args); const wrap = isReadonly ? toReadonly : toReactive; !isReadonly && track(target, "iterate" /* ITERATE */, isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY); // return a wrapped iterator which returns observed versions of the // values emitted from the real iterator return { // iterator protocol next() { const { value, done } = innerIterator.next(); return done ? { value, done } : { value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value), done }; }, // iterable protocol [Symbol.iterator]() { return this; } }; }; } function createReadonlyMethod(type) { return function (...args) { { const key = args[0] ? `on key "${args[0]}" ` : ``; console.warn(`${capitalize(type)} operation ${key}failed: target is readonly.`, toRaw(this)); } return type === "delete" /* DELETE */ ? false : this; }; } const mutableInstrumentations = { get(key) { return get$1(this, key, toReactive); }, get size() { return size(this); }, has: has$1, add, set: set$1, delete: deleteEntry, clear, forEach: createForEach(false) }; const readonlyInstrumentations = { get(key) { return get$1(this, key, toReadonly); }, get size() { return size(this); }, has: has$1, add: createReadonlyMethod("add" /* ADD */), set: createReadonlyMethod("set" /* SET */), delete: createReadonlyMethod("delete" /* DELETE */), clear: createReadonlyMethod("clear" /* CLEAR */), forEach: createForEach(true) }; const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]; iteratorMethods.forEach(method => { mutableInstrumentations[method] = createIterableMethod(method, false); readonlyInstrumentations[method] = createIterableMethod(method, true); }); function createInstrumentationGetter(isReadonly) { const instrumentations = isReadonly ? readonlyInstrumentations : mutableInstrumentations; return (target, key, receiver) => { if (key === "__v_isReactive" /* isReactive */) { return !isReadonly; } else if (key === "__v_isReadonly" /* isReadonly */) { return isReadonly; } else if (key === "__v_raw" /* raw */) { return target; } return Reflect.get(hasOwn(instrumentations, key) && key in target ? instrumentations : target, key, receiver); }; } const mutableCollectionHandlers = { get: createInstrumentationGetter(false) }; const readonlyCollectionHandlers = { get: createInstrumentationGetter(true) }; function checkIdentityKeys(target, has, key) { const rawKey = toRaw(key); if (rawKey !== key && has.call(target, rawKey)) { const type = toRawType(target); console.warn(`Reactive ${type} contains both the raw and reactive ` + `versions of the same object${type === `Map` ? `as keys` : ``}, ` + `which can lead to inconsistencies. ` + `Avoid differentiating between the raw and reactive versions ` + `of an object and only use the reactive version if possible.`); } } const collectionTypes = new Set([Set, Map, WeakMap, WeakSet]); const isObservableType = /*#__PURE__*/ makeMap('Object,Array,Map,Set,WeakMap,WeakSet'); const canObserve = (value) => { return (!value.__v_skip && isObservableType(toRawType(value)) && !Object.isFrozen(value)); }; function reactive(target) { // if trying to observe a readonly proxy, return the readonly version. if (target && target.__v_isReadonly) { return target; } return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers); } // Return a reactive-copy of the original object, where only the root level // properties are reactive, and does NOT unwrap refs nor recursively convert // returned properties. function shallowReactive(target) { return createReactiveObject(target, false, shallowReactiveHandlers, mutableCollectionHandlers); } function readonly(target) { return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers); } // Return a reactive-copy of the original object, where only the root level // properties are readonly, and does NOT unwrap refs nor recursively convert // returned properties. // This is used for creating the props proxy object for stateful components. function shallowReadonly(target) { return createReactiveObject(target, true, shallowReadonlyHandlers, readonlyCollectionHandlers); } function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) { if (!isObject(target)) { { console.warn(`value cannot be made reactive: ${String(target)}`); } return target; } // target is already a Proxy, return it. // exception: calling readonly() on a reactive object if (target.__v_raw && !(isReadonly && target.__v_isReactive)) { return target; } // target already has corresponding Proxy if (hasOwn(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */)) { return isReadonly ? target.__v_readonly : target.__v_reactive; } // only a whitelist of value types can be observed. if (!canObserve(target)) { return target; } const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers); def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed); return observed; } function isReactive(value) { if (isReadonly(value)) { return isReactive(value.__v_raw); } return !!(value && value.__v_isReactive); } function isReadonly(value) { return !!(value && value.__v_isReadonly); } function isProxy(value) { return isReactive(value) || isReadonly(value); } function toRaw(observed) { return (observed && toRaw(observed.__v_raw)) || observed; } function markRaw(value) { def(value, "__v_skip" /* skip */, true); return value; } const convert = (val) => isObject(val) ? reactive(val) : val; function isRef(r) { return r ? r.__v_isRef === true : false; } function ref(value) { return createRef(value); } function shallowRef(value) { return createRef(value, true); } function createRef(rawValue, shallow = false) { if (isRef(rawValue)) { return rawValue; } let value = shallow ? rawValue : convert(rawValue); const r = { __v_isRef: true, get value() { track(r, "get" /* GET */, 'value'); return value; }, set value(newVal) { if (hasChanged(toRaw(newVal), rawValue)) { rawValue = newVal; value = shallow ? newVal : convert(newVal); trigger(r, "set" /* SET */, 'value', { newValue: newVal } ); } } }; return r; } function triggerRef(ref) { trigger(ref, "set" /* SET */, 'value', { newValue: ref.value } ); } function unref(ref) { return isRef(ref) ? ref.value : ref; } function customRef(factory) { const { get, set } = factory(() => track(r, "get" /* GET */, 'value'), () => trigger(r, "set" /* SET */, 'value')); const r = { __v_isRef: true, get value() { return get(); }, set value(v) { set(v); } }; return r; } function toRefs(object) { if ( !isProxy(object)) { console.warn(`toRefs() expects a reactive object but received a plain one.`); } const ret = {}; for (const key in object) { ret[key] = toRef(object, key); } return ret; } function toRef(object, key) { return { __v_isRef: true, get value() { return object[key]; }, set value(newVal) { object[key] = newVal; } }; } function computed(getterOrOptions) { let getter; let setter; if (isFunction(getterOrOptions)) { getter = getterOrOptions; setter = () => { console.warn('Write operation failed: computed value is readonly'); } ; } else { getter = getterOrOptions.get; setter = getterOrOptions.set; } let dirty = true; let value; let computed; const runner = effect(getter, { lazy: true, // mark effect as computed so that it gets priority during trigger computed: true, scheduler: () => { if (!dirty) { dirty = true; trigger(computed, "set" /* SET */, 'value'); } } }); computed = { __v_isRef: true, // expose effect so computed can be stopped effect: runner, get value() { if (dirty) { value = runner(); dirty = false; } track(computed, "get" /* GET */, 'value'); return value; }, set value(newValue) { setter(newValue); } }; return computed; } function createSignal(value) { const r = shallowRef(value); return [() => r.value, v => r.value = v]; } function createRoot(fn) { return fn(); } module.exports = { createSignal, createRoot, createComputed: effect }; ================================================ FILE: packages/solid/bench/prototypes/message-noarray.cjs ================================================ const equalFn = (a, b) => a === b; const signalOptions = { equals: equalFn }; let runEffects = runQueue; const UNOWNED = { owned: null, cleanups: null, context: null, owner: null }; var Owner = null; let Listener = null; let Effects = null; let ExecCount = 0; function createRoot(fn, detachedOwner) { detachedOwner && (Owner = detachedOwner); const listener = Listener, owner = Owner, root = fn.length === 0 && !false ? UNOWNED : { owned: null, cleanups: null, context: null, owner }; Owner = root; Listener = null; let result; try { runUpdates(() => (result = fn(() => cleanNode(root)))); } finally { Listener = listener; Owner = owner; } return result; } function createSignal(value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const s = { value, observer: null, observerSlot: 0, observers: null, observerSlots: null, comparator: options.equals || undefined }; return [ () => { if (Listener !== null) logRead(s); return s.value; }, value => { if (typeof value === "function") { value = value(s.value); } if (writeSignal(s, value) && (s.observer || s.observers)) { markDownstream(s, false, true, true); runUpdates(() => runImmediate(s)); } return value; } ]; } function createComputed(fn, value) { updateComputation(createComputation(fn, value, true), true); } function createMemo(fn, value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const c = createComputation(fn, value, true); c.observer = null; c.observerSlot = 0; c.observers = null; c.observerSlots = null; c.comparator = options.equals || undefined; updateComputation(c); return () => { if (c.source) updateNode(c); if (Listener !== null) logRead(c); return c.value; }; } function batch(fn) { return runUpdates(fn); } function untrack(fn) { let result, listener = Listener; Listener = null; result = fn(); Listener = listener; return result; } function logRead(node) { let to = Listener, fromslot, toslot = to.source === null ? -1 : to.sources === null ? 0 : to.sources.length; if (node.observer === null) { node.observer = to; node.observerSlot = toslot; fromslot = -1; } else if (node.observerSlots === null) { if (node.observer === to) return node.value; node.observers = [to]; node.observerSlots = [toslot]; fromslot = 0; } else { fromslot = node.observerSlots.length; node.observers.push(to); node.observerSlots.push(toslot); } if (to.source === null) { to.source = node; to.sourceSlot = fromslot; } else if (to.sources === null) { to.sources = [node]; to.sourceSlots = [fromslot]; } else { to.sources.push(node); to.sourceSlots.push(fromslot); } } function writeSignal(node, value) { if (node.comparator && node.comparator(node.value, value)) return; node.value = value; return true; } function updateComputation(node, init) { if (!node.fn) return; cleanNode(node); const owner = Owner, listener = Listener, time = ExecCount; Listener = Owner = node; runComputation(node, node.value, time); Listener = listener; Owner = owner; } function runComputation(node, value, time) { let nextValue; nextValue = node.fn(value); if (!node.updatedAt || node.updatedAt <= time) { if (node.observer || (node.observers && node.observers.length)) { markDownstream(node, !writeSignal(node, nextValue)); runImmediate(node); } else node.value = nextValue; node.updatedAt = time; } } function createComputation(fn, init, pure, options) { const c = { fn, stale: 0, ready: 0, updatedAt: 0, owned: null, source: null, sourceSlot: 0, sources: null, sourceSlots: null, cleanups: null, value: init, owner: Owner, context: null, pure }; if (Owner === null); else if (Owner !== UNOWNED) { if (!Owner.owned) Owner.owned = [c]; else Owner.owned.push(c); } return c; } function runTop(node) { let ancestors = node; while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) { if (node.stale) { if (!ancestors.length) ancestors = [node]; ancestors.push(node); } } if (!ancestors.length) return updateNode(ancestors); for (let i = ancestors.length - 1; i >= 0; i--) { updateNode(ancestors[i]); } } function runUpdates(fn) { let wait = false; if (Effects) wait = true; else Effects = []; ExecCount++; try { const res = fn(); completeUpdates(wait); return res; } finally { if (!wait) Effects = null; } } function completeUpdates(wait) { if (wait) return; const e = Effects; Effects = null; if (e.length) runUpdates(() => runEffects(e)); } function runQueue(queue) { for (let i = 0; i < queue.length; i++) queue[i].stale && runTop(queue[i]); } function lookUpstream(node) { if (node.source) { lookUpstreamNode(node.source); } if (node.sources) { for (let i = 0; i < node.sources.length; i += 1) { lookUpstreamNode(node.sources[i]); } } } function lookUpstreamNode(node) { if (node.source || node.sources) { if (node.stale === node.ready) runTop(node); else if (node.stale) lookUpstream(node); } } function markDownstream(node, decrease, stale, top) { if (node.observer) { markDownstreamNode(node.observer, decrease, stale, top); } if (node.observers) { for (let i = 0; i < node.observers.length; i += 1) { markDownstreamNode(node.observers[i], decrease, stale, top); } } } function markDownstreamNode(o, decrease, stale, top) { if (stale) { if (top) o.ready++; !o.stale++ && (o.observer || o.observers) && markDownstream(o, false, true); } else if (decrease) o.stale && o.stale--; else if (o.stale) o.ready++; } function runImmediate(node) { let observers; if (node.observers && node.observers.length) observers = [...node.observers]; if (node.observer) runNode(node.observer); if (observers) { for (let i = 0; i < observers.length; i += 1) { runNode(observers[i]); } } } function runNode(node) { if (node.stale === node.ready && node.updatedAt < ExecCount) { if (node.stale) { if (node.pure) runTop(node); else Effects.push(node); } else if (node.observer || (node.observers && node.observers.length)) { markDownstream(node, true); runImmediate(node); } } } function updateNode(node) { if (node.stale) { if (node.stale === node.ready) { updateComputation(node); } else lookUpstream(node); } } function cleanNode(node) { let i; if (node.source != null) { cleanupSource(node.source, node.sourceSlot); node.source = null; } if (node.sources != null) { for (let i = 0, len = node.sources.length; i < len; i++) { cleanupSource(node.sources.pop(), node.sourceSlots.pop()); } } if (node.owned) { for (i = 0; i < node.owned.length; i++) cleanNode(node.owned[i]); node.owned = null; } if (node.cleanups) { for (i = 0; i < node.cleanups.length; i++) node.cleanups[i](); node.cleanups = null; } node.stale = node.ready = 0; node.context = null; } function cleanupSource(source, slot) { let nodes = source.observers, nodeslots = source.observerSlots, last, lastslot; if (slot === -1) { source.observer = null; } else { last = nodes.pop(); lastslot = nodeslots.pop(); if (slot !== nodes.length) { nodes[slot] = last; nodeslots[slot] = lastslot; if (lastslot === -1) { last.sourceSlot = slot; } else { last.sourceSlots[lastslot] = slot; } } } } exports.createComputed = createComputed; exports.createMemo = createMemo; exports.createRoot = createRoot; exports.createSignal = createSignal; exports.batch = batch; ================================================ FILE: packages/solid/bench/prototypes/message.cjs ================================================ const equalFn = (a, b) => a === b; const signalOptions = { equals: equalFn }; let runEffects = runQueue; const UNOWNED = { owned: null, cleanups: null, context: null, owner: null }; var Owner = null; let Listener = null; let Effects = null; let ExecCount = 0; function createRoot(fn, detachedOwner) { detachedOwner && (Owner = detachedOwner); const listener = Listener, owner = Owner, root = fn.length === 0 && !false ? UNOWNED : { owned: null, cleanups: null, context: null, owner }; Owner = root; Listener = null; let result; try { runUpdates(() => (result = fn(() => cleanNode(root)))); } finally { Listener = listener; Owner = owner; } return result; } function createSignal(value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const s = { value, observers: null, observerSlots: null, comparator: options.equals || undefined }; return [ readSignal.bind(s), value => { if (typeof value === "function") { value = value(s.value); } if (writeSignal(s, value) && s.observers && s.observers.length) { runUpdates(() => { markDownstream(s, false, true, true); runImmediate(s); }); } return value; } ]; } function createComputed(fn, value) { updateComputation(createComputation(fn, value, true)); } function createMemo(fn, value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const c = createComputation(fn, value, true); c.observers = null; c.observerSlots = null; c.comparator = options.equals || undefined; updateComputation(c); return readSignal.bind(c); } function batch(fn) { return runUpdates(fn); } function untrack(fn) { let result, listener = Listener; Listener = null; result = fn(); Listener = listener; return result; } function readSignal() { if (this.sources) updateNode(this); if (Listener) { const sSlot = this.observers ? this.observers.length : 0; if (!Listener.sources) { Listener.sources = [this]; Listener.sourceSlots = [sSlot]; } else { Listener.sources.push(this); Listener.sourceSlots.push(sSlot); } if (!this.observers) { this.observers = [Listener]; this.observerSlots = [Listener.sources.length - 1]; } else { this.observers.push(Listener); this.observerSlots.push(Listener.sources.length - 1); } } return this.value; } function writeSignal(node, value) { if (node.comparator && node.comparator(node.value, value)) return; node.value = value; return true; } function updateComputation(node) { if (!node.fn) return; cleanNode(node); const owner = Owner, listener = Listener, time = ExecCount; Listener = Owner = node; runComputation(node, node.value, time); Listener = listener; Owner = owner; } function runComputation(node, value, time) { let nextValue; nextValue = node.fn(value); if (!node.updatedAt || node.updatedAt <= time) { if (node.observers && node.observers.length) { markDownstream(node, !writeSignal(node, nextValue)); runImmediate(node); } else node.value = nextValue; node.updatedAt = time; } } function createComputation(fn, init, pure, options) { const c = { fn, stale: 0, ready: 0, updatedAt: 0, owned: null, sources: null, sourceSlots: null, cleanups: null, value: init, owner: Owner, context: null, pure }; if (Owner === null); else if (Owner !== UNOWNED) { if (!Owner.owned) Owner.owned = [c]; else Owner.owned.push(c); } return c; } function runTop(node) { let ancestors = node; while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) { if (node.stale) { if (!ancestors.length) ancestors = [node]; ancestors.push(node); } } if (!ancestors.length) return updateNode(ancestors); for (let i = ancestors.length - 1; i >= 0; i--) { updateNode(ancestors[i]); } } function runUpdates(fn) { let wait = false; if (Effects) wait = true; else Effects = []; ExecCount++; try { const res = fn(); completeUpdates(wait); return res; } finally { if (!wait) Effects = null; } } function completeUpdates(wait) { if (wait) return; const e = Effects; Effects = null; if (e.length) runUpdates(() => runEffects(e)); } function runQueue(queue) { for (let i = 0; i < queue.length; i++) queue[i].stale && runTop(queue[i]); } function lookUpstream(node) { for (let i = 0; i < node.sources.length; i += 1) { const source = node.sources[i]; if (source.sources) { if (source.stale === source.ready) runTop(source); else if (source.stale) lookUpstream(source); } } } function markDownstream(node, decrease, stale, top) { for (let i = 0; i < node.observers.length; i += 1) { const o = node.observers[i]; if (stale) { if (top) o.ready++; !o.stale++ && o.observers && o.observers.length && markDownstream(o, false, true); } else if (decrease) o.stale && o.stale--; else o.ready++; } } function runImmediate(node) { if (node.observers.length === 1) { return runNode(node.observers[0]); } const clone = [...node.observers]; for (let i = 0; i < clone.length; i += 1) { runNode(clone[i]); } } function runNode(node) { if (node.stale === node.ready) { if (node.stale) { if (node.pure) runTop(node); else Effects.push(node); } else if (node.observers && node.observers.length) { markDownstream(node, true); runImmediate(node); } } } function updateNode(node) { if (node.stale) { if (node.stale === node.ready) { updateComputation(node); } else lookUpstream(node); } } function cleanNode(node) { let i; if (node.sources) { while (node.sources.length) { const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers; if (obs && obs.length) { const n = obs.pop(), s = source.observerSlots.pop(); if (index < obs.length) { n.sourceSlots[s] = index; obs[index] = n; source.observerSlots[index] = s; } } } } if (node.owned) { for (i = 0; i < node.owned.length; i++) cleanNode(node.owned[i]); node.owned = null; } if (node.cleanups) { for (i = 0; i < node.cleanups.length; i++) node.cleanups[i](); node.cleanups = null; } node.stale = node.ready = 0; node.context = null; } exports.createComputed = createComputed; exports.createMemo = createMemo; exports.createRoot = createRoot; exports.createSignal = createSignal; exports.batch = batch; ================================================ FILE: packages/solid/bench/prototypes/queue-noarray.cjs ================================================ const equalFn = (a, b) => a === b; const signalOptions = { equals: equalFn }; let ERROR = null; let runEffects = runQueue; const STALE = 1; const PENDING = 2; const UNOWNED = { owned: null, cleanups: null, context: null, owner: null }; var Owner = null; let Listener = null; let Updates = null; let Effects = null; let ExecCount = 0; function createRoot(fn, detachedOwner) { detachedOwner && (Owner = detachedOwner); const listener = Listener, owner = Owner, root = fn.length === 0 && !false ? UNOWNED : { owned: null, cleanups: null, context: null, owner }; Owner = root; Listener = null; let result; try { runUpdates(() => (result = fn(() => cleanNode(root))), true); } finally { Listener = listener; Owner = owner; } return result; } function createSignal(value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const s = { value, observer: null, observerSlot: 0, observers: null, observerSlots: null, comparator: options.equals || undefined }; return [ () => { if (Listener) logRead(s); return s.value; }, value => { if (typeof value === "function") { value = value(s.value); } return writeSignal(s, value); } ]; } function createComputed(fn, value) { updateComputation(createComputation(fn, value, true, STALE)); } function createMemo(fn, value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const c = createComputation(fn, value, true, 0); c.observer = null; c.observerSlot = 0; c.observers = null; c.observerSlots = null; c.comparator = options.equals || undefined; updateComputation(c); return () => { if (c.state && (c.source || c.sources)) { const updates = Updates; Updates = null; c.state === STALE ? updateComputation(c) : lookUpstream(c); Updates = updates; } if (Listener) logRead(c); return c.value; }; } function batch(fn) { return runUpdates(fn, false); } function untrack(fn) { let result, listener = Listener; Listener = null; result = fn(); Listener = listener; return result; } function logRead(node) { let to = Listener, fromslot, toslot = to.source === null ? -1 : to.sources === null ? 0 : to.sources.length; if (node.observer === null) { node.observer = to; node.observerSlot = toslot; fromslot = -1; } else if (node.observerSlots === null) { if (node.observer === to) return node.value; node.observers = [to]; node.observerSlots = [toslot]; fromslot = 0; } else { fromslot = node.observerSlots.length; node.observers.push(to); node.observerSlots.push(toslot); } if (to.source === null) { to.source = node; to.sourceSlot = fromslot; } else if (to.sources === null) { to.sources = [node]; to.sourceSlots = [fromslot]; } else { to.sources.push(node); to.sourceSlots.push(fromslot); } } function writeSignal(node, value) { if (node.comparator) { if (node.comparator(node.value, value)) return value; } node.value = value; if (node.observer || (node.observers && node.observers.length)) { runUpdates(() => { if (node.observer) queueUpdates(node.observer); if (node.observers) { for (let i = 0; i < node.observers.length; i += 1) queueUpdates(node.observers[i]); } if (Updates.length > 10e5) { Updates = []; throw new Error(); } }, false); } return value; } function queueUpdates(o) { if (!o.state) { if (o.pure) Updates.push(o); else Effects.push(o); if (o.observer || o.observers) markDownstream(o); } o.state = STALE; } function updateComputation(node) { if (!node.fn) return; cleanNode(node); const owner = Owner, listener = Listener, time = ExecCount; Listener = Owner = node; runComputation(node, node.value, time); Listener = listener; Owner = owner; } function runComputation(node, value, time) { let nextValue; nextValue = node.fn(value); if (!node.updatedAt || node.updatedAt <= time) { if (node.updatedAt && "observers" in node) { writeSignal(node, nextValue); } else node.value = nextValue; node.updatedAt = time; } } function createComputation(fn, init, pure, state = STALE, options) { const c = { fn, state: state, updatedAt: null, owned: null, source: null, sourceSlot: 0, sources: null, sourceSlots: null, cleanups: null, value: init, owner: Owner, context: null, pure }; if (Owner === null); else if (Owner !== UNOWNED) { if (!Owner.owned) Owner.owned = [c]; else Owner.owned.push(c); } return c; } function runTop(node) { if (node.state === 0) return; if (node.state === PENDING) return lookUpstream(node); const ancestors = [node]; while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) { if (node.state) ancestors.push(node); } for (let i = ancestors.length - 1; i >= 0; i--) { node = ancestors[i]; if (node.state === STALE) { updateComputation(node); } else if (node.state === PENDING) { const updates = Updates; Updates = null; lookUpstream(node); Updates = updates; } } } function runUpdates(fn, init) { if (Updates) return fn(); let wait = false; if (!init) Updates = []; if (Effects) wait = true; else Effects = []; ExecCount++; try { const res = fn(); completeUpdates(wait); return res; } finally { Updates = null; if (!wait) Effects = null; } } function completeUpdates(wait) { if (Updates) { runQueue(Updates); Updates = null; } if (wait) return; const e = Effects; Effects = null; if (e.length) runUpdates(() => runEffects(e), false); } function runQueue(queue) { for (let i = 0; i < queue.length; i++) runTop(queue[i]); } function lookUpstream(node) { node.state = 0; if (node.source) lookUpstreamNode(node.source) if (node.sources) { for (let i = 0; i < node.sources.length; i += 1) { lookUpstream(node.sources[i]); } } } function lookUpstreamNode(source) { if (source.source || source.sources) { if (source.state === STALE) runTop(source); else if (source.state === PENDING) lookUpstream(source); } } function markDownstream(node) { if (node.observer) markDownstreamNode(node.observer); if (node.observers) { for (let i = 0; i < node.observers.length; i += 1) markDownstreamNode(node.observers[i]); } } function markDownstreamNode(o) { if (!o.state) { o.state = PENDING; if (o.pure) Updates.push(o); else Effects.push(o); (o.observer || o.observers) && markDownstream(o); } } function cleanNode(node) { let i; if (node.source != null) { cleanupSource(node.source, node.sourceSlot); node.source = null; } if (node.sources != null) { for (let i = 0, len = node.sources.length; i < len; i++) { cleanupSource(node.sources.pop(), node.sourceSlots.pop()); } } if (node.owned) { for (i = 0; i < node.owned.length; i++) cleanNode(node.owned[i]); node.owned = null; } if (node.cleanups) { for (i = 0; i < node.cleanups.length; i++) node.cleanups[i](); node.cleanups = null; } node.state = 0; node.context = null; } function cleanupSource(source, slot) { let nodes = source.observers, nodeslots = source.observerSlots, last, lastslot; if (slot === -1) { source.observer = null; } else { last = nodes.pop(); lastslot = nodeslots.pop(); if (slot !== nodes.length) { nodes[slot] = last; nodeslots[slot] = lastslot; if (lastslot === -1) { last.sourceSlot = slot; } else { last.sourceSlots[lastslot] = slot; } } } } exports.createComputed = createComputed; exports.createMemo = createMemo; exports.createRoot = createRoot; exports.createSignal = createSignal; exports.batch = batch; ================================================ FILE: packages/solid/bench/prototypes/queue.cjs ================================================ const equalFn = (a, b) => a === b; const signalOptions = { equals: equalFn }; let ERROR = null; let runEffects = runQueue; const STALE = 1; const PENDING = 2; const UNOWNED = { owned: null, cleanups: null, context: null, owner: null }; var Owner = null; let Listener = null; let Updates = null; let Effects = null; let ExecCount = 0; function createRoot(fn, detachedOwner) { detachedOwner && (Owner = detachedOwner); const listener = Listener, owner = Owner, root = fn.length === 0 && !false ? UNOWNED : { owned: null, cleanups: null, context: null, owner }; Owner = root; Listener = null; let result; try { runUpdates(() => (result = fn(() => cleanNode(root))), true); } finally { Listener = listener; Owner = owner; } return result; } function createSignal(value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const s = { value, observers: null, observerSlots: null, comparator: options.equals || undefined }; return [ readSignal.bind(s), value => { if (typeof value === "function") { value = value(s.value); } return writeSignal(s, value); } ]; } function createComputed(fn, value) { updateComputation(createComputation(fn, value, true, STALE)); } function createMemo(fn, value, options) { options = options ? Object.assign({}, signalOptions, options) : signalOptions; const c = createComputation(fn, value, true, 0); c.observers = null; c.observerSlots = null; c.comparator = options.equals || undefined; updateComputation(c); return readSignal.bind(c); } function batch(fn) { return runUpdates(fn, false); } function untrack(fn) { let result, listener = Listener; Listener = null; result = fn(); Listener = listener; return result; } function readSignal() { if (this.state && this.sources) { const updates = Updates; Updates = null; this.state === STALE ? updateComputation(this) : lookUpstream(this); Updates = updates; } if (Listener) { const sSlot = this.observers ? this.observers.length : 0; if (!Listener.sources) { Listener.sources = [this]; Listener.sourceSlots = [sSlot]; } else { Listener.sources.push(this); Listener.sourceSlots.push(sSlot); } if (!this.observers) { this.observers = [Listener]; this.observerSlots = [Listener.sources.length - 1]; } else { this.observers.push(Listener); this.observerSlots.push(Listener.sources.length - 1); } } return this.value; } function writeSignal(node, value, isComp) { if (node.comparator) { if (node.comparator(node.value, value)) return value; } node.value = value; if (node.observers && node.observers.length) { runUpdates(() => { for (let i = 0; i < node.observers.length; i += 1) { const o = node.observers[i]; if (!o.state) { if (o.pure) Updates.push(o); else Effects.push(o); if (o.observers) markDownstream(o); } o.state = STALE; } if (Updates.length > 10e5) { Updates = []; throw new Error(); } }, false); } return value; } function updateComputation(node) { if (!node.fn) return; cleanNode(node); const owner = Owner, listener = Listener, time = ExecCount; Listener = Owner = node; runComputation(node, node.value, time); Listener = listener; Owner = owner; } function runComputation(node, value, time) { let nextValue; nextValue = node.fn(value); if (!node.updatedAt || node.updatedAt <= time) { if (node.updatedAt != null && "observers" in node) { writeSignal(node, nextValue, true); } else node.value = nextValue; node.updatedAt = time; } } function createComputation(fn, init, pure, state = STALE, options) { const c = { fn, state: state, updatedAt: null, owned: null, sources: null, sourceSlots: null, cleanups: null, value: init, owner: Owner, context: null, pure }; if (Owner === null); else if (Owner !== UNOWNED) { if (!Owner.owned) Owner.owned = [c]; else Owner.owned.push(c); } return c; } function runTop(node) { if (node.state === 0) return; if (node.state === PENDING) return lookUpstream(node); const ancestors = [node]; while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) { if (node.state) ancestors.push(node); } for (let i = ancestors.length - 1; i >= 0; i--) { node = ancestors[i]; if (node.state === STALE) { updateComputation(node); } else if (node.state === PENDING) { const updates = Updates; Updates = null; lookUpstream(node); Updates = updates; } } } function runUpdates(fn, init) { if (Updates) return fn(); let wait = false; if (!init) Updates = []; if (Effects) wait = true; else Effects = []; ExecCount++; try { const res = fn(); completeUpdates(wait); return res; } finally { Updates = null; if (!wait) Effects = null; } } function completeUpdates(wait) { if (Updates) { runQueue(Updates); Updates = null; } if (wait) return; const e = Effects; Effects = null; if (e.length) runUpdates(() => runEffects(e), false); } function runQueue(queue) { for (let i = 0; i < queue.length; i++) runTop(queue[i]); } function lookUpstream(node) { node.state = 0; for (let i = 0; i < node.sources.length; i += 1) { const source = node.sources[i]; if (source.sources) { if (source.state === STALE) runTop(source); else if (source.state === PENDING) lookUpstream(source); } } } function markDownstream(node) { for (let i = 0; i < node.observers.length; i += 1) { const o = node.observers[i]; if (!o.state) { o.state = PENDING; if (o.pure) Updates.push(o); else Effects.push(o); o.observers && markDownstream(o); } } } function cleanNode(node) { let i; if (node.sources) { while (node.sources.length) { const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers; if (obs && obs.length) { const n = obs.pop(), s = source.observerSlots.pop(); if (index < obs.length) { n.sourceSlots[s] = index; obs[index] = n; source.observerSlots[index] = s; } } } } if (node.owned) { for (i = 0; i < node.owned.length; i++) cleanNode(node.owned[i]); node.owned = null; } if (node.cleanups) { for (i = 0; i < node.cleanups.length; i++) node.cleanups[i](); node.cleanups = null; } node.state = 0; node.context = null; } exports.createComputed = createComputed; exports.createMemo = createMemo; exports.createRoot = createRoot; exports.createSignal = createSignal; exports.batch = batch; ================================================ FILE: packages/solid/bench/results.md ================================================ # Benchmark Results (22/09/2022) This benchmark is mostly to serve Solid's own R&D. Everything is coerced to Solid's API shape as that is a necessity here. While most libraries presented are unsuitable for Solid's rendering because of missing features/capabilities it is still useful to help gauge where the implementations fall. In most UI libraries you'd be bringing in overhead from a VDOM or other rendering model where Solid's reactivity takes the brunt of that cost. ## Released Libraries ### Solid This is Solid's production build with Transitions and Timeslicing. Look below to see equivalent pure reactive approach. ``` createDataSignals: 6 createComputations0to1: 5 createComputations1to1: 18 createComputations2to1: 16 createComputations4to1: 8 createComputations1000to1: 7 createComputations1to2: 24 createComputations1to4: 17 createComputations1to8: 17 createComputations1to1000: 33 create total: 151 --- updateComputations1to1: 31 updateComputations2to1: 20 updateComputations4to1: 15 updateComputations1000to1: 28 updateComputations1to2: 27 updateComputations1to4: 28 updateComputations1to1000: 26 update total: 176 total: 327 ``` ### S.js ``` createDataSignals: 4 createComputations0to1: 3 createComputations1to1: 15 createComputations2to1: 13 createComputations4to1: 15 createComputations1000to1: 16 createComputations1to2: 16 createComputations1to4: 15 createComputations1to8: 10 createComputations1to1000: 9 create total: 116 --- updateComputations1to1: 18 updateComputations2to1: 14 updateComputations4to1: 11 updateComputations1000to1: 19 updateComputations1to2: 18 updateComputations1to4: 19 updateComputations1to1000: 15 update total: 115 total: 232 ``` ### Kairo ``` createDataSignals: 7 createComputations0to1: 5 createComputations1to1: 19 createComputations2to1: 21 createComputations4to1: 13 createComputations1000to1: 6 createComputations1to2: 24 createComputations1to4: 16 createComputations1to8: 16 createComputations1to1000: 27 create total: 153 --- updateComputations1to1: 19 updateComputations2to1: 14 updateComputations4to1: 11 updateComputations1000to1: 13 updateComputations1to2: 22 updateComputations1to4: 20 updateComputations1to1000: 20 update total: 120 total: 273 ``` ### Preact Signals ``` createDataSignals: 11 createComputations0to1: 6 createComputations1to1: 18 createComputations2to1: 13 createComputations4to1: 11 createComputations1000to1: 9 createComputations1to2: 15 createComputations1to4: 14 createComputations1to8: 16 createComputations1to1000: 22 create total: 135 --- updateComputations1to1: 24 updateComputations2to1: 14 updateComputations4to1: 16 updateComputations1000to1: 20 updateComputations1to2: 21 updateComputations1to4: 21 updateComputations1to1000: 18 update total: 134 total: 269 ``` ### usignal ``` createDataSignals: 20 createComputations0to1: 12 createComputations1to1: 35 createComputations2to1: 23 createComputations4to1: 17 createComputations1000to1: 11 createComputations1to2: 32 createComputations1to4: 30 createComputations1to8: 51 createComputations1to1000: 54 create total: 284 --- updateComputations1to1: 50 updateComputations2to1: 31 updateComputations4to1: 23 updateComputations1000to1: 38 updateComputations1to2: 43 updateComputations1to4: 42 updateComputations1to1000: 45 update total: 272 total: 556 ``` ### @vue/reactivity ``` createDataSignals: 35 createComputations0to1: 14 createComputations1to1: 82 createComputations2to1: 60 createComputations4to1: 51 createComputations1000to1: 42 createComputations1to2: 60 createComputations1to4: 49 createComputations1to8: 46 createComputations1to1000: 51 create total: 491 --- updateComputations1to1: 138 updateComputations2to1: 91 updateComputations4to1: 65 updateComputations1000to1: 119 updateComputations1to2: 111 updateComputations1to4: 101 updateComputations1to1000: 107 update total: 732 total: 1222 ``` ### Sinuous ``` createDataSignals: 20 createComputations0to1: 53 createComputations1to1: 53 createComputations2to1: 35 createComputations4to1: 26 createComputations1000to1: 45 createComputations1to2: 51 createComputations1to4: 55 createComputations1to8: 72 createComputations1to1000: 62 create total: 472 --- updateComputations1to1: 114 updateComputations2to1: 69 updateComputations4to1: 46 updateComputations1000to1: 61 updateComputations1to2: 86 updateComputations1to4: 82 updateComputations1to1000: 87 update total: 546 total: 1018 ``` ## Mods of other Libraries ### S.js mod Modified to allow conditional notification of memos ``` createDataSignals: 4 createComputations0to1: 4 createComputations1to1: 12 createComputations2to1: 7 createComputations4to1: 7 createComputations1000to1: 6 createComputations1to2: 11 createComputations1to4: 15 createComputations1to8: 10 createComputations1to1000: 17 create total: 93 --- updateComputations1to1: 27 updateComputations2to1: 17 updateComputations4to1: 11 updateComputations1000to1: 21 updateComputations1to2: 33 updateComputations1to4: 25 updateComputations1to1000: 21 update total: 155 total: 248 ``` ### Sinuous-mod ``` createDataSignals: 14 createComputations0to1: 25 createComputations1to1: 36 createComputations2to1: 24 createComputations4to1: 22 createComputations1000to1: 51 createComputations1to2: 37 createComputations1to4: 40 createComputations1to8: 36 createComputations1to1000: 47 create total: 332 --- updateComputations1to1: 91 updateComputations2to1: 59 updateComputations4to1: 45 updateComputations1000to1: 87 updateComputations1to2: 65 updateComputations1to4: 57 updateComputations1to1000: 71 update total: 475 total: 807 ``` ### RVal Mod ``` createDataSignals: 13 createComputations0to1: 29 createComputations1to1: 63 createComputations2to1: 40 createComputations4to1: 35 createComputations1000to1: 15 createComputations1to2: 62 createComputations1to4: 86 createComputations1to8: 83 createComputations1to1000: 91 create total: 517 --- updateComputations1to1: 195 updateComputations2to1: 116 updateComputations4to1: 77 updateComputations1000to1: 110 updateComputations1to2: 180 updateComputations1to4: 169 updateComputations1to1000: 164 update total: 1011 total: 1527 ``` ## Solid Prototypes These are raw implementations that I use for designing Solid. Solid has overhead due to Transitions/Timeslicing and other features that are important to the framework so it's nice to look at the raw implementation. Typically it's about a 10% performance improvement. No Array optimization is nice but adds about 30 LoC. So far I haven't seen an impact in non-reactive based benchmarks. Ie.. things that render the actual DOM so haven't implemented it. Message approaches are faster but it have deeper callstack, and doesn't play nice with time slicing. To be fair MobX also has this call stack limitation so it isn't a big deal unless doing absurd things like the CellX benchmark. ### queue (current version of Solid) Typical queue approach. We use a queue so we can easily support Time Slicing. ``` createDataSignals: 5 createComputations0to1: 4 createComputations1to1: 17 createComputations2to1: 15 createComputations4to1: 7 createComputations1000to1: 7 createComputations1to2: 23 createComputations1to4: 17 createComputations1to8: 17 createComputations1to1000: 31 create total: 143 --- updateComputations1to1: 26 updateComputations2to1: 19 updateComputations4to1: 13 updateComputations1000to1: 28 updateComputations1to2: 24 updateComputations1to4: 24 updateComputations1to1000: 23 update total: 158 total: 300 ``` ### queue-noarray Array optimization on Queue approach ``` createDataSignals: 6 createComputations0to1: 4 createComputations1to1: 13 createComputations2to1: 8 createComputations4to1: 7 createComputations1000to1: 7 createComputations1to2: 13 createComputations1to4: 16 createComputations1to8: 11 createComputations1to1000: 10 create total: 95 --- updateComputations1to1: 24 updateComputations2to1: 17 updateComputations4to1: 14 updateComputations1000to1: 18 updateComputations1to2: 22 updateComputations1to4: 21 updateComputations1to1000: 19 update total: 136 total: 231 ``` ### message Inspired by MobX algorithm. ``` createDataSignals: 5 createComputations0to1: 3 createComputations1to1: 17 createComputations2to1: 15 createComputations4to1: 7 createComputations1000to1: 7 createComputations1to2: 24 createComputations1to4: 17 createComputations1to8: 17 createComputations1to1000: 32 create total: 144 --- updateComputations1to1: 19 updateComputations2to1: 14 updateComputations4to1: 11 updateComputations1000to1: 26 updateComputations1to2: 24 updateComputations1to4: 23 updateComputations1to1000: 22 update total: 138 total: 282 ``` ### message-noarray MobX algorithm inspired + array optimized. Currently this is the fastest approach I have. ``` createDataSignals: 6 createComputations0to1: 4 createComputations1to1: 13 createComputations2to1: 7 createComputations4to1: 8 createComputations1000to1: 6 createComputations1to2: 14 createComputations1to4: 17 createComputations1to8: 11 createComputations1to1000: 11 create total: 97 --- updateComputations1to1: 15 updateComputations2to1: 13 updateComputations4to1: 11 updateComputations1000to1: 18 updateComputations1to2: 20 updateComputations1to4: 21 updateComputations1to1000: 18 update total: 115 total: 212 ``` ================================================ FILE: packages/solid/h/README.md ================================================ # Solid HyperScript This sub module provides a HyperScript method for Solid. This is useful to use Solid in non-compiled environments or in some environments where you can only use a standard JSX transform. This method can be used as the JSX factory function. HyperScript function takes a few forms. The 2nd props argument is optional. Children may be passed as either an array to the 2nd/3rd argument or as every argument past the 2nd. ```js // create an element with a title attribute h("button", { title: "My button" }, "Click Me") // create a component with a title prop h(Button, { title: "My button" }, "Click Me") // create an element with many children h("div", { title: "My button" }, h("span", "1"), h("span", "2"), h("span", "3")) ``` This is the least efficient way to use Solid as it requires a slightly larger runtime that isn't treeshakeable, and cannot leverage anything in the way of analysis, so it requires manual wrapping of expressions and has a few other caveats (see below). ## Example ```js import { render } from "solid-js/web"; import h from "solid-js/h"; import { createSignal } from "solid-js"; function Button(props) { return h("button.btn-primary", props) } function Counter() { const [count, setCount] = createSignal(0); const increment = (e) => setCount(c => c + 1); return h(Button, { type: "button", onClick: increment }, count); } render(Counter, document.getElementById("app")); ``` ## Differences from JSX There are a few differences from Solid's JSX that are important to note. And also apply when attempting use any transformation that would compile to HyperScript. 1. Reactive expression must be manually wrapped in functions to be reactive. ```js // jsx
{firstName() + lastName()}
// hyperscript h("div", { id: () => props.id }, () => firstName() + lastName()) ``` 2. Merging spreads requires using the merge props helper to keep reactivity ```js // jsx
// hyperscript import { mergeProps } from "solid-js" h("div", mergeProps({ class: selectedClass }, props)) ``` 3. Events on components require explicit event in the arguments Solid's HyperScript automatically wraps functions passed to props of components with no arguments in getters so you need to provide one to prevent this. The same applies to render props like in the `` component. ```js // good h(Button, { onClick: (e) => console.log("Hi")}); // bad h(Button, { onClick: () => console.log("Hi")}) ``` 4. All refs are callback form We can't do the compiled assignment trick so only the callback form is supported. ```js let myEl; h(div, { ref: (el) => myEl = el }); ``` 5. There is a shorthand for static id and classes ```js h("div#some-id.my-class") ``` 6. Fragments are just arrays ```js [h("span", "1"), h("span", "2")] ``` ================================================ FILE: packages/solid/h/jsx-dev-runtime/package.json ================================================ { "name": "solid-js/h/jsx-dev-runtime", "main": "../jsx-runtime/dist/jsx.cjs", "module": "../jsx-runtime/dist/jsx.js", "types": "../jsx-runtime/types/index.d.ts", "type": "module", "sideEffects": false } ================================================ FILE: packages/solid/h/jsx-runtime/package.json ================================================ { "name": "solid-js/h/jsx-runtime", "main": "./dist/jsx.cjs", "module": "./dist/jsx.js", "types": "./types/index.d.ts", "type": "module", "sideEffects": false } ================================================ FILE: packages/solid/h/jsx-runtime/src/index.ts ================================================ import h from "solid-js/h"; export type { JSX } from "./jsx.d.ts"; import type { JSX } from "./jsx.d.ts"; function Fragment(props: { children: JSX.Element }) { return props.children; } function jsx(type: any, props: any) { return h(type, props); } // support React Transform in case someone really wants it for some reason export { jsx, jsx as jsxs, jsx as jsxDEV, Fragment }; ================================================ FILE: packages/solid/h/jsx-runtime/tsconfig.json ================================================ { "extends": "../../../../tsconfig.json", "compilerOptions": { "outDir": "./types", "baseUrl": "src", "paths": { "solid-js/h": ["../.."] } }, "include": ["./src"] } ================================================ FILE: packages/solid/h/package.json ================================================ { "name": "solid-js/h", "main": "./dist/h.cjs", "module": "./dist/h.js", "types": "./types/index.d.ts", "type": "module", "sideEffects": false } ================================================ FILE: packages/solid/h/src/hyperscript.ts ================================================ export * from "hyper-dom-expressions"; ================================================ FILE: packages/solid/h/src/index.ts ================================================ import { createHyperScript } from "./hyperscript.js"; import type { HyperScript } from "./hyperscript.js"; import { spread, assign, insert, createComponent, dynamicProperty, SVGElements } from "solid-js/web"; const h: HyperScript = createHyperScript({ spread, assign, insert, createComponent, dynamicProperty, SVGElements }); export default h; ================================================ FILE: packages/solid/h/tsconfig.json ================================================ { "extends": "../../../tsconfig.json", "compilerOptions": { "outDir": "./types", "baseUrl": "src", "paths": { "solid-js/web": ["../../web"], "solid-js": ["../.."] } }, "include": ["./src"], } ================================================ FILE: packages/solid/html/README.md ================================================ # Solid Tagged Template Literals This sub module provides a Tagged Template Literal `html` method for Solid. This is useful to use Solid in non-compiled environments. This method can be used as replacement for JSX. `html` uses `${}` to escape into JavaScript expressions. Components are closed with `` ```js // create an element with a title attribute html`` // create a component with a title prop html`<${Button} title="My button">Click me` // create an element with dynamic attribute and spread html`
selectedClass()} ...${props} />` ``` Using `html` is slightly less efficient than JSX(but more than HyperScript), requires a larger runtime that isn't treeshakeable, and cannot leverage expression analysis, so it requires manual wrapping of expressions and has a few other caveats (see below). ## Example ```js import { render } from "solid-js/web"; import html from "solid-js/html"; import { createSignal } from "solid-js"; function Button(props) { return html`
}> * {(item, index) =>
{item()}
} * * ``` * If you have a list with changing indices, better use ``. * * @description https://docs.solidjs.com/reference/components/index-component */ export function Index(props: { each: T | undefined | null | false; fallback?: JSX.Element; children: (item: Accessor, index: number) => U; }) { const fallback = "fallback" in props && { fallback: () => props.fallback }; return (IS_DEV ? createMemo( indexArray(() => props.each, props.children, fallback || undefined), undefined, { name: "value" } ) : createMemo( indexArray(() => props.each, props.children, fallback || undefined) )) as unknown as JSX.Element; } type RequiredParameter = T extends () => unknown ? never : T; /** * Conditionally render its children or an optional fallback component * @description https://docs.solidjs.com/reference/components/show */ export function Show< T, TRenderFunction extends (item: Accessor>) => JSX.Element >(props: { when: T | undefined | null | false; keyed?: false; fallback?: JSX.Element; children: JSX.Element | RequiredParameter; }): JSX.Element; export function Show) => JSX.Element>(props: { when: T | undefined | null | false; keyed: true; fallback?: JSX.Element; children: JSX.Element | RequiredParameter; }): JSX.Element; export function Show(props: { when: T | undefined | null | false; keyed?: boolean; fallback?: JSX.Element; children: JSX.Element | ((item: NonNullable | Accessor>) => JSX.Element); }): JSX.Element { const keyed = props.keyed; const conditionValue = createMemo( () => props.when, undefined, IS_DEV ? { name: "condition value" } : undefined ); const condition = keyed ? conditionValue : createMemo( conditionValue, undefined, IS_DEV ? { equals: (a, b) => !a === !b, name: "condition" } : { equals: (a, b) => !a === !b } ); return createMemo( () => { const c = condition(); if (c) { const child = props.children; const fn = typeof child === "function" && child.length > 0; return fn ? untrack(() => (child as any)( keyed ? (c as T) : () => { if (!untrack(condition)) throw narrowedError("Show"); return conditionValue(); } ) ) : child; } return props.fallback; }, undefined, IS_DEV ? { name: "value" } : undefined ) as unknown as JSX.Element; } type EvalConditions = readonly [number, Accessor, MatchProps]; /** * Switches between content based on mutually exclusive conditions * ```typescript * }> * * * * * * * * ``` * @description https://docs.solidjs.com/reference/components/switch-and-match */ export function Switch(props: { fallback?: JSX.Element; children: JSX.Element }): JSX.Element { const chs = children(() => props.children); const switchFunc = createMemo(() => { const ch = chs() as unknown as MatchProps | MatchProps[]; const mps = Array.isArray(ch) ? ch : [ch]; let func: Accessor = () => undefined; for (let i = 0; i < mps.length; i++) { const index = i; const mp = mps[i]; const prevFunc = func; const conditionValue = createMemo( () => (prevFunc() ? undefined : mp.when), undefined, IS_DEV ? { name: "condition value" } : undefined ); const condition = mp.keyed ? conditionValue : createMemo( conditionValue, undefined, IS_DEV ? { equals: (a, b) => !a === !b, name: "condition" } : { equals: (a, b) => !a === !b } ); func = () => prevFunc() || (condition() ? [index, conditionValue, mp] : undefined); } return func; }); return createMemo( () => { const sel = switchFunc()(); if (!sel) return props.fallback; const [index, conditionValue, mp] = sel; const child = mp.children; const fn = typeof child === "function" && child.length > 0; return fn ? untrack(() => (child as any)( mp.keyed ? (conditionValue() as any) : () => { if (untrack(switchFunc)()?.[0] !== index) throw narrowedError("Match"); return conditionValue(); } ) ) : child; }, undefined, IS_DEV ? { name: "eval conditions" } : undefined ) as unknown as JSX.Element; } export type MatchProps = { when: T | undefined | null | false; keyed?: boolean; children: JSX.Element | ((item: NonNullable | Accessor>) => JSX.Element); }; /** * Selects a content based on condition when inside a `` control flow * ```typescript * * * * ``` * @description https://docs.solidjs.com/reference/components/switch-and-match */ export function Match< T, TRenderFunction extends (item: Accessor>) => JSX.Element >(props: { when: T | undefined | null | false; keyed?: false; children: JSX.Element | RequiredParameter; }): JSX.Element; export function Match) => JSX.Element>(props: { when: T | undefined | null | false; keyed: true; children: JSX.Element | RequiredParameter; }): JSX.Element; export function Match(props: MatchProps) { return props as unknown as JSX.Element; } let Errors: Set>; export function resetErrorBoundaries() { Errors && [...Errors].forEach(fn => fn()); } /** * Catches uncaught errors inside components and renders a fallback content * * Also supports a callback form that passes the error and a reset function: * ```typescript *
Error: {err.toString()}
* }> * *
* ``` * Errors thrown from the fallback can be caught by a parent ErrorBoundary * * @description https://docs.solidjs.com/reference/components/error-boundary */ export function ErrorBoundary(props: { fallback: JSX.Element | ((err: any, reset: () => void) => JSX.Element); children: JSX.Element; }): JSX.Element { let err; if (sharedConfig!.context && sharedConfig!.load) err = sharedConfig.load(sharedConfig.getContextId()); const [errored, setErrored] = createSignal(err, IS_DEV ? { name: "errored" } : undefined); Errors || (Errors = new Set()); Errors.add(setErrored); onCleanup(() => Errors.delete(setErrored)); return createMemo( () => { let e: any; if ((e = errored())) { const f = props.fallback; if (IS_DEV && (typeof f !== "function" || f.length == 0)) console.error(e); return typeof f === "function" && f.length ? untrack(() => f(e, () => setErrored())) : f; } return catchError(() => props.children, setErrored); }, undefined, IS_DEV ? { name: "value" } : undefined ) as unknown as JSX.Element; } ================================================ FILE: packages/solid/src/render/hydration.ts ================================================ import { Computation } from "../reactive/signal.js"; export type HydrationContext = { id: string; count: number }; type SharedConfig = { context?: HydrationContext; resources?: { [key: string]: any }; load?: (id: string) => Promise | any; has?: (id: string) => boolean; gather?: (key: string) => void; registry?: Map; done?: boolean; count?: number; effects?: Computation[]; getContextId(): string; getNextContextId(): string; }; export const sharedConfig: SharedConfig = { context: undefined, registry: undefined, effects: undefined, done: false, getContextId() { return getContextId(this.context!.count); }, getNextContextId() { return getContextId(this.context!.count++); } }; function getContextId(count: number) { const num = String(count), len = num.length - 1; return sharedConfig.context!.id + (len ? String.fromCharCode(96 + len) : "") + num; } export function setHydrateContext(context?: HydrationContext): void { sharedConfig.context = context; } export function nextHydrateContext(): HydrationContext | undefined { return { ...sharedConfig.context, id: sharedConfig.getNextContextId(), count: 0 }; } ================================================ FILE: packages/solid/src/render/index.ts ================================================ export * from "./component.js"; export * from "./flow.js"; export * from "./Suspense.js"; export { sharedConfig } from "./hydration.js"; ================================================ FILE: packages/solid/src/server/index.ts ================================================ export { catchError, createRoot, createSignal, createComputed, createRenderEffect, createEffect, createReaction, createDeferred, createSelector, createMemo, getListener, onMount, onCleanup, onError, untrack, batch, on, children, createContext, useContext, getOwner, runWithOwner, equalFn, requestCallback, mapArray, indexArray, observable, from, $PROXY, $DEVCOMP, $TRACK, DEV, enableExternalSource } from "./reactive.js"; export { mergeProps, splitProps, createComponent, For, Index, Show, Switch, Match, ErrorBoundary, Suspense, SuspenseList, createResource, resetErrorBoundaries, enableScheduling, enableHydration, startTransition, useTransition, createUniqueId, lazy, sharedConfig } from "./rendering.js"; export type { Component, Resource } from "./rendering.js"; ================================================ FILE: packages/solid/src/server/reactive.ts ================================================ import type { JSX } from "../jsx.js"; export const equalFn = (a: T, b: T) => a === b; export const $PROXY = Symbol("solid-proxy"); export const $TRACK = Symbol("solid-track"); export const $DEVCOMP = Symbol("solid-dev-component"); export const DEV = undefined; export type Accessor = () => T; export type Setter = undefined extends T ? (value?: (U extends Function ? never : U) | ((prev?: T) => U)) => U : (value: (U extends Function ? never : U) | ((prev: T) => U)) => U; export type Signal = [get: Accessor, set: Setter]; const ERROR = Symbol("error"); export function castError(err: unknown): Error { if (err instanceof Error) return err; return new Error(typeof err === "string" ? err : "Unknown error", { cause: err }); } function handleError(err: unknown, owner = Owner): void { const fns = owner && owner.context && owner.context[ERROR]; const error = castError(err); if (!fns) throw error; try { for (const f of fns) f(error); } catch (e) { handleError(e, (owner && owner.owner) || null); } } const UNOWNED: Owner = { context: null, owner: null, owned: null, cleanups: null }; export let Owner: Owner | null = null; interface Owner { owner: Owner | null; context: any | null; owned: Owner[] | null; cleanups: (() => void)[] | null; } export function createOwner(): Owner { const o = { owner: Owner, context: Owner ? Owner.context : null, owned: null, cleanups: null }; if (Owner) { if (!Owner.owned) Owner.owned = [o]; else Owner.owned.push(o); } return o; } export function createRoot(fn: (dispose: () => void) => T, detachedOwner?: typeof Owner): T { const owner = Owner, current = detachedOwner === undefined ? owner : detachedOwner, root = fn.length === 0 ? UNOWNED : { context: current ? current.context : null, owner: current, owned: null, cleanups: null }; Owner = root; let result: T; try { result = fn(fn.length === 0 ? () => {} : () => cleanNode(root)); } catch (err) { handleError(err); } finally { Owner = owner; } return result!; } export function createSignal( value: T, options?: { equals?: false | ((prev: T, next: T) => boolean); name?: string } ): [get: () => T, set: (v: (T extends Function ? never : T) | ((prev: T) => T)) => T] { return [ () => value as T, v => { return (value = typeof v === "function" ? (v as (prev: T) => T)(value) : v); } ]; } export function createComputed(fn: (v?: T) => T, value?: T): void { Owner = createOwner(); try { fn(value); } catch (err) { handleError(err); } finally { Owner = Owner.owner; } } export const createRenderEffect = createComputed; export function createEffect(fn: (v?: T) => T, value?: T): void {} export function createReaction(fn: () => void) { return (fn: () => void) => { fn(); }; } export function createMemo(fn: (v?: T) => T, value?: T): () => T { Owner = createOwner(); let v: T; try { v = fn(value); } catch (err) { handleError(err); } finally { Owner = Owner.owner; } return () => v; } export function createDeferred(source: () => T) { return source; } export function createSelector(source: () => T, fn: (k: T, value: T) => boolean = equalFn) { return (k: T) => fn(k, source()); } export function batch(fn: () => T): T { return fn(); } export const untrack = batch; export function on( deps: Array<() => T> | (() => T), fn: (value: Array | T, prev?: Array | T, prevResults?: U) => U, options: { defer?: boolean } = {} ): (prev?: U) => U | undefined { const isArray = Array.isArray(deps); const defer = options.defer; return () => { if (defer) return undefined; let value: Array | T; if (isArray) { value = []; for (let i = 0; i < deps.length; i++) value.push((deps as Array<() => T>)[i]()); } else value = (deps as () => T)(); return fn!(value); }; } export function onMount(fn: () => void) {} export function onCleanup(fn: () => void) { if (Owner) { if (!Owner.cleanups) Owner.cleanups = [fn]; else Owner.cleanups.push(fn); } return fn; } export function cleanNode(node: Owner) { if (node.owned) { for (let i = 0; i < node.owned.length; i++) cleanNode(node.owned[i]); node.owned = null; } if (node.cleanups) { for (let i = 0; i < node.cleanups.length; i++) node.cleanups[i](); node.cleanups = null; } } export function catchError(fn: () => T, handler: (err: Error) => void) { const owner = createOwner(); owner.context = { ...owner.context, [ERROR]: [handler] }; Owner = owner; try { return fn(); } catch (err) { handleError(err); } finally { Owner = Owner!.owner; } } export function getListener() { return null; } // Context API export interface Context { id: symbol; Provider: (props: { value: T; children: any }) => any; defaultValue?: T; } export function createContext(defaultValue?: T): Context { const id = Symbol("context"); return { id, Provider: createProvider(id), defaultValue }; } export function useContext(context: Context): T { return Owner && Owner.context && Owner.context[context.id] !== undefined ? Owner.context[context.id] : context.defaultValue; } export function getOwner() { return Owner; } type ChildrenReturn = Accessor & { toArray: () => any[] }; export function children(fn: () => any): ChildrenReturn { const memo = createMemo(() => resolveChildren(fn())); (memo as ChildrenReturn).toArray = () => { const c = memo(); return Array.isArray(c) ? c : c != null ? [c] : []; }; return memo as ChildrenReturn; } export function runWithOwner(o: typeof Owner, fn: () => T): T | undefined { const prev = Owner; Owner = o; try { return fn(); } catch (err) { handleError(err); } finally { Owner = prev; } } function resolveChildren(children: any): unknown { // `!children.length` avoids running functions that arent signals if (typeof children === "function" && !children.length) return resolveChildren(children()); if (Array.isArray(children)) { const results: any[] = []; for (let i = 0; i < children.length; i++) { const result = resolveChildren(children[i]); Array.isArray(result) ? results.push.apply(results, result) : results.push(result); } return results; } return children; } function createProvider(id: symbol) { return function provider(props: { value: unknown; children: any }) { return createMemo(() => { Owner!.context = { ...Owner!.context, [id]: props.value }; return children(() => props.children) as unknown as JSX.Element; }); }; } export interface Task { id: number; fn: ((didTimeout: boolean) => void) | null; startTime: number; expirationTime: number; } export function requestCallback(fn: () => void, options?: { timeout: number }): Task { return { id: 0, fn: () => {}, startTime: 0, expirationTime: 0 }; } export function cancelCallback(task: Task) {} export function mapArray( list: Accessor, mapFn: (v: T, i: Accessor) => U, options: { fallback?: Accessor } = {} ): () => U[] { const items = list(); let s: U[] = []; if (items && items.length) { for (let i = 0, len = items.length; i < len; i++) s.push(mapFn(items[i], () => i)); } else if (options.fallback) s = [options.fallback()]; return () => s; } export function indexArray( list: Accessor, mapFn: (v: Accessor, i: number) => U, options: { fallback?: Accessor } = {} ): () => U[] { const items = list(); let s: U[] = []; if (items && items.length) { for (let i = 0, len = items.length; i < len; i++) s.push(mapFn(() => items[i], i)); } else if (options.fallback) s = [options.fallback()]; return () => s; } export type ObservableObserver = | ((v: T) => void) | { next: (v: T) => void; error?: (v: any) => void; complete?: (v: boolean) => void; }; export function observable(input: Accessor) { return { subscribe(observer: ObservableObserver) { if (!(observer instanceof Object) || observer == null) { throw new TypeError("Expected the observer to be an object."); } const handler = typeof observer === "function" ? observer : observer.next && observer.next.bind(observer); if (!handler) { return { unsubscribe() {} }; } const dispose = createRoot(disposer => { createEffect(() => { const v = input(); untrack(() => handler(v)); }); return disposer; }); if (getOwner()) onCleanup(dispose); return { unsubscribe() { dispose(); } }; }, [Symbol.observable || "@@observable"]() { return this; } }; } export function from( producer: | ((setter: Setter) => () => void) | { subscribe: (fn: (v: T) => void) => (() => void) | { unsubscribe: () => void }; } ): Accessor { const [s, set] = createSignal(undefined, { equals: false }) as [ Accessor, Setter ]; if ("subscribe" in producer) { const unsub = producer.subscribe(v => set(() => v)); onCleanup(() => ("unsubscribe" in unsub ? unsub.unsubscribe() : unsub())); } else { const clean = producer(set); onCleanup(clean); } return s; } export function enableExternalSource(factory: any) {} /** * @deprecated since version 1.7.0 and will be removed in next major - use catchError instead */ export function onError(fn: (err: Error) => void): void { if (Owner) { if (Owner.context === null || !Owner.context[ERROR]) { // terrible de-opt Owner.context = { ...Owner.context, [ERROR]: [fn] }; mutateContext(Owner, ERROR, [fn]); } else Owner.context[ERROR].push(fn); } } function mutateContext(o: Owner, key: symbol, value: any) { if (o.owned) { for (let i = 0; i < o.owned.length; i++) { if (o.owned[i].context === o.context) mutateContext(o.owned[i], key, value); if (!o.owned[i].context) { o.owned[i].context = o.context; mutateContext(o.owned[i], key, value); } else if (!o.owned[i].context[key]) { o.owned[i].context[key] = value; mutateContext(o.owned[i], key, value); } } } } ================================================ FILE: packages/solid/src/server/rendering.ts ================================================ import { Accessor, castError, catchError, cleanNode, createContext, createMemo, createOwner, Owner, runWithOwner, Setter, Signal, useContext } from "./reactive.js"; import type { JSX } from "../jsx.js"; export type Component

= (props: P) => JSX.Element; export type VoidProps

= P & { children?: never }; export type VoidComponent

= Component>; export type ParentProps

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

= Component>; export type FlowProps

= P & { children: C }; export type FlowComponent

= Component>; export type Ref = T | ((val: T) => void); export type ValidComponent = keyof JSX.IntrinsicElements | Component | (string & {}); export type ComponentProps = T extends Component ? P : T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T] : Record; // these methods are duplicates from solid-js/web // we need a better solution for this in the future function escape(s: any, attr?: boolean) { const t = typeof s; if (t !== "string") { if (!attr && t === "function") return escape(s()); if (!attr && Array.isArray(s)) { for (let i = 0; i < s.length; i++) s[i] = escape(s[i]); return s; } if (attr && t === "boolean") return String(s); return s; } const delim = attr ? '"' : "<"; const escDelim = attr ? """ : "<"; let iDelim = s.indexOf(delim); let iAmp = s.indexOf("&"); if (iDelim < 0 && iAmp < 0) return s; let left = 0, out = ""; while (iDelim >= 0 && iAmp >= 0) { if (iDelim < iAmp) { if (left < iDelim) out += s.substring(left, iDelim); out += escDelim; left = iDelim + 1; iDelim = s.indexOf(delim, left); } else { if (left < iAmp) out += s.substring(left, iAmp); out += "&"; left = iAmp + 1; iAmp = s.indexOf("&", left); } } if (iDelim >= 0) { do { if (left < iDelim) out += s.substring(left, iDelim); out += escDelim; left = iDelim + 1; iDelim = s.indexOf(delim, left); } while (iDelim >= 0); } else while (iAmp >= 0) { if (left < iAmp) out += s.substring(left, iAmp); out += "&"; left = iAmp + 1; iAmp = s.indexOf("&", left); } return left < s.length ? out + s.substring(left) : out; } function resolveSSRNode(node: any): string { const t = typeof node; if (t === "string") return node; if (node == null || t === "boolean") return ""; if (Array.isArray(node)) { let prev = {}; let mapped = ""; for (let i = 0, len = node.length; i < len; i++) { if (typeof prev !== "object" && typeof node[i] !== "object") mapped += ``; mapped += resolveSSRNode((prev = node[i])); } return mapped; } if (t === "object") return node.t; if (t === "function") return resolveSSRNode(node()); return String(node); } type SharedConfig = { context?: HydrationContext; getContextId(): string; getNextContextId(): string; }; export const sharedConfig: SharedConfig = { context: undefined, getContextId() { if (!this.context) throw new Error(`getContextId cannot be used under non-hydrating context`); return getContextId(this.context.count); }, getNextContextId() { if (!this.context) throw new Error(`getNextContextId cannot be used under non-hydrating context`); return getContextId(this.context.count++); } }; function getContextId(count: number) { const num = String(count), len = num.length - 1; return sharedConfig.context!.id + (len ? String.fromCharCode(96 + len) : "") + num; } function setHydrateContext(context?: HydrationContext): void { sharedConfig.context = context; } function nextHydrateContext(): HydrationContext | undefined { return sharedConfig.context ? { ...sharedConfig.context, id: sharedConfig.getNextContextId(), count: 0 } : undefined; } export function createUniqueId(): string { return sharedConfig.getNextContextId(); } export function createComponent(Comp: (props: T) => JSX.Element, props: T): JSX.Element { if (sharedConfig.context && !sharedConfig.context.noHydrate) { const c = sharedConfig.context; setHydrateContext(nextHydrateContext()); const r = Comp(props || ({} as T)); setHydrateContext(c); return r; } return Comp(props || ({} as 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 mergeProps(...sources: any): any { const target = {}; for (let i = 0; i < sources.length; i++) { let source = sources[i]; if (typeof source === "function") source = source(); if (source) { const descriptors = Object.getOwnPropertyDescriptors(source); for (const key in descriptors) { if (key in target) continue; Object.defineProperty(target, key, { enumerable: true, get() { for (let i = sources.length - 1; i >= 0; i--) { let v, s = sources[i]; if (typeof s === "function") s = s(); v = (s || {})[key]; if (v !== undefined) return v; } } }); } } } return target; } export function splitProps( props: T, ...keys: [K1[]] ): [Pick, Omit]; export function splitProps( props: T, ...keys: [K1[], K2[]] ): [Pick, Pick, Omit]; export function splitProps< T extends object, K1 extends keyof T, K2 extends keyof T, K3 extends keyof T >( props: T, ...keys: [K1[], K2[], K3[]] ): [Pick, Pick, Pick, Omit]; export function splitProps< T extends object, K1 extends keyof T, K2 extends keyof T, K3 extends keyof T, K4 extends keyof T >( props: T, ...keys: [K1[], K2[], K3[], K4[]] ): [Pick, Pick, Pick, Pick, Omit]; export function splitProps< T extends object, K1 extends keyof T, K2 extends keyof T, K3 extends keyof T, K4 extends keyof T, K5 extends keyof T >( props: T, ...keys: [K1[], K2[], K3[], K4[], K5[]] ): [ Pick, Pick, Pick, Pick, 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]; } } return clone; }; return keys.map(split).concat(split(Object.keys(descriptors) as (keyof T)[])); } function simpleMap( props: { each: any[]; children: Function; fallback?: string }, wrap: (fn: Function, item: any, i: number) => string ) { const list = props.each || [], len = list.length, fn = props.children; if (len) { let mapped = Array(len); for (let i = 0; i < len; i++) mapped[i] = wrap(fn, list[i], i); return mapped; } return props.fallback; } export function For(props: { each: T[]; fallback?: string; children: (item: T, index: () => number) => string; }) { return simpleMap(props, (fn, item, i) => fn(item, () => i)); } // non-keyed export function Index(props: { each: T[]; fallback?: string; children: (item: () => T, index: number) => string; }) { return simpleMap(props, (fn, item, i) => fn(() => item, i)); } type RequiredParameter = T extends () => unknown ? never : T; /** * Conditionally render its children or an optional fallback component * @description https://docs.solidjs.com/reference/components/show */ export function Show(props: { when: T | undefined | null | false; keyed?: boolean; fallback?: string; children: string | ((item: NonNullable | Accessor>) => string); }): string { let c: string | ((item: NonNullable | Accessor>) => string); return props.when ? typeof (c = props.children) === "function" ? c(props.keyed ? props.when! : () => props.when as any) : c : props.fallback || ""; } export function Switch(props: { fallback?: string; children: MatchProps | MatchProps[]; }) { let conditions = props.children; Array.isArray(conditions) || (conditions = [conditions]); for (let i = 0; i < conditions.length; i++) { const w = conditions[i].when; if (w) { const c = conditions[i].children; return typeof c === "function" ? c(conditions[i].keyed ? w : () => w) : c; } } return props.fallback || ""; } type MatchProps = { when: T | false; keyed?: boolean; children: string | ((item: NonNullable | Accessor>) => string); }; export function Match(props: MatchProps) { return props; } export function resetErrorBoundaries() {} export function ErrorBoundary(props: { fallback: string | ((err: any, reset: () => void) => string); children: string; }) { let error: any, res: any, clean: any, sync = true; const ctx = sharedConfig.context!; const id = sharedConfig.getContextId(); function displayFallback() { cleanNode(clean); ctx.serialize(id, error); setHydrateContext({ ...ctx, count: 0 }); const f = props.fallback; return typeof f === "function" && f.length ? f(error, () => {}) : f; } createMemo(() => { clean = Owner; return catchError( () => (res = props.children), err => { error = err; !sync && ctx.replace("e" + id, displayFallback); sync = true; } ); }); if (error) return displayFallback(); sync = false; return { t: `${resolveSSRNode(escape(res))}` }; } // Suspense Context export interface Resource { (): T | undefined; state: "unresolved" | "pending" | "ready" | "refreshing" | "errored"; loading: boolean; error: any; latest: T | undefined; } type SuspenseContextType = { resources: Map; completed: () => void; }; export type ResourceActions = { mutate: Setter; refetch: (info?: unknown) => void }; export type ResourceReturn = [Resource, ResourceActions]; export type ResourceSource = S | false | null | undefined | (() => S | false | null | undefined); export type ResourceFetcher = (k: S, info: ResourceFetcherInfo) => T | Promise; export type ResourceFetcherInfo = { value: T | undefined; refetching?: unknown }; export type ResourceOptions = undefined extends T ? { initialValue?: T; name?: string; deferStream?: boolean; ssrLoadFrom?: "initial" | "server"; storage?: () => Signal; onHydrated?: (k: S, info: ResourceFetcherInfo) => void; } : { initialValue: T; name?: string; deferStream?: boolean; ssrLoadFrom?: "initial" | "server"; storage?: (v?: T) => Signal; onHydrated?: (k: S, info: ResourceFetcherInfo) => void; }; const SuspenseContext = createContext(); let resourceContext: any[] | null = null; export function createResource( fetcher: ResourceFetcher, options?: ResourceOptions ): ResourceReturn; export function createResource( fetcher: ResourceFetcher, options: ResourceOptions ): ResourceReturn; export function createResource( source: ResourceSource, fetcher: ResourceFetcher, options?: ResourceOptions ): ResourceReturn; export function createResource( source: ResourceSource, fetcher: ResourceFetcher, options: ResourceOptions ): ResourceReturn; export function createResource( source: ResourceSource | ResourceFetcher, fetcher?: ResourceFetcher | ResourceOptions | ResourceOptions, options: ResourceOptions | ResourceOptions = {} ): ResourceReturn | ResourceReturn { if (typeof fetcher !== "function") { options = (fetcher || {}) as ResourceOptions | ResourceOptions; fetcher = source as ResourceFetcher; source = true as ResourceSource; } const contexts = new Set(); const id = sharedConfig.getNextContextId(); let resource: { ref?: any; data?: T } = {}; let value = options.storage ? options.storage(options.initialValue)[0]() : options.initialValue; let p: Promise | T | null | undefined; let error: any; if (sharedConfig.context!.async && options.ssrLoadFrom !== "initial") { resource = sharedConfig.context!.resources[id] || (sharedConfig.context!.resources[id] = {}); if (resource.ref) { if (!resource.data && !resource.ref[0]._loading && !resource.ref[0].error) resource.ref[1].refetch(); return resource.ref; } } const prepareResource = () => { if (error) throw error; const resolved = options.ssrLoadFrom !== "initial" && sharedConfig.context!.async && "data" in sharedConfig.context!.resources[id]; if (!resolved && resourceContext) resourceContext.push(id); if (!resolved && read._loading) { const ctx = useContext(SuspenseContext); if (ctx) { ctx.resources.set(id, read); contexts.add(ctx); } } return resolved; }; const read = () => { return prepareResource() ? sharedConfig.context!.resources[id].data : value; }; const loading = () => { prepareResource(); return read._loading; }; read._loading = false; read.error = undefined as any; read.state = "initialValue" in options ? "ready" : "unresolved"; Object.defineProperties(read, { latest: { get() { return read(); } }, loading: { get() { return loading(); } } }); function load() { const ctx = sharedConfig.context!; if (!ctx.async) return (read._loading = !!(typeof source === "function" ? (source as () => S)() : source)); if (ctx.resources && id in ctx.resources && "data" in ctx.resources[id]) { value = ctx.resources[id].data; return; } let lookup; try { resourceContext = []; lookup = typeof source === "function" ? (source as () => S)() : source; if (resourceContext.length) return; } finally { resourceContext = null; } if (!p) { if (lookup == null || lookup === false) return; p = (fetcher as ResourceFetcher)(lookup, { value }); } if (p != undefined && typeof p === "object" && "then" in p) { read._loading = true; read.state = "pending"; p = p .then(res => { read._loading = false; read.state = "ready"; ctx.resources[id].data = res; p = null; notifySuspense(contexts); return res; }) .catch(err => { read._loading = false; read.state = "errored"; read.error = error = castError(err); p = null; notifySuspense(contexts); throw error; }); if (ctx.serialize) ctx.serialize(id, p, options.deferStream); return p; } ctx.resources[id].data = p; if (ctx.serialize) ctx.serialize(id, p); p = null; return ctx.resources[id].data; } if (options.ssrLoadFrom !== "initial") load(); const ref = [ read as unknown as Resource, { refetch: load, mutate: (v: T) => (value = v) } ] as ResourceReturn; if (p) resource.ref = ref; return ref; } export function lazy>( fn: () => Promise<{ default: T }> ): T & { preload: () => Promise<{ default: T }> } { let p: Promise<{ default: T }> & { resolved?: T }; let load = (id?: string) => { if (!p) { p = fn(); p.then(mod => (p.resolved = mod.default)); if (id) sharedConfig.context!.lazy[id] = p; } return p; }; const contexts = new Set(); const wrap: Component> & { preload?: () => Promise<{ default: T }>; } = props => { const id = sharedConfig.context!.id; let ref = sharedConfig.context!.lazy[id]; if (ref) p = ref; else load(id); if (p.resolved) return p.resolved(props); const ctx = useContext(SuspenseContext); const track = { _loading: true, error: undefined }; if (ctx) { ctx.resources.set(id, track); contexts.add(ctx); } if (sharedConfig.context!.async) { sharedConfig.context!.block( p.then(() => { track._loading = false; notifySuspense(contexts); }) ); } return ""; }; wrap.preload = load; return wrap as T & { preload: () => Promise<{ default: T }> }; } function suspenseComplete(c: SuspenseContextType) { for (const r of c.resources.values()) { if (r._loading) return false; } return true; } function notifySuspense(contexts: Set) { for (const c of contexts) { if (!suspenseComplete(c)) { continue; } c.completed(); contexts.delete(c); } } export function enableScheduling() {} export function enableHydration() {} export function startTransition(fn: () => any): void { fn(); } export function useTransition(): [() => boolean, (fn: () => any) => void] { return [ () => false, fn => { fn(); } ]; } type HydrationContext = { id: string; count: number; serialize: (id: string, v: Promise | any, deferStream?: boolean) => void; nextRoot: (v: any) => string; replace: (id: string, replacement: () => any) => void; block: (p: Promise) => void; resources: Record; suspense: Record; registerFragment: (v: string) => (v?: string, err?: any) => boolean; lazy: Record>; async?: boolean; noHydrate: boolean; }; export function SuspenseList(props: { children: string; revealOrder: "forwards" | "backwards" | "together"; tail?: "collapsed" | "hidden"; }) { // TODO: support revealOrder and tail options if (sharedConfig.context && !sharedConfig.context.noHydrate) { const c = sharedConfig.context; setHydrateContext(nextHydrateContext()); const result = props.children; setHydrateContext(c); return result; } return props.children; } export function Suspense(props: { fallback?: string; children: string }) { let done: undefined | ((html?: string, error?: any) => boolean); const ctx = sharedConfig.context!; const id = sharedConfig.getContextId(); const o = createOwner(); const value: SuspenseContextType = ctx.suspense[id] || (ctx.suspense[id] = { resources: new Map(), completed: () => { const res = runSuspense(); if (suspenseComplete(value)) { done!(resolveSSRNode(escape(res))); } } }); function suspenseError(err: Error) { if (!done || !done(undefined, err)) { runWithOwner(o.owner!, () => { throw err; }); } } function runSuspense() { setHydrateContext({ ...ctx, count: 0 }); cleanNode(o); return runWithOwner(o, () => createComponent(SuspenseContext.Provider, { value, get children() { return catchError(() => props.children, suspenseError); } }) ); } const res = runSuspense(); // never suspended if (suspenseComplete(value)) { delete ctx.suspense[id]; return res; } done = ctx.async ? ctx.registerFragment(id) : undefined; return catchError(() => { if (ctx.async) { setHydrateContext({ ...ctx, count: 0, id: ctx.id + "0F", noHydrate: true }); const res = { t: `${resolveSSRNode( escape(props.fallback) )}` }; setHydrateContext(ctx); return res; } setHydrateContext({ ...ctx, count: 0, id: ctx.id + "0F" }); ctx.serialize(id, "$$f"); return props.fallback; }, suspenseError); } ================================================ FILE: packages/solid/store/README.md ================================================ # Solid Store This submodules contains the means for handling deeps nested reactivity. It provides 2 main primitives `createStore` and `createMutable` which leverage proxies to create dynamic nested reactive structures. This also contains helper methods `produce` and `reconcile` which augment the behavior of the store setter method to allow for localized mutation and data diffing. For full documentation, check out the [website](https://docs.solidjs.com). ## Example ```js import { createStore } from "solid-js/store"; const [store, setStore] = createStore({ user: { firstName: "John", lastName: "Smith" } }); // update store.user.firstName setStore("user", "firstName", "Will"); ``` ================================================ FILE: packages/solid/store/package.json ================================================ { "name": "solid-js/store", "main": "./dist/server.cjs", "module": "./dist/server.js", "unpkg": "./dist/store.cjs", "types": "./types/index.d.ts", "type": "module", "sideEffects": false, "exports": { ".": { "worker": { "types": "./types/index.d.ts", "import": "./dist/server.js", "require": "./dist/server.cjs" }, "browser": { "development": { "types": "./types/index.d.ts", "import": "./dist/dev.js", "require": "./dist/dev.cjs" }, "types": "./types/index.d.ts", "import": "./dist/store.js", "require": "./dist/store.cjs" }, "deno": { "types": "./types/index.d.ts", "import": "./dist/server.js", "require": "./dist/server.cjs" }, "node": { "types": "./types/index.d.ts", "import": "./dist/server.js", "require": "./dist/server.cjs" }, "development": { "types": "./types/index.d.ts", "import": "./dist/dev.js", "require": "./dist/dev.cjs" }, "types": "./types/index.d.ts", "import": "./dist/store.js", "require": "./dist/store.cjs" } } } ================================================ FILE: packages/solid/store/src/index.ts ================================================ export { $RAW, createStore, unwrap } from "./store.js"; export type { ArrayFilterFn, DeepMutable, DeepReadonly, NotWrappable, Part, SetStoreFunction, SolidStore, Store, StoreNode, StorePathRange, StoreSetter } from "./store.js"; export * from "./mutable.js"; export * from "./modifiers.js"; // dev import { $NODE, isWrappable, DevHooks, IS_DEV } from "./store.js"; export const DEV = IS_DEV ? ({ $NODE, isWrappable, hooks: DevHooks } as const) : undefined; ================================================ FILE: packages/solid/store/src/modifiers.ts ================================================ import { setProperty, unwrap, isWrappable, StoreNode, $RAW } from "./store.js"; const $ROOT = Symbol("store-root"); export type ReconcileOptions = { key?: string | null; merge?: boolean; }; function applyState( target: any, parent: any, property: PropertyKey, merge: boolean | undefined, key: string | null ) { const previous = parent[property]; if (target === previous) return; const isArray = Array.isArray(target); if ( property !== $ROOT && (!isWrappable(target) || !isWrappable(previous) || isArray !== Array.isArray(previous) || (key && target[key] !== previous[key])) ) { setProperty(parent, property, target); return; } if (isArray) { if ( target.length && previous.length && (!merge || (key && target[0] && target[0][key] != null)) ) { let i, j, start, end, newEnd, item, newIndicesNext, keyVal; // common prefix for ( start = 0, end = Math.min(previous.length, target.length); start < end && (previous[start] === target[start] || (key && previous[start] && target[start] && previous[start][key] && previous[start][key] === target[start][key])); start++ ) { applyState(target[start], previous, start, merge, key); } const temp = new Array(target.length), newIndices = new Map(); // common suffix for ( end = previous.length - 1, newEnd = target.length - 1; end >= start && newEnd >= start && (previous[end] === target[newEnd] || (key && previous[end] && target[newEnd] && previous[end][key] && previous[end][key] === target[newEnd][key])); end--, newEnd-- ) { temp[newEnd] = previous[end]; } // insert any remaining updates and remove any remaining nodes and we're done if (start > newEnd || start > end) { for (j = start; j <= newEnd; j++) setProperty(previous, j, target[j]); for (; j < target.length; j++) { setProperty(previous, j, temp[j]); applyState(target[j], previous, j, merge, key); } if (previous.length > target.length) setProperty(previous, "length", target.length); return; } // prepare a map of all indices in target newIndicesNext = new Array(newEnd + 1); for (j = newEnd; j >= start; j--) { item = target[j]; keyVal = key && item ? item[key] : item; i = newIndices.get(keyVal); newIndicesNext[j] = i === undefined ? -1 : i; newIndices.set(keyVal, j); } // step through all old items to check reuse for (i = start; i <= end; i++) { item = previous[i]; keyVal = key && item ? item[key] : item; j = newIndices.get(keyVal); if (j !== undefined && j !== -1) { temp[j] = previous[i]; j = newIndicesNext[j]; newIndices.set(keyVal, j); } } // set all the new values for (j = start; j < target.length; j++) { if (j in temp) { setProperty(previous, j, temp[j]); applyState(target[j], previous, j, merge, key); } else setProperty(previous, j, target[j]); } } else { for (let i = 0, len = target.length; i < len; i++) { applyState(target[i], previous, i, merge, key); } } if (previous.length > target.length) setProperty(previous, "length", target.length); return; } const targetKeys = Object.keys(target); for (let i = 0, len = targetKeys.length; i < len; i++) { applyState(target[targetKeys[i]], previous, targetKeys[i], merge, key); } const previousKeys = Object.keys(previous); for (let i = 0, len = previousKeys.length; i < len; i++) { if (target[previousKeys[i]] === undefined) setProperty(previous, previousKeys[i], undefined); } } // Diff method for setStore export function reconcile( value: T, options: ReconcileOptions = {} ): (state: U) => T { const { merge, key = "id" } = options, v = unwrap(value); return state => { if (!isWrappable(state) || !isWrappable(v)) return v; const res = applyState(v, { [$ROOT]: state }, $ROOT, merge, key); return res === undefined ? (state as T) : res; }; } const producers = new WeakMap(); const setterTraps: ProxyHandler = { get(target, property): any { if (property === $RAW) return target; const value = target[property]; let proxy; return isWrappable(value) ? producers.get(value) || (producers.set(value, (proxy = new Proxy(value, setterTraps))), proxy) : value; }, set(target, property, value) { setProperty(target, property, unwrap(value)); return true; }, deleteProperty(target, property) { setProperty(target, property, undefined, true); return true; } }; // Immer style mutation style export function produce(fn: (state: T) => void): (state: T) => T { return state => { if (isWrappable(state)) { let proxy; if (!(proxy = producers.get(state as Record))) { producers.set( state as Record, (proxy = new Proxy(state as Extract, setterTraps)) ); } fn(proxy); } return state; }; } ================================================ FILE: packages/solid/store/src/mutable.ts ================================================ import { batch, getListener, DEV, $PROXY, $TRACK } from "solid-js"; import { unwrap, isWrappable, getNodes, trackSelf, getNode, $RAW, $NODE, $HAS, StoreNode, setProperty, ownKeys, IS_DEV } from "./store.js"; function proxyDescriptor(target: StoreNode, property: PropertyKey) { const desc = Reflect.getOwnPropertyDescriptor(target, property); if ( !desc || desc.get || desc.set || !desc.configurable || property === $PROXY || property === $NODE ) return desc; delete desc.value; delete desc.writable; desc.get = () => target[$PROXY][property]; desc.set = v => (target[$PROXY][property] = v); return desc; } const proxyTraps: ProxyHandler = { get(target, property, receiver) { if (property === $RAW) return target; if (property === $PROXY) return receiver; if (property === $TRACK) { trackSelf(target); return receiver; } const nodes = getNodes(target, $NODE); const tracked = nodes[property]; let value = tracked ? tracked() : target[property]; if (property === $NODE || property === $HAS || property === "__proto__") return value; if (!tracked) { const desc = Object.getOwnPropertyDescriptor(target, property); const isFunction = typeof value === "function"; if (getListener() && (!isFunction || target.hasOwnProperty(property)) && !(desc && desc.get)) value = getNode(nodes, property, value)(); else if (value != null && isFunction && value === Array.prototype[property as any]) { return (...args: unknown[]) => batch(() => Array.prototype[property as any].apply(receiver, args)); } } return isWrappable(value) ? wrap(value) : value; }, has(target, property) { if ( property === $RAW || property === $PROXY || property === $TRACK || property === $NODE || property === $HAS || property === "__proto__" ) return true; getListener() && getNode(getNodes(target, $HAS), property)(); return property in target; }, set(target, property, value) { batch(() => setProperty(target, property, unwrap(value))); return true; }, deleteProperty(target, property) { batch(() => setProperty(target, property, undefined, true)); return true; }, ownKeys: ownKeys, getOwnPropertyDescriptor: proxyDescriptor }; function wrap(value: T): T { let p = value[$PROXY]; if (!p) { Object.defineProperty(value, $PROXY, { value: (p = new Proxy(value, proxyTraps)) }); const keys = Object.keys(value), desc = Object.getOwnPropertyDescriptors(value); const proto = Object.getPrototypeOf(value); const isClass = proto !== null && value !== null && typeof value === "object" && !Array.isArray(value) && proto !== Object.prototype; if (isClass) { let curProto = proto; while (curProto != null) { const descriptors = Object.getOwnPropertyDescriptors(curProto); keys.push(...Object.keys(descriptors)); Object.assign(desc, descriptors); curProto = Object.getPrototypeOf(curProto); } } for (let i = 0, l = keys.length; i < l; i++) { const prop = keys[i]; if (isClass && prop === "constructor") continue; if (desc[prop].get) { const get = desc[prop].get!.bind(p); Object.defineProperty(value, prop, { get, configurable: true }); } if (desc[prop].set) { const og = desc[prop].set!, set = (v: T[keyof T]) => batch(() => og.call(p, v)); Object.defineProperty(value, prop, { set, configurable: true }); } } } return p; } export function createMutable(state: T, options?: { name?: string }): T { const unwrappedStore = unwrap(state || {}); if (IS_DEV && typeof unwrappedStore !== "object" && typeof unwrappedStore !== "function") throw new Error( `Unexpected type ${typeof unwrappedStore} received when initializing 'createMutable'. Expected an object.` ); const wrappedStore = wrap(unwrappedStore); if (IS_DEV) DEV!.registerGraph({ value: unwrappedStore, name: options && options.name }); return wrappedStore; } export function modifyMutable(state: T, modifier: (state: T) => T) { batch(() => modifier(unwrap(state))); } ================================================ FILE: packages/solid/store/src/server.ts ================================================ import type { SetStoreFunction, Store } from "./store.js"; export type { ArrayFilterFn, DeepMutable, DeepReadonly, NotWrappable, Part, SetStoreFunction, SolidStore, Store, StoreNode, StorePathRange, StoreSetter } from "./store.js"; export const $RAW = Symbol("state-raw"); export function isWrappable(obj: any) { return ( obj != null && typeof obj === "object" && (Object.getPrototypeOf(obj) === Object.prototype || Array.isArray(obj)) ); } export function unwrap(item: T): T { return item; } export function setProperty(state: any, property: PropertyKey, value: any, force?: boolean) { if (!force && state[property] === value) return; if (value === undefined) { delete state[property]; } else state[property] = value; } function mergeStoreNode(state: any, value: any, force?: boolean) { const keys = Object.keys(value); for (let i = 0; i < keys.length; i += 1) { const key = keys[i]; setProperty(state, key, value[key], force); } } function updateArray( current: any, next: Array | Record | ((prev: any) => Array | Record) ) { if (typeof next === "function") next = next(current); if (Array.isArray(next)) { if (current === next) return; let i = 0, len = next.length; for (; i < len; i++) { const value = next[i]; if (current[i] !== value) setProperty(current, i, value); } setProperty(current, "length", len); } else mergeStoreNode(current, next); } export function updatePath(current: any, path: any[], traversed: PropertyKey[] = []) { let part, next = current; if (path.length > 1) { part = path.shift(); const partType = typeof part, isArray = Array.isArray(current); if (Array.isArray(part)) { // Ex. update('data', [2, 23], 'label', l => l + ' !!!'); for (let i = 0; i < part.length; i++) { updatePath(current, [part[i]].concat(path), traversed); } return; } else if (isArray && partType === "function") { // Ex. update('data', i => i.id === 42, 'label', l => l + ' !!!'); for (let i = 0; i < current.length; i++) { if (part(current[i], i)) updatePath(current, [i].concat(path), traversed); } return; } else if (isArray && partType === "object") { // Ex. update('data', { from: 3, to: 12, by: 2 }, 'label', l => l + ' !!!'); const { from = 0, to = current.length - 1, by = 1 } = part; for (let i = from; i <= to; i += by) { updatePath(current, [i].concat(path), traversed); } return; } else if (path.length > 1) { updatePath(current[part], path, [part].concat(traversed)); return; } next = current[part]; traversed = [part].concat(traversed); } let value = path[0]; if (typeof value === "function") { value = value(next, traversed); if (value === next) return; } if (part === undefined && value == undefined) return; if (part === undefined || (isWrappable(next) && isWrappable(value) && !Array.isArray(value))) { mergeStoreNode(next, value); } else setProperty(current, part, value); } export function createStore(state: T | Store): [Store, SetStoreFunction] { const isArray = Array.isArray(state); function setStore(...args: any[]): void { isArray && args.length === 1 ? updateArray(state, args[0]) : updatePath(state, args); } return [state as Store, setStore]; } export function createMutable(state: T | Store): T { return state as T; } export function modifyMutable(state: T, modifier: (state: T) => T) { modifier(state); } type ReconcileOptions = { key?: string | null; merge?: boolean; }; // Diff method for setStore export function reconcile( value: T, options: ReconcileOptions = {} ): (state: U) => T { return state => { if (!isWrappable(state) || !isWrappable(value)) return value; const targetKeys = Object.keys(value) as (keyof T)[]; for (let i = 0, len = targetKeys.length; i < len; i++) { const key = targetKeys[i]; setProperty(state, key, value[key]); } const previousKeys = Object.keys(state) as (keyof T)[]; for (let i = 0, len = previousKeys.length; i < len; i++) { if (value[previousKeys[i]] === undefined) setProperty(state, previousKeys[i], undefined); } return state as T; }; } // Immer style mutation style export function produce(fn: (state: T) => void): (state: T) => T { return state => { if (isWrappable(state)) fn(state); return state; }; } export const DEV = undefined; ================================================ FILE: packages/solid/store/src/store.ts ================================================ import { getListener, batch, DEV, $PROXY, $TRACK, createSignal } from "solid-js"; // replaced during build export const IS_DEV = "_SOLID_DEV_" as string | boolean; export const $RAW = Symbol("store-raw"), $NODE = Symbol("store-node"), $HAS = Symbol("store-has"), $SELF = Symbol("store-self"); // debug hooks for devtools export const DevHooks: { onStoreNodeUpdate: OnStoreNodeUpdate | null } = { onStoreNodeUpdate: null }; type DataNode = { (): any; $(value?: any): void; }; export type DataNodes = Record; export type OnStoreNodeUpdate = ( state: StoreNode, property: PropertyKey, value: StoreNode | NotWrappable, prev: StoreNode | NotWrappable ) => void; export interface StoreNode { [$NODE]?: DataNodes; [key: PropertyKey]: any; } export namespace SolidStore { export interface Unwrappable {} } export type NotWrappable = | string | number | bigint | symbol | boolean | Function | null | undefined | SolidStore.Unwrappable[keyof SolidStore.Unwrappable]; export type Store = T; function wrap(value: T): T { let p = value[$PROXY]; if (!p) { Object.defineProperty(value, $PROXY, { value: (p = new Proxy(value, proxyTraps)) }); if (!Array.isArray(value)) { const 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) { Object.defineProperty(value, prop, { enumerable: desc[prop].enumerable, get: desc[prop].get!.bind(p) }); } } } } return p; } export function isWrappable(obj: T | NotWrappable): obj is T; export function isWrappable(obj: any) { let proto; return ( obj != null && typeof obj === "object" && (obj[$PROXY] || !(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype || Array.isArray(obj)) ); } /** * Returns the underlying data in the store without a proxy. * @param item store proxy object * @example * ```js * const initial = {z...}; * const [state, setState] = createStore(initial); * initial === state; // => false * initial === unwrap(state); // => true * ``` */ export function unwrap(item: T, set?: Set): T; export function unwrap(item: any, set = new Set()): T { let result, unwrapped, v, prop; if ((result = item != null && item[$RAW])) return result; if (!isWrappable(item) || set.has(item)) return item; if (Array.isArray(item)) { if (Object.isFrozen(item)) item = item.slice(0); else set.add(item); for (let i = 0, l = item.length; i < l; i++) { v = item[i]; if ((unwrapped = unwrap(v, set)) !== v) item[i] = unwrapped; } } else { if (Object.isFrozen(item)) item = Object.assign({}, item); else set.add(item); const keys = Object.keys(item), desc = Object.getOwnPropertyDescriptors(item); for (let i = 0, l = keys.length; i < l; i++) { prop = keys[i]; if (desc[prop].get) continue; v = item[prop]; if ((unwrapped = unwrap(v, set)) !== v) item[prop] = unwrapped; } } return item; } export function getNodes(target: StoreNode, symbol: typeof $NODE | typeof $HAS): DataNodes { let nodes = target[symbol]; if (!nodes) Object.defineProperty(target, symbol, { value: (nodes = Object.create(null) as DataNodes) }); return nodes; } export function getNode(nodes: DataNodes, property: PropertyKey, value?: any) { if (nodes[property]) return nodes[property]!; const [s, set] = createSignal(value, { equals: false, internal: true }); (s as DataNode).$ = set; return (nodes[property] = s as DataNode); } export function proxyDescriptor(target: StoreNode, property: PropertyKey) { const desc = Reflect.getOwnPropertyDescriptor(target, property); if (!desc || desc.get || !desc.configurable || property === $PROXY || property === $NODE) return desc; delete desc.value; delete desc.writable; desc.get = () => target[$PROXY][property]; return desc; } export function trackSelf(target: StoreNode) { getListener() && getNode(getNodes(target, $NODE), $SELF)(); } export function ownKeys(target: StoreNode) { trackSelf(target); return Reflect.ownKeys(target); } const proxyTraps: ProxyHandler = { get(target, property, receiver) { if (property === $RAW) return target; if (property === $PROXY) return receiver; if (property === $TRACK) { trackSelf(target); return receiver; } const nodes = getNodes(target, $NODE); const tracked = nodes[property]; let value = tracked ? tracked() : target[property]; if (property === $NODE || property === $HAS || property === "__proto__") return value; if (!tracked) { const desc = Object.getOwnPropertyDescriptor(target, property); if ( getListener() && (typeof value !== "function" || target.hasOwnProperty(property)) && !(desc && desc.get) ) value = getNode(nodes, property, value)(); } return isWrappable(value) ? wrap(value) : value; }, has(target, property) { if ( property === $RAW || property === $PROXY || property === $TRACK || property === $NODE || property === $HAS || property === "__proto__" ) return true; getListener() && getNode(getNodes(target, $HAS), property)(); return property in target; }, set() { if (IS_DEV) console.warn("Cannot mutate a Store directly"); return true; }, deleteProperty() { if (IS_DEV) console.warn("Cannot mutate a Store directly"); return true; }, ownKeys: ownKeys, getOwnPropertyDescriptor: proxyDescriptor }; export function setProperty( state: StoreNode, property: PropertyKey, value: any, deleting: boolean = false ): void { if (!deleting && state[property] === value) return; const prev = state[property], len = state.length; if (IS_DEV) DevHooks.onStoreNodeUpdate && DevHooks.onStoreNodeUpdate(state, property, value, prev); if (value === undefined) { delete state[property]; if (state[$HAS] && state[$HAS][property] && prev !== undefined) state[$HAS][property].$(); } else { state[property] = value; if (state[$HAS] && state[$HAS][property] && prev === undefined) state[$HAS][property].$(); } let nodes = getNodes(state, $NODE), node: DataNode | undefined; if ((node = getNode(nodes, property, prev))) node.$(() => value); if (Array.isArray(state) && state.length !== len) { for (let i = state.length; i < len; i++) (node = nodes[i]) && node.$(); (node = getNode(nodes, "length", len)) && node.$(state.length); } (node = nodes[$SELF]) && node.$(); } function mergeStoreNode(state: StoreNode, value: Partial) { const keys = Object.keys(value); for (let i = 0; i < keys.length; i += 1) { const key = keys[i]; setProperty(state, key, value[key]); } } function updateArray( current: StoreNode, next: Array | Record | ((prev: StoreNode) => Array | Record) ) { if (typeof next === "function") next = next(current); next = unwrap(next) as Array | Record; if (Array.isArray(next)) { if (current === next) return; let i = 0, len = next.length; for (; i < len; i++) { const value = next[i]; if (current[i] !== value) setProperty(current, i, value); } setProperty(current, "length", len); } else mergeStoreNode(current, next); } export function updatePath(current: StoreNode, path: any[], traversed: PropertyKey[] = []) { let part, prev = current; if (path.length > 1) { part = path.shift(); const partType = typeof part, isArray = Array.isArray(current); if (Array.isArray(part)) { // Ex. update('data', [2, 23], 'label', l => l + ' !!!'); for (let i = 0; i < part.length; i++) { updatePath(current, [part[i]].concat(path), traversed); } return; } else if (isArray && partType === "function") { // Ex. update('data', i => i.id === 42, 'label', l => l + ' !!!'); for (let i = 0; i < current.length; i++) { if (part(current[i], i)) updatePath(current, [i].concat(path), traversed); } return; } else if (isArray && partType === "object") { // Ex. update('data', { from: 3, to: 12, by: 2 }, 'label', l => l + ' !!!'); const { from = 0, to = current.length - 1, by = 1 } = part; for (let i = from; i <= to; i += by) { updatePath(current, [i].concat(path), traversed); } return; } else if (path.length > 1) { updatePath(current[part], path, [part].concat(traversed)); return; } prev = current[part]; traversed = [part].concat(traversed); } let value = path[0]; if (typeof value === "function") { value = value(prev, traversed); if (value === prev) return; } if (part === undefined && value == undefined) return; value = unwrap(value); if (part === undefined || (isWrappable(prev) && isWrappable(value) && !Array.isArray(value))) { mergeStoreNode(prev, value); } else setProperty(current, part, value); } /** @deprecated */ export type DeepReadonly = 0 extends 1 & T ? T : T extends NotWrappable ? T : { readonly [K in keyof T]: DeepReadonly; }; /** @deprecated */ export type DeepMutable = 0 extends 1 & T ? T : T extends NotWrappable ? T : { -readonly [K in keyof T]: DeepMutable; }; export type CustomPartial = T extends readonly unknown[] ? "0" extends keyof T ? { [K in Extract]?: T[K] } : { [x: number]: T[number] } : Partial; export type PickMutable = { [K in keyof T as (() => U extends { [V in K]: T[V] } ? 1 : 2) extends () => U extends { -readonly [V in K]: T[V]; } ? 1 : 2 ? K : never]: T[K]; }; export type StorePathRange = { from?: number; to?: number; by?: number }; export type ArrayFilterFn = (item: T, index: number) => boolean; export type StoreSetter = | T | CustomPartial | ((prevState: T, traversed: U) => T | CustomPartial); export type Part = KeyOf> = | K | ([K] extends [never] ? never : readonly K[]) | ([T] extends [readonly unknown[]] ? ArrayFilterFn | StorePathRange : never); // shortcut to avoid writing `Exclude` too many times type W = Exclude; // specially handle keyof to avoid errors with arrays and any type KeyOf = number extends keyof T // have to check this otherwise ts won't allow KeyOf to index T ? 0 extends 1 & T // if it's any just return keyof T ? keyof T : [T] extends [never] ? never // keyof never is PropertyKey, which number extends. this must go before : // checking [T] extends [readonly unknown[]] because never extends everything [T] extends [readonly unknown[]] ? number // it's an array or tuple; exclude the non-number properties : keyof T // it's something which contains an index signature for strings or numbers : keyof T; type MutableKeyOf = KeyOf & keyof PickMutable; // rest must specify at least one (additional) key, followed by a StoreSetter if the key is mutable. type Rest = KeyOf> = [T] extends [never] ? never : K extends MutableKeyOf ? [Part, ...RestSetterOrContinue] : K extends KeyOf ? [Part, ...RestContinue] : never; type RestContinue = 0 extends 1 & T ? [...Part[], StoreSetter] : Rest, U>; type RestSetterOrContinue = [StoreSetter] | RestContinue; export interface SetStoreFunction { < K1 extends KeyOf>, K2 extends KeyOf[K1]>>, K3 extends KeyOf[K1]>[K2]>>, K4 extends KeyOf[K1]>[K2]>[K3]>>, K5 extends KeyOf[K1]>[K2]>[K3]>[K4]>>, K6 extends KeyOf[K1]>[K2]>[K3]>[K4]>[K5]>>, K7 extends MutableKeyOf[K1]>[K2]>[K3]>[K4]>[K5]>[K6]>> >( k1: Part, K1>, k2: Part[K1]>, K2>, k3: Part[K1]>[K2]>, K3>, k4: Part[K1]>[K2]>[K3]>, K4>, k5: Part[K1]>[K2]>[K3]>[K4]>, K5>, k6: Part[K1]>[K2]>[K3]>[K4]>[K5]>, K6>, k7: Part[K1]>[K2]>[K3]>[K4]>[K5]>[K6]>, K7>, setter: StoreSetter< W[K1]>[K2]>[K3]>[K4]>[K5]>[K6]>[K7], [K7, K6, K5, K4, K3, K2, K1] > ): void; < K1 extends KeyOf>, K2 extends KeyOf[K1]>>, K3 extends KeyOf[K1]>[K2]>>, K4 extends KeyOf[K1]>[K2]>[K3]>>, K5 extends KeyOf[K1]>[K2]>[K3]>[K4]>>, K6 extends MutableKeyOf[K1]>[K2]>[K3]>[K4]>[K5]>> >( k1: Part, K1>, k2: Part[K1]>, K2>, k3: Part[K1]>[K2]>, K3>, k4: Part[K1]>[K2]>[K3]>, K4>, k5: Part[K1]>[K2]>[K3]>[K4]>, K5>, k6: Part[K1]>[K2]>[K3]>[K4]>[K5]>, K6>, setter: StoreSetter[K1]>[K2]>[K3]>[K4]>[K5]>[K6], [K6, K5, K4, K3, K2, K1]> ): void; < K1 extends KeyOf>, K2 extends KeyOf[K1]>>, K3 extends KeyOf[K1]>[K2]>>, K4 extends KeyOf[K1]>[K2]>[K3]>>, K5 extends MutableKeyOf[K1]>[K2]>[K3]>[K4]>> >( k1: Part, K1>, k2: Part[K1]>, K2>, k3: Part[K1]>[K2]>, K3>, k4: Part[K1]>[K2]>[K3]>, K4>, k5: Part[K1]>[K2]>[K3]>[K4]>, K5>, setter: StoreSetter[K1]>[K2]>[K3]>[K4]>[K5], [K5, K4, K3, K2, K1]> ): void; < K1 extends KeyOf>, K2 extends KeyOf[K1]>>, K3 extends KeyOf[K1]>[K2]>>, K4 extends MutableKeyOf[K1]>[K2]>[K3]>> >( k1: Part, K1>, k2: Part[K1]>, K2>, k3: Part[K1]>[K2]>, K3>, k4: Part[K1]>[K2]>[K3]>, K4>, setter: StoreSetter[K1]>[K2]>[K3]>[K4], [K4, K3, K2, K1]> ): void; < K1 extends KeyOf>, K2 extends KeyOf[K1]>>, K3 extends MutableKeyOf[K1]>[K2]>> >( k1: Part, K1>, k2: Part[K1]>, K2>, k3: Part[K1]>[K2]>, K3>, setter: StoreSetter[K1]>[K2]>[K3], [K3, K2, K1]> ): void; >, K2 extends MutableKeyOf[K1]>>>( k1: Part, K1>, k2: Part[K1]>, K2>, setter: StoreSetter[K1]>[K2], [K2, K1]> ): void; >>(k1: Part, K1>, setter: StoreSetter[K1], [K1]>): void; (setter: StoreSetter): void; // fallback < K1 extends KeyOf>, K2 extends KeyOf[K1]>>, K3 extends KeyOf[K1]>[K2]>>, K4 extends KeyOf[K1]>[K2]>[K3]>>, K5 extends KeyOf[K1]>[K2]>[K3]>[K4]>>, K6 extends KeyOf[K1]>[K2]>[K3]>[K4]>[K5]>>, K7 extends KeyOf[K1]>[K2]>[K3]>[K4]>[K5]>[K6]>> >( k1: Part, K1>, k2: Part[K1]>, K2>, k3: Part[K1]>[K2]>, K3>, k4: Part[K1]>[K2]>[K3]>, K4>, k5: Part[K1]>[K2]>[K3]>[K4]>, K5>, k6: Part[K1]>[K2]>[K3]>[K4]>[K5]>, K6>, k7: Part[K1]>[K2]>[K3]>[K4]>[K5]>[K6]>, K7>, ...rest: Rest[K1]>[K2]>[K3]>[K4]>[K5]>[K6]>[K7], [K7, K6, K5, K4, K3, K2, K1]> ): void; } /** * Creates a reactive store that can be read through a proxy object and written with a setter function * * @description https://docs.solidjs.com/reference/store-utilities/create-store */ export function createStore( ...[store, options]: {} extends T ? [store?: T | Store, options?: { name?: string }] : [store: T | Store, options?: { name?: string }] ): [get: Store, set: SetStoreFunction] { const unwrappedStore = unwrap((store || {}) as T); const isArray = Array.isArray(unwrappedStore); if (IS_DEV && typeof unwrappedStore !== "object" && typeof unwrappedStore !== "function") throw new Error( `Unexpected type ${typeof unwrappedStore} received when initializing 'createStore'. Expected an object.` ); const wrappedStore = wrap(unwrappedStore); if (IS_DEV) DEV!.registerGraph({ value: unwrappedStore, name: options && options.name }); function setStore(...args: any[]): void { batch(() => { isArray && args.length === 1 ? updateArray(unwrappedStore, args[0]) : updatePath(unwrappedStore, args); }); } return [wrappedStore, setStore]; } ================================================ FILE: packages/solid/store/test/modifiers.spec.ts ================================================ import { describe, expect, test } from "vitest"; import { createRoot, createSignal, createEffect } from "../../src/index.js"; import { createStore, createMutable, reconcile, produce, unwrap, modifyMutable } from "../src/index.js"; describe("setState with reconcile", () => { test("Reconcile a simple object", () => { const [state, setState] = createStore<{ data: number; missing?: string }>({ data: 2, missing: "soon" }); expect(state.data).toBe(2); expect(state.missing).toBe("soon"); setState(reconcile({ data: 5 })); expect(state.data).toBe(5); expect(state.missing).toBeUndefined(); }); test("Reconcile array with nulls", () => { const [state, setState] = createStore([null, "a"]); expect(state[0]).toBe(null); expect(state[1]).toBe("a"); setState(reconcile(["b", null])); expect(state[0]).toBe("b"); expect(state[1]).toBe(null); }); test("Reconcile a simple object on a nested path", () => { const [state, setState] = createStore<{ data: { user: { firstName: string; middleName: string; lastName?: string } }; }>({ data: { user: { firstName: "John", middleName: "", lastName: "Snow" } } }); expect(state.data.user.firstName).toBe("John"); expect(state.data.user.lastName).toBe("Snow"); setState("data", "user", reconcile({ firstName: "Jake", middleName: "R" })); expect(state.data.user.firstName).toBe("Jake"); expect(state.data.user.middleName).toBe("R"); expect(state.data.user.lastName).toBeUndefined(); }); test("Reconcile a simple object on a nested path with no prev state", () => { const [state, setState] = createStore<{ user?: { firstName: string; middleName: string } }>({}); expect(state.user).toBeUndefined(); setState("user", reconcile({ firstName: "Jake", middleName: "R" })); expect(state.user!.firstName).toBe("Jake"); expect(state.user!.middleName).toBe("R"); }); test("Reconcile reorder a keyed array", () => { const JOHN = { id: 1, firstName: "John", lastName: "Snow" }, NED = { id: 2, firstName: "Ned", lastName: "Stark" }, BRANDON = { id: 3, firstName: "Brandon", lastName: "Start" }, ARYA = { id: 4, firstName: "Arya", lastName: "Start" }; const [state, setState] = createStore({ users: [JOHN, NED, BRANDON] }); expect(Object.is(unwrap(state.users[0]), JOHN)).toBe(true); expect(Object.is(unwrap(state.users[1]), NED)).toBe(true); expect(Object.is(unwrap(state.users[2]), BRANDON)).toBe(true); setState("users", reconcile([NED, JOHN, BRANDON])); expect(Object.is(unwrap(state.users[0]), NED)).toBe(true); expect(Object.is(unwrap(state.users[1]), JOHN)).toBe(true); expect(Object.is(unwrap(state.users[2]), BRANDON)).toBe(true); setState("users", reconcile([NED, BRANDON, JOHN])); expect(Object.is(unwrap(state.users[0]), NED)).toBe(true); expect(Object.is(unwrap(state.users[1]), BRANDON)).toBe(true); expect(Object.is(unwrap(state.users[2]), JOHN)).toBe(true); setState("users", reconcile([NED, BRANDON, JOHN, ARYA])); expect(Object.is(unwrap(state.users[0]), NED)).toBe(true); expect(Object.is(unwrap(state.users[1]), BRANDON)).toBe(true); expect(Object.is(unwrap(state.users[2]), JOHN)).toBe(true); expect(Object.is(unwrap(state.users[3]), ARYA)).toBe(true); setState("users", reconcile([BRANDON, JOHN, ARYA])); expect(Object.is(unwrap(state.users[0]), BRANDON)).toBe(true); expect(Object.is(unwrap(state.users[1]), JOHN)).toBe(true); expect(Object.is(unwrap(state.users[2]), ARYA)).toBe(true); }); test("Reconcile overwrite in non-keyed merge mode", () => { const JOHN = { id: 1, firstName: "John", lastName: "Snow" }, NED = { id: 2, firstName: "Ned", lastName: "Stark" }, BRANDON = { id: 3, firstName: "Brandon", lastName: "Start" }; const [state, setState] = createStore({ users: [{ ...JOHN }, { ...NED }, { ...BRANDON }] }); expect(state.users[0].id).toBe(1); expect(state.users[0].firstName).toBe("John"); expect(state.users[1].id).toBe(2); expect(state.users[1].firstName).toBe("Ned"); expect(state.users[2].id).toBe(3); expect(state.users[2].firstName).toBe("Brandon"); setState( "users", reconcile([{ ...NED }, { ...JOHN }, { ...BRANDON }], { merge: true, key: null }) ); expect(state.users[0].id).toBe(2); expect(state.users[0].firstName).toBe("Ned"); expect(state.users[1].id).toBe(1); expect(state.users[1].firstName).toBe("John"); expect(state.users[2].id).toBe(3); expect(state.users[2].firstName).toBe("Brandon"); }); test("Reconcile top level key mismatch", () => { const JOHN = { id: 1, firstName: "John", lastName: "Snow" }, NED = { id: 2, firstName: "Ned", lastName: "Stark" }; const [user, setUser] = createStore(JOHN); expect(user.id).toBe(1); expect(user.firstName).toBe("John"); setUser(reconcile(NED)); expect(user.id).toBe(2); expect(user.firstName).toBe("Ned"); }); test("Reconcile nested top level key mismatch", () => { const JOHN = { id: 1, firstName: "John", lastName: "Snow" }, NED = { id: 2, firstName: "Ned", lastName: "Stark" }; const [user, setUser] = createStore({ user: JOHN }); expect(user.user.id).toBe(1); expect(user.user.firstName).toBe("John"); setUser("user", reconcile(NED)); expect(user.user.id).toBe(2); expect(user.user.firstName).toBe("Ned"); }); test("Reconcile top level key missing", () => { const [store, setStore] = createStore<{ id?: number; value?: string }>({ id: 0, value: "value" }); setStore(reconcile({})); expect(store.id).toBe(undefined); expect(store.value).toBe(undefined); }); test("Reconcile overwrite an object with an array", () => { const [store, setStore] = createStore<{ value: {} | [] }>({ value: { a: { b: 1 } } }); setStore(reconcile({ value: { c: [1, 2, 3] } })); expect(store.value).toEqual({ c: [1, 2, 3] }); }); test("Reconcile overwrite an array with an object", () => { const [store, setStore] = createStore<{ value: {} | [] }>({ value: [1, 2, 3] }); setStore(reconcile({ value: { name: "John" } })); expect(Array.isArray(store.value)).toBeFalsy(); expect(store.value).toEqual({ name: "John" }); setStore(reconcile({ value: [1, 2, 3] })); expect(store.value).toEqual([1, 2, 3]); setStore(reconcile({ value: { q: "aa" } })); expect(store.value).toEqual({ q: "aa" }); }); }); describe("setState with produce", () => { interface DataState { data: { ending?: number; starting: number }; } test("Top Level Mutation", () => { const [state, setState] = createStore({ data: { starting: 1, ending: 1 } }); setState( produce(s => { s.data.ending = s.data.starting + 1; }) ); expect(state.data.starting).toBe(1); expect(state.data.ending).toBe(2); }); test("referential equality", () => { const [state, setState] = createStore({ data: { starting: 1, ending: 1 } }); setState( produce(s => { if (s.data === s.data) s.data.ending!++; }) ); expect(state.data.starting).toBe(1); expect(state.data.ending).toBe(2); }); test("Top Level Mutation in computation", () => { createRoot(() => { const [s, set] = createSignal(1); const [state, setState] = createStore<{ data: number[] }>({ data: [] }); createEffect(() => { setState( produce(state => { state.data.push(s()); }) ); }); createEffect(() => state.data.length); }); expect(true).toBe(true); }); test("Nested Level Mutation", () => { const [state, setState] = createStore({ data: { starting: 1, ending: 1 } }); setState( "data", produce(s => { s.ending = s.starting + 1; }) ); expect(state.data.starting).toBe(1); expect(state.data.ending).toBe(2); }); test("Top Level Deletion", () => { const [state, setState] = createStore({ data: { starting: 1, ending: 1 } }); setState( produce(s => { delete s.data.ending; }) ); expect(state.data.starting).toBe(1); expect(state.data.ending).not.toBeDefined(); }); test("Top Level Object Mutation", () => { const [state, setState] = createStore({ data: { starting: 1, ending: 1 } }), next = { starting: 3, ending: 6 }; setState( produce(s => { s.data = next; }) ); expect(unwrap(state.data)).toBe(next); expect(state.data.starting).toBe(3); expect(state.data.ending).toBe(6); }); test("Test Array Mutation", () => { interface TodoState { todos: { id: number; title: string; done: boolean }[]; } const [state, setState] = createStore({ todos: [ { id: 1, title: "Go To Work", done: true }, { id: 2, title: "Eat Lunch", done: false } ] }); setState( produce(s => { s.todos[1].done = true; s.todos.push({ id: 3, title: "Go Home", done: false }); }) ); expect(Array.isArray(state.todos)).toBe(true); expect(state.todos[1].done).toBe(true); expect(state.todos[2].title).toBe("Go Home"); }); test("Test Top-Level Array Mutation", () => { type TodoState = Array<{ id: number; title: string; done: boolean; }>; const [state, setState] = createStore([ { id: 1, title: "Go To Work", done: true }, { id: 2, title: "Eat Lunch", done: false } ]); setState( produce(s => { s[1].done = true; s.push({ id: 3, title: "Go Home", done: false }); }) ); expect(Array.isArray(state)).toBe(true); expect(state[1].done).toBe(true); expect(state[2].title).toBe("Go Home"); }); }); describe("modifyMutable with reconcile", () => { test("Reconcile a simple object", () => { const state = createMutable<{ data: number; missing?: string }>({ data: 2, missing: "soon" }); expect(state.data).toBe(2); expect(state.missing).toBe("soon"); modifyMutable(state, reconcile({ data: 5 })); expect(state.data).toBe(5); expect(state.missing).toBeUndefined(); }); test("Reconcile a simple object on a nested path", () => { const state = createMutable<{ data: { user: { firstName: string; middleName: string; lastName?: string } }; }>({ data: { user: { firstName: "John", middleName: "", lastName: "Snow" } } }); expect(state.data.user.firstName).toBe("John"); expect(state.data.user.lastName).toBe("Snow"); modifyMutable(state.data.user, reconcile({ firstName: "Jake", middleName: "R" })); expect(state.data.user.firstName).toBe("Jake"); expect(state.data.user.middleName).toBe("R"); expect(state.data.user.lastName).toBeUndefined(); }); test("Reconcile reorder a keyed array", () => { const JOHN = { id: 1, firstName: "John", lastName: "Snow" }, NED = { id: 2, firstName: "Ned", lastName: "Stark" }, BRANDON = { id: 3, firstName: "Brandon", lastName: "Start" }, ARYA = { id: 4, firstName: "Arya", lastName: "Start" }; const state = createMutable({ users: [JOHN, NED, BRANDON] }); expect(Object.is(unwrap(state.users[0]), JOHN)).toBe(true); expect(Object.is(unwrap(state.users[1]), NED)).toBe(true); expect(Object.is(unwrap(state.users[2]), BRANDON)).toBe(true); modifyMutable(state.users, reconcile([NED, JOHN, BRANDON])); expect(Object.is(unwrap(state.users[0]), NED)).toBe(true); expect(Object.is(unwrap(state.users[1]), JOHN)).toBe(true); expect(Object.is(unwrap(state.users[2]), BRANDON)).toBe(true); modifyMutable(state.users, reconcile([NED, BRANDON, JOHN])); expect(Object.is(unwrap(state.users[0]), NED)).toBe(true); expect(Object.is(unwrap(state.users[1]), BRANDON)).toBe(true); expect(Object.is(unwrap(state.users[2]), JOHN)).toBe(true); modifyMutable(state.users, reconcile([NED, BRANDON, JOHN, ARYA])); expect(Object.is(unwrap(state.users[0]), NED)).toBe(true); expect(Object.is(unwrap(state.users[1]), BRANDON)).toBe(true); expect(Object.is(unwrap(state.users[2]), JOHN)).toBe(true); expect(Object.is(unwrap(state.users[3]), ARYA)).toBe(true); modifyMutable(state.users, reconcile([BRANDON, JOHN, ARYA])); expect(Object.is(unwrap(state.users[0]), BRANDON)).toBe(true); expect(Object.is(unwrap(state.users[1]), JOHN)).toBe(true); expect(Object.is(unwrap(state.users[2]), ARYA)).toBe(true); }); test("Reconcile overwrite in non-keyed merge mode", () => { const JOHN = { id: 1, firstName: "John", lastName: "Snow" }, NED = { id: 2, firstName: "Ned", lastName: "Stark" }, BRANDON = { id: 3, firstName: "Brandon", lastName: "Start" }; const state = createMutable({ users: [{ ...JOHN }, { ...NED }, { ...BRANDON }] }); expect(state.users[0].id).toBe(1); expect(state.users[0].firstName).toBe("John"); expect(state.users[1].id).toBe(2); expect(state.users[1].firstName).toBe("Ned"); expect(state.users[2].id).toBe(3); expect(state.users[2].firstName).toBe("Brandon"); modifyMutable( state.users, reconcile([{ ...NED }, { ...JOHN }, { ...BRANDON }], { merge: true, key: null }) ); expect(state.users[0].id).toBe(2); expect(state.users[0].firstName).toBe("Ned"); expect(state.users[1].id).toBe(1); expect(state.users[1].firstName).toBe("John"); expect(state.users[2].id).toBe(3); expect(state.users[2].firstName).toBe("Brandon"); }); }); // type tests // reconcile () => { const [state, setState] = createStore<{ data: number; missing: string; partial?: { v: number } }>( { data: 2, missing: "soon" } ); // @ts-expect-error should not be able to reconcile partial type setState(reconcile({ data: 5 })); // should be able to reconcile unwrappable types setState("partial", reconcile(undefined)); }; // produce () => { const [store, setStore] = createStore({ todos: [{ id: 1, text: "" }] }); // should be able to strip readonly from arrays setStore( "todos", produce(todos => { todos.push({ id: todos[todos.length - 1].id + 1, text: "" }); }) ); }; // works regardless of depth () => { const [store, setStore] = createStore({ a: { b: { c: { d: { e: { f: { g: { h: { i: 1 } } } } } } } } }); setStore(produce(v => v)); setStore(reconcile({ ...store })); setStore( "a", "b", "c", "d", "e", "f", "g", "h", produce(v => ({ i: v.i })) ); setStore("a", "b", "c", "d", "e", "f", "g", "h", reconcile({ i: 2 })); }; ================================================ FILE: packages/solid/store/test/mutable.spec.ts ================================================ import { describe, expect, test } from "vitest"; import { createRoot, createSignal, createMemo, batch, createEffect } from "../../src/index.js"; import { Accessor, Setter } from "../../types/index.js"; import { createMutable, unwrap, $RAW } from "../src/index.js"; test("Object.create(null) is allowed", () => { const user = createMutable(Object.assign(Object.create(null), { name: "John" })); expect(user.name).toBe("John"); }); describe("State Mutability", () => { test("Setting a property", () => { const user = createMutable({ name: "John" }); expect(user.name).toBe("John"); user.name = "Jake"; expect(user.name).toBe("Jake"); }); test("Deleting a property", () => { const user = createMutable({ name: "John" }); expect(user.name).toBe("John"); // @ts-ignore delete user.name; expect(user.name).toBeUndefined(); }); }); describe("State Getter/Setters", () => { test("Testing an update from state", () => { let user: any; createRoot(() => { user = createMutable({ name: "John", get greeting(): string { return `Hi, ${this.name}`; } }); }); expect(user.greeting).toBe("Hi, John"); user.name = "Jake"; expect(user.greeting).toBe("Hi, Jake"); }); test("setting a value with setters", () => { let user: any; createRoot(() => { user = createMutable({ firstName: "John", lastName: "Smith", get fullName(): string { return `${this.firstName} ${this.lastName}`; }, set fullName(value) { const parts = value.split(" "); this.firstName = parts[0]; this.lastName = parts[1]; } }); }); expect(user.fullName).toBe("John Smith"); user.fullName = "Jake Murray"; expect(user.firstName).toBe("Jake"); expect(user.lastName).toBe("Murray"); }); }); describe("Simple update modes", () => { test("Simple Key Value", () => { const state = createMutable({ key: "" }); state.key = "value"; expect(state.key).toBe("value"); }); test("Nested update", () => { const state = createMutable({ data: { starting: 1, ending: 1 } }); state.data.ending = 2; expect(state.data.starting).toBe(1); expect(state.data.ending).toBe(2); }); test("Test Array", () => { const todos = createMutable([ { id: 1, title: "Go To Work", done: true }, { id: 2, title: "Eat Lunch", done: false } ]); todos[1].done = true; todos.push({ id: 3, title: "Go Home", done: false }); expect(Array.isArray(todos)).toBe(true); expect(todos[1].done).toBe(true); expect(todos[2].title).toBe("Go Home"); }); }); describe("Unwrapping Edge Cases", () => { test("Unwrap nested frozen state object", () => { const state = createMutable({ data: Object.freeze({ user: { firstName: "John", lastName: "Snow" } }) }), s = unwrap({ ...state }); expect(s.data.user.firstName).toBe("John"); expect(s.data.user.lastName).toBe("Snow"); // @ts-ignore check if proxy still expect(s.data.user[$RAW]).toBeUndefined(); }); test("Unwrap nested frozen array", () => { const state = createMutable({ data: [{ user: { firstName: "John", lastName: "Snow" } }] }), s = unwrap({ data: state.data.slice(0) }); expect(s.data[0].user.firstName).toBe("John"); expect(s.data[0].user.lastName).toBe("Snow"); // @ts-ignore check if proxy still expect(s.data[0].user[$RAW]).toBeUndefined(); }); test("Unwrap nested frozen state array", () => { const state = createMutable({ data: Object.freeze([{ user: { firstName: "John", lastName: "Snow" } }]) }), s = unwrap({ ...state }); expect(s.data[0].user.firstName).toBe("John"); expect(s.data[0].user.lastName).toBe("Snow"); // @ts-ignore check if proxy still expect(s.data[0].user[$RAW]).toBeUndefined(); }); }); describe("Tracking State changes", () => { test("Track a state change", () => { let state: { data: number }; createRoot(() => { state = createMutable({ data: 2 }); let executionCount = 0; expect.assertions(2); createEffect(() => { if (executionCount === 0) expect(state.data).toBe(2); else if (executionCount === 1) { expect(state.data).toBe(5); } else { // should never get here expect(executionCount).toBe(-1); } executionCount++; }); }); state!.data = 5; // same value again should not retrigger state!.data = 5; }); test("Deleting an undefined property", () => { let state: { firstName: string; lastName: string | undefined }; let executionCount = 0; createRoot(() => { state = createMutable({ firstName: "John", lastName: undefined }); createEffect(() => { state.lastName; executionCount++; }); //this should retrigger the execution despite it being undefined }); delete state!.lastName; expect(executionCount).toBe(2); }); test("Track a nested state change", () => { let executionCount = 0; let state: { user: { firstName: string; lastName: string } }; createRoot(() => { state = createMutable({ user: { firstName: "John", lastName: "Smith" } }); expect.assertions(2); createEffect(() => { if (executionCount === 0) { expect(state.user.firstName).toBe("John"); } else if (executionCount === 1) { expect(state.user.firstName).toBe("Jake"); } else { // should never get here expect(executionCount).toBe(-1); } executionCount++; }); }); state!.user.firstName = "Jake"; }); }); describe("Handling functions in state", () => { test("Array Native Methods: Array.Filter", () => { createRoot(() => { const list = createMutable([0, 1, 2]), getFiltered = createMemo(() => list.filter(i => i % 2)); expect(getFiltered()).toStrictEqual([1]); }); }); test("Track function change", () => { createRoot(() => { const state = createMutable<{ fn: () => number }>({ fn: () => 1 }), getValue = createMemo(() => state.fn()); state.fn = () => 2; expect(getValue()).toBe(2); }); }); }); describe("Setting state from Effects", () => { test("Setting state from signal", () => { let state: { data: string }; let getData: Accessor, setData: Setter; createRoot(() => { (([getData, setData] = createSignal("init")), (state = createMutable({ data: "" }))); // don't do this often createEffect(() => (state.data = getData())); }); setData!("signal"); expect(state!.data).toBe("signal"); }); test("Select Promise", () => new Promise(done => { createRoot(async () => { const p = new Promise(resolve => { setTimeout(resolve, 20, "promised"); }), state = createMutable({ data: "" }); p.then(v => (state.data = v)); await p; expect(state.data).toBe("promised"); done(undefined); }); })); }); describe("State wrapping", () => { test("Setting plain object", () => { const data = { withProperty: "y" }, state = createMutable({ data }); // not wrapped expect(state.data).not.toBe(data); }); test("Setting plain array", () => { const data = [1, 2, 3], state = createMutable({ data }); // not wrapped expect(state.data).not.toBe(data); }); test("Setting non-wrappable", () => { const date = new Date(), state = createMutable({ time: date }); // not wrapped expect(state.time).toBe(date); }); test("Respects batch in array mutate 2", () => { const state = createMutable([1, 2, 3]); batch(() => { expect(state.length).toBe(3); const move = state.splice(1, 1); expect(state.length).toBe(2); state.splice(0, 0, ...move); expect(state.length).toBe(3); expect(state).toEqual([2, 1, 3]); }); expect(state.length).toBe(3); expect(state).toEqual([2, 1, 3]); }); }); describe("In Operator", () => { test("wrapped nested class", () => { let access = 0; const store = createMutable<{ a?: number; b?: number; c?: number }>({ a: 1, get b() { access++; return 2; } }); expect("a" in store).toBe(true); expect("b" in store).toBe(true); expect("c" in store).toBe(false); expect(access).toBe(0); const [a, b, c] = createRoot(() => { return [ createMemo(() => "a" in store), createMemo(() => "b" in store), createMemo(() => "c" in store) ]; }); expect(a()).toBe(true); expect(b()).toBe(true); expect(c()).toBe(false); expect(access).toBe(0); store.c = 3; expect(a()).toBe(true); expect(b()).toBe(true); expect(c()).toBe(true); expect(access).toBe(0); delete store.a; expect(a()).toBe(false); expect(b()).toBe(true); expect(c()).toBe(true); expect(access).toBe(0); expect("a" in store).toBe(false); expect("b" in store).toBe(true); expect("c" in store).toBe(true); expect(access).toBe(0); }); }); ================================================ FILE: packages/solid/store/test/mutableWithClass.spec.tsx ================================================ import { describe, expect, test } from "vitest"; import { createEffect, createRoot } from "../../src/index.js"; import { createMutable } from "../src/index.js"; describe("Class Operator test", () => { test("read and set class", () => { class D { f = 1; get e() { return this.f * 4; } } class A { a = 1; get b() { return this.a * 4; } child = new D(); } let m: any; let count = 0, childCount = 0; const increment = () => { m.a++; m.child.f++; }; createRoot(() => { m = createMutable(new A()); createEffect(() => { m.b; count++; }); createEffect(() => { m.child.f; childCount++; }); increment(); }); expect(m.b).toBe(8); expect(m.child.e).toBe(8); expect(count).toBe(1); expect(childCount).toBe(1); increment(); expect(m.b).toBe(12); expect(m.child.e).toBe(12); expect(count).toBe(2); expect(childCount).toBe(1); increment(); expect(m.b).toBe(16); expect(m.child.e).toBe(16); expect(count).toBe(3); expect(childCount).toBe(1); }); test("inherited properties", () => { class A { val = 0; get getVal() { return this.val; } } class B extends A {} const instance = createMutable(new B()); let lastVal: number | undefined; createRoot(() => { createEffect(() => { lastVal = instance.getVal; }); }); expect(lastVal).toBe(0); instance.val = 1; expect(lastVal).toBe(1); }); }); ================================================ FILE: packages/solid/store/test/store.spec.ts ================================================ import { describe, expect, test } from "vitest"; import { createRoot, createSignal, createEffect, createMemo, batch, on, untrack, mapArray } from "../../src/index.js"; import { createStore, unwrap, $RAW, NotWrappable } from "../src/index.js"; describe("State immutability", () => { test("Setting a property", () => { const [state] = createStore({ name: "John" }); expect(state.name).toBe("John"); state.name = "Jake"; expect(state.name).toBe("John"); }); test("Deleting a property", () => { const [state] = createStore({ name: "John" }); expect(state.name).toBe("John"); // @ts-expect-error can't delete required property delete state.name; expect(state.name).toBe("John"); }); test("Immutable state is not mutable even inside setter", () => { const [state, setState] = createStore({ name: "John" }); expect(state.name).toBe("John"); setState(() => { state.name = "Jake"; }); expect(state.name).toBe("John"); }); }); describe("State Getters", () => { test("Testing an update from state", () => { let state: any, setState: Function; createRoot(() => { [state, setState] = createStore({ name: "John", get greeting(): string { return `Hi, ${this.name}`; } }); }); expect(state!.greeting).toBe("Hi, John"); setState!({ name: "Jake" }); expect(state!.greeting).toBe("Hi, Jake"); }); test("Testing an update from state", () => { let state: any, setState: Function; createRoot(() => { let greeting: () => string; [state, setState] = createStore({ name: "John", get greeting(): string { return greeting(); } }); greeting = createMemo(() => `Hi, ${state.name}`); }); expect(state!.greeting).toBe("Hi, John"); setState!({ name: "Jake" }); expect(state!.greeting).toBe("Hi, Jake"); }); }); describe("Simple setState modes", () => { test("Simple Key Value", () => { const [state, setState] = createStore({ key: "" }); setState("key", "value"); expect(state.key).toBe("value"); }); test("Top level merge", () => { const [state, setState] = createStore({ starting: 1, ending: 1 }); setState({ ending: 2 }); expect(state.starting).toBe(1); expect(state.ending).toBe(2); }); test("Top level merge no arguments", () => { const [state, setState] = createStore({ starting: 1 }); setState({}); expect(state.starting).toBe(1); }); test("Top level state function merge", () => { const [state, setState] = createStore({ starting: 1, ending: 1 }); setState((s, t) => { expect(t).toStrictEqual([]); return { ending: s.starting + 1 }; }); expect(state.starting).toBe(1); expect(state.ending).toBe(2); }); test("Nested merge", () => { const [state, setState] = createStore({ data: { starting: 1, ending: 1 } }); setState("data", { ending: 2 }); expect(state.data.starting).toBe(1); expect(state.data.ending).toBe(2); }); test("Nested state function merge", () => { const [state, setState] = createStore({ data: { starting: 1, ending: 1 } }); setState("data", (d, t) => { expect(t).toStrictEqual(["data"]); return { ending: d.starting + 1 }; }); expect(state.data.starting).toBe(1); expect(state.data.ending).toBe(2); }); test("Test Array", () => { const [todos, setTodos] = createStore([ { id: 1, title: "Go To Work", done: true }, { id: 2, title: "Eat Lunch", done: false } ]); setTodos(1, { done: true }); setTodos([...todos, { id: 3, title: "Go Home", done: false }]); setTodos(t => [...t.slice(1)]); expect(Array.isArray(todos)).toBe(true); expect(todos[0].done).toBe(true); expect(todos[1].title).toBe("Go Home"); }); test("Test Array Nested", () => { const [state, setState] = createStore({ todos: [ { id: 1, title: "Go To Work", done: true }, { id: 2, title: "Eat Lunch", done: false } ] }); setState("todos", 1, { done: true }); setState("todos", [...state.todos, { id: 3, title: "Go Home", done: false }]); expect(Array.isArray(state.todos)).toBe(true); expect(state.todos[1].done).toBe(true); expect(state.todos[2].title).toBe("Go Home"); }); }); describe("Array setState modes", () => { test("Update Specific", () => { const [state, setState] = createStore([1, 2, 3, 4, 5]); setState([1, 3], (r, t) => { expect(typeof t[0]).toBe("number"); return r * 2; }); expect(state[0]).toBe(1); expect(state[1]).toBe(4); expect(state[2]).toBe(3); expect(state[3]).toBe(8); expect(state[4]).toBe(5); expect(Object.keys(state)).toStrictEqual(["0", "1", "2", "3", "4"]); }); test("Update Specific Object", () => { const [state, setState] = createStore([1, 2, 3, 4, 5]); setState({ 1: 4, 3: 8 }); expect(state[0]).toBe(1); expect(state[1]).toBe(4); expect(state[2]).toBe(3); expect(state[3]).toBe(8); expect(state[4]).toBe(5); expect(Object.keys(state)).toStrictEqual(["0", "1", "2", "3", "4"]); }); test("Update filterFn", () => { const [state, setState] = createStore([1, 2, 3, 4, 5]); setState( (r, i) => Boolean(i % 2), (r, t) => { expect(typeof t[0]).toBe("number"); return r * 2; } ); expect(state[0]).toBe(1); expect(state[1]).toBe(4); expect(state[2]).toBe(3); expect(state[3]).toBe(8); expect(state[4]).toBe(5); }); test("Update traversal range", () => { const [state, setState] = createStore([1, 2, 3, 4, 5]); setState({ from: 1, to: 4, by: 2 }, (r, t) => { expect(typeof t[0]).toBe("number"); return r * 2; }); expect(state[0]).toBe(1); expect(state[1]).toBe(4); expect(state[2]).toBe(3); expect(state[3]).toBe(8); expect(state[4]).toBe(5); }); test("Update traversal range defaults", () => { const [state, setState] = createStore([1, 2, 3, 4, 5]); setState({}, (r, t) => { expect(typeof t[0]).toBe("number"); return r * 2; }); expect(state[0]).toBe(2); expect(state[1]).toBe(4); expect(state[2]).toBe(6); expect(state[3]).toBe(8); expect(state[4]).toBe(10); }); }); describe("Unwrapping Edge Cases", () => { test("Unwrap nested frozen state object", () => { const [state] = createStore({ data: Object.freeze({ user: { firstName: "John", lastName: "Snow" } }) }), s = unwrap({ ...state }); expect(s.data.user.firstName).toBe("John"); expect(s.data.user.lastName).toBe("Snow"); // @ts-ignore check if proxy still expect(s.data.user[$RAW]).toBeUndefined(); }); test("Unwrap nested frozen array", () => { const [state] = createStore({ data: [{ user: { firstName: "John", lastName: "Snow" } }] }), s = unwrap({ data: state.data.slice(0) }); expect(s.data[0].user.firstName).toBe("John"); expect(s.data[0].user.lastName).toBe("Snow"); // @ts-ignore check if proxy still expect(s.data[0].user[$RAW]).toBeUndefined(); }); test("Unwrap nested frozen state array", () => { const [state] = createStore({ data: Object.freeze([{ user: { firstName: "John", lastName: "Snow" } }]) }), s = unwrap({ ...state }); expect(s.data[0].user.firstName).toBe("John"); expect(s.data[0].user.lastName).toBe("Snow"); // @ts-ignore check if proxy still expect(s.data[0].user[$RAW]).toBeUndefined(); }); }); describe("Tracking State changes", () => { test("Track a state change", () => { const [state, setState] = createStore({ data: 2 }); createRoot(() => { let executionCount = 0; expect.assertions(2); createEffect(() => { if (executionCount === 0) expect(state.data).toBe(2); else if (executionCount === 1) { expect(state.data).toBe(5); } else { // should never get here expect(executionCount).toBe(-1); } executionCount++; }); }); setState({ data: 5 }); // same value again should not retrigger setState({ data: 5 }); }); test("Track a nested state change", () => { const [state, setState] = createStore({ user: { firstName: "John", lastName: "Smith" } }); createRoot(() => { let executionCount = 0; expect.assertions(2); createEffect(() => { if (executionCount === 0) { expect(state.user.firstName).toBe("John"); } else if (executionCount === 1) { expect(state.user.firstName).toBe("Jake"); } else { // should never get here expect(executionCount).toBe(-1); } executionCount++; }); }); setState("user", "firstName", "Jake"); }); test("Track array item on removal", () => { const [state, setState] = createStore([1]); createRoot(() => { let executionCount = 0; expect.assertions(2); createEffect(() => { if (executionCount === 0) { expect(state[0]).toBe(1); } else if (executionCount === 1) { expect(state[0]).toBe(undefined); } else { // should never get here expect(executionCount).toBe(-1); } executionCount++; }); }); setState([]); }); test("Tracking Top-Level Array iteration", () => { const [state, setState] = createStore(["hi"]); let executionCount = 0; let executionCount2 = 0; let executionCount3 = 0; createRoot(() => { createEffect(() => { for (let i = 0; i < state.length; i++) state[i]; untrack(() => { if (executionCount === 0) expect(state.length).toBe(1); else if (executionCount === 1) { expect(state.length).toBe(2); expect(state[1]).toBe("item"); } else if (executionCount === 2) { expect(state.length).toBe(2); expect(state[1]).toBe("new"); } else if (executionCount === 3) { expect(state.length).toBe(1); } else { // should never get here expect(executionCount).toBe(-1); } }); executionCount++; }); createEffect(() => { for (const item of state); untrack(() => { if (executionCount2 === 0) expect(state.length).toBe(1); else if (executionCount2 === 1) { expect(state.length).toBe(2); expect(state[1]).toBe("item"); } else if (executionCount2 === 2) { expect(state.length).toBe(2); expect(state[1]).toBe("new"); } else if (executionCount2 === 3) { expect(state.length).toBe(1); } else { // should never get here expect(executionCount2).toBe(-1); } }); executionCount2++; }); const mapped = mapArray( () => state, item => item ); createEffect(() => { mapped(); untrack(() => { if (executionCount3 === 0) expect(state.length).toBe(1); else if (executionCount3 === 1) { expect(state.length).toBe(2); expect(state[1]).toBe("item"); } else if (executionCount3 === 2) { expect(state.length).toBe(2); expect(state[1]).toBe("new"); } else if (executionCount3 === 3) { expect(state.length).toBe(1); } else { // should never get here expect(executionCount3).toBe(-1); } }); executionCount3++; }); }); // add setState(1, "item"); // update setState(1, "new"); // delete setState(s => [s[0]]); expect.assertions(18); }); test("Tracking iteration Object key addition/removal", () => { const [state, setState] = createStore<{ obj: { item?: number } }>({ obj: {} }); let executionCount = 0; let executionCount2 = 0; createRoot(() => { createEffect(() => { const keys = Object.keys(state.obj); if (executionCount === 0) expect(keys.length).toBe(0); else if (executionCount === 1) { expect(keys.length).toBe(1); expect(keys[0]).toBe("item"); } else if (executionCount === 2) { expect(keys.length).toBe(0); } else { // should never get here expect(executionCount).toBe(-1); } executionCount++; }); createEffect(() => { for (const key in state.obj) { key; } const u = unwrap(state.obj); if (executionCount2 === 0) expect(u.item).toBeUndefined(); else if (executionCount2 === 1) { expect(u.item).toBe(5); } else if (executionCount2 === 2) { expect(u.item).toBeUndefined(); } else { // should never get here expect(executionCount2).toBe(-1); } executionCount2++; }); }); // add setState("obj", "item", 5); // update // setState("obj", "item", 10); // delete setState("obj", "item", undefined); expect.assertions(7); }); test("Doesn't trigger object on addition/removal", () => { const [state, setState] = createStore<{ obj: { item?: number } }>({ obj: {} }); let executionCount = 0; createRoot(() => { createEffect( on( () => state.obj, v => { if (executionCount === 0) expect(v.item).toBeUndefined(); else if (executionCount === 1) { expect(v.item).toBe(5); } else { // should never get here expect(executionCount).toBe(-1); } executionCount++; } ) ); }); // add setState("obj", "item", 5); // delete setState("obj", "item", undefined); expect.assertions(1); }); test("Tracking Top level iteration Object key addition/removal", () => { const [state, setState] = createStore<{ item?: number }>({}); let executionCount = 0; let executionCount2 = 0; createRoot(() => { createEffect(() => { const keys = Object.keys(state); if (executionCount === 0) expect(keys.length).toBe(0); else if (executionCount === 1) { expect(keys.length).toBe(1); expect(keys[0]).toBe("item"); } else if (executionCount === 2) { expect(keys.length).toBe(0); } else { // should never get here expect(executionCount).toBe(-1); } executionCount++; }); createEffect(() => { for (const key in state) { key; } const u = unwrap(state); if (executionCount2 === 0) expect(u.item).toBeUndefined(); else if (executionCount2 === 1) { expect(u.item).toBe(5); } else if (executionCount2 === 2) { expect(u.item).toBeUndefined(); } else { // should never get here expect(executionCount2).toBe(-1); } executionCount2++; }); }); // add setState("item", 5); // delete setState("item", undefined); expect.assertions(7); }); test("Not Tracking Top level key addition/removal", () => { const [state, setState] = createStore<{ item?: number; item2?: number }>({}); let executionCount = 0; createRoot(() => { createEffect(() => { if (executionCount === 0) expect(state.item2).toBeUndefined(); else { // should never get here expect(executionCount).toBe(-1); } executionCount++; }); }); // add setState("item", 5); // delete setState("item", undefined); expect.assertions(1); }); }); describe("Handling functions in state", () => { test("Array Native Methods: Array.Filter", () => { createRoot(() => { const [state] = createStore({ list: [0, 1, 2] }), getFiltered = createMemo(() => state.list.filter(i => i % 2)); expect(getFiltered()).toStrictEqual([1]); }); }); test("Track function change", () => { createRoot(() => { const [state, setState] = createStore<{ fn: () => number }>({ fn: () => 1 }), getValue = createMemo(() => state.fn()); setState({ fn: () => 2 }); expect(getValue()).toBe(2); }); }); }); describe("Setting state from Effects", () => { test("Setting state from signal", () => { const [getData, setData] = createSignal("init"), [state, setState] = createStore({ data: "" }); createRoot(() => { createEffect(() => setState("data", getData())); }); setData("signal"); expect(state.data).toBe("signal"); }); test("Select Promise", () => new Promise(done => { createRoot(async () => { const p = new Promise(resolve => { setTimeout(resolve, 20, "promised"); }); const [state, setState] = createStore({ data: "" }); p.then(v => setState("data", v)); await p; expect(state.data).toBe("promised"); done(undefined); }); })); }); describe("Batching", () => { test("Respects batch", () => { let data = 1; const [state, setState] = createStore({ data: 1 }); const memo = createRoot(() => createMemo(() => (data = state.data))); batch(() => { expect(state.data).toBe(1); expect(memo()).toBe(1); expect(data).toBe(1); setState("data", 2); expect(state.data).toBe(2); expect(data).toBe(1); expect(memo()).toBe(2); expect(data).toBe(2); }); expect(state.data).toBe(2); expect(memo!()).toBe(2); expect(data).toBe(2); }); test("Respects batch in array", () => { let data = 1; const [state, setState] = createStore([1]); const memo = createRoot(() => createMemo(() => (data = state[0]))); batch(() => { expect(state[0]).toBe(1); expect(memo()).toBe(1); expect(data).toBe(1); setState(0, 2); expect(state[0]).toBe(2); expect(data).toBe(1); expect(memo()).toBe(2); expect(data).toBe(2); }); expect(state[0]).toBe(2); expect(memo()).toBe(2); expect(data).toBe(2); }); test("Respects batch in array mutate", () => { let data = 1; const [state, setState] = createStore([1]); const memo = createRoot(() => createMemo(() => (data = state.length))); batch(() => { expect(state.length).toBe(1); expect(memo()).toBe(1); expect(data).toBe(1); setState([...state, 2]); expect(state.length).toBe(2); expect(data).toBe(1); expect(memo()).toBe(2); expect(data).toBe(2); }); expect(state.length).toBe(2); expect(memo()).toBe(2); expect(data).toBe(2); }); }); describe("State wrapping", () => { test("Setting plain object", () => { const data = { withProperty: "y" }, [state] = createStore({ data }); // not wrapped expect(state.data).not.toBe(data); }); test("Setting plain array", () => { const data = [1, 2, 3], [state] = createStore({ data }); // not wrapped expect(state.data).not.toBe(data); }); test("Setting non-wrappable", () => { const date = new Date(), [state] = createStore({ time: date }); // not wrapped expect(state.time).toBe(date); }); }); describe("Array length", () => { test("Setting plain object", () => { const [state, setState] = createStore<{ list: number[] }>({ list: [] }); let length; // isolate length tracking const list = state.list; createRoot(() => { createEffect(() => { length = list.length; }); }); expect(length).toBe(0); // insert at index 0 setState("list", 0, 1); expect(length).toBe(1); }); }); describe("State recursion", () => { test("there is no infinite loop", () => { const x: { a: number; b: any } = { a: 1, b: undefined }; x.b = x; const [state, setState] = createStore(x); expect(state.a).toBe(state.b.a); }); }); describe("Nested Classes", () => { test("wrapped nested class", () => { class CustomThing { a: number; b: number; constructor(value: number) { this.a = value; this.b = 10; } } const [inner] = createStore(new CustomThing(1)); const [store, setStore] = createStore({ inner }); expect(store.inner.a).toBe(1); expect(store.inner.b).toBe(10); let sum; createRoot(() => { createEffect(() => { sum = store.inner.a + store.inner.b; }); }); expect(sum).toBe(11); setStore("inner", "a", 10); expect(sum).toBe(20); setStore("inner", "b", 5); expect(sum).toBe(15); }); test("not wrapped nested class", () => { class CustomThing { a: number; b: number; constructor(value: number) { this.a = value; this.b = 10; } } const [store, setStore] = createStore({ inner: new CustomThing(1) }); expect(store.inner.a).toBe(1); expect(store.inner.b).toBe(10); let sum; createRoot(() => { createEffect(() => { sum = store.inner.a + store.inner.b; }); }); expect(sum).toBe(11); setStore("inner", "a", 10); expect(sum).toBe(11); setStore("inner", "b", 5); expect(sum).toBe(11); }); }); describe("In Operator", () => { test("wrapped nested class", () => { let access = 0; const [store, setStore] = createStore<{ a?: number; b?: number; c?: number }>({ a: 1, get b() { access++; return 2; } }); expect("a" in store).toBe(true); expect("b" in store).toBe(true); expect("c" in store).toBe(false); expect(access).toBe(0); const [a, b, c] = createRoot(() => { return [ createMemo(() => "a" in store), createMemo(() => "b" in store), createMemo(() => "c" in store) ]; }); expect(a()).toBe(true); expect(b()).toBe(true); expect(c()).toBe(false); expect(access).toBe(0); setStore("c", 3); expect(a()).toBe(true); expect(b()).toBe(true); expect(c()).toBe(true); expect(access).toBe(0); setStore("a", undefined); expect(a()).toBe(false); expect(b()).toBe(true); expect(c()).toBe(true); expect(access).toBe(0); expect("a" in store).toBe(false); expect("b" in store).toBe(true); expect("c" in store).toBe(true); expect(access).toBe(0); }); }); // type tests // NotWrappable keys are ignored () => { const [, setStore] = createStore<{ a?: | undefined | { b: null | { c: number | { d: bigint | { e: Function | { f: symbol | { g: string } } } } }; }; }>({}); setStore("a", "b", "c", "d", "e", "f", "g", "h"); }; // Cannot update readonly keys () => { const [, setK1] = createStore({} as { readonly i: number }); // @ts-expect-error i is readonly setK1("i", 2); const [, setK2] = createStore({} as { i: { readonly j: number } }); // @ts-expect-error j is readonly setK2("i", "j", 3); const [, setK3] = createStore({} as { i: { j: { readonly k: number } } }); // @ts-expect-error k is readonly setK3("i", "j", "k", 4); const [, setK4] = createStore({} as { i: { j: { k: { readonly l: number } } } }); // @ts-expect-error l is readonly setK4("i", "j", "k", "l", 5); const [, setK5] = createStore({} as { i: { j: { k: { l: { readonly m: number } } } } }); // @ts-expect-error m is readonly setK5("i", "j", "k", "l", "m", 6); const [, setK6] = createStore({} as { i: { j: { k: { l: { m: { readonly n: number } } } } } }); // @ts-expect-error n is readonly, but has unreadable error due to method overloading setK6("i", "j", "k", "l", "m", "n", 7); const [, setK7] = createStore( {} as { i: { j: { k: { l: { m: { n: { readonly o: number } } } } } } } ); // @ts-expect-error o is readonly, but has unreadable error due to method overloading setK7("i", "j", "k", "l", "m", "n", "o", 8); const [, setKn] = createStore( {} as { i: { j: { k: { l: { m: { n: { o: { readonly p: number } } } } } } } } ); // @ts-expect-error p is readonly setKn("i", "j", "k", "l", "m", "n", "o", "p", 9); }; // keys are narrowed () => { const [store, setStore] = createStore({ a: { b: 1 }, c: { d: 2 } }); setStore("a", "b", 3); setStore("c", "d", 4); // @ts-expect-error a.d is not valid setStore("a", "d", 5); // @ts-expect-error a.d is not valid store.a.d; // @ts-expect-error c.b is not valid setStore("c", "b", 6); // @ts-expect-error c.b is not valid store.c.b; }; // array key types are inferred () => { const [, setStore] = createStore({ list: [1, 2, 3] }); setStore( "list", (v, i) => i === 0, (v, t) => v * 2 ); setStore("list", { from: 1, to: 2 }, 4); setStore("list", [2, 3], 4); }; // fallback overload correctly infers keys and setter () => { const [, setStore] = createStore({ a: { b: { c: { d: { e: { f: { g: { h: { i: { j: { k: 1 } } } } } } } } } } }); setStore("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", (v, t) => ({ k: 2 })); setStore("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", 2); }; // same as the above but with strings which have more types of keys () => { const [, setStore] = createStore({ a: { b: { c: { d: { e: { f: { g: { h: { i: { j: { k: "l" } } } } } } } } } } }); setStore("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "m"); setStore("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", (v, t) => ({ k: "m" })); }; // tuples are correctly typed () => { const [, setStore] = createStore({ data: ["a", 1] as [string, number] }); setStore("data", 0, "hello"); setStore("data", 1, 2); // @ts-expect-error number not assignable to string setStore("data", 0, 3); // @ts-expect-error string not assignable to number setStore("data", 1, "world"); }; // // cannot mutate a store directly // () => { // const [store, setStore] = createStore({ a: 1, nested: { a: 1 } }); // // @ts-expect-error cannot set // store.a = 1; // // @ts-expect-error cannot set // store.nested.a = 1; // // @ts-expect-error cannot delete // delete store.a; // // @ts-expect-error cannot delete // delete store.nested.a; // // @ts-expect-error cannot set in setter // setStore(s => (s.a = 1)); // // @ts-expect-error cannot set in setter // setStore(s => (s.nested.a = 1)); // }; // cannot mutate unnested classes () => { const [store, setStore] = createStore({ inner: new Uint8Array() }); // TODO @ts-expect-error setStore("inner", 0, 2); const [inner] = createStore(new Uint8Array()); const [, setNested] = createStore({ inner }); setNested("inner", 0, 2); }; // createStore initial value () => { createStore(); createStore<{ a?: { b: 1 } }>(); createStore(() => 1); // @ts-expect-error cannot create store from null createStore(null); // @ts-expect-error cannot create store from number createStore(1); // @ts-expect-error cannot create store from string createStore("a"); // @ts-expect-error cannot create store from symbol createStore(Symbol()); // @ts-expect-error cannot create store from bigint createStore(BigInt(0)); // @ts-expect-error must provide initial value if {} cannot be assigned to it createStore<{ a: 1 }>(); }; // recursive () => { type Recursive = { a: Recursive }; const [store, setStore] = createStore({} as Recursive); setStore("a", "a", "a", "a", {}); // @ts-expect-error TODO should work with recursive types even at rest depth setStore("a", "a", "a", "a", "a", "a", "a", "a", "a", "a", {}); store.a.a.a.a.a.a.a.a.a; }; // TODO Wrappable instead of NotWrappable () => { type User = { name: string; data: number[]; }; let user: User = { name: "Jake", data: [1, 2, 3] }; // @ts-expect-error plain objects are wrappable let a: NotWrappable = user; let b: NotWrappable = 1; let c: NotWrappable = "string"; let d: NotWrappable = BigInt(0); let e: NotWrappable = Symbol(); let f: NotWrappable = undefined; let g: NotWrappable = null; let h: NotWrappable = () => 1; // @ts-expect-error TODO classes are not wrappable let i: NotWrappable = new Uint8Array(); }; // interactions with `any` () => { const [, setStore] = createStore<{ a: any; b?: { c: string } }>({ a: {} }); // allows anything when accessing `any` setStore("a", "b", "c", "d", "e", "f", "g", "h", "i"); setStore("a", 1, "c", Symbol(), 2, 1, 2, 3, 4, 5, 6, 1, 2, 3, "a", Symbol()); // still infers correctly on other paths setStore("b", "c", "d"); // @ts-expect-error setStore("b", 2); setStore("b", "c", v => v); }; // interactions with `unknown` () => { const [, setStore] = createStore<{ a: unknown }>({ a: {} }); // allows any setter setStore("a", "a"); setStore("a", () => ({ a: { b: 1 } })); // @ts-expect-error doesn't allow string setStore("a", "b", 1); // @ts-expect-error doesn't allow number setStore("a", 1, 1); // @ts-expect-error doesn't allow symbol setStore("a", Symbol(), 1); }; // interactions with generics (v: T) => { type A = { a: T; b: Record; c: Record }; const a = {} as A; const [store, setStore] = createStore(a); // should allow setStore("a", v); setStore("b", "a", "c"); setStore("b", v, "c"); // @ts-expect-error TODO generic should index Record setStore("c", v, "c"); const b = store.c[v]; const c: typeof b = "1"; const d = a.c[v]; const e: typeof d = "1"; }; // traversed contains the correct types of keys () => { const [, setStore] = createStore({ a: [{ b: 1 }] }); setStore((v, t) => { const expectedT: [] = t; t = [] as []; return v; }); setStore("a", (v, t) => { const expectedT: ["a"] = t; t = ["a"]; return v; }); setStore("a", 0, (v, t) => { const expectedT: [0, "a"] = t; t = [0, "a"]; return v; }); // array of keys setStore(["a"], [0], (v, t) => { const expectedT: [0, "a"] = t; t = [0, "a"]; return v; }); // callback setStore( ["a"], () => true, (v, t) => { const expectedT: [number, "a"] = t; t = [0, "a"]; return v; } ); // { from, to, by } setStore(["a"], {}, (v, t) => { const expectedT: [number, "a"] = t; t = [0, "a"]; return v; }); }; // types with a string index signature are not wrongly assumed to be arrays in setStore () => { const [store, setStore] = createStore<{ [x: string]: number }>({}); // @ts-expect-error filter function not allowed for objects setStore(() => true, 1); // @ts-expect-error from to by not allowed for objects setStore({ from: 0, to: 10, by: 3 }, 1); }; // can set overly complex? types () => { const [store, setStore] = createStore<{ el?: Element }>({}); setStore("el", {} as Element); }; // can set tuple indices () => { const [store, setStore] = createStore({ list: [0] as [number, number?] }); setStore("list", { 0: 1, 1: 2 }); setStore("list", { 0: 1 }); setStore("list", { 1: 2 }); setStore("list", {}); // @ts-expect-error tuple only contains two items setStore("list", { 2: 3 }); }; // can set array indices () => { const [store, setStore] = createStore({ list: [0] as number[] }); setStore("list", { 0: 1, 1: 2 }); setStore("list", { 0: 1 }); setStore("list", { 1: 2 }); setStore("list", { 99: 100 }); }; // can set top-level tuple indices () => { const [store, setStore] = createStore([0] as [number, number?]); setStore({ 0: 1, 1: 2 }); setStore({ 0: 1 }); setStore({ 1: 2 }); setStore({}); // @ts-expect-error tuple only contains two items setStore({ 2: 3 }); }; // can set top-level array indices () => { const [store, setStore] = createStore([0] as number[]); setStore({ 0: 1, 1: 2 }); setStore({ 0: 1 }); setStore({ 1: 2 }); setStore({ 99: 100 }); }; ================================================ FILE: packages/solid/store/tsconfig.build.json ================================================ { "extends": "../../../tsconfig.json", "compilerOptions": { "outDir": "./types", "baseUrl": "src", "paths": { "solid-js": ["../.."], "solid-js/jsx-runtime": ["../../src/jsx"], "solid-js/jsx-dev-runtime": ["../../src/jsx"], } }, "include": ["./src"] } ================================================ FILE: packages/solid/store/tsconfig.json ================================================ { "extends": "./tsconfig.build.json", "include": ["./src", "./test"] } ================================================ FILE: packages/solid/test/MessageChannel.ts ================================================ // @ts-ignore globalThis.MessageChannel = class { port1: { onmessage?: any } = {}; port2 = { postMessage: () => { setTimeout(this.port1.onmessage, 0); } }; }; ================================================ FILE: packages/solid/test/array.spec.ts ================================================ import { describe, expect, test } from "vitest"; import { mapArray, indexArray, createSignal, createMemo, createRoot } from "../src/index.js"; describe("Map operator", () => { test("simple mapArray", () => { createRoot(() => { const [s, set] = createSignal([1, 2, 3, 4]), r = createMemo(mapArray(s, v => v * 2)); expect(r()).toEqual([2, 4, 6, 8]); set([3, 4, 5]); expect(r()).toEqual([6, 8, 10]); }); }); test("show fallback", () => { createRoot(() => { const [s, set] = createSignal([1, 2, 3, 4]), double = mapArray(s, v => v * 2, { fallback: () => "Empty" }), r = createMemo(double); expect(r()).toEqual([2, 4, 6, 8]); set([]); expect(r()).toEqual(["Empty"]); set([3, 4, 5]); expect(r()).toEqual([6, 8, 10]); }); }); }); describe("Index operator", () => { test("simple indexArray", () => { createRoot(() => { const [s, set] = createSignal([1, 2, 3, 4]), r = createMemo(indexArray(s, v => v() * 2)); expect(r()).toEqual([2, 4, 6, 8]); }); }); test("show fallback", () => { createRoot(() => { const [s, set] = createSignal([1, 2, 3, 4]), double = indexArray(s, v => v() * 2, { fallback: () => "Empty" }), r = createMemo(double); expect(r()).toEqual([2, 4, 6, 8]); set([]); expect(r()).toEqual(["Empty"]); set([3, 4, 5]); expect(r()).toEqual([6, 8, 10]); }); }); }); ================================================ FILE: packages/solid/test/component.bench.ts ================================================ import { mergeProps, splitProps } from "../src/index.js"; import { $PROXY } from "../src/reactive/signal.js"; import { bench, describe } from "vitest"; const staticDesc = { value: 1, writable: true, configurable: true, enumerable: true }; const signalDesc = { get() { return 1; }, configurable: true, enumerable: true }; const cache = new Map(); const createObject = ( name: string, amount: number, desc: (index: number) => PropertyDescriptor ) => { const key = `${name}-${amount}`; const cached = cache.get(key); if (cached) return cached; const proto: Record = {}; for (let index = 0; index < amount; ++index) proto[`${name}${index}`] = desc(index); const result = Object.defineProperties({}, proto) as Record; cache.set(key, result); return result; }; const keys = (o: Record) => Object.keys(o); type Test = { title: string; benchs: { title: string; func: any }[]; }; function createTest any, G extends (...args: any[]) => any>(options: { name: string; /** * `vitest bench -t "FILTER"` does not work */ filter?: RegExp; subjects: { name: string; func: T; }[]; generator: Record; inputs: (generator: G) => Record>; }) { const tests: Test[] = []; for (const generatorName in options.generator) { const generator = options.generator[generatorName]; const inputs = options.inputs(generator); for (const inputName in inputs) { const args = inputs[inputName]; const test: Test = { title: `${options.name}-${generatorName}${inputName}`, benchs: [] }; if (options.filter && !options.filter.exec(test.title)) continue; for (const subject of options.subjects) { test.benchs.push({ title: subject.name, func: () => subject.func(...args) }); } tests.push(test); } } return tests; } type SplitProps = (...args: any[]) => Record[]; const generator = { static: (amount: number) => createObject("static", amount, () => staticDesc), signal: (amount: number) => createObject("signal", amount, () => signalDesc), mixed: (amount: number) => createObject("mixed", amount, v => (v % 2 ? staticDesc : signalDesc)), store: (amount: number) => { const data = createObject("store", amount, () => staticDesc); // Create a proxy that mimics store behavior with $PROXY symbol const proxy = new Proxy(data, { get(target, property) { if (property === $PROXY) return proxy; return target[property]; }, has(target, property) { if (property === $PROXY) return true; return property in target; }, ownKeys(target) { return Reflect.ownKeys(target); } }); Object.defineProperty(data, $PROXY, { value: proxy, configurable: true }); return proxy; } } as const; const filter = new RegExp(process.env.FILTER || ".+"); const splitPropsTests = createTest({ filter, name: "splitProps", subjects: [ { name: "splitProps", func: splitProps as SplitProps } ], generator, inputs: g => ({ "(5, 1)": [g(5), keys(g(1))], "(2, 15)": [g(2), keys(g(15))], "(2, 100)": [g(2), keys(g(100))], "(0, 15)": [g(0), keys(g(15))], "(25, 5)": [g(25), keys(g(5))], "(25, 100)": [g(25), keys(g(100))], "(50, 100)": [g(50), keys(g(100))], "(100, 25)": [g(100), keys(g(25))], "(5, 1, 2)": [g(5), keys(g(1)), keys(g(2))], "(2, 3, 2)": [g(2), keys(g(3)), keys(g(2))], "(2, 100, 3, 2)": [g(2), keys(g(100)), keys(g(3)), keys(g(2))] }) }); const mergePropsTest = createTest({ name: "mergeProps", filter, subjects: [ { name: "mergeProps", func: mergeProps } ], generator, inputs: g => ({ "(5, 1)": [g(5), g(1)], "(2, 15)": [g(2), g(15)], "(2, 100)": [g(2), g(100)], "(0, 15)": [g(0), g(15)], "(25, 5)": [g(25), g(5)], "(25, 100)": [g(25), g(100)], "(50, 100)": [g(50), g(100)], "(100, 25)": [g(100), g(25)], "(5, 1, 2)": [g(5), g(1), g(2)], "(2, 3, 2)": [g(2), g(3), g(2)], "(2, 100, 3, 2)": [g(2), g(100), g(3), g(2)] }) }); for (const test of splitPropsTests) { describe(test.title, () => { for (const { title, func } of test.benchs) bench(title, func); }); } for (const test of mergePropsTest) { describe(test.title, () => { for (const { title, func } of test.benchs) bench(title, func); }); } ================================================ FILE: packages/solid/test/component.spec.ts ================================================ import { describe, expect, it, test } from "vitest"; import { createRoot, createComponent, mergeProps, splitProps, createUniqueId, createSignal, createEffect, JSX } from "../src/index.js"; import { createStore } from "../store/src/index.js"; type SimplePropTypes = { a?: string | null; b?: string | null; c?: string | null; d?: string | null; }; const Comp = (props: { greeting: string; name: string }) => `${props.greeting} ${props.name}`; const Comp2 = (props: { greeting: string; name: string; optional?: string }) => { const [p, q] = splitProps(props, ["greeting", "optional"]); expect((p as any).name).toBeUndefined(); expect((q as any).greeting).toBeUndefined(); return `${p.greeting} ${q.name}`; }; describe("CreateComponent", () => { test("create simple component", () => { createRoot(() => { const out = createComponent(Comp, { greeting: "Hi", get name() { return "dynamic"; } }); expect(out).toBe("Hi dynamic"); }); }); test("null/undefined props are replaced with empty props", () => { createRoot(() => { const nonObjects = [null, undefined, false]; nonObjects.forEach(nonObject => { const out = createComponent(p => p as JSX.Element, nonObject as any); expect(out).toEqual({}); }); }); }); }); describe("mergeProps", () => { test("falsey values", () => { let props: SimplePropTypes = { get a() { return "ji"; }, b: null, c: "j" }; props = mergeProps(props, false, null, undefined); expect(props.a).toBe("ji"); expect(props.b).toBe(null); expect(props.c).toBe("j"); }); it("skips undefined values", () => { let bValue: number | undefined; const a = { value: 1 }; const b = { get value() { return bValue; } }; const c = { get value() { return undefined; } }; const d = { value: undefined }; const props = mergeProps(a, b, c, d); expect(props.value).toBe(1); bValue = 2; expect(props.value).toBe(2); }); it("includes undefined property", () => { const value = { a: undefined }; const getter = { get a() { return undefined; } }; expect("a" in mergeProps(value)).toBeTruthy(); expect("a" in mergeProps(getter)).toBeTruthy(); expect("a" in mergeProps(value, getter)).toBeTruthy(); expect("a" in mergeProps(getter, value)).toBeTruthy(); }); it("doesn't keep references for non-getters", () => { const a = { value1: 1 }; const b = { value2: 2 }; const props = mergeProps(a, b); a.value1 = b.value2 = 3; expect(props.value1).toBe(1); expect(props.value2).toBe(2); expect(Object.keys(props).join()).toBe("value1,value2"); }); it("without getter transfers only value", () => { const a = { value1: 1 }; const b = { get value2() { return undefined; } }; const props = mergeProps(a, b); a.value1 = 3; expect(props.value1).toBe(1); expect(Object.keys(props).join()).toBe("value1,value2"); }); it("overrides enumerables", () => { const a = Object.defineProperties( {}, { value1: { enumerable: false, value: 2 } } ); const props = mergeProps({}, a); expect((props as any).value1).toBe(2); expect(Object.getOwnPropertyDescriptor(props, "value1")?.enumerable).toBeTruthy(); expect(Object.keys(props).join()).toBe("value1"); }); it("does not write the target", () => { const props = { value1: 1 }; mergeProps(props, { value2: 2, get value3() { return 3; } }); expect(Object.keys(props).join("")).toBe("value1"); }); it("always returns a new reference", () => { const props = {}; const newProps = mergeProps(props); expect(props === newProps).toBeFalsy(); }); it("uses the source instances", () => { const source1 = { get a() { return this; } }; const source2 = { get b() { return this; } }; const props = mergeProps(source1, source2); expect(props.a === source1).toBeTruthy(); expect(props.b === source2).toBeTruthy(); }); it("does not clone nested objects", () => { const b = { value: 1 }; const props = mergeProps({ a: 1 }, { b }); b.value = 2; expect(props.b.value).toBe(2); }); it("ignores undefined values", () => { const props = mergeProps({ a: 1 }, { a: undefined }); expect(props.a).toBe(1); }); it("handles null values", () => { const props = mergeProps({ a: 1 }, { a: null }); expect(props.a).toBeNull(); }); it("contains null values", () => { const props = mergeProps({ a: null, get b() { return null; } }); expect(props.a).toBeNull(); expect(props.b).toBeNull(); }); it("contains undefined values", () => { const props = mergeProps({ a: undefined, get b() { return undefined; } }); expect(Object.keys(props).join()).toBe("a,b"); expect("a" in props).toBeTruthy(); expect("b" in props).toBeTruthy(); expect(props.a).toBeUndefined(); expect(props.b).toBeUndefined(); }); it("ignores falsy sources", () => { const props = mergeProps(undefined, null, { value: 1 }, null, undefined); expect(Object.keys(props).join()).toBe("value"); }); it("fails with non objects sources", () => { expect(() => mergeProps({ value: 1 }, true)).toThrowError(); expect(() => mergeProps({ value: 1 }, 1)).toThrowError(); }); it("works with a array source", () => { const props = mergeProps({ value: 1 }, [2]); expect(Object.keys(props).join()).toBe("0,value,length"); expect(props.value).toBe(1); expect(props.length).toBe(1); expect(props[0]).toBe(2); }); it("is safe", () => { mergeProps({}, JSON.parse('{ "__proto__": { "evil": true } }')); expect(({} as any).evil).toBeUndefined(); mergeProps({}, JSON.parse('{ "prototype": { "evil": true } }')); expect(({} as any).evil).toBeUndefined(); mergeProps({ value: 1 }, JSON.parse('{ "__proto__": { "evil": true } }')); expect(({} as any).evil).toBeUndefined(); mergeProps({ value: 1 }, JSON.parse('{ "prototype": { "evil": true } }')); expect(({} as any).evil).toBeUndefined(); }); it("sets already prototyped properties", () => { expect(mergeProps({ toString: 1 }).toString).toBe(1); expect({}.toString).toBeTypeOf("function"); }); }); describe("Set Default Props", () => { test("simple set", () => { let props: SimplePropTypes = { get a() { return "ji"; }, b: null, c: "j" }, defaults: SimplePropTypes = { a: "yy", b: "ggg", d: "DD" }; props = mergeProps(defaults, props); expect(props.a).toBe("ji"); expect(props.b).toBe(null); expect(props.c).toBe("j"); expect(props.d).toBe("DD"); }); }); describe("Clone Props", () => { test("simple set", () => { let reactive = false; const props: SimplePropTypes = { get a() { reactive = true; return "ji"; }, b: null, c: "j" }; const newProps = mergeProps({}, props); expect(reactive).toBe(false); expect(newProps.a).toBe("ji"); expect(reactive).toBe(true); expect(newProps.b).toBe(null); expect(newProps.c).toBe("j"); expect(newProps.d).toBe(undefined); }); }); describe("Clone Store", () => { test("simple set", () => { const [state, setState] = createStore<{ a: string; b: string; c?: string }>({ a: "Hi", b: "Jo" }); 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("John"); }); }); describe("Merge Signal", () => { test("simple set", () => { const [s, set] = createSignal({ get a() { return "ji"; }, b: null, c: "j" }), defaults: SimplePropTypes = { a: "yy", b: "ggg", d: "DD" }; let props!: SimplePropTypes; const res: string[] = []; createRoot(() => { props = mergeProps(defaults, s); createEffect(() => { res.push(props.a as string); }); }); expect(props.a).toBe("ji"); expect(props.b).toBe(null); expect(props.c).toBe("j"); expect(props.d).toBe("DD"); set({ a: "h" }); expect(props.a).toBe("h"); expect(props.b).toBe("ggg"); expect(props.c).toBeUndefined(); expect(props.d).toBe("DD"); expect(res[0]).toBe("ji"); expect(res[1]).toBe("h"); expect(res.length).toBe(2); }); test("null/undefined/false are ignored", () => { const props = mergeProps({ a: 1 }, null, undefined, false); expect(props).toEqual({ a: 1 }); }); }); describe("SplitProps Props", () => { test("SplitProps in two", () => { createRoot(() => { const out = createComponent(Comp2, { greeting: "Hi", get name() { return "dynamic"; } }); expect(out).toBe("Hi dynamic"); }); }); test("SplitProps in two with store", () => { createRoot(() => { const [state] = createStore({ greeting: "Yo", name: "Bob" }); const out = createComponent(Comp2, state); expect(out).toBe("Yo Bob"); }); }); test("SplitProps result is inmutable", () => { const inProps = { first: 1, second: 2 }; const [props, otherProps] = splitProps(inProps, ["first"]); inProps.first = inProps.second = 3; expect(props.first).toBe(1); expect(otherProps.second).toBe(2); }); test("SplitProps clones the descriptor", () => { let signalValue = 1; const desc = { signal: { enumerable: true, get() { return signalValue; } }, static: { configurable: true, enumerable: false, value: 2 } }; const inProps = Object.defineProperties({}, desc) as { signal: number; value1: number }; const [props, otherProps] = splitProps(inProps, ["signal"]); expect(props.signal).toBe(1); signalValue++; expect(props.signal).toBe(2); const signalDesc = Object.getOwnPropertyDescriptor(props, "signal")!; expect(signalDesc.get === desc.signal.get).toBeTruthy(); expect(signalDesc.set).toBeUndefined(); expect(signalDesc.enumerable).toBeTruthy(); expect(signalDesc.configurable).toBeFalsy(); const staticDesc = Object.getOwnPropertyDescriptor(otherProps, "static")!; expect(staticDesc.value).toBe(2); expect(staticDesc.get).toBeUndefined(); expect(staticDesc.set).toBeUndefined(); expect(staticDesc.enumerable).toBeFalsy(); expect(staticDesc.configurable).toBeTruthy(); }); test("SplitProps with multiple keys", () => { const inProps: { id?: string; color?: string; margin?: number; padding?: number; variant?: string; description?: string; } = { id: "input", color: "red", margin: 3, variant: "outlined", description: "test" }; const [styleProps, inputProps, otherProps] = splitProps( inProps, ["color", "margin", "padding"], ["variant", "description"] ); expect(styleProps.color).toBe("red"); expect(styleProps.margin).toBe(3); expect(styleProps.padding).toBeUndefined(); expect(Object.keys(styleProps).length).toBe(2); expect(inputProps.description).toBe("test"); expect(inputProps.variant).toBe("outlined"); expect(Object.keys(inputProps).length).toBe(2); expect(otherProps.id).toBe("input"); expect(Object.keys(otherProps).length).toBe(1); }); test("SplitProps returns same prop descriptors", () => { const inProps = { a: 1, b: 2, get c() { return 3; }, d: undefined, x: 1, y: 2, get w() { return 3; }, z: undefined }; const inDescriptor = Object.getOwnPropertyDescriptors(inProps); const [props, otherProps] = splitProps(inProps, ["a", "b", "c", "d", "e" as "d"]); const propsDesc = Object.getOwnPropertyDescriptors(props); expect(propsDesc.a).toMatchObject(inDescriptor.a); expect(propsDesc.b).toMatchObject(inDescriptor.b); expect(propsDesc.c).toMatchObject(inDescriptor.c); expect(propsDesc.d).toMatchObject(inDescriptor.d); expect(propsDesc.e).toBeUndefined(); const otherDesc = Object.getOwnPropertyDescriptors(otherProps); expect(otherDesc.w).toMatchObject(otherDesc.w); expect(otherDesc.x).toMatchObject(otherDesc.x); expect(otherDesc.y).toMatchObject(otherDesc.y); expect(otherDesc.z).toMatchObject(otherDesc.z); }); test("SplitProps is safe", () => { const inProps = JSON.parse('{"__proto__": { "evil": true } }'); const [, evilProps1] = splitProps(inProps, []); expect(evilProps1.__proto__?.evil).toBeTruthy(); expect(({} as any).evil).toBeUndefined(); const [evilProps2] = splitProps(inProps, ["__proto__"]); expect(evilProps2.__proto__?.evil).toBeTruthy(); expect(({} as any).evil).toBeUndefined(); }); test("Merge SplitProps", () => { let value: string | undefined = undefined; const [splittedProps] = splitProps({ color: "blue" } as { color: string; other?: string }, [ "color", "other" ]); const mergedProps = mergeProps(splittedProps, { get color() { return value; }, other: "value" }); expect(mergedProps.color).toBe("blue"); value = "red"; expect(mergedProps.color).toBe("red"); }); }); describe("createUniqueId", () => { test("creating some", () => { const id1 = createUniqueId(); const id2 = createUniqueId(); expect(id1).toBeDefined(); expect(id2).toBeDefined(); expect(id1).not.toEqual(id2); }); }); ================================================ FILE: packages/solid/test/component.type-tests.ts ================================================ import { mergeProps, splitProps } from "../src/index.js"; type Assert = never; // from: https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650 type IsExact = (() => G extends T | I ? 1 : 2) extends () => G extends U | I ? 1 : 2 ? true : false; // m1: mergeProps multiple property case const m1 = mergeProps( {} as { a: number; b: number; c: number; d?: number; e?: number; f?: number; i: number; j?: number; m: undefined; n: undefined; o?: undefined; p: number; q: 1; r: number; s: 1; }, {} as { b: string; c?: string; e: string; f?: string; g: string; h?: string; i: undefined; j: undefined; k: undefined; l?: undefined; m: string; n?: string; o?: string; p: 1; q: number; r?: 1; s?: number; } ); type M1 = typeof m1; type TestM1 = Assert< IsExact< M1, { a: number; b: string; c: string | number; d?: number | undefined; e: string; f?: string | number | undefined; g: string; h?: string; i: number; j: number | undefined; k: undefined; l?: undefined; m: string; n: string | undefined; o?: string | undefined; p: 1; q: number; r: number; s: number; } > >; // m2-m3: mergeProps single property cases // optional is kept optional const m2 = mergeProps({ a: 1 } as { a?: number }, { a: 1 } as { a?: number }); type M2 = typeof m2; type TestM2 = Assert>; // undefined is ignored const m3 = mergeProps({ a: 1 }, { a: undefined }); type M3 = typeof m3; type TestM3 = Assert>; // m4: mergeProps works with generics (best effort) type M4Type = { a: { aProp: string; test: string }; b: { bProp: number; test: string }; }; function M4( props: { prop: "a" | "b" } & { as: T } & Omit ) { const defaultProperties = { prop: "a" }; const test1 = mergeProps(defaultProperties, props); const prop1: "a" | "b" = test1.prop; const propstr1: string = test1.prop; const as1: T = test1.as; const str1: string = test1.test; test1.prop = "a"; test1.as = "" as T; test1.test = ""; const test2 = mergeProps(defaultProperties, props as { prop: "a" | "b" } & { as: T } & M4Type[T]); const prop2: "a" | "b" = test2.prop; const propstr2: string = test2.prop; const as2: T = test2.as; const str2: string = test2.test; test2.prop = "a"; test2.as = "" as T; test2.test = ""; const test3 = mergeProps(defaultProperties, ...[props]); const prop3: "a" | "b" = test3.prop; const propstr3: string = test3.prop; const as3: T = test3.as!; const str3: string = test3.test!; test3.prop = "a"; test3.as = "" as T; test3.test = ""; const test4 = mergeProps(...[defaultProperties], props); const prop4: "a" | "b" = test4.prop; const propstr4: string = test4.prop; const as4: T = test4.as; const str4: string = test4.test; test4.prop = "a"; test4.as = "" as T; test4.test = ""; const test5 = mergeProps(defaultProperties, ...[props], props); const prop5: "a" | "b" = test5.prop; const propstr5: string = test5.prop; const as5: T = test5.as; const str5: string = test5.test; test5.prop = "a"; test5.as = "" as T; test5.test = ""; const test6 = mergeProps(props, props); const prop6: "a" | "b" = test6.prop; const propstr6: string = test6.prop; const as6: T = test6.as; const str6: string = test6.test; test6.prop = "a"; test6.as = "" as T; test6.test = ""; } { const a = { a: 1 }; const b = { b: 2 }; const c = { c: 3 }; const bc = { b: 2, c: 3 }; // m5-m7: mergeProps spreading arrays is valid const m5 = mergeProps(a, ...[b], c); type M5 = typeof m5; type TestM5 = Assert>; const m6 = mergeProps(...[b], c); type M6 = typeof m6; type TestM6 = Assert>; const m7 = mergeProps(a, ...[b]); type M7 = typeof m7; type TestM7 = Assert>; const m8 = mergeProps(...[b]); type M8 = typeof m8; type TestM8 = Assert>; const m9 = mergeProps(...[a], ...[b], ...[c]); type M9 = typeof m9; type TestM9 = Assert>; } // s1-s3: splitProps return type is correct regardless of usage const s1 = splitProps({ a: 1, b: 2 }, ["a"]); type S1 = typeof s1; type TestS1 = Assert>; const [, s2] = splitProps({ a: 1, b: 2 }, ["a"]); type S2 = typeof s2; type TestS2 = Assert>; const [s3] = splitProps({ a: 1, b: 2 }, ["a"]); type S3 = typeof s3; type TestS3 = Assert>; ================================================ FILE: packages/solid/test/dev.spec.ts ================================================ import { describe, expect, test, vi } from "vitest"; import { createRoot, getOwner, createSignal, createEffect, createComputed, DEV, createContext, createComponent } from "../src/index.js"; import type { DevComponent } from "../src/reactive/signal.js"; import { createStore, unwrap, DEV as STORE_DEV } from "../store/src/index.js"; describe("Dev features", () => { test("Signals being added to sourceMap with user-provided names", () => { createRoot(() => { const owner = getOwner()!; createSignal(3, { name: "test" }); createSignal(5); createSignal(6, { name: "explicit" }); expect(owner).toHaveProperty("sourceMap"); expect(owner.sourceMap![0].name).toBe("test"); expect(owner.sourceMap![0].value).toBe(3); expect(owner.sourceMap![1].name).toBe(undefined); expect(owner.sourceMap![1].value).toBe(5); expect(owner.sourceMap![2].name).toBe("explicit"); expect(owner.sourceMap![2].value).toBe(6); }); }); test("Computations can be named", () => { createRoot(() => { const owner = getOwner()!; createComputed(() => {}, undefined, { name: "test" }); createEffect(() => {}, undefined, { name: "test_effect" }); createComputed(() => {}); createEffect(() => {}); expect(owner).toHaveProperty("owned"); expect(owner.owned![0].name).toBe("test"); expect(owner.owned![1].name).toBe("test_effect"); expect(owner.owned![2].name).toBe(undefined); expect(owner.owned![3].name).toBe(undefined); }); }); test("Context nodes can be named", () => { createRoot(dispose => { const ctx1 = createContext(undefined); const ctx2 = createContext(undefined, { name: "test" }); ctx1.Provider({ value: undefined, children: undefined }); ctx2.Provider({ value: undefined, children: undefined }); expect(getOwner()!.owned![0].name).toBe(undefined); expect(getOwner()!.owned![1].name).toBe("test"); dispose(); }); }); test("AfterUpdate Hook", () => { let triggered = 0; let set1: (v: number) => number, setState1: any; DEV!.hooks.afterUpdate = () => triggered++; createRoot(() => { const [s, set] = createSignal(5); const [s2] = createSignal(5); createEffect(() => { const [s] = createSignal(6, { name: "explicit" }); }); const [state, setState] = createStore({ firstName: "John", lastName: "Smith" }); createEffect(() => { s(); s2(); state.firstName; }); set1 = set; setState1 = setState; }); expect(triggered).toBe(1); set1!(7); expect(triggered).toBe(2); setState1({ middleInitial: "R.", firstName: "Matt" }); expect(triggered).toBe(3); }); test("AfterUpdate Hook with effect write", () => { let triggered = 0; let set1: (v: number) => number; let log = ""; DEV!.hooks.afterUpdate = () => triggered++; createRoot(() => { const [s, set] = createSignal(5); const [s2, set2] = createSignal(0); const [s3, set3] = createSignal(0); createComputed(() => { log += "a"; set3(s2()); }); createEffect(() => { log += "b"; set2(s()); }); createEffect(() => { log += "c"; s3(); }); set1 = set; }); expect(triggered).toBe(1); expect(log).toBe("abcac"); log = ""; set1!(7); expect(triggered).toBe(2); expect(log).toBe("bac"); }); test("afterCreateOwner Hook", () => { const cb = vi.fn(); DEV!.hooks.afterCreateOwner = cb; createRoot(() => { expect(cb).toHaveBeenCalledTimes(1); expect(cb).toHaveBeenLastCalledWith(getOwner()); createRoot(_ => { expect(cb).toHaveBeenCalledTimes(2); expect(cb).toHaveBeenLastCalledWith(getOwner()); }); createComputed(() => { expect(cb).toHaveBeenCalledTimes(3); expect(cb).toHaveBeenLastCalledWith(getOwner()); }); }); }); test("afterRegisterGraph Hook", () => { createRoot(() => { const owner = getOwner()!; const cb = vi.fn(); DEV!.hooks.afterRegisterGraph = cb; createSignal(1); expect(cb).toHaveBeenCalledTimes(1); expect(cb).toHaveBeenLastCalledWith(owner.sourceMap![0]); expect(owner.sourceMap).toHaveLength(1); createSignal(2, { internal: true }); expect(cb).toHaveBeenCalledTimes(1); expect(owner.sourceMap).toHaveLength(1); createStore({}); expect(cb).toHaveBeenCalledTimes(2); expect(cb).toHaveBeenLastCalledWith(owner.sourceMap![1]); expect(owner.sourceMap).toHaveLength(2); const customValue = { value: 3 }; DEV!.registerGraph(customValue); expect(cb).toHaveBeenCalledTimes(3); expect(cb).toHaveBeenLastCalledWith(customValue); expect(owner.sourceMap).toHaveLength(3); }); }); test("OnStoreNodeUpdate Hook", () => { const cb = vi.fn(); STORE_DEV!.hooks.onStoreNodeUpdate = cb; const [s, set] = createStore({ firstName: "John", lastName: "Smith", inner: { foo: 1 } }); expect(cb).toHaveBeenCalledTimes(0); set({ firstName: "Matt" }); expect(cb).toHaveBeenCalledTimes(1); expect(cb).toHaveBeenCalledWith(unwrap(s), "firstName", "Matt", "John"); set("inner", "foo", 2); expect(cb).toHaveBeenCalledTimes(2); expect(cb).toHaveBeenCalledWith(unwrap(s.inner), "foo", 2, 1); }); test("createComponent should create a component owner in DEV", () => { createRoot(() => { const props = {}; createComponent(function MyComponent() { const owner = getOwner() as DevComponent<{}>; expect(owner.name).toBe("MyComponent"); expect(owner.props).toBe(props); expect(owner.component).toBe(MyComponent); return null; }, props); }); }); }); ================================================ FILE: packages/solid/test/external-source.spec.ts ================================================ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createRoot, createMemo, untrack, enableExternalSource } from "../src/index.js"; import "./MessageChannel"; class ExternalSource { listeners: Set<() => void> = new Set(); constructor(private value: T) {} update(x: T) { this.value = x; this.listeners.forEach(x => x()); } get() { if (listener) { this.listeners.add(listener!); sources.get(listener!)!.add(this); } return this.value; } removeListener(listener: () => void) { this.listeners.delete(listener); } } let listener: (() => void) | null = null; function untrackSource(fn: () => T) { const tmp = listener; listener = null; try { return fn(); } finally { listener = tmp; } } let sources: Map<() => void, Set> = new Map(); describe("external source", () => { beforeEach(() => { enableExternalSource((fn, trigger) => { sources.set(trigger, new Set()); return { track: x => { const tmp = listener; // trigger could play the role of listener,as it has stable reference listener = trigger; try { return fn(x); } finally { listener = tmp; } }, dispose: () => { sources.get(trigger)!.forEach(x => x.removeListener(trigger)); sources.delete(trigger); } }; }, untrackSource); enableExternalSource(fn => { return { track: fn, dispose: () => {} }; }); // do nothing, make sure multiple factories be piped. }); it("should trigger solid primitive update", () => { createRoot(fn => { const e = new ExternalSource(0); const memo = createMemo(() => { return e.get(); }); const memo2 = createMemo(() => { return untrack(() => e.get()); }); expect(memo()).toBe(0); expect(memo2()).toBe(0); e.update(1); expect(memo()).toBe(1); expect(memo2()).toBe(0); fn(); }); }); afterEach(() => { vi.resetModules(); }); }); ================================================ FILE: packages/solid/test/observable.spec.ts ================================================ import { describe, expect, test, vi } from "vitest"; import { createRoot, createSignal, from, observable } from "../src/index.js"; describe("Observable operator", () => { test("to observable", () => { let out: string; let set: (v: string) => void; createRoot(() => { 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"); }); test("preserve the observer's next binding", () => { const observer = { next: vi.fn().mockReturnThis() }; createRoot(() => { const [s] = createSignal("Hi"), obsv$ = observable(s); obsv$.subscribe(observer); }); expect(observer.next).toHaveReturnedWith(observer); }); test("observable throws TypeError on non-object", () => { const [s, _set] = createSignal("Hi"); const o = observable(s); expect(() => o.subscribe(null as any)).toThrow(TypeError); }); test("observable unsubscribe", () => { const [s, set] = createSignal("Hi"); const o = observable(s); let out: string; let subscription: any; createRoot(() => { subscription = o.subscribe({ next(v) { out = v; } }); }); set("John"); expect(out!).toBe("John"); subscription.unsubscribe(); set("Benjamin"); expect(out!).toBe("John"); }); }); describe("from transform", () => { test("from subscribable", async () => { let out: () => string | undefined; let set: (v: string) => void; createRoot(() => { const [s, _set] = createSignal("Hi"), obsv$ = observable(s); set = _set; out = from(obsv$); }); expect(out!()).toBe("Hi"); set!("John"); expect(out!()).toBe("John"); }); test("from producer", async () => { let out: () => string | undefined; let set: (v: string) => void; createRoot(() => { const [s, _set] = createSignal("Hi"), obsv$ = observable(s); set = _set; out = from(set => { const sub = obsv$.subscribe(set); return () => sub.unsubscribe(); }); }); expect(out!()).toBe("Hi"); set!("John"); expect(out!()).toBe("John"); }); }); ================================================ FILE: packages/solid/test/rendering.spec.ts ================================================ import { describe, expect, test } from "vitest"; import { createResource } from "../src/index.js"; import { resolveSSRNode } from "dom-expressions/src/server.js"; describe("resolveSSRNode", () => { test("should resolve a string node", () => { expect(resolveSSRNode("Hello World")).toBe("Hello World"); }); test("should resolve a null or boolean node", () => { expect(resolveSSRNode(null)).toBe(""); expect(resolveSSRNode(false)).toBe(""); }); test("should resolve an array of nodes", () => { const nodes = ["

"]; expect(resolveSSRNode(nodes)).toBe("
"); }); test("should resolve an object with 't' property", () => { const node = { t: "
Text
" }; expect(resolveSSRNode(node)).toBe("
Text
"); }); test("should resolve a function node", () => { const fn = () => "dynamic content"; expect(resolveSSRNode(fn)).toBe("dynamic content"); }); }); describe("createResource", () => { test("should return initial value immediately if provided", () => { const [data] = createResource(() => Promise.resolve("test"), { initialValue: "loading" }); expect(data()).toBe("loading"); }); test("should handle a promise and update the value", async () => { const [data, { refetch }] = createResource( () => new Promise(resolve => setTimeout(() => resolve("Success!"), 10)) ); // Initially, data should be undefined, and loading should be true expect(data()).toBeUndefined(); expect(data.loading).toBe(true); await new Promise(r => setTimeout(r, 20)); // Wait for the promise to resolve // After resolution, data should have the new value, and loading should be false expect(data()).toBe("Success!"); expect(data.loading).toBe(false); }); }); ================================================ FILE: packages/solid/test/resource.spec.ts ================================================ import { describe, expect, test } from "vitest"; import { createRoot, createSignal, createResource, createRenderEffect, catchError, Resource, ResourceFetcherInfo, Signal, createMemo, getOwner } from "../src/index.js"; import { createStore, reconcile, ReconcileOptions, Store, unwrap } from "../store/src/index.js"; describe("Simulate a dynamic fetch", () => { let resolve: (v: string) => void, reject: (r: string) => void, trigger: (v: string) => void, value: Resource, error: Error; function fetcher(id: string) { return new Promise((r, f) => { resolve = r; reject = f; }); } test("initial async resource", async () => { createRoot(() => { const [id, setId] = createSignal("1"); trigger = setId; catchError( () => { [value] = createResource(id, fetcher); createRenderEffect(value); }, e => (error = e) ); }); expect(value()).toBeUndefined(); expect(value.latest).toBeUndefined(); expect(value.loading).toBe(true); resolve("John"); await Promise.resolve(); expect(value()).toBe("John"); expect(value.latest).toBe("John"); expect(value.loading).toBe(false); }); test("test out of order", async () => { trigger("2"); expect(value.loading).toBe(true); const resolve1 = resolve; trigger("3"); const resolve2 = resolve; resolve2("Jake"); resolve1("Jo"); await Promise.resolve(); expect(value()).toBe("Jake"); expect(value.loading).toBe(false); }); test("promise rejection", async () => { trigger("4"); expect(value.loading).toBe(true); expect(value.error).toBeUndefined(); reject("Because I said so"); await Promise.resolve(); expect(error).toBeInstanceOf(Error); expect(error.message).toBe("Because I said so"); expect(value.error).toBeInstanceOf(Error); expect(value.error.message).toBe("Because I said so"); expect(value.loading).toBe(false); }); }); describe("Simulate a dynamic fetch with state and reconcile", () => { interface User { firstName: string; address: { streetNumber: number; streetName: string; }; } let resolve: (v: User) => void, refetch: (info?: unknown) => void, user: Resource, state: { user?: User; userLoading: boolean }, count = 0; function fetcher(_: unknown, { value }: ResourceFetcherInfo>) { expect(getOwner()).toBeDefined(); return new Promise(r => { resolve = r; }).then(next => reconcile(next)(value!)); } const data: User[] = []; data.push({ firstName: "John", address: { streetNumber: 4, streetName: "Grindel Rd" } }); data.push({ ...data[0], firstName: "Joseph" }); test("initial async resource", async () => { createRoot(async () => { [user, { refetch }] = createResource(fetcher); [state] = createStore<{ user?: User; userLoading: boolean }>({ get user() { return user(); }, get userLoading() { return user.loading; } }); createRenderEffect(() => (state.user, count++)); }); expect(state.user).toBeUndefined(); expect(state.userLoading).toBe(true); resolve(data[0]); await Promise.resolve(); await Promise.resolve(); expect(unwrap(state.user)).toStrictEqual(data[0]); expect(state.userLoading).toBe(false); expect(count).toBe(2); refetch(); expect(state.userLoading).toBe(true); resolve(data[1]); await Promise.resolve(); await Promise.resolve(); expect(unwrap(state.user)).toStrictEqual(data[0]); expect(state.user?.firstName).toBe("Joseph"); expect(unwrap(state.user?.address)).toStrictEqual(data[0].address); expect(state.userLoading).toBe(false); expect(count).toBe(2); }); }); describe("using Resource with no root", () => { test("loads default value", () => { expect(() => { let resolve: (v: string) => void; createResource("error", () => new Promise(r => (resolve = r))); resolve!("Hi"); }).not.toThrow(); }); }); describe("using Resource with initial Value", () => { let resolve: (v: string) => void, reject: (r: string) => void, trigger: (v: string) => void, value: Resource, error: Error; function fetcher(id: string) { return new Promise((r, f) => { resolve = r; reject = f; }); } test("loads default value", async () => { createRoot(() => { const [id, setId] = createSignal("1"); trigger = setId; catchError( () => { [value] = createResource(id, fetcher, { initialValue: "Loading" }); createRenderEffect(value); }, e => (error = e) ); }); expect(value()).toBe("Loading"); expect(value.loading).toBe(true); resolve("John"); await Promise.resolve(); expect(value()).toBe("John"); expect(value.loading).toBe(false); }); }); describe("using Resource with errors", () => { let resolve: (v: string) => void, reject: (e: any) => void, trigger: (v: string) => void, value: Resource, error: Error; function fetcher(id: string) { return new Promise((r, f) => { resolve = r; reject = f; }); } test("works with falsy errors", async () => { createRoot(() => { const [id, setId] = createSignal("1"); trigger = setId; catchError( () => { [value] = createResource(id, fetcher); createRenderEffect(value); }, e => (error = e) ); }); expect(value()).toBeUndefined(); expect(value.state === "pending").toBe(true); expect(value.error).toBeUndefined(); reject(null); await Promise.resolve(); expect(value.state === "errored").toBe(true); expect(value.error.message).toBe("Unknown error"); }); }); describe("using Resource with synchronous error", () => { let value: Resource; let error: Error; test("catches the error", async () => { createRoot(() => { catchError( () => { [value] = createResource(() => { throw new Error("Fetcher error"); }); createRenderEffect(value); }, e => { error = e; } ); }); expect(value.state === "errored").toBe(true); expect(value.error).toBe(error); expect(value.error.message).toBe("Fetcher error"); }); }); describe("using Resource with custom store", () => { type User = { firstName: string; lastName: string; address: { streetNumber: number; streetName: string; city: string; state: string; zip: number; }; }; let resolve: (v: User) => void; let value: Resource; function fetcher() { return new Promise(r => { resolve = r; }); } function createDeepSignal(value: T, options?: ReconcileOptions): Signal { const [store, setStore] = createStore({ value }); return [ () => store.value, (v: T) => { const unwrapped = unwrap(store.value); typeof v === "function" && (v = v(unwrapped)); setStore("value", reconcile(v, options)); return store.value; } ] as Signal; } test("loads and diffs", async () => { let first = 0; let last = 0; let addr = 0; let street = 0; createRoot(() => { [value] = createResource(fetcher, { initialValue: { firstName: "John", lastName: "Smith", address: { streetNumber: 4, streetName: "Grindel Rd", city: "New York", state: "NY", zip: 10001 } }, storage: createDeepSignal }); createRenderEffect(() => (first++, value()?.firstName)); createRenderEffect(() => (last++, value()?.lastName)); const address = createMemo(() => (addr++, value()?.address)); createRenderEffect(() => (street++, address()?.streetName)); }); expect(value()).toEqual({ firstName: "John", lastName: "Smith", address: { streetNumber: 4, streetName: "Grindel Rd", city: "New York", state: "NY", zip: 10001 } }); expect(value.loading).toBe(true); expect(first).toBe(1); expect(last).toBe(1); expect(addr).toBe(1); expect(street).toBe(1); resolve({ firstName: "Matt", lastName: "Smith", address: { streetNumber: 4, streetName: "Central Rd", city: "New York", state: "NY", zip: 10001 } }); await Promise.resolve(); expect(value()).toEqual({ firstName: "Matt", lastName: "Smith", address: { streetNumber: 4, streetName: "Central Rd", city: "New York", state: "NY", zip: 10001 } }); expect(value.loading).toBe(false); expect(first).toBe(2); expect(last).toBe(1); expect(addr).toBe(1); expect(street).toBe(2); }); test("mutates", async () => { let first = 0; let last = 0; let addr = 0; let street = 0; let mutate: (v: T) => T; createRoot(() => { [value, { mutate }] = createResource(false, fetcher, { initialValue: { firstName: "John", lastName: "Smith", address: { streetNumber: 4, streetName: "Grindel Rd", city: "New York", state: "NY", zip: 10001 } }, storage: createDeepSignal }); createRenderEffect(() => (first++, value()?.firstName)); createRenderEffect(() => (last++, value()?.lastName)); const address = createMemo(() => (addr++, value()?.address)); createRenderEffect(() => (street++, address()?.streetName)); }); expect(value()).toEqual({ firstName: "John", lastName: "Smith", address: { streetNumber: 4, streetName: "Grindel Rd", city: "New York", state: "NY", zip: 10001 } }); expect(value.loading).toBe(false); expect(first).toBe(1); expect(last).toBe(1); expect(addr).toBe(1); expect(street).toBe(1); mutate!({ firstName: "Matt", lastName: "Smith", address: { streetNumber: 4, streetName: "Central Rd", city: "New York", state: "NY", zip: 10001 } }); await Promise.resolve(); expect(value()).toEqual({ firstName: "Matt", lastName: "Smith", address: { streetNumber: 4, streetName: "Central Rd", city: "New York", state: "NY", zip: 10001 } }); expect(value.loading).toBe(false); expect(first).toBe(2); expect(last).toBe(1); expect(addr).toBe(1); expect(street).toBe(2); }); }); describe("createResource can be wrapped", () => { const fns: [name: string, function: typeof createResource][] = [ ["original createResource", createResource], // @ts-ignore ["createResource(...args)", (...args) => createResource(...args)], // @ts-ignore ["createResource(a, b, c)", (a, b, c) => createResource(a, b, c)] ]; for (const [name, fn] of fns) { test(`only fetcher in ${name}`, () => { const [[data], dispose] = createRoot(dispose => [fn(() => 123), dispose]); expect(data()).toBe(123); dispose(); }); test(`fetcher and source in ${name}`, () => { const [source, setSource] = createSignal(1); const [[data], dispose] = createRoot(dispose => [fn(source, v => v), dispose]); expect(data()).toBe(1); setSource(2); expect(data()).toBe(2); dispose(); }); } }); ================================================ FILE: packages/solid/test/resource.type-tests.ts ================================================ import { createResource, ResourceReturn, createSignal, Resource, Setter } from "../src/index.js"; import { InitializedResource, InitializedResourceReturn } from "../src/reactive/signal.js"; type Assert = T; // https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650 type Equals = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false; /* createResource inference tests */ // without source, initialValue // with fetcher { const resourceReturn = createResource( k => { return Promise.resolve(1); type Tests = Assert>> & Assert>; }, { storage: createSignal, name: "test", deferStream: true, onHydrated: (k, info) => { type Tests = Assert> & Assert>; } } ); } // without source // with fetcher, initialValue { const resourceReturn = createResource( k => { return Promise.resolve(1); type Tests = Assert< Equals> > & Assert>; }, { initialValue: 1, storage: createSignal, name: "test", deferStream: true, onHydrated: (k, info) => { type Tests = Assert> & Assert>; } } ); type ResourceActions = (typeof resourceReturn)[1]; type Tests = Assert>>; } // without initialValue // with source, fetcher { const resourceReturn = createResource( () => 1, k => { return Promise.resolve(1); type Tests = Assert>> & Assert>; }, { storage: createSignal, name: "test", deferStream: true, onHydrated: (k, info) => { type Tests = Assert> & Assert>; } } ); type ResourceActions = (typeof resourceReturn)[1]; type Tests = Assert>>; } // with source, fetcher, initialValue { const resourceReturn = createResource( () => 1, k => { return Promise.resolve(1); type Tests = Assert< Equals> > & Assert>; }, { initialValue: 1, storage: createSignal, name: "test", deferStream: true, onHydrated: (k, info) => { type Tests = Assert> & Assert>; } } ); } /* Resource type tests */ { let resource!: Resource; const resourceValue = resource(); let initializedResource!: InitializedResource; const initializedResourceValue = initializedResource(); type Tests = Assert> & Assert> & Assert< Equals > & Assert>; switch (resource.state) { case "errored": const errorValue = resource(); break; case "pending": const pendingValue = resource(); break; case "ready": const readyValue = resource(); break; case "refreshing": const refreshingValue = resource(); break; case "unresolved": const unresolvedValue = resource(); break; // this is weird but it works type Test = Assert> & Assert> & Assert> & Assert> & Assert>; } switch (initializedResource.state) { case "errored": const errorValue = initializedResource(); break; case "ready": const readyValue = initializedResource(); break; case "refreshing": const refreshingValue = initializedResource(); break; // this is weird but it works type Test = Assert> & Assert> & Assert>; } } ================================================ FILE: packages/solid/test/scheduler.spec.ts ================================================ /** @vitest-environment jsdom */ import { describe, expect, test } from "vitest"; import { cancelCallback, requestCallback } from "../src/index.js"; import "./MessageChannel"; describe("requestCallback basics", () => { test("queue a task", () => new Promise(done => { requestCallback(() => { done(undefined); }); })); test("queue a task in correct order", () => new Promise(done => { let count = 0; requestCallback(() => { expect(count).toBe(2); done(undefined); }); requestCallback( () => { count++; expect(count).toBe(1); }, { timeout: 10 } ); requestCallback( () => { count++; expect(count).toBe(2); }, { timeout: 40 } ); })); test("supports cancelling a callback", () => new Promise((done, reject) => { const task = requestCallback(() => { reject(new Error("should not be called")); }); cancelCallback(task); requestCallback(() => done(undefined)); })); }); ================================================ FILE: packages/solid/test/signals.memo.spec.ts ================================================ import { describe, expect, it } from "vitest"; import { createRoot, createSignal, createMemo, Accessor } from "../src/index.js"; describe("createMemo", () => { describe("executing propagating", () => { it("propagates in topological order", () => { createRoot(() => { // // c1 // / \ // / \ // b1 b2 // \ / // \ / // a1 // var seq = "", [a1, setA1] = createSignal(false), b1 = createMemo( () => { a1(); seq += "b1"; }, undefined, { equals: false } ), b2 = createMemo( () => { a1(); seq += "b2"; }, undefined, { equals: false } ), c1 = createMemo( () => { (b1(), b2()); seq += "c1"; }, undefined, { equals: false } ); seq = ""; setA1(true); expect(seq).toBe("b1b2c1"); }); }); it("only propagates once with linear convergences", () => { createRoot(() => { // d // | // +---+---+---+---+ // v v v v v // f1 f2 f3 f4 f5 // | | | | | // +---+---+---+---+ // v // g var [d, setD] = createSignal(0), f1 = createMemo(() => d()), f2 = createMemo(() => d()), f3 = createMemo(() => d()), f4 = createMemo(() => d()), f5 = createMemo(() => d()), gcount = 0, g = createMemo(() => { gcount++; return f1() + f2() + f3() + f4() + f5(); }); gcount = 0; setD(1); expect(gcount).toBe(1); }); }); it("only propagates once with exponential convergence", () => { createRoot(() => { // d // | // +---+---+ // v v v // f1 f2 f3 // \ | / // O // / | \ // v v v // g1 g2 g3 // +---+---+ // v // h var [d, setD] = createSignal(0), f1 = createMemo(() => { return d(); }), f2 = createMemo(() => { return d(); }), f3 = createMemo(() => { return d(); }), g1 = createMemo(() => { return f1() + f2() + f3(); }), g2 = createMemo(() => { return f1() + f2() + f3(); }), g3 = createMemo(() => { return f1() + f2() + f3(); }), hcount = 0, h = createMemo(() => { hcount++; return g1() + g2() + g3(); }); hcount = 0; setD(1); expect(hcount).toBe(1); }); }); it("does not trigger downstream computations unless changed", () => { createRoot(() => { const [s1, set] = createSignal(1, { equals: false }); let order = ""; const t1 = createMemo(() => { order += "t1"; return s1(); }); createMemo(() => { order += "c1"; t1(); }); expect(order).toBe("t1c1"); order = ""; set(1); expect(order).toBe("t1"); order = ""; set(2); expect(order).toBe("t1c1"); }); }); it("applies updates to changed dependees in same order as createMemo", () => { createRoot(() => { const [s1, set] = createSignal(0); let order = ""; const t1 = createMemo(() => { order += "t1"; return s1() === 0; }); createMemo(() => { order += "c1"; return s1(); }); createMemo(() => { order += "c2"; return t1(); }); expect(order).toBe("t1c1c2"); order = ""; set(1); expect(order).toBe("t1c2c1"); }); }); it("updates downstream pending computations", () => { createRoot(() => { const [s1, set] = createSignal(0); const [s2] = createSignal(0); let order = ""; const t1 = createMemo(() => { order += "t1"; return s1() === 0; }); createMemo(() => { order += "c1"; return s1(); }); createMemo(() => { order += "c2"; t1(); createMemo(() => { order += "c2_1"; return s2(); }); }); order = ""; set(1); expect(order).toBe("t1c2c2_1c1"); }); }); }); describe("with changing dependencies", () => { let i: () => boolean, setI: (v: boolean) => void; let t: () => number, setT: (v: number) => void; let e: () => number, setE: (v: number) => void; let fevals: number; let f: () => number; function init() { [i, setI] = createSignal(true); [t, setT] = createSignal(1); [e, setE] = createSignal(2); fevals = 0; f = createMemo(() => { fevals++; return i() ? t() : e(); }); fevals = 0; } it("updates on active dependencies", () => { createRoot(() => { init(); setT(5); expect(fevals).toBe(1); expect(f()).toBe(5); }); }); it("does not update on inactive dependencies", () => { createRoot(() => { init(); setE(5); expect(fevals).toBe(0); expect(f()).toBe(1); }); }); it("deactivates obsolete dependencies", () => { createRoot(() => { init(); setI(false); fevals = 0; setT(5); expect(fevals).toBe(0); }); }); it("activates new dependencies", () => { createRoot(() => { init(); setI(false); fevals = 0; setE(5); expect(fevals).toBe(1); }); }); it("ensures that new dependencies are updated before dependee", () => { createRoot(() => { var order = "", [a, setA] = createSignal(0), b = createMemo(() => { order += "b"; return a() + 1; }), c = createMemo(() => { order += "c"; const check = b(); if (check) { return check; } return e(); }), d = createMemo(() => { return a(); }), e = createMemo(() => { order += "d"; return d() + 10; }); expect(order).toBe("bcd"); order = ""; setA(-1); expect(order).toBe("bcd"); expect(c()).toBe(9); order = ""; setA(0); expect(order).toBe("bcd"); expect(c()).toBe(1); }); }); }); describe("with intercepting computations", () => { it("does not update subsequent pending computations after stale invocations", () => { createRoot(() => { const [s1, set1] = createSignal(1); const [s2, set2] = createSignal(false); let count = 0; /* s1 | +---+---+ t1 t2 c1 t3 \ / c3 [PN,PN,STL,void] */ const t1 = createMemo(() => s1() > 0); const t2 = createMemo(() => s1() > 0); const c1 = createMemo(() => s1()); const t3 = createMemo(() => { const a = s1(); const b = s2(); return a && b; }); createMemo(() => { t1(); t2(); c1(); t3(); count++; }); set2(true); expect(count).toBe(2); set1(2); expect(count).toBe(3); }); }); it("evaluates stale computations before dependees when trackers stay unchanged", () => { createRoot(() => { let [s1, set] = createSignal(1, { equals: false }); let order = ""; let t1 = createMemo(() => { order += "t1"; return s1() > 2; }); let t2 = createMemo(() => { order += "t2"; return s1() > 2; }); let c1 = createMemo( () => { order += "c1"; s1(); }, undefined, { equals: false } ); createMemo(() => { order += "c2"; t1(); t2(); c1(); }); order = ""; set(1); expect(order).toBe("t1t2c1c2"); order = ""; set(3); expect(order).toBe("t2c2t1c1"); }); }); it("evaluates nested trackings", () => { createRoot(() => { const [s1, set1] = createSignal(1); const [s2] = createSignal(1); let count = 0; let c1: () => number; createMemo(() => { c1 = createMemo(() => s2()); return s1(); }); createMemo(() => { count++; c1(); }); set1(2); expect(count).toBe(1); }); }); it("propagates in topological order", () => { createRoot(() => { const [s1, set] = createSignal(true); let order = ""; const t1 = createMemo(() => { order += "t1"; return s1(); }); const t2 = createMemo(() => { order += "t2"; return s1(); }); createMemo(() => { t1(); t2(); order += "c1"; }); order = ""; set(false); expect(order).toBe("t1t2c1"); }); }); it("does not evaluate dependencies with tracking sources that have not changed", () => { createRoot(() => { const [s1, set] = createSignal(1); let order = ""; let c2: () => boolean; createMemo(() => { order += "c1"; if (s1() > 1) { c2(); } }); const t1 = createMemo(() => { order += "t1"; return s1() < 3; }); const t2 = createMemo(() => { order += "t2"; return t1(); }); c2 = createMemo(() => { order += "c2"; return t2(); }); order = ""; set(2); expect(order).toBe("c1t1"); order = ""; set(3); expect(order).toBe("c1t1t2c2"); }); }); it("correctly marks downstream computations as stale on change", () => { createRoot(() => { const [s1, set] = createSignal(1); let order = ""; const t1 = createMemo(() => { order += "t1"; return s1(); }); const c1 = createMemo(() => { order += "c1"; return t1(); }); const c2 = createMemo(() => { order += "c2"; return c1(); }); createMemo(() => { order += "c3"; return c2(); }); order = ""; set(2); expect(order).toBe("t1c1c2c3"); }); }); }); describe("with unending changes", () => { it("throws when continually setting a direct dependency", () => { createRoot(() => { const [d, set] = createSignal(1); expect(() => { createMemo(() => { return set(d() + 1); }); }).toThrow(); }); }); it("throws when continually setting an indirect dependency", () => { createRoot(() => { let i = 2; const [d, set] = createSignal(1), f1 = createMemo(() => d()), f2 = createMemo(() => f1()), f3 = createMemo(() => f2()); expect(() => { createMemo(() => { f3(); set(i++); }); }).toThrow(); }); }); }); describe("with circular dependencies", () => { it("throws when cycle created by modifying a branch", () => { createRoot(() => { var [d, set] = createSignal(1), f: Accessor = createMemo(() => (f ? f() : d()), undefined, { equals: false }); expect(() => { set(0); }).toThrow(); }); }); }); }); ================================================ FILE: packages/solid/test/signals.spec.ts ================================================ /** @vitest-environment jsdom */ import { describe, expect, test } from "vitest"; import { createRoot, createSignal, createEffect, createRenderEffect, createComputed, createReaction, createDeferred, createMemo, createSelector, untrack, on, onMount, onCleanup, catchError, createContext, useContext, getOwner, runWithOwner } from "../src/index.js"; import "./MessageChannel"; describe("Create signals", () => { test("Create and read a Signal", () => { const [value] = createSignal(5); expect(value()).toBe(5); }); test("Create and read a Signal with comparator", () => { const [value] = createSignal(5, { equals: (a, b) => a === b }); expect(value()).toBe(5); }); test("Create and read a Memo", () => { createRoot(() => { const memo = createMemo(() => "Hello"); expect(memo()).toBe("Hello"); }); }); test("Create and read a Memo with initial value", () => { createRoot(() => { const memo = createMemo(i => `${i} John`, "Hello"); expect(memo()).toBe("Hello John"); }); }); test("Create onMount", () => { let temp: string; createRoot(() => { onMount(() => (temp = "impure")); }); expect(temp!).toBe("impure"); }); test("Create a Effect with explicit deps", () => { let temp: string; createRoot(() => { const [sign] = createSignal("thoughts"); const fn = on(sign, v => (temp = `impure ${v}`)); createEffect(fn); createEffect(on(sign, v => (temp = `impure ${v}`))); }); expect(temp!).toBe("impure thoughts"); }); test("Create a Effect with multiple explicit deps", () => { let temp: string; createRoot(() => { const [sign] = createSignal("thoughts"); const [num] = createSignal(3); const fn = on([sign, num], v => (temp = `impure ${v[1]}`)); createEffect(fn); }); expect(temp!).toBe("impure 3"); }); test("Create a Effect with explicit deps and lazy evaluation", () => { let temp: string; const [sign, set] = createSignal("thoughts"); createRoot(() => { const fn = on(sign, v => (temp = `impure ${v}`), { defer: true }); createEffect(fn); }); expect(temp!).toBeUndefined(); set("minds"); expect(temp!).toBe("impure minds"); }); test("Create a Effect with explicit deps, lazy evaluation, and initial value", () => { let temp: string; const [sign, set] = createSignal("thoughts"); createRoot(() => { const fn = on(sign, (v, _, p) => (temp = `impure ${p} ${v}`), { defer: true }); createEffect(fn, "numbers"); }); expect(temp!).toBeUndefined(); set("minds"); expect(temp!).toBe("impure numbers minds"); }); }); describe("Update signals", () => { test("Create and update a Signal", () => { const [value, setValue] = createSignal(5); setValue(10); expect(value()).toBe(10); }); test("Create and update a Signal with fn", () => { const [value, setValue] = createSignal(5); setValue(p => p + 5); expect(value()).toBe(10); }); test("Create Signal and set different value", () => { const [value, setValue] = createSignal(5); setValue(10); expect(value()).toBe(10); }); test("Create Signal and set equivalent value", () => { const [value, setValue] = createSignal(5, { equals: (a, b) => a > b }); setValue(3); expect(value()).toBe(5); }); test("Create and read a Signal with function value", () => { const [value, setValue] = createSignal<() => string>(() => "Hi"); expect(value()()).toBe("Hi"); setValue(() => () => "Hello"); expect(value()()).toBe("Hello"); }); test("Create and trigger a Memo", () => { createRoot(() => { const [name, setName] = createSignal("John"), memo = createMemo(() => `Hello ${name()}`); expect(memo()).toBe("Hello John"); setName("Jake"); expect(memo()).toBe("Hello Jake"); }); }); test("Create Signal and set equivalent value not trigger Memo", () => { createRoot(() => { const [name, setName] = createSignal("John", { equals: (a, b) => b.startsWith("J") }), memo = createMemo(() => `Hello ${name()}`); expect(name()).toBe("John"); expect(memo()).toBe("Hello John"); setName("Jake"); expect(name()).toBe("John"); expect(memo()).toBe("Hello John"); }); }); test("Create and trigger a Memo in an effect", () => new Promise(done => { createRoot(() => { let temp: string; const [name, setName] = createSignal("John"), memo = createMemo(() => `Hello ${name()}`); createEffect(() => (temp = `${memo()}!!!`)); setTimeout(() => { expect(temp).toBe("Hello John!!!"); setName("Jake"); expect(temp).toBe("Hello Jake!!!"); done(undefined); }); }); })); test("Create and trigger an Effect", () => new Promise(done => { createRoot(() => { let temp: string; const [sign, setSign] = createSignal("thoughts"); createEffect(() => (temp = `unpure ${sign()}`)); setTimeout(() => { expect(temp).toBe("unpure thoughts"); setSign("mind"); expect(temp).toBe("unpure mind"); done(undefined); }); }); })); test("Create and trigger an Effect with function signals", () => new Promise(done => { createRoot(() => { let temp: string; const [sign, setSign] = createSignal<() => string>(() => "thoughts"); createEffect(() => (temp = `unpure ${sign()()}`)); setTimeout(() => { expect(temp).toBe("unpure thoughts"); setSign(() => () => "mind"); expect(temp).toBe("unpure mind"); done(undefined); }); }); })); test("Set signal returns argument", () => { const [_, setValue] = createSignal(); const res1: undefined = setValue(undefined); expect(res1).toBe(undefined); const res2: number = setValue(12); expect(res2).toBe(12); const res3 = setValue(Math.random() >= 0 ? 12 : undefined); expect(res3).toBe(12); const res4 = setValue(); expect(res4).toBe(undefined); }); }); describe("Untrack signals", () => { test("Mute an effect", () => new Promise(done => { createRoot(() => { let temp: string; const [sign, setSign] = createSignal("thoughts"); createEffect(() => (temp = `unpure ${untrack(sign)}`)); setTimeout(() => { expect(temp).toBe("unpure thoughts"); setSign("mind"); expect(temp).toBe("unpure thoughts"); done(undefined); }); }); })); }); describe("Batching signals", () => { test("Mute an effect", () => new Promise(done => { createRoot(() => { let temp: string; const [sign, setSign] = createSignal("thoughts"); createEffect(() => (temp = `unpure ${untrack(sign)}`)); setTimeout(() => { expect(temp).toBe("unpure thoughts"); setSign("mind"); expect(temp).toBe("unpure thoughts"); done(undefined); }); }); })); }); describe("Effect grouping of signals", () => { test("Groups updates", () => new Promise(done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(0); const [b, setB] = createSignal(0); createEffect(() => { setA(1); setB(1); }); createMemo(() => (count += a() + b())); setTimeout(() => { expect(count).toBe(2); done(undefined); }); }); })); test("Groups updates with repeated sets", () => new Promise(done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(0); createEffect(() => { setA(1); setA(4); }); createMemo(() => (count += a())); setTimeout(() => { expect(count).toBe(4); done(undefined); }); }); })); test("Groups updates with fn setSignal", () => new Promise(done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(0); const [b, setB] = createSignal(0); createEffect(() => { setA(a => a + 1); setB(b => b + 1); }); createMemo(() => (count += a() + b())); setTimeout(() => { expect(count).toBe(2); done(undefined); }); }); })); test("Groups updates with fn setSignal with repeated sets", () => new Promise(done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(0); createEffect(() => { setA(a => a + 1); setA(a => a + 2); }); createMemo(() => (count += a())); setTimeout(() => { expect(count).toBe(3); done(undefined); }); }); })); test("Test cross setting in a effect update", () => new Promise(done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(1); const [b, setB] = createSignal(0); createEffect(() => { setA(a => a + b()); }); createMemo(() => (count += a())); setTimeout(() => { setB(b => b + 1); setTimeout(() => { expect(count).toBe(3); done(undefined); }); }); }); })); test("Handles errors gracefully", () => new Promise(done => { createRoot(() => { let error: Error; const [a, setA] = createSignal(0); const [b, setB] = createSignal(0); createEffect(() => { try { setA(1); throw new Error("test"); } catch (e) { error = e as Error; } }); createMemo(() => a() + b()); setTimeout(() => { expect(a()).toBe(1); expect(b()).toBe(0); setA(2); expect(a()).toBe(2); expect(error).toBeInstanceOf(Error); expect(error.message).toBe("test"); done(undefined); }); }); })); test("Multiple sets", () => new Promise(done => { createRoot(() => { let count = 0; const [a, setA] = createSignal(0); createEffect(() => { setA(1); setA(0); }); createMemo(() => (count = a())); setTimeout(() => { expect(count).toBe(0); done(undefined); }); }); })); }); describe("Typecheck computed and effects", () => { test("No default value can return undefined", () => { createRoot(() => { let count = 0; const [sign, setSign] = createSignal("thoughts"); const fn = (arg?: number) => { count++; sign(); expect(arg).toBe(undefined); return arg; }; createComputed(fn); createRenderEffect(fn); createEffect(fn); setTimeout(() => { expect(count).toBe(3); setSign("update"); expect(count).toBe(6); }); }); }); test("Default value never receives undefined", () => { createRoot(() => { let count = 0; const [sign, setSign] = createSignal("thoughts"); const fn = (arg: number) => { count++; sign(); expect(arg).toBe(12); return arg; }; createComputed(fn, 12); createRenderEffect(fn, 12); createEffect(fn, 12); setTimeout(() => { expect(count).toBe(3); setSign("update"); expect(count).toBe(6); }); }); }); }); describe("onCleanup", () => { test("Clean an effect", () => new Promise(done => { createRoot(() => { let temp: string; const [sign, setSign] = createSignal("thoughts"); createEffect(() => { sign(); onCleanup(() => (temp = "after")); }); setTimeout(() => { expect(temp).toBeUndefined(); setSign("mind"); expect(temp).toBe("after"); done(undefined); }); }); })); test("Explicit root disposal", () => { let temp: string | undefined, disposer: () => void; createRoot(dispose => { disposer = dispose; onCleanup(() => (temp = "disposed")); }); expect(temp).toBeUndefined(); disposer!(); expect(temp).toBe("disposed"); }); test("Failed Root disposal from arguments", () => { let temp: string | undefined, disposer: () => void; createRoot((...args) => { disposer = args[0]; onCleanup(() => (temp = "disposed")); }); expect(temp).toBeUndefined(); expect(disposer!).toThrow(); }); }); describe("catchError", () => { test("No Handler", () => { expect(() => createRoot(() => { throw "fail"; }) ).toThrow("fail"); }); test("Top level", () => { let errored = false; expect(() => createRoot(() => { catchError( () => { throw "fail"; }, () => (errored = true) ); }) ).not.toThrow("fail"); expect(errored).toBe(true); }); test("Nested in catchError", () => { let errored = false; expect(() => createRoot(() => { catchError( () => { catchError( () => { throw "fail"; }, error => { throw error; } ); }, () => (errored = true) ); }) ).not.toThrow("fail"); expect(errored).toBe(true); }); test("In initial effect", () => { let errored = false; expect(() => createRoot(() => { createEffect(() => { catchError( () => { throw "fail"; }, () => (errored = true) ); }); }) ).not.toThrow("fail"); expect(errored).toBe(true); }); // test("With multiple error handlers", () => { // let errored = false; // let errored2 = false; // expect(() => // createRoot(() => { // createEffect(() => { // onError(() => (errored = true)); // onError(() => (errored2 = true)); // throw "fail"; // }); // }) // ).not.toThrow("fail"); // expect(errored).toBe(true); // expect(errored2).toBe(true); // }); test("In update effect", () => { let errored = false; expect(() => createRoot(() => { const [s, set] = createSignal(0); createEffect(() => { const v = s(); catchError( () => { if (v) throw "fail"; }, () => (errored = true) ); }); set(1); }) ).not.toThrow("fail"); expect(errored).toBe(true); }); test("In initial nested effect", () => { let errored = false; expect(() => createRoot(() => { createEffect(() => { createEffect(() => { catchError( () => { throw "fail"; }, () => (errored = true) ); }); }); }) ).not.toThrow("fail"); expect(errored).toBe(true); }); test("In nested update effect", () => { let errored = false; expect(() => createRoot(() => { const [s, set] = createSignal(0); createEffect(() => { createEffect(() => { const v = s(); catchError( () => { if (v) throw "fail"; }, () => (errored = true) ); }); }); set(1); }) ).not.toThrow("fail"); expect(errored).toBe(true); }); test("In nested update effect different levels", () => { let errored = false; expect(() => createRoot(() => { const [s, set] = createSignal(0); createEffect(() => { catchError( () => createEffect(() => { const v = s(); if (v) throw "fail"; }), () => (errored = true) ); }); set(1); }) ).not.toThrow("fail"); expect(errored).toBe(true); }); test("In nested memo", () => { let errored = false; expect(() => createRoot(() => { createMemo(() => { catchError( () => { createEffect(() => {}); throw new Error("fail"); }, () => (errored = true) ); }); }) ).not.toThrow("fail"); expect(errored).toBe(true); }); }); describe("createDeferred", () => { test("simple defer", () => new Promise(done => { createRoot(() => { const [s, set] = createSignal("init"), r = createDeferred(s, { timeoutMs: 20 }); expect(r()).toBe("init"); set("Hi"); expect(r()).toBe("init"); setTimeout(() => { expect(r()).toBe("Hi"); done(undefined); }, 100); }); })); }); describe("createSelector", () => { test("simple selection", () => new Promise(done => { createRoot(() => { const [s, set] = createSignal(), isSelected = createSelector(s); let count = 0; const list = Array.from({ length: 100 }, (_, i) => createMemo(() => { count++; return isSelected(i) ? "selected" : "no"; }) ); expect(count).toBe(100); expect(list[3]()).toBe("no"); setTimeout(() => { count = 0; set(3); expect(count).toBe(1); expect(list[3]()).toBe("selected"); count = 0; set(6); expect(count).toBe(2); expect(list[3]()).toBe("no"); expect(list[6]()).toBe("selected"); set(undefined); expect(count).toBe(3); expect(list[6]()).toBe("no"); set(5); expect(count).toBe(4); expect(list[5]()).toBe("selected"); done(undefined); }); }); })); test("double selection", () => new Promise(done => { createRoot(() => { const [s, set] = createSignal(-1), isSelected = createSelector(s); let count = 0; const list = Array.from({ length: 100 }, (_, i) => [ createMemo(() => { count++; return isSelected(i) ? "selected" : "no"; }), createMemo(() => { count++; return isSelected(i) ? "oui" : "non"; }) ]); expect(count).toBe(200); expect(list[3][0]()).toBe("no"); expect(list[3][1]()).toBe("non"); setTimeout(() => { count = 0; set(3); expect(count).toBe(2); expect(list[3][0]()).toBe("selected"); expect(list[3][1]()).toBe("oui"); count = 0; set(6); expect(count).toBe(4); expect(list[3][0]()).toBe("no"); expect(list[6][0]()).toBe("selected"); expect(list[3][1]()).toBe("non"); expect(list[6][1]()).toBe("oui"); done(undefined); }); }); })); test("zero index", () => new Promise(done => { createRoot(() => { const [s, set] = createSignal(-1), isSelected = createSelector(s); let count = 0; const list = [ createMemo(() => { count++; return isSelected(0) ? "selected" : "no"; }) ]; expect(count).toBe(1); expect(list[0]()).toBe("no"); setTimeout(() => { count = 0; set(0); expect(count).toBe(1); expect(list[0]()).toBe("selected"); count = 0; set(-1); expect(count).toBe(1); expect(list[0]()).toBe("no"); done(undefined); }); }); })); }); describe("create and use context", () => { test("createContext without arguments defaults to undefined", () => { const context = createContext(); const res = useContext(context); expect(res).toBe(undefined); }); }); describe("createRoot", () => { test("roots with dispose function unused are unowned", () => { createRoot(_ => { const root1 = getOwner()!; createRoot(_ => { const root2 = getOwner()!; createRoot(() => { const root3 = getOwner()!; expect(root2.owner).toBe(root1); expect(root3.owner).toBe(null); }); }); }); }); test("Allows to define detachedOwner", () => { let owner1: any; let owner2: any; let owner3: any; let owner4: any; let owner5: any; createRoot(_ => (owner1 = getOwner()!)); createRoot(_ => (owner2 = getOwner()!), owner1); createRoot(_ => { owner3 = getOwner()!; createRoot(_ => (owner4 = getOwner()!)); createRoot(_ => (owner5 = getOwner()!), null); }); expect(owner1.owner).toBe(null); expect(owner2.owner).toBe(owner1); expect(owner3.owner).toBe(null); expect(owner4.owner).toBe(owner3); expect(owner5.owner).toBe(null); }); }); describe("runWithOwner", () => { test("Top level owner execute and disposal", () => { let effectRun = false; let cleanupRun = false; const [owner, dispose] = createRoot(dispose => { return [getOwner()!, dispose]; }); runWithOwner(owner, () => { createEffect(() => (effectRun = true)); onCleanup(() => (cleanupRun = true)); expect(effectRun).toBe(false); expect(cleanupRun).toBe(false); }); expect(effectRun).toBe(true); expect(cleanupRun).toBe(false); dispose(); expect(cleanupRun).toBe(true); }); }); describe("createReaction", () => { test("Create and trigger a Reaction", () => new Promise(done => { createRoot(() => { let count = 0; const [sign, setSign] = createSignal("thoughts"); const track = createReaction(() => count++); expect(count).toBe(0); track(sign); expect(count).toBe(0); setTimeout(() => { expect(count).toBe(0); setSign("mind"); expect(count).toBe(1); setSign("body"); expect(count).toBe(1); track(sign); setSign("everything"); expect(count).toBe(2); done(undefined); }); }); })); }); ================================================ FILE: packages/solid/test/signals.type-tests.ts ================================================ import { createEffect, createComputed, createRenderEffect, createMemo, Accessor, on, createSignal, createSelector, Signal, Setter } from "../src/index.js"; class Animal { #animal = null; } class Dog extends Animal { #dog = null; } ////////////////////////////////////////////////////////////////////////// // createEffect //////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// createEffect(() => { return "hello"; }, "init"); createEffect(prev => { // @ts-expect-error FIXME prev is inferred as unknown, so not assignable to string|number. Can we make it inferred? const p: string = prev; return p + "hello"; }, "init"); createEffect((prev: string) => { const p: string = prev; return p + "hello"; }, "init"); createEffect(() => { return "hello"; }, 123); createEffect(prev => { // @ts-expect-error FIXME prev is inferred as unknown, so not assignable to string|number. Can we make it inferred? const p: string = prev; return p + "hello"; }, 123); createEffect((prev: number | string) => { const p: number | string = prev; return p + "hello"; }, 123); createEffect(() => { return "hello"; }); createEffect(_prev => { return "hello"; }); createEffect(_prev => {}); createEffect((v: number | string): number => 123, "asdf"); createEffect((num: number | undefined): number | undefined => 123); createEffect((num?: number): number | undefined => 123); createEffect((v: number | string): number => 123, 123); createEffect((v: number | string): number => 123, 123); // @ts-expect-error undefined initial value not assignable to input parameter createEffect((v: number | boolean): number | boolean => false); createEffect((v: Animal): Dog => new Dog(), new Dog()); createEffect((v: Animal): Dog => new Dog(), new Animal()); createEffect( // @ts-expect-error the Animal arg is not assignable to the Dog parameter (v: Dog): Dog => new Dog(), new Animal() ); // @ts-expect-error the missing second arg is undefined, and undefined is not assignable to the Animal parameter createEffect((v: Animal): Dog => new Dog()); createEffect( // @ts-expect-error because if number|boolean were returnable from the passed-in function, it wouldn't be assignable to the input of that function. // TODO can we improve this? Technically, the return type of the function is always assignable to number|boolean, which is really all we should care about. (v: number | string): number => 123, 123 ); createEffect((v: number | string): number => 123, "asdf"); createEffect((v: number) => 123, 123); createEffect( (v?: number) => { return 123; }, 123, {} ); createEffect(() => 123); createEffect(() => {}); createEffect( // @ts-expect-error the void return is not assignable to the number|undefined parameter (v?: number) => {} ); // @ts-expect-error undefined initial value is not assignable to the number parameter createEffect((v: number) => 123); createEffect(() => { return 123; }, 123); createEffect(() => { return 123; }, undefined); createEffect((v: number) => 123, 123); createEffect((v?: number) => 123, undefined); createEffect( // @ts-expect-error the void return is not assignable to the number|undefined parameter (v?: number) => {}, 123 ); createEffect( // @ts-expect-error the void return is not assignable to the explicitly specified number|undefined return v => {}, 123 ); createEffect( // @ts-expect-error the void return is not assignable to the number|undefined parameter (v?: number) => {}, undefined ); createEffect(v => {}); // useless, but ok // @ts-expect-error the void return is not assignable to the number|undefined parameter createEffect((v: number) => {}); createEffect( // @ts-expect-error void return not assignable to number parameter (v: number) => {}, 123 ); createEffect( // @ts-expect-error undefined second arg is not assignable to the number parameter (v: number) => {}, undefined ); // @ts-expect-error undefined second arg is not assignable to the number parameter createEffect((v: number) => 123, undefined); // @ts-expect-error void not assignable to number|undefined createEffect((v?: number) => {}, 123); ////////////////////////////////////////////////////////////////////////// // createComputed //////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// createComputed(() => { return "hello"; }, "init"); createComputed(prev => { // @ts-expect-error FIXME prev is inferred as unknown, so not assignable to string|number. Can we make it inferred? const p: string = prev; return p + "hello"; }, "init"); createComputed((prev: string) => { const p: string = prev; return p + "hello"; }, "init"); createComputed(() => { return "hello"; }, 123); createComputed(prev => { // @ts-expect-error FIXME prev is inferred as unknown, so not assignable to string|number. Can we make it inferred? const p: string = prev; return p + "hello"; }, 123); createComputed((prev: number | string) => { const p: number | string = prev; return p + "hello"; }, 123); createComputed(() => { return "hello"; }); createComputed(_prev => { return "hello"; }); createComputed(_prev => {}); createComputed((v: number | string): number => 123, "asdf"); createComputed((num: number | undefined): number | undefined => 123); createComputed((num?: number): number | undefined => 123); createComputed((v: number | string): number => 123, 123); createComputed((v: number | string): number => 123, 123); // @ts-expect-error undefined initial value not assignable to input parameter createComputed((v: number | boolean): number | boolean => false); createComputed((v: Animal): Dog => new Dog(), new Dog()); createComputed((v: Animal): Dog => new Dog(), new Animal()); createComputed( // @ts-expect-error the Animal arg is not assignable to the Dog parameter (v: Dog): Dog => new Dog(), new Animal() ); // @ts-expect-error the missing second arg is undefined, and undefined is not assignable to the Animal parameter createComputed((v: Animal): Dog => new Dog()); createComputed( // @ts-expect-error because if number|boolean were returnable from the passed-in function, it wouldn't be assignable to the input of that function. // TODO can we improve this? Technically, the return type of the function is always assignable to number|boolean, which is really all we should care about. (v: number | string): number => 123, 123 ); createComputed((v: number | string): number => 123, "asdf"); createComputed((v: number) => 123, 123); createComputed( (v?: number) => { return 123; }, 123, {} ); createComputed(() => 123); createComputed(() => {}); createComputed( // @ts-expect-error the void return is not assignable to the number|undefined parameter (v?: number) => {} ); // @ts-expect-error undefined initial value is not assignable to the number parameter createComputed((v: number) => 123); createComputed(() => { return 123; }, 123); createComputed(() => { return 123; }, undefined); createComputed((v: number) => 123, 123); createComputed((v?: number) => 123, undefined); createComputed( // @ts-expect-error the void return is not assignable to the number|undefined parameter (v?: number) => {}, 123 ); createComputed( // @ts-expect-error the void return is not assignable to the explicitly specified number|undefined return v => {}, 123 ); createComputed( // @ts-expect-error the void return is not assignable to the number|undefined parameter (v?: number) => {}, undefined ); createComputed(v => {}); // useless, but ok // @ts-expect-error the void return is not assignable to the number|undefined parameter createComputed((v: number) => {}); createComputed( // @ts-expect-error void return not assignable to number parameter (v: number) => {}, 123 ); createComputed( // @ts-expect-error undefined second arg is not assignable to the number parameter (v: number) => {}, undefined ); // @ts-expect-error undefined second arg is not assignable to the number parameter createComputed((v: number) => 123, undefined); // @ts-expect-error void not assignable to number|undefined createComputed((v?: number) => {}, 123); ////////////////////////////////////////////////////////////////////////// // createRenderEffect //////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// createRenderEffect(() => { return "hello"; }, "init"); createRenderEffect(prev => { // @ts-expect-error FIXME prev is inferred as unknown, so not assignable to string|number. Can we make it inferred? const p: string = prev; return p + "hello"; }, "init"); createRenderEffect((prev: string) => { const p: string = prev; return p + "hello"; }, "init"); createRenderEffect(() => { return "hello"; }, 123); createRenderEffect(prev => { // @ts-expect-error FIXME prev is inferred as unknown, so not assignable to string|number. Can we make it inferred? const p: string = prev; return p + "hello"; }, 123); createRenderEffect((prev: number | string) => { const p: number | string = prev; return p + "hello"; }, 123); createRenderEffect(() => { return "hello"; }); createRenderEffect(_prev => { return "hello"; }); createRenderEffect(_prev => {}); createRenderEffect((v: number | string): number => 123, "asdf"); createRenderEffect((num: number | undefined): number | undefined => 123); createRenderEffect((num?: number): number | undefined => 123); createRenderEffect((v: number | string): number => 123, 123); createRenderEffect((v: number | string): number => 123, 123); // @ts-expect-error undefined initial value not assignable to input parameter createRenderEffect((v: number | boolean): number | boolean => false); createRenderEffect((v: Animal): Dog => new Dog(), new Dog()); createRenderEffect((v: Animal): Dog => new Dog(), new Animal()); createRenderEffect( // @ts-expect-error the Animal arg is not assignable to the Dog parameter (v: Dog): Dog => new Dog(), new Animal() ); // @ts-expect-error the missing second arg is undefined, and undefined is not assignable to the Animal parameter createRenderEffect((v: Animal): Dog => new Dog()); createRenderEffect( // @ts-expect-error because if number|boolean were returnable from the passed-in function, it wouldn't be assignable to the input of that function. // TODO can we improve this? Technically, the return type of the function is always assignable to number|boolean, which is really all we should care about. (v: number | string): number => 123, 123 ); createRenderEffect((v: number | string): number => 123, "asdf"); createRenderEffect((v: number) => 123, 123); createRenderEffect( (v?: number) => { return 123; }, 123, {} ); createRenderEffect(() => 123); createRenderEffect(() => {}); createRenderEffect( // @ts-expect-error the void return is not assignable to the number|undefined parameter (v?: number) => {} ); // @ts-expect-error undefined initial value is not assignable to the number parameter createRenderEffect((v: number) => 123); createRenderEffect(() => { return 123; }, 123); createRenderEffect(() => { return 123; }, undefined); createRenderEffect((v: number) => 123, 123); createRenderEffect((v?: number) => 123, undefined); createRenderEffect( // @ts-expect-error the void return is not assignable to the number|undefined parameter (v?: number) => {}, 123 ); createRenderEffect( // @ts-expect-error the void return is not assignable to the explicitly specified number|undefined return v => {}, 123 ); createRenderEffect( // @ts-expect-error the void return is not assignable to the number|undefined parameter (v?: number) => {}, undefined ); createRenderEffect(v => {}); // useless, but ok // @ts-expect-error the void return is not assignable to the number|undefined parameter createRenderEffect((v: number) => {}); createRenderEffect( // @ts-expect-error void return not assignable to number parameter (v: number) => {}, 123 ); createRenderEffect( // @ts-expect-error undefined second arg is not assignable to the number parameter (v: number) => {}, undefined ); // @ts-expect-error undefined second arg is not assignable to the number parameter createRenderEffect((v: number) => 123, undefined); // @ts-expect-error void not assignable to number|undefined createRenderEffect((v?: number) => {}, 123); ////////////////////////////////////////////////////////////////////////// // createMemo //////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// createMemo((v: number | string): number => 123, "asdf"); createMemo((num: number | undefined): number | undefined => 123); // Return type should be `Accessor` // Not sure how to write a test for this, because `Accessor` is assignable to `Accessor`. let c1 = createMemo((num?: number): number | undefined => undefined); let n = c1(); // @ts-expect-error n might be undefined const n2 = n + 3; // n is undefined createMemo((v: number | string): number => 123, 123); createMemo((v: number | string): number => 123, 123); // @ts-expect-error undefined initial value not assignable to input parameter createMemo((v: number | boolean): number | boolean => false); createMemo((v: Animal): Dog => new Dog(), new Dog()); createMemo((v: Animal): Dog => new Dog(), new Animal()); createMemo( // @ts-expect-error the Animal arg is not assignable to the Dog parameter (v: Dog): Dog => new Dog(), new Animal() ); // @ts-expect-error the missing second arg is undefined, and undefined is not assignable to the Animal parameter createMemo((v: Animal): Dog => new Dog()); createMemo( // @ts-expect-error because if number|boolean were returnable from the passed-in function, it wouldn't be assignable to the input of that function. // TODO can we improve this? Technically, the return type of the function is always assignable to number|boolean, which is really all we should care about. (v: number | string): number => 123, 123 ); createMemo((v: number | string): number => 123, "asdf"); createMemo((v: number) => 123, 123); const mv0 = createMemo(() => { return "hello"; }, "init"); const mv0t: string = mv0(); const mv1 = createMemo(prev => { // @ts-expect-error FIXME prev is inferred as unknown, so not assignable to string|number. Can we make it inferred? const p: string = prev; return p + "hello"; }, "init"); const mv1t: string = mv1(); const mv11 = createMemo((prev: string) => { const p: string = prev; return p + "hello"; }, "init"); const mv11t: string = mv11(); const mv2 = createMemo(() => { return "hello"; }, 123); const mv2t: string = mv2(); const mv3 = createMemo(prev => { // @ts-expect-error FIXME prev is inferred as unknown, so not assignable to string|number. Can we make it inferred? const p: string | number = prev; return p + "hello"; }, 123); const mv3t: string = mv3(); const mv31 = createMemo((prev: string | number) => { const p: string | number = prev; return p + "hello"; }, 123); const mv31t: string = mv31(); const mv4 = createMemo(() => { return "hello"; }); const mv4t: string = mv4(); const mv5 = createMemo(_prev => { return "hello"; }); const mv5t: string = mv5(); const mv6 = createMemo(() => {}); const mv6t: void = mv6(); const mv7 = createMemo(_prev => {}); const mv7t: void = mv7(); const v1 = createMemo( (v?: number) => { return 123; }, 123, {} ); const v2 = createMemo(() => 123); // @ts-expect-error number return value can not be assigned to the input string arg const v3 = createMemo((v: string) => 123); const v4 = createMemo(v => 123); const v5 = createMemo(() => {}); // @ts-expect-error because void return of the effect function cannot be assigned to number | undefined of the effect function's parameter const v6 = createMemo((v?: number) => {}); const v7 = createMemo(() => 123, 123); const v8 = createMemo(() => 123, undefined); // @ts-expect-error undefined initial value is not assignable to the number parameter const v9 = createMemo((v: number) => 123); const v10 = createMemo((v: number) => 123, 123); const v11 = createMemo((v?: number) => 123, 123); const v12 = createMemo((v?: number) => 123, undefined); const v13 = createMemo((v?: number) => 123, 123); const v14 = createMemo( // @ts-expect-error because void return of the effect function cannot be assigned to number | undefined of the effect function's parameter (v?: number) => {}, 123 ); const v15 = createMemo( // @ts-expect-error effect function does not match the specified memo type v => {}, 123 ); const v16 = createMemo( // @ts-expect-error because void return of the effect function cannot be assigned to number | undefined of the effect function's parameter (v?: number) => {}, undefined ); const v17 = createMemo(v => {}); // @ts-expect-error because void return of the effect function cannot be assigned to number | undefined of the effect function's parameter const v18 = createMemo((v: number) => {}); const v19 = createMemo( // @ts-expect-error void is not assignable to anything (v: number) => {}, 123 ); const v20 = createMemo( // @ts-expect-error clearly undefined can't be assigned into the input parameter of the effect function (v: number) => {}, undefined ); const v21 = // @ts-expect-error and this one makes complete sense, undefined cannot go into the effect function's number parameter. createMemo((v: number) => 123, undefined); const v22 = createMemo( // @ts-expect-error because void return of the effect function cannot be assigned to number | undefined of the effect function's parameter (v?: number) => {}, 123 ); const m: Accessor = createMemo( (v?: number) => { return 123; }, 123, {} ); const m2: Accessor = createMemo(() => 123); // @ts-expect-error void can't be assigned to anything! const m3: // Accessor = createMemo(() => {}); const m4: Accessor = createMemo(() => {}); // @ts-expect-error void can't be assigned to anything! const m5: Accessor = createMemo( // @ts-expect-error void can't be assigned to anything! (v?: number) => {} ); const mm5 = createMemo( // @ts-expect-error void can't be assigned to anything! (v?: number) => {} ); const m6: Accessor = createMemo(() => 123, 123); const m7: Accessor = createMemo(() => 123, undefined); const m8: Accessor = createMemo((v: number) => 123, 123); const m9: Accessor = createMemo((v?: number) => 123, undefined); const m10: Accessor = createMemo( // @ts-expect-error void can't be assigned to anything! (v?: number) => {}, 123 ); const m11: Accessor = createMemo( // @ts-expect-error void can't be assigned to anything! v => {}, 123 ); const m12: Accessor = createMemo( // @ts-expect-error void can't be assigned to anything! (v?: number) => {}, undefined ); const m13 = createMemo((v?: number): number | undefined => 123, undefined); const testm13: Accessor = m13; const m14: Accessor = createMemo((v?: number): number => 123, undefined); const m15: Accessor = // @ts-expect-error undefined initial value is not assignable to the number parameter createMemo((v: number): number => 123); const m16: Accessor = // @ts-expect-error undefined initial value can't be assign to the number parameter createMemo((v: number): number => 123, undefined); const m17: Accessor = // @ts-expect-error no overload matches because the second string arg cannot be assigned to the number|boolean parameter. createMemo((v: number | boolean): number => 123, "asdf"); const m18: Accessor = // @ts-expect-error undefined initial value is not assignable to the number parameter createMemo((v: number | boolean): number => 123); const m19: Accessor = // @ts-expect-error undefined initial value is not assignable to the number parameter createMemo((v: number | string): number => 123); const m20: Accessor = // @ts-expect-error because the number return cannot be assigned to the boolean|string parameter createMemo((v: boolean | string): number => 123); const m21: Accessor = // @ts-expect-error because the second boolean arg cannot be assigned to the number|string parameter. createMemo((v: number | string): number => 123, true); const m22: Accessor = createMemo((v: number | string): number => 123, "asdf"); const m23: Accessor = createMemo((v?: number | string): number => 123, undefined); const m24: Accessor = // @ts-expect-error true not assignable to number|string createMemo((v: number | string): number => 123, true); const asdf = createMemo(() => num()); // @ts-expect-error Accessor is not assignable to Accessor const asdf2: // Accessor = asdf; ////////////////////////////////////////////////////////////////////////// // on //////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// const one = (): number => 123; const two = () => Boolean(Math.random()); createEffect( on( (): [number, boolean] => [1, true], (input, prevInput, prev) => { const [one, two]: [number, boolean] = input; if (prevInput) { const [prevOne, prevTwo]: [number, boolean] = prevInput; } // @ts-expect-error FIXME computed type is unknown, should be `number`. const _prev: number = prev; return one + +two; } ) ); const onMemo1 = createMemo( on([one, two], (input, prevInput, prev) => { const [one, two]: [number, boolean] = input; if (prevInput) { const [prevOne, prevTwo]: [number, boolean] = prevInput; } // @ts-expect-error FIXME computed type is unknown, should be `number`. const _prev: number = prev; return one + +two; }) ); const onMemo2: Accessor = onMemo1; createEffect( on( [one, two], (input, prevInput, prev) => { const [one, two]: [number, boolean] = input; if (prevInput) { const [prevOne, prevTwo]: [number, boolean] = prevInput; } // @ts-expect-error FIXME computed type is unknown, should be `number`. const _prev: number = prev; return one + +two; }, { defer: true } ) ); const onMemo3 = createMemo( on( [one, two], (input, prevInput, prev) => { const [one, two]: [number, boolean] = input; if (prevInput) { const [prevOne, prevTwo]: [number, boolean] = prevInput; } // @ts-expect-error FIXME computed type is unknown, should be `number`. const _prev: number = prev; return one + +two; }, { defer: true } ) ); // @ts-expect-error when deferred the type includes undefined const onMemo4: Accessor = onMemo3; // Allow passing boolean to defer const memoCreator = (defer: boolean) => createMemo( on( [one, two], (input, prevInput, prev) => { const [one, two]: [number, boolean] = input; if (prevInput) { const [prevOne, prevTwo]: [number, boolean] = prevInput; } // @ts-expect-error FIXME computed type is unknown, should be `number`. const _prev: number = prev; return one + +two; }, { defer } ) ); const memoCreator1: Accessor = memoCreator(true); const memoCreator2: Accessor = memoCreator(false); ////////////////////////////////////////////////////////////////////////// // createSelector //////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// { const selector = createSelector(() => 123); const bool: boolean = selector(123); // @ts-expect-error Argument of type 'string' is not assignable to parameter of type 'number'. ts(2345) const bool2: boolean = selector("123"); } { const selector = createSelector(() => 123, undefined, { name: "test" }); const bool: boolean = selector(123); // @ts-expect-error Argument of type 'string' is not assignable to parameter of type 'number'. ts(2345) const bool2: boolean = selector("123"); } { const selector = createSelector(() => 123); const bool: boolean = selector(123); const bool2: boolean = selector("123"); // @ts-expect-error Argument of type 'null' is not assignable to parameter of type 'string | number'. ts(2345) const bool3: boolean = selector(null); } { const selector = createSelector( () => 123, (key, source) => key === source ); const bool: boolean = selector(123); // @ts-expect-error Argument of type 'string' is not assignable to parameter of type 'number'. ts(2345) const bool2: boolean = selector("123"); } { const selector = createSelector( () => 123, (key: string, source) => Number(key) === source ); // @ts-expect-error Argument of type 'number' is not assignable to parameter of type 'string'. ts(2345) const bool: boolean = selector(123); const bool2: boolean = selector("123"); } { const selector = createSelector( () => 123, (key, source) => key === source, { name: "test" } ); const bool: boolean = selector(123); // @ts-expect-error Argument of type 'string' is not assignable to parameter of type 'number'. ts(2345) const bool2: boolean = selector("123"); } ////////////////////////////////////////////////////////////////////////// // variations of signal types //////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// const [num, setN] = createSignal(1); let n1: number = num(); setN(123); setN(n => (n1 = n + 1)); // @ts-expect-error Expected 1 arguments, but got 0. ts(2554) setN(); const [num3, setN3] = createSignal(); // @ts-expect-error Type 'undefined' is not assignable to type 'number'. ts(2322) let n3: number = num3(); setN3(123); setN3(undefined); // ok, accepts undefined // @ts-expect-error Object is possibly 'undefined'. ts(2532) (the `n` value) setN3(n => (n3 = n + 1)); setN3(); // ok, accepts undefined // @ts-expect-error Argument of type 'boolean' is not assignable to parameter of type 'number'. ts(2345) const [num4, setN4] = createSignal(true); const [bool, setBool] = createSignal(true); let b1: boolean = bool(); setBool(false); setBool(b => (b1 = !b)); // @ts-expect-error Expected 1 arguments, but got 0. ts(2554) setBool(); const [bool2, setBool2] = createSignal(); // @ts-expect-error Type 'undefined' is not assignable to type 'number'. ts(2322) let n4: boolean = bool2(); setBool2(false); setBool2(undefined); // ok, accepts undefined setBool2(n => (n4 = !n)); // ok because undefined is being converted to boolean // @ts-expect-error Type 'boolean | undefined' is not assignable to type 'boolean'. ts(2322) setBool2(n => (n4 = n)); setBool2(); // ok, accepts undefined const [func, setFunc] = createSignal(() => 1); // @ts-expect-error 1 is not assignable to function (no overload matches) setFunc(() => 1); setFunc(() => (): 1 => 1); // ok, set the value to a function const fn: () => 1 = func(); // ok, returns function value const n5: 1 = func()(); const [func2, setFunc2] = createSignal<() => number>(() => 1); // @ts-expect-error number is not assignable to function (no overload matches) setFunc2(() => 1); setFunc2(() => () => 1); // ok, set the value to a function const fn2: () => number = func2(); // ok, returns function value const n6: number = func2()(); const [stringOrFunc1, setStringOrFunc1] = createSignal<(() => number) | string>(""); // @ts-expect-error number should not be assignable to string setStringOrFunc1(() => 1); const sf1: () => 1 = setStringOrFunc1(() => () => 1); const sf2: "oh yeah" = setStringOrFunc1("oh yeah"); const sf3: "oh yeah" = setStringOrFunc1(() => "oh yeah"); // @ts-expect-error cannot set signal to undefined setStringOrFunc1(); // @ts-expect-error cannot set signal to undefined setStringOrFunc1(undefined); // @ts-expect-error return value might be string const sf6: () => number = stringOrFunc1(); const sf7: (() => number) | string | undefined = stringOrFunc1(); const sf8: (() => number) | string = stringOrFunc1(); const [stringOrFunc2, setStringOrFunc2] = createSignal<(() => number) | string>(); // @ts-expect-error number should not be assignable to string setStringOrFunc2(() => 1); const sf9: () => 1 = setStringOrFunc2(() => () => 1); const sf10: "oh yeah" = setStringOrFunc2("oh yeah"); const sf11: "oh yeah" = setStringOrFunc2(() => "oh yeah"); const sf12: undefined = setStringOrFunc2(); const sf13: undefined = setStringOrFunc2(undefined); const sf14: (() => number) | string | undefined = stringOrFunc2(); // @ts-expect-error return value might be undefined const sf15: (() => number) | string = stringOrFunc2(); const [stringOrNumber, setStringOrNumber] = createSignal(1); setStringOrNumber(1 as number | string); setStringOrNumber(1); setStringOrNumber("" as number | string); setStringOrNumber(""); function createGenericSignal(): Signal { const [generic, setGeneric] = createSignal(); const customSet: Setter = (v?) => setGeneric(v!); return [generic, (v?) => setGeneric(v!)]; } function createInitializedSignal(init: T): Signal { const [generic, setGeneric] = createSignal(init); const customSet: Setter = (v?) => setGeneric(v!); return [generic, (v?) => setGeneric(v!)]; } interface KobalteBaseSelectProps { options: Array
` unless the target is document.head or `isSVG` is true. setting `useShadow` to true places the element in a shadow root to isolate styles. * * @description https://docs.solidjs.com/reference/components/portal */ export function Portal(props: { mount?: Node; useShadow?: T; isSVG?: S; ref?: | (S extends true ? SVGGElement : HTMLDivElement) | (( el: (T extends true ? { readonly shadowRoot: ShadowRoot } : {}) & (S extends true ? SVGGElement : HTMLDivElement) ) => void); children: JSX.Element; }) { const { useShadow } = props, marker = document.createTextNode(""), mount = () => props.mount || document.body, owner = getOwner(); let content: undefined | (() => JSX.Element); let hydrating = !!sharedConfig.context; createEffect( () => { // basically we backdoor into a sort of renderEffect here if (hydrating) (getOwner() as any).user = hydrating = false; content || (content = runWithOwner(owner, () => createMemo(() => props.children))); const el = mount(); if (el instanceof HTMLHeadElement) { const [clean, setClean] = createSignal(false); const cleanup = () => setClean(true); createRoot(dispose => insert(el, () => (!clean() ? content!() : dispose()), null)); onCleanup(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; }, configurable: true }); insert(renderRoot, content); el.appendChild(container); props.ref && (props as any).ref(container); onCleanup(() => el.removeChild(container)); } }, undefined, { render: !hydrating } ); return marker; } export type DynamicProps> = { [K in keyof P]: P[K]; } & { component: T | undefined; }; /** * Renders an arbitrary component or element with the given props * * This is a lower level version of the `Dynamic` component, useful for * performance optimizations in libraries. Do not use this unless you know * what you are doing. * ```typescript * const element = () => multiline() ? 'textarea' : 'input'; * createDynamic(element, { value: value() }); * ``` * @description https://docs.solidjs.com/reference/components/dynamic */ export function createDynamic( component: () => T | undefined, props: ComponentProps ): JSX.Element { const cached = createMemo(component); return createMemo(() => { const component = cached(); switch (typeof component) { case "function": if (isDev) Object.assign(component, { [$DEVCOMP]: true }); return untrack(() => component(props)); case "string": const isSvg = SVGElements.has(component); const el = sharedConfig.context ? getNextElement() : createElement( component, isSvg, untrack(() => props.is) ); spread(el, props, isSvg); return el; default: break; } }) as unknown as JSX.Element; } /** * Renders an arbitrary custom or native component and passes the other props * ```typescript * * ``` * @description https://docs.solidjs.com/reference/components/dynamic */ export function Dynamic(props: DynamicProps): JSX.Element { const [, others] = splitProps(props, ["component"]); return createDynamic(() => props.component, others as ComponentProps); } ================================================ FILE: packages/solid/web/src/jsx.ts ================================================ export type { JSX } from "../../types/jsx.js"; ================================================ FILE: packages/solid/web/src/server-mock.ts ================================================ //@ts-nocheck function throwInBrowser(func: Function) { const err = new Error(`${func.name} is not supported in the browser, returning undefined`); console.error(err); } export function renderToString( fn: () => T, options?: { nonce?: string; renderId?: string; } ): string { throwInBrowser(renderToString); } export function renderToStringAsync( fn: () => T, options?: { timeoutMs?: number; nonce?: string; renderId?: string; } ): Promise { throwInBrowser(renderToStringAsync); } export function renderToStream( fn: () => T, options?: { nonce?: string; renderId?: string; onCompleteShell?: (info: { write: (v: string) => void }) => void; onCompleteAll?: (info: { write: (v: string) => void }) => void; } ): { pipe: (writable: { write: (v: string) => void }) => void; pipeTo: (writable: WritableStream) => void; } { throwInBrowser(renderToStream); } export function ssr(template: string[] | string, ...nodes: any[]): { t: string } {} export function ssrElement( name: string, props: any, children: any, needsId: boolean ): { t: string } {} export function ssrClassList(value: { [k: string]: boolean }): string {} export function ssrStyle(value: { [k: string]: string }): string {} export function ssrAttribute(key: string, value: boolean): string {} export function ssrHydrationKey(): string {} export function resolveSSRNode(node: any): string {} export function escape(html: string): string {} /** * @deprecated Replaced by ssrElement */ export function ssrSpread(props: any, isSVG: boolean, skipChildren: boolean): void {} export type LegacyResults = { startWriting: () => void; }; /** * @deprecated Replaced by renderToStream */ export function pipeToWritable( fn: () => T, writable: WritableStream, options?: { nonce?: string; onReady?: (res: LegacyResults) => void; onCompleteAll?: () => void; } ): void; /** * @deprecated Replaced by renderToStream */ export function pipeToNodeWritable( fn: () => T, writable: { write: (v: string) => void }, options?: { nonce?: string; onReady?: (res: LegacyResults) => void; onCompleteAll?: () => void; } ): void; ================================================ FILE: packages/solid/web/storage/package.json ================================================ { "name": "solid-js/web/storage", "main": "./dist/storage.cjs", "module": "./dist/storage.js", "types": "./types/index.d.ts", "type": "module", "sideEffects": false, "exports": { ".": { "types": "./types/index.d.ts", "import": "./dist/storage.js", "require": "./dist/storage.cjs" } } } ================================================ FILE: packages/solid/web/storage/src/index.ts ================================================ import { AsyncLocalStorage } from "node:async_hooks"; import type { RequestEvent } from "solid-js/web"; import { isServer, RequestContext } from "solid-js/web"; // using global on a symbol for locating it later and detaching for environments that don't support it. export function provideRequestEvent(init: T, cb: () => U): U { if (!isServer) throw new Error("Attempting to use server context in non-server build"); const ctx: AsyncLocalStorage = ((globalThis as any)[RequestContext] = (globalThis as any)[RequestContext] || new AsyncLocalStorage()); return ctx.run(init, cb); } ================================================ FILE: packages/solid/web/storage/tsconfig.build.json ================================================ { "extends": "../../../../tsconfig.json", "compilerOptions": { "outDir": "./types", "baseUrl": "src", "paths": { "solid-js": ["../../.."], "solid-js/web": ["../.."], "solid-js/jsx-runtime": ["../../../src/jsx"], "solid-js/jsx-dev-runtime": ["../../../src/jsx"], } }, "include": ["./src"] } ================================================ FILE: packages/solid/web/storage/tsconfig.json ================================================ { "extends": "./tsconfig.build.json", "include": ["./src"] } ================================================ FILE: packages/solid/web/test/context.spec.tsx ================================================ /** * @jsxImportSource solid-js * @vitest-environment jsdom */ import { describe, expect, it } from "vitest"; import { createContext, useContext } from "../../src/index.js"; import { render, Show } from "../src/index.js"; describe("Testing Context", () => { const ThemeContext = createContext("light"); const Component = () => { const theme = useContext(ThemeContext); return
{theme}
; }; const CondComponent = () => { const theme = useContext(ThemeContext); return (
{theme}
); }; const div = document.createElement("div"); it("should create context properly", () => { expect(ThemeContext.id).toBeDefined(); expect(ThemeContext.defaultValue).toBe("light"); }); it("should work with single provider child", () => { render( () => ( ), div ); expect((div.firstChild as HTMLDivElement).innerHTML).toBe("dark"); div.innerHTML = ""; }); it("should work with single conditional provider child", () => { render( () => ( ), div ); expect((div.firstChild as HTMLDivElement).innerHTML).toBe("dark"); div.innerHTML = ""; }); it("should work with multi provider child", () => { render( () => (
Hi
), div ); expect((div.firstChild!.nextSibling! as HTMLDivElement).innerHTML).toBe("dark"); div.innerHTML = ""; }); it("should work with multi conditional provider child", () => { render( () => (
Hi
), div ); expect((div.firstChild!.nextSibling! as HTMLDivElement).innerHTML).toBe("dark"); div.innerHTML = ""; }); it("should work with dynamic multi provider child", () => { const child = () => ; render( () => (
Hi
{child()}
), div ); expect((div.firstChild!.nextSibling! as HTMLDivElement).innerHTML).toBe("dark"); div.innerHTML = ""; }); it("should work with dynamic multi conditional provider child", () => { const child = () => ; render( () => (
Hi
{child()}
), div ); expect((div.firstChild!.nextSibling! as HTMLDivElement).innerHTML).toBe("dark"); div.innerHTML = ""; }); }); ================================================ FILE: packages/solid/web/test/dynamic.spec.tsx ================================================ /** * @jsxImportSource solid-js * @vitest-environment jsdom */ import { describe, expect, test, beforeEach, afterEach } from "vitest"; import { createRoot, createSignal, Component, JSX } from "../../src/index.js"; import { createStore } from "../../store/src/index.js"; import { Dynamic } from "../src/index.js"; describe("Testing Dynamic control flow", () => { let div!: HTMLDivElement, disposer: () => void; interface ExampleProps { id: string; } const [comp, setComp] = createSignal | keyof JSX.IntrinsicElements>(), [name, setName] = createSignal("Smith"); const Component = () => (
), CompA: Component = props =>
Hi {props.id}
, CompB: Component = props => Yo {props.id}; beforeEach(() => { createRoot(dispose => { disposer = dispose; ; }); }); afterEach(() => disposer()); test("Toggle Dynamic control flow", () => { expect(div.innerHTML).toBe(""); setComp(() => CompA); expect(div.innerHTML).toBe("
Hi Smith
"); setName("Smithers"); expect(div.innerHTML).toBe("
Hi Smithers
"); setComp(() => CompB); expect(div.innerHTML).toBe("Yo Smithers"); setComp("h1"); expect(div.innerHTML).toBe(`

`); setName("Sunny"); expect(div.innerHTML).toBe(`

`); expect(div.querySelector("h1")).toBeInstanceOf(HTMLElement); }); test("Renders SVG elements", () => { setComp("svg"); expect(div.querySelector("svg")).toBeInstanceOf(SVGSVGElement); setComp("path"); expect(div.querySelector("path")).toBeInstanceOf(SVGElement); }); }); describe("Testing Dynamic with state spread", () => { let div!: HTMLDivElement, disposer: () => void; interface ExampleProps { id: string; } const [comp, setComp] = createSignal | keyof JSX.IntrinsicElements>(), [state, setState] = createStore({ id: "Smith" }); const Component = () => (
), CompA: Component = props =>
Hi {props.id}
, CompB: Component = props => Yo {props.id}; 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("id", "Smithers"); expect(div.innerHTML).toBe("
Hi Smithers
"); setComp(() => CompB); expect(div.innerHTML).toBe("Yo Smithers"); setComp("h1"); expect(div.innerHTML).toBe(`

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

`); expect(div.querySelector("h1")).toBeInstanceOf(HTMLElement); }); }); ================================================ FILE: packages/solid/web/test/element.spec.tsx ================================================ /** * @jsxImportSource solid-js * @vitest-environment jsdom */ import { describe, expect, test } from "vitest"; import { createRoot, createSignal, createUniqueId, JSX, children } from "../../src/index.js"; declare module "solid-js/jsx-runtime" { namespace JSX { interface Directives { getRef: boolean; } } } describe("Basic element attributes", () => { test("spread", () => { let div: HTMLDivElement; const props: JSX.HTMLAttributes = { id: "main", title: "main", children:

Hi

, ref: (ref: HTMLDivElement) => { div = ref; }, onClick: () => console.log("clicked") }, 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.innerHTML).toBe("

Hi

"); }); test("classList", () => { const classes = { first: true, second: false, "third fourth": true }, d = (
) as HTMLDivElement; expect(d.className).toBe("first third fourth"); }); test("ternary expression triggered", () => new Promise(done => { let div: HTMLDivElement; createRoot(() => { const [s, setS] = createSignal(0); div = (
{s() > 5 ? "Large" : "Small"}
) as HTMLDivElement; expect(div.innerHTML).toBe("Small"); setTimeout(() => { setS(7); expect(div.innerHTML).toBe("Large"); done(undefined); }); }); })); test("boolean expression triggered once", () => { let div1: HTMLDivElement, div2: HTMLDivElement; createRoot(() => { const [s, setS] = createSignal(6);
{s() > 5 && (div1 = (
) as HTMLDivElement)}
; div2 = div1; setS(7); expect(div1).toBe(div2); }); }); test("directives work properly", () => { let ref: HTMLDivElement, el!: HTMLDivElement, getRef = (el: HTMLDivElement) => (ref = el), d = (
) as HTMLDivElement; expect(ref!).toBe(el); }); test("uniqueId", () => { let div: HTMLDivElement; createRoot(() => { const id = createUniqueId(); div = (
) as HTMLDivElement; }); expect((div!.firstChild as HTMLLabelElement).htmlFor).toBe( (div!.firstChild!.nextSibling as HTMLInputElement).id ); }); test("children", () => { const Comp = (props: { children?: JSX.Element }) => { const c = children(() => props.children); return ( <> {c.toArray().map(i => (
{i}
))} ); }; const res: HTMLDivElement = createRoot(() => { return (
Hello Hello Jake
) as HTMLDivElement; }); expect(res.innerHTML).toBe( "
Hello
Hello
Jake
" ); }); }); ================================================ FILE: packages/solid/web/test/errorboundary.spec.tsx ================================================ /** * @jsxImportSource solid-js * @vitest-environment jsdom */ import { describe, expect, test } from "vitest"; import { createRoot, resetErrorBoundaries } from "../../src/index.js"; import { ErrorBoundary } from "../src/index.js"; describe("Testing ErrorBoundary control flow", () => { let div!: HTMLDivElement, disposer: () => void; const Component = () => { throw new Error("Failure"); }; let first = true; const Component2 = () => { if (first) { first = false; throw new Error("Failure"); } return "Success"; }; const Component3 = () => { throw null; }; test("Create an Error", () => { createRoot(dispose => { disposer = dispose;
; }); expect(div.innerHTML).toBe("Failed Miserably"); }); test("Create an Error with null", () => { createRoot(dispose => { disposer = dispose;
; }); expect(div.innerHTML).toBe("Failed Miserably"); }); test("Create an Error callback", () => { createRoot(dispose => { disposer = dispose;
e.message}>
; }); expect(div.innerHTML).toBe("Failure"); }); test("Create an Error callback and reset", () => { let r: () => void; createRoot(dispose => { disposer = dispose;
{ r = reset; return e.message; }} >
; }); expect(div.innerHTML).toBe("Failure"); r!(); expect(div.innerHTML).toBe("Success"); first = true; }); test("Create an Error global reset", () => { let r: () => void; createRoot(dispose => { disposer = dispose;
e.message}>
; }); expect(div.innerHTML).toBe("Failure"); resetErrorBoundaries(); expect(div.innerHTML).toBe("Success"); first = true; }); test("Create an Error in an Error Fallback", () => { createRoot(dispose => { disposer = dispose;
}>
; }); expect(div.innerHTML).toBe("Failed Miserably"); }); test("dispose", () => disposer()); }); ================================================ FILE: packages/solid/web/test/for.spec.tsx ================================================ /** * @jsxImportSource solid-js * @vitest-environment jsdom */ import { describe, expect, test } from "vitest"; import { createRoot, createSignal } from "../../src/index.js"; import { insert, For } from "../src/index.js"; describe("Testing an only child each control flow", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([n1, n2, n3, n4]); const Component = () => (
{item => item}
); function apply(array: string[]) { setList(array); expect(div.innerHTML).toBe(array.join("")); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("abcd"); } test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("abcd"); }); test("1 missing", () => { apply([n2, n3, n4]); apply([n1, n3, n4]); apply([n1, n2, n4]); apply([n1, n2, n3]); }); test("2 missing", () => { apply([n3, n4]); apply([n2, n4]); apply([n2, n3]); apply([n1, n4]); apply([n1, n3]); apply([n1, n2]); }); test("3 missing", () => { apply([n1]); apply([n2]); apply([n3]); apply([n4]); }); test("all missing", () => { apply([]); }); test("swaps", () => { apply([n2, n1, n3, n4]); apply([n3, n2, n1, n4]); apply([n4, n2, n3, n1]); }); test("rotations", () => { apply([n2, n3, n4, n1]); apply([n3, n4, n1, n2]); apply([n4, n1, n2, n3]); }); test("reversal", () => { apply([n4, n3, n2, n1]); }); test("full replace", () => { apply(["e", "f", "g", "h"]); }); test("swap backward edge", () => { setList(["milk", "bread", "chips", "cookie", "honey"]); setList(["chips", "bread", "cookie", "milk", "honey"]); }); test("dispose", () => disposer()); }); describe("Testing an multi child each control flow", () => { const div = document.createElement("div"); div.appendChild(document.createTextNode("z")); const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([n1, n2, n3, n4]); const Component = () => {item => item}; let disposer: () => void; function apply(array: string[]) { setList(array); expect(div.innerHTML).toBe(`${array.join("")}z`); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("abcdz"); } test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; insert(div, , div.firstChild); }); expect(div.innerHTML).toBe("abcdz"); }); test("1 missing", () => { apply([n2, n3, n4]); apply([n1, n3, n4]); apply([n1, n2, n4]); apply([n1, n2, n3]); }); test("2 missing", () => { apply([n3, n4]); apply([n2, n4]); apply([n2, n3]); apply([n1, n4]); apply([n1, n3]); apply([n1, n2]); }); test("3 missing", () => { apply([n1]); apply([n2]); apply([n3]); apply([n4]); }); test("all missing", () => { apply([]); }); test("swaps", () => { apply([n2, n1, n3, n4]); apply([n3, n2, n1, n4]); apply([n4, n2, n3, n1]); }); test("rotations", () => { apply([n2, n3, n4, n1]); apply([n3, n4, n1, n2]); apply([n4, n1, n2, n3]); }); test("reversal", () => { apply([n4, n3, n2, n1]); }); test("full replace", () => { apply(["e", "f", "g", "h"]); }); test("swap backward edge", () => { setList(["milk", "bread", "chips", "cookie", "honey"]); setList(["chips", "bread", "cookie", "milk", "honey"]); }); test("dispose", () => disposer()); }); describe("Testing an only child each control flow with fragment children", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([n1, n2, n3, n4]); const Component = () => (
{item => ( <> {item} {item} )}
); function apply(array: string[]) { setList(array); expect(div.innerHTML).toBe(array.map(p => `${p}${p}`).join("")); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("aabbccdd"); } test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("aabbccdd"); }); test("1 missing", () => { apply([n2, n3, n4]); apply([n1, n3, n4]); apply([n1, n2, n4]); apply([n1, n2, n3]); }); test("2 missing", () => { apply([n3, n4]); apply([n2, n4]); apply([n2, n3]); apply([n1, n4]); apply([n1, n3]); apply([n1, n2]); }); test("3 missing", () => { apply([n1]); apply([n2]); apply([n3]); apply([n4]); }); test("all missing", () => { apply([]); }); test("swaps", () => { apply([n2, n1, n3, n4]); apply([n3, n2, n1, n4]); apply([n4, n2, n3, n1]); }); test("rotations", () => { apply([n2, n3, n4, n1]); apply([n3, n4, n1, n2]); apply([n4, n1, n2, n3]); }); test("reversal", () => { apply([n4, n3, n2, n1]); }); test("full replace", () => { apply(["e", "f", "g", "h"]); }); test("swap backward edge", () => { setList(["milk", "bread", "chips", "cookie", "honey"]); setList(["chips", "bread", "cookie", "milk", "honey"]); }); test("dispose", () => disposer()); }); describe("Testing an only child each control flow with array children", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([n1, n2, n3, n4]); const Component = () => (
{item => [item, item]}
); function apply(array: string[]) { setList(array); expect(div.innerHTML).toBe(array.map(p => `${p}${p}`).join("")); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("aabbccdd"); } test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("aabbccdd"); }); test("1 missing", () => { apply([n2, n3, n4]); apply([n1, n3, n4]); apply([n1, n2, n4]); apply([n1, n2, n3]); }); test("2 missing", () => { apply([n3, n4]); apply([n2, n4]); apply([n2, n3]); apply([n1, n4]); apply([n1, n3]); apply([n1, n2]); }); test("3 missing", () => { apply([n1]); apply([n2]); apply([n3]); apply([n4]); }); test("all missing", () => { apply([]); }); test("swaps", () => { apply([n2, n1, n3, n4]); apply([n3, n2, n1, n4]); apply([n4, n2, n3, n1]); }); test("rotations", () => { apply([n2, n3, n4, n1]); apply([n3, n4, n1, n2]); apply([n4, n1, n2, n3]); }); test("reversal", () => { apply([n4, n3, n2, n1]); }); test("full replace", () => { apply(["e", "f", "g", "h"]); }); test("swap backward edge", () => { setList(["milk", "bread", "chips", "cookie", "honey"]); setList(["chips", "bread", "cookie", "milk", "honey"]); }); test("dispose", () => disposer()); }); describe("Testing each control flow with fallback", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([]); const Component = () => (
{item => item}
); test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("Empty"); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("abcd"); setList([]); expect(div.innerHTML).toBe("Empty"); }); test("dispose", () => disposer()); }); describe("Testing each that maps to undefined", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([]); const Component = () => (
{item => undefined}
); test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe(""); setList([]); expect(div.innerHTML).toBe(""); }); test("dispose", () => disposer()); }); describe("Testing each with indexes", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([]); const Component = () => (
{(item, i) => {item + i()}}
); test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("Hi"); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("a0b1c2d3"); setList([n2, n3, n4, n1]); expect(div.innerHTML).toBe("b0c1d2a3"); setList([n3, n4, n1]); expect(div.innerHTML).toBe("c0d1a2"); setList([n3, n2, n4, n1]); expect(div.innerHTML).toBe("c0b1d2a3"); setList([]); expect(div.innerHTML).toBe("Hi"); setList([n1]); expect(div.innerHTML).toBe("a0"); }); test("dispose", () => disposer()); }); ================================================ FILE: packages/solid/web/test/index.spec.tsx ================================================ /** * @jsxImportSource solid-js * @vitest-environment jsdom */ import { describe, expect, test } from "vitest"; import { createRoot, createSignal } from "../../src/index.js"; import { insert, Index } from "../src/index.js"; describe("Testing an only child each control flow", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([n1, n2, n3, n4]); const Component = () => (
{item => <>{item()}}
); function apply(array: string[]) { setList(array); expect(div.innerHTML).toBe(array.join("")); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("abcd"); } test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("abcd"); }); test("1 missing", () => { apply([n2, n3, n4]); apply([n1, n3, n4]); apply([n1, n2, n4]); apply([n1, n2, n3]); }); test("2 missing", () => { apply([n3, n4]); apply([n2, n4]); apply([n2, n3]); apply([n1, n4]); apply([n1, n3]); apply([n1, n2]); }); test("3 missing", () => { apply([n1]); apply([n2]); apply([n3]); apply([n4]); }); test("all missing", () => { apply([]); }); test("swaps", () => { apply([n2, n1, n3, n4]); apply([n3, n2, n1, n4]); apply([n4, n2, n3, n1]); }); test("rotations", () => { apply([n2, n3, n4, n1]); apply([n3, n4, n1, n2]); apply([n4, n1, n2, n3]); }); test("reversal", () => { apply([n4, n3, n2, n1]); }); test("full replace", () => { apply(["e", "f", "g", "h"]); }); test("swap backward edge", () => { setList(["milk", "bread", "chips", "cookie", "honey"]); setList(["chips", "bread", "cookie", "milk", "honey"]); }); test("dispose", () => disposer()); }); describe("Testing an multi child each control flow", () => { const div = document.createElement("div"); div.appendChild(document.createTextNode("z")); const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([n1, n2, n3, n4]); const Component = () => {item => <>{item()}}; let disposer: () => void; function apply(array: string[]) { setList(array); expect(div.innerHTML).toBe(`${array.join("")}z`); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("abcdz"); } test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; insert(div, , div.firstChild); }); expect(div.innerHTML).toBe("abcdz"); }); test("1 missing", () => { apply([n2, n3, n4]); apply([n1, n3, n4]); apply([n1, n2, n4]); apply([n1, n2, n3]); }); test("2 missing", () => { apply([n3, n4]); apply([n2, n4]); apply([n2, n3]); apply([n1, n4]); apply([n1, n3]); apply([n1, n2]); }); test("3 missing", () => { apply([n1]); apply([n2]); apply([n3]); apply([n4]); }); test("all missing", () => { apply([]); }); test("swaps", () => { apply([n2, n1, n3, n4]); apply([n3, n2, n1, n4]); apply([n4, n2, n3, n1]); }); test("rotations", () => { apply([n2, n3, n4, n1]); apply([n3, n4, n1, n2]); apply([n4, n1, n2, n3]); }); test("reversal", () => { apply([n4, n3, n2, n1]); }); test("full replace", () => { apply(["e", "f", "g", "h"]); }); test("swap backward edge", () => { setList(["milk", "bread", "chips", "cookie", "honey"]); setList(["chips", "bread", "cookie", "milk", "honey"]); }); test("dispose", () => disposer()); }); describe("Testing an only child each control flow with fragment children", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([n1, n2, n3, n4]); const Component = () => (
{item => ( <> {item} {item} )}
); function apply(array: string[]) { setList(array); expect(div.innerHTML).toBe(array.map(p => `${p}${p}`).join("")); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("aabbccdd"); } test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("aabbccdd"); }); test("1 missing", () => { apply([n2, n3, n4]); apply([n1, n3, n4]); apply([n1, n2, n4]); apply([n1, n2, n3]); }); test("2 missing", () => { apply([n3, n4]); apply([n2, n4]); apply([n2, n3]); apply([n1, n4]); apply([n1, n3]); apply([n1, n2]); }); test("3 missing", () => { apply([n1]); apply([n2]); apply([n3]); apply([n4]); }); test("all missing", () => { apply([]); }); test("swaps", () => { apply([n2, n1, n3, n4]); apply([n3, n2, n1, n4]); apply([n4, n2, n3, n1]); }); test("rotations", () => { apply([n2, n3, n4, n1]); apply([n3, n4, n1, n2]); apply([n4, n1, n2, n3]); }); test("reversal", () => { apply([n4, n3, n2, n1]); }); test("full replace", () => { apply(["e", "f", "g", "h"]); }); test("swap backward edge", () => { setList(["milk", "bread", "chips", "cookie", "honey"]); setList(["chips", "bread", "cookie", "milk", "honey"]); }); test("dispose", () => disposer()); }); describe("Testing an only child each control flow with array children", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([n1, n2, n3, n4]); const Component = () => (
{item => ( <> {item()} {item()} )}
); function apply(array: string[]) { setList(array); expect(div.innerHTML).toBe(array.map(p => `${p}${p}`).join("")); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("aabbccdd"); } test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("aabbccdd"); }); test("1 missing", () => { apply([n2, n3, n4]); apply([n1, n3, n4]); apply([n1, n2, n4]); apply([n1, n2, n3]); }); test("2 missing", () => { apply([n3, n4]); apply([n2, n4]); apply([n2, n3]); apply([n1, n4]); apply([n1, n3]); apply([n1, n2]); }); test("3 missing", () => { apply([n1]); apply([n2]); apply([n3]); apply([n4]); }); test("all missing", () => { apply([]); }); test("swaps", () => { apply([n2, n1, n3, n4]); apply([n3, n2, n1, n4]); apply([n4, n2, n3, n1]); }); test("rotations", () => { apply([n2, n3, n4, n1]); apply([n3, n4, n1, n2]); apply([n4, n1, n2, n3]); }); test("reversal", () => { apply([n4, n3, n2, n1]); }); test("full replace", () => { apply(["e", "f", "g", "h"]); }); test("swap backward edge", () => { setList(["milk", "bread", "chips", "cookie", "honey"]); setList(["chips", "bread", "cookie", "milk", "honey"]); }); test("dispose", () => disposer()); }); describe("Testing each control flow with fallback", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([]); const Component = () => (
{item => <>{item()}}
); test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("Empty"); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("abcd"); setList([]); expect(div.innerHTML).toBe("Empty"); }); test("dispose", () => disposer()); }); describe("Testing each that maps to undefined", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([]); const Component = () => (
{item => undefined}
); test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe(""); setList([]); expect(div.innerHTML).toBe(""); }); test("dispose", () => disposer()); }); describe("Testing each with indexes", () => { let div!: HTMLDivElement, disposer: () => void; const n1 = "a", n2 = "b", n3 = "c", n4 = "d"; const [list, setList] = createSignal([]); const Component = () => (
{(item, i) => {item() + i}}
); test("Create each control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); setList([n1, n2, n3, n4]); expect(div.innerHTML).toBe("a0b1c2d3"); setList([n2, n3, n4, n1]); expect(div.innerHTML).toBe("b0c1d2a3"); setList([n3, n4, n1]); expect(div.innerHTML).toBe("c0d1a2"); setList([n3, n2, n4, n1]); expect(div.innerHTML).toBe("c0b1d2a3"); setList([]); expect(div.innerHTML).toBe(""); }); test("dispose", () => disposer()); }); ================================================ FILE: packages/solid/web/test/portal.spec.tsx ================================================ /** * @jsxImportSource solid-js * @vitest-environment jsdom */ import { describe, expect, test } from "vitest"; import { createSignal } from "../../src/index.js"; import { render, clearDelegatedEvents, Portal, Show } from "../src/index.js"; describe("Testing a simple Portal", () => { let div = document.createElement("div"), disposer: () => void; const testMount = document.createElement("div"); const Component = () => Hi; test("Create portal control flow", () => { disposer = render(Component, div); expect(div.innerHTML).toBe(""); expect((testMount.firstChild as HTMLDivElement).innerHTML).toBe("Hi"); expect((testMount.firstChild as HTMLDivElement & { _$host: HTMLElement })._$host).toBe(div); }); test("dispose", () => { disposer(); expect(div.innerHTML).toBe(""); }); }); describe("Testing an SVG Portal", () => { let div = document.createElement("div"), disposer: () => void; const testMount = document.createElement("svg"); const Component = () => ( Hi ); test("Create portal control flow", () => { disposer = render(Component, div); expect(div.innerHTML).toBe(""); expect((testMount.firstChild as SVGGElement).innerHTML).toBe("Hi"); expect((testMount.firstChild as SVGGElement & { _$host: SVGElement })._$host).toBe(div); }); test("dispose", () => disposer()); }); describe("Testing a Portal to the head", () => { let div = document.createElement("div"), disposer: () => void, [s, set] = createSignal("A Meaningful Page Title"), [visible, setVisible] = createSignal(true); const Component = () => ( {s()} ); test("Create portal control flow", () => { disposer = render(Component, div); expect(div.innerHTML).toBe(""); expect(document.head.innerHTML).toBe("A Meaningful Page Title"); }); test("Update title text", () => { set("A New Better Page Title"); expect(document.head.innerHTML).toBe("A New Better Page Title"); }); test("Hide Portal", () => { setVisible(false); expect(document.head.innerHTML).toBe(""); setVisible(true); expect(document.head.innerHTML).toBe("A New Better Page Title"); }); test("dispose", async () => { expect(document.head.innerHTML).toBe("A New Better Page Title"); disposer(); expect(document.head.innerHTML).toBe(""); }); }); describe("Testing a Portal with Synthetic Events", () => { let div = document.createElement("div"), disposer: () => void, checkElem!: HTMLDivElement, testElem!: HTMLDivElement, clicked = false; const Component = () => (
(clicked = true)} /> ); test("Create portal control flow", () => { disposer = render(Component, div); expect(div.innerHTML).toBe(""); expect(testElem).toBe(checkElem.firstChild); }); test("Test portal element clicked", () => { expect(clicked).toBe(false); testElem.click(); expect(clicked).toBe(true); clicked = false; clearDelegatedEvents(); expect(clicked).toBe(false); testElem.click(); expect(clicked).toBe(false); }); test("dispose", () => disposer()); }); describe("Testing a Portal with direct reactive children", () => { let div = document.createElement("div"), disposer: () => void, [count, setCount] = createSignal(0), portalElem!: HTMLDivElement; const Component = () => {count()}; test("Create portal control flow", () => { disposer = render(Component, div); expect(div.innerHTML).toBe(""); expect(document.body.firstChild).toBe(portalElem); }); test("Click to trigger reactive update", () => { expect(portalElem.innerHTML).toBe("0"); setCount(count() + 1); expect(portalElem.innerHTML).toBe("1"); setCount(count() + 1); expect(portalElem.innerHTML).toBe("2"); }); test("dispose", () => disposer()); }); ================================================ FILE: packages/solid/web/test/server-mock.spec.tsx ================================================ import { expect, test, vi, beforeEach, afterAll } from "vitest"; import { renderToString, renderToStringAsync, renderToStream } from "../src/server-mock.js"; const origConsoleError = console.error; const mockConsoleError = vi.fn(); beforeEach(() => { console.error = mockConsoleError; mockConsoleError.mockReset(); }); afterAll(() => { console.error = origConsoleError; }); test("renderToString", () => { const result = renderToString(() => {}); const err: Error = mockConsoleError.mock.calls[0][0]; expect(err.message).toContain( "renderToString is not supported in the browser, returning undefined" ); expect(result).toBeUndefined(); }); test("renderToStringAsync", () => { const result = renderToStringAsync(() => {}); const err: Error = mockConsoleError.mock.calls[0][0]; expect(err.message).toContain( "renderToStringAsync is not supported in the browser, returning undefined" ); expect(result).toBeUndefined(); }); test("renderToStream", () => { const result = renderToStream(() => {}); const err: Error = mockConsoleError.mock.calls[0][0]; expect(err.message).toContain( "renderToStream is not supported in the browser, returning undefined" ); expect(result).toBeUndefined(); }); ================================================ FILE: packages/solid/web/test/show.spec.tsx ================================================ /** * @jsxImportSource solid-js * @vitest-environment jsdom */ import { describe, expect, test } from "vitest"; import { createRoot, createSignal } from "../../src/index.js"; import { Show } from "../src/index.js"; describe("Testing an only child show control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [count, setCount] = createSignal(0); const Component = () => (
= 5}>{count()}
); test("Create show control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); }); test("Toggle show control flow", () => { setCount(7); expect(div.innerHTML).toBe("7"); setCount(5); expect(div.innerHTML).toBe("5"); setCount(2); expect(div.innerHTML).toBe(""); }); test("dispose", () => disposer()); }); describe("Testing an only child show control flow with DOM children", () => { let div!: HTMLDivElement, disposer: () => void; const [count, setCount] = createSignal(0); const Component = () => (
= 5}> {count()} counted
); test("Create show control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); }); test("Toggle show control flow", () => { setCount(7); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("7"); setCount(5); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("5"); setCount(2); expect(div.innerHTML).toBe(""); }); test("dispose", () => disposer()); }); describe("Testing nonkeyed show control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [count, setCount] = createSignal(0); let whenExecuted = 0; let childrenExecuted = 0; function when() { whenExecuted++; return count(); } const Component = () => (
{count()} {childrenExecuted++}
); test("Create show control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); expect(whenExecuted).toBe(1); expect(childrenExecuted).toBe(0); }); test("Toggle show control flow", () => { setCount(7); expect(whenExecuted).toBe(2); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("7"); expect(childrenExecuted).toBe(1); setCount(5); expect(whenExecuted).toBe(3); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("5"); expect(childrenExecuted).toBe(1); setCount(5); expect(whenExecuted).toBe(3); setCount(0); expect(whenExecuted).toBe(4); expect(div.innerHTML).toBe(""); expect(childrenExecuted).toBe(1); setCount(5); expect(whenExecuted).toBe(5); }); test("dispose", () => disposer()); }); describe("Testing keyed show control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [count, setCount] = createSignal(0); let whenExecuted = 0; let childrenExecuted = 0; function when() { whenExecuted++; return count(); } const Component = () => (
{count()} {childrenExecuted++}
); test("Create show control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); expect(whenExecuted).toBe(1); expect(childrenExecuted).toBe(0); }); test("Toggle show control flow", () => { setCount(7); expect(whenExecuted).toBe(2); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("7"); expect(childrenExecuted).toBe(1); setCount(5); expect(whenExecuted).toBe(3); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("5"); expect(childrenExecuted).toBe(2); setCount(5); expect(whenExecuted).toBe(3); setCount(0); expect(whenExecuted).toBe(4); expect(div.innerHTML).toBe(""); expect(childrenExecuted).toBe(2); setCount(5); expect(whenExecuted).toBe(5); }); test("dispose", () => disposer()); }); describe("Testing nonkeyed function show control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [count, setCount] = createSignal(0); let whenExecuted = 0; let childrenExecuted = 0; function when() { whenExecuted++; return count(); } const Component = () => (
{count => ( <> {count()} {childrenExecuted++} )}
); test("Create show control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); expect(whenExecuted).toBe(1); expect(childrenExecuted).toBe(0); }); test("Toggle show control flow", () => { setCount(7); expect(whenExecuted).toBe(2); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("7"); expect(childrenExecuted).toBe(1); setCount(5); expect(whenExecuted).toBe(3); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("5"); expect(childrenExecuted).toBe(1); setCount(5); expect(whenExecuted).toBe(3); setCount(0); expect(whenExecuted).toBe(4); expect(div.innerHTML).toBe(""); expect(childrenExecuted).toBe(1); setCount(5); expect(whenExecuted).toBe(5); }); test("dispose", () => disposer()); }); describe("Testing keyed function show control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [count, setCount] = createSignal(0); let executed = 0; const Component = () => (
{count => ( <> {count} {executed++} )}
); test("Create show control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); expect(executed).toBe(0); }); test("Toggle show control flow", () => { setCount(7); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("7"); expect(executed).toBe(1); setCount(5); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("5"); expect(executed).toBe(2); setCount(0); expect(div.innerHTML).toBe(""); expect(executed).toBe(2); }); test("dispose", () => disposer()); }); describe("Testing an only child show control flow with keyed function", () => { let div!: HTMLDivElement, disposer: () => void; const [data, setData] = createSignal<{ count: number }>(); const Component = () => (
{({ count }) => ( <> {count} counted )}
); test("Create show control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); }); test("Toggle show control flow", () => { setData({ count: 7 }); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("7"); setData({ count: 5 }); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("5"); setData({ count: 2 }); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("2"); }); test("dispose", () => disposer()); }); describe("Testing an only child show control flow with non-keyed function", () => { let div!: HTMLDivElement, disposer: () => void; const [data, setData] = createSignal<{ count: number }>(); const Component = () => (
{data => ( <> {data().count} counted )}
); test("Create show control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe(""); }); test("Toggle show control flow", () => { setData({ count: 7 }); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("7"); setData({ count: 5 }); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("5"); setData({ count: 2 }); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("2"); }); test("dispose", () => disposer()); }); describe("Testing an only child show control flow with DOM children and fallback", () => { let div!: HTMLDivElement, disposer: () => void; const [count, setCount] = createSignal(0); const Component = () => (
= 5} fallback={Too Low}> {count()}
); test("Create when control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("Too Low"); }); test("Toggle show control flow", () => { setCount(7); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("7"); setCount(5); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("5"); setCount(2); expect((div.firstChild as HTMLSpanElement).innerHTML).toBe("Too Low"); }); test("dispose", () => disposer()); }); ================================================ FILE: packages/solid/web/test/suspense.spec.tsx ================================================ /** * @jsxImportSource solid-js * @vitest-environment jsdom */ import { describe, expect, test, beforeEach, afterEach, vi } from "vitest"; import "../../test/MessageChannel"; import { lazy, createSignal, createResource, useTransition, enableScheduling } from "../../src/index.js"; import { render, Suspense, SuspenseList } from "../src/index.js"; import { createStore } from "../../store/src/index.js"; enableScheduling(); beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); describe("Testing Basics", () => { test("Children are reactive", () => { let div = document.createElement("div"); let increment: () => void; render(() => { const [count, setCount] = createSignal(0); increment = () => setCount(count() + 1); return {count()}; }, div); expect(div.innerHTML).toBe("0"); increment!(); expect(div.innerHTML).toBe("1"); }); }); describe("Testing Suspense", () => { let div = document.createElement("div"), disposer: () => void, resolvers: Function[] = [], [triggered, trigger] = createSignal(); const LazyComponent = lazy(() => new Promise(r => resolvers.push(r))), ChildComponent = (props: { greeting: string }) => { const [value] = createResource( triggered, () => new Promise(r => setTimeout(() => r("Jo"), 300)), { initialValue: "" } ); return ( <> {props.greeting} {value()} ); }, Component = () => ( . ); test("Create Suspense control flow", () => { disposer = render(Component, div); expect(div.innerHTML).toBe("Loading"); }); test("Toggle Suspense control flow", async () => { for (const r of resolvers) r({ default: ChildComponent }); await Promise.resolve(); vi.runAllTimers(); await Promise.resolve(); expect(div.innerHTML).toBe("Hi, .Hello "); }); test("Toggle with refresh transition", async () => { const [pending, start] = useTransition(); let finished = false; start(() => trigger("Jo")).then(() => (finished = true)); expect(div.innerHTML).toBe("Hi, .Hello "); expect(finished).toBe(false); // wait trigger resource refetch await Promise.resolve(); expect(div.innerHTML).toBe("Hi, .Hello "); expect(pending()).toBe(true); expect(finished).toBe(false); // Exhausts create-resource setTimeout vi.runAllTimers(); // wait update suspense state await Promise.resolve(); // wait update computation vi.runAllTimers(); // wait write signal suc await Promise.resolve(); vi.runAllTimers(); await Promise.resolve(); expect(div.innerHTML).toBe("Hi, Jo.Hello Jo"); expect(pending()).toBe(false); expect(finished).toBe(true); }); test("Toggle with store and refresh transition", async () => { const [store, setStore] = createStore({ count: 0 }); const [pending, start] = useTransition(); let finished = false; start(() => { setStore({ count: 1 }); trigger("Jack"); }).then(() => (finished = true)); expect(store.count).toBe(0); expect(finished).toBe(false); // wait trigger resource refetch await Promise.resolve(); expect(store.count).toBe(0); expect(pending()).toBe(true); expect(finished).toBe(false); // Exhausts create-resource setTimeout vi.runAllTimers(); // wait update suspense state await Promise.resolve(); // wait update computation vi.runAllTimers(); // Await the rest of the things, TODO: figure out what these are await Promise.resolve(); vi.runAllTimers(); await Promise.resolve(); expect(pending()).toBe(false); expect(finished).toBe(true); expect(store.count).toBe(1); }); test("dispose", () => { div.innerHTML = ""; disposer(); }); }); describe("SuspenseList", () => { const promiseFactory = (time: number) => { return (v: string) => new Promise(r => { setTimeout(() => { r(v); }, time); }); }, A = () => { const [value] = createResource("A", promiseFactory(200)); return
{value()}
; }, B = () => { const [value] = createResource("B", promiseFactory(100)); return
{value()}
; }, C = () => { const [value] = createResource("C", promiseFactory(300)); return
{value()}
; }; test("revealOrder together", async () => { const div = document.createElement("div"), Comp = () => ( Loading 1
}>
Loading 2
}> Loading 3
}> ); const dispose = render(Comp, div); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(110); await Promise.resolve(); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(100); // wait effect update await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
C
"); dispose(); }); test("revealOrder forwards", async () => { const div = document.createElement("div"), Comp = () => ( Loading 1
}> Loading 2
}> Loading 3
}> ); const dispose = render(Comp, div); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(110); await Promise.resolve(); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
C
"); dispose(); }); test("revealOrder forwards hidden", async () => { const div = document.createElement("div"), Comp = () => ( Loading 1
}> Loading 2
}> Loading 3
}> ); const dispose = render(Comp, div); expect(div.innerHTML).toBe(""); vi.advanceTimersByTime(110); await Promise.resolve(); expect(div.innerHTML).toBe(""); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
C
"); dispose(); }); test("revealOrder forwards", async () => { const div = document.createElement("div"), Comp = () => ( Loading 1
}> Loading 2
}> Loading 3
}> ); const dispose = render(Comp, div); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(110); await Promise.resolve(); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
C
"); dispose(); }); test("revealOrder forwards collapse", async () => { const div = document.createElement("div"), Comp = () => ( Loading 1
}> Loading 2
}> Loading 3
}> ); const dispose = render(Comp, div); expect(div.innerHTML).toBe("
Loading 1
"); vi.advanceTimersByTime(110); await Promise.resolve(); expect(div.innerHTML).toBe("
Loading 1
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
C
"); dispose(); }); test("revealOrder backwards collapse", async () => { const div = document.createElement("div"), Comp = () => ( Loading 1
}> Loading 2}> Loading 3}> ); const dispose = render(Comp, div); expect(div.innerHTML).toBe("
Loading 3
"); vi.advanceTimersByTime(110); await Promise.resolve(); expect(div.innerHTML).toBe("
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
C
"); dispose(); }); test("nested SuspenseList together", async () => { const div = document.createElement("div"), Comp = () => ( Loading 1}>
Loading 2}> Loading 3}> ); const dispose = render(Comp, div); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(110); await Promise.resolve(); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
C
"); dispose(); }); test("nested SuspenseList forwards", async () => { const div = document.createElement("div"), Comp = () => ( Loading 1}>
Loading 2}> Loading 3}> ); const dispose = render(Comp, div); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(110); await Promise.resolve(); expect(div.innerHTML).toBe("
Loading 1
Loading 2
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
Loading 3
"); vi.advanceTimersByTime(100); await Promise.resolve(); expect(div.innerHTML).toBe("
A
B
C
"); dispose(); }); }); ================================================ FILE: packages/solid/web/test/switch.spec.tsx ================================================ /** * @jsxImportSource solid-js * @vitest-environment jsdom */ import { describe, expect, test } from "vitest"; import { render, Switch, Match, For } from "../src/index.js"; import { createRoot, createSignal } from "../../src/index.js"; import { createStore } from "../../store/src/index.js"; describe("Testing a single match switch control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [count, setCount] = createSignal(0); const Component = () => (
1
); test("Create Switch control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("fallback"); }); test("Toggle Switch control flow", () => { setCount(1); expect(div.innerHTML).toBe("1"); setCount(3); expect(div.innerHTML).toBe("fallback"); }); test("dispose", () => disposer()); }); describe("Testing an only child Switch control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [count, setCount] = createSignal(0); const Component = () => (
1 2 3
); test("Create Switch control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("fallback"); }); test("Toggle Switch control flow", () => { setCount(1); expect(div.innerHTML).toBe("1"); setCount(4); expect(div.innerHTML).toBe("2"); setCount(7); expect(div.innerHTML).toBe("3"); setCount(9); expect(div.innerHTML).toBe("fallback"); }); test("doesn't re-render on same option", () => { setCount(4); expect(div.innerHTML).toBe("2"); const c = div.firstChild; setCount(4); expect(div.innerHTML).toBe("2"); expect(div.firstChild).toBe(c); }); test("dispose", () => disposer()); }); describe("Testing keyed Switch control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [a, setA] = createSignal(0), [b, setB] = createSignal(0), [c, setC] = createSignal(0); const Component = () => (
{a()} {b()} {c()}
); test("Create Switch control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("fallback"); }); test("Toggle Switch control flow", () => { setC(1); expect(div.innerHTML).toBe("1"); setB(2); expect(div.innerHTML).toBe("2"); setA(3); expect(div.innerHTML).toBe("3"); setA(0); expect(div.innerHTML).toBe("2"); }); test("dispose", () => disposer()); }); describe("Testing keyed function handler Switch control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [a, setA] = createSignal(0), [b, setB] = createSignal(0), [c, setC] = createSignal(0); const Component = () => (
{a => a} {b => b} {c => c}
); test("Create Switch control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("fallback"); }); test("Toggle Switch control flow", () => { setC(1); expect(div.innerHTML).toBe("1"); setB(2); expect(div.innerHTML).toBe("2"); setA(3); expect(div.innerHTML).toBe("3"); setA(0); expect(div.innerHTML).toBe("2"); }); test("dispose", () => disposer()); }); describe("Testing non-keyed function handler Switch control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [a, setA] = createSignal(0), [b, setB] = createSignal(0), [c, setC] = createSignal(0); const Component = () => (
{a => <>{a()}} {b => <>{b()}} {c => <>{c()}}
); test("Create Switch control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("fallback"); }); test("Toggle Switch control flow", () => { setC(1); expect(div.innerHTML).toBe("1"); setB(2); expect(div.innerHTML).toBe("2"); setA(3); expect(div.innerHTML).toBe("3"); setA(0); expect(div.innerHTML).toBe("2"); }); test("dispose", () => disposer()); }); describe("Testing Switch conditions evaluation counts", () => { let div!: HTMLDivElement, disposer: () => void; function makeCondition() { const [get, set] = createSignal(0); const result = { get, set, evalCount: 0, getAndCount: () => { result.evalCount++; return get(); } }; return result; } const a = makeCondition(), b = makeCondition(), c = makeCondition(); const Component = () => (
a={a.get()} {b => <>b={b()}} {c => <>c={c}}
); test("Create Switch control flow", () => { createRoot(dispose => { disposer = dispose; ; }); expect(div.innerHTML).toBe("fallback"); expect(a.evalCount).toBe(1); expect(b.evalCount).toBe(1); expect(c.evalCount).toBe(1); }); test("Toggle conditions", () => { c.set(5); expect(div.innerHTML).toBe("c=5"); expect(a.evalCount).toBe(1); expect(b.evalCount).toBe(1); expect(c.evalCount).toBe(2); a.set(1); expect(div.innerHTML).toBe("a=1"); expect(a.evalCount).toBe(2); expect(b.evalCount).toBe(1); expect(c.evalCount).toBe(2); b.set(3); expect(div.innerHTML).toBe("a=1"); expect(a.evalCount).toBe(2); expect(b.evalCount).toBe(1); // did not evaluate expect(c.evalCount).toBe(2); b.set(2); expect(div.innerHTML).toBe("a=1"); expect(a.evalCount).toBe(2); expect(b.evalCount).toBe(1); // did not evaluate expect(c.evalCount).toBe(2); a.set(0); expect(div.innerHTML).toBe("b=2"); expect(a.evalCount).toBe(3); expect(b.evalCount).toBe(2); // evaluated now expect(c.evalCount).toBe(2); b.set(3); expect(div.innerHTML).toBe("b=3"); expect(a.evalCount).toBe(3); expect(b.evalCount).toBe(3); expect(c.evalCount).toBe(2); c.set(3); expect(div.innerHTML).toBe("b=3"); expect(a.evalCount).toBe(3); expect(b.evalCount).toBe(3); expect(c.evalCount).toBe(2); // did not evaluate a.set(1); expect(div.innerHTML).toBe("a=1"); expect(a.evalCount).toBe(4); expect(b.evalCount).toBe(3); expect(c.evalCount).toBe(2); b.set(1); expect(div.innerHTML).toBe("a=1"); expect(a.evalCount).toBe(4); expect(b.evalCount).toBe(3); // did not evaluate expect(c.evalCount).toBe(2); b.set(0); expect(div.innerHTML).toBe("a=1"); expect(a.evalCount).toBe(4); expect(b.evalCount).toBe(3); // did not evaluate expect(c.evalCount).toBe(2); a.set(0); expect(div.innerHTML).toBe("c=3"); expect(a.evalCount).toBe(5); expect(b.evalCount).toBe(4); // evaluated now, as b changed since its last evaluation expect(c.evalCount).toBe(3); // evaluated now c.set(0); expect(div.innerHTML).toBe("fallback"); expect(a.evalCount).toBe(5); expect(b.evalCount).toBe(4); expect(c.evalCount).toBe(4); }); test("dispose", () => disposer()); }); describe("Testing non-keyed function handler Switch control flow with dangling callback", () => { let div!: HTMLDivElement, disposer: () => void; const [a, setA] = createSignal(0), [b] = createSignal(2); let callback: () => void; let delayed: number; const Component = () => (
{a => <>{a()}} {b => { setTimeout(() => { expect(() => (delayed = b())).toThrow(); callback(); }, 0); return <>{b()}; }}
); test("Create Switch control flow", () => { return new Promise(c => { createRoot(dispose => { disposer = dispose; ; }); setA(1); expect(div.innerHTML).toBe("1"); callback = () => { expect(delayed).toBeUndefined(); c(); }; }); }); test("dispose", () => disposer()); }); describe("Testing a For in a Switch control flow", () => { let div!: HTMLDivElement, disposer: () => void; const [state, setState] = createStore({ 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; const [count, setCount] = createSignal(0); const Component = () => ( 1 ); test("Create switch control flow", () => { disposer = render(Component, div); expect(div.innerHTML).toBe("fallback"); setCount(1); expect(div.innerHTML).toBe("1"); }); test("dispose", () => disposer()); }); ================================================ FILE: packages/solid/web/tsconfig.build.json ================================================ { "extends": "../../../tsconfig.json", "compilerOptions": { "outDir": "./types", "baseUrl": "src", "paths": { "solid-js": ["../.."], "solid-js/jsx-runtime": ["../../src/jsx"], "solid-js/jsx-dev-runtime": ["../../src/jsx"], } }, "include": ["./src"] } ================================================ FILE: packages/solid/web/tsconfig.json ================================================ { "extends": "./tsconfig.build.json", "include": ["./src", "./test"] } ================================================ FILE: packages/solid-element/CHANGELOG.md ================================================ # solid-element ## 1.9.1 ### Patch Changes - 5a14ab8: fix #2337 empty attributes in solid-element - Updated dependencies [bb6ce8b] - Updated dependencies [9b70a15] - solid-js@1.9.3 ## 1.9.0 ### Minor Changes - 2a3a1980: update dom-expressions - Improved Custom Element/Shadow DOM traversal - @olivercoad - Better heuristic to determine when to importNode - @titoBouzout - handleEvent syntax to allow custom event properties when not delegated - @titoBouzout - support for bool: attribute namespace - @titoBouzout - add "is" as detection for custom element - @titoBouzout - fix missing exports in different envs - @trusktr - better hydration mismatch errors - @ryansolid - improved HTML validation of JSX partials - @titoBouzout ### Patch Changes - Updated dependencies [4f8597dc] - Updated dependencies [120bf06d] - Updated dependencies [80b09589] - Updated dependencies [2a3a1980] - Updated dependencies [51bec61a] - solid-js@1.9.0 ## 1.8.1 ### Patch Changes - 6693b56f: update TS, custom elements, and a lot compiler fixes fixes #2144, #2145, #2178, #2192 - Updated dependencies [6693b56f] - Updated dependencies [a8c2a8f3] - solid-js@1.8.18 ## 1.8.0 ### Patch Changes - Updated dependencies [2c087cbb] - Updated dependencies [2c087cbb] - Updated dependencies [2c087cbb] - Updated dependencies [2c087cbb] - Updated dependencies [2c087cbb] - Updated dependencies [2c087cbb] - solid-js@1.8.0 ## 1.8.0-beta.1 ### Patch Changes - Updated dependencies [e3a97d28] - Updated dependencies [d797a143] - solid-js@1.8.0-beta.2 ## 1.8.0-beta.0 ### Patch Changes - Updated dependencies [d8e0e8e8] - Updated dependencies [bf09b838] - solid-js@1.8.0-beta.0 ## 1.7.1 ### Patch Changes - f99dd044: Solid-Element: Add clarification on 'props' parameter in customElement function - Updated dependencies [83c99d51] - Updated dependencies [f99dd044] - Updated dependencies [88493691] - Updated dependencies [514ef679] - Updated dependencies [20261537] - Updated dependencies [194f93c7] - solid-js@1.7.6 ## 1.7.0 ### Patch Changes - Updated dependencies [6b77d9ed] - Updated dependencies [503b6328] - Updated dependencies [41ca6522] - Updated dependencies [840933b8] - Updated dependencies [86c32279] - Updated dependencies [f7dc355f] - Updated dependencies [940e5745] - Updated dependencies [cb6a383d] - Updated dependencies [3de9432c] - Updated dependencies [608b3c3a] - Updated dependencies [2cb6f3d6] - Updated dependencies [24469762] - Updated dependencies [2b80f706] - Updated dependencies [8d0877e4] - Updated dependencies [5545d3ee] - Updated dependencies [0dc8e365] - Updated dependencies [4929530b] - Updated dependencies [71c40af6] - Updated dependencies [6a4fe46c] - Updated dependencies [5d671b89] - Updated dependencies [74f00e15] - Updated dependencies [23c157ac] - solid-js@1.7.0 ## 1.7.0-beta.2 ### Patch Changes - Updated dependencies [940e5745] - solid-js@1.7.0-beta.2 ## 1.7.0-beta.1 ### Patch Changes - Updated dependencies [6b77d9ed] - Updated dependencies [608b3c3a] - Updated dependencies [24469762] - Updated dependencies [2b80f706] - Updated dependencies [8d0877e4] - Updated dependencies [5545d3ee] - Updated dependencies [74f00e15] - solid-js@1.7.0-beta.1 ## 1.7.0-beta.0 ### Patch Changes - Updated dependencies [503b632] - Updated dependencies [86c3227] - Updated dependencies [f7dc355] - Updated dependencies [4929530] - Updated dependencies [71c40af] - Updated dependencies [e245736] - Updated dependencies [6a4fe46] - solid-js@1.7.0-beta.0 ## 1.6.4 ### Patch Changes - 676ed331: docs: fix typos - Updated dependencies [e2888c77] - Updated dependencies [676ed331] - Updated dependencies [b8a3ff13] - Updated dependencies [1aff80c6] - Updated dependencies [53db3f0f] - Updated dependencies [47d574a8] - Updated dependencies [e245736f] - Updated dependencies [61d1fe25] - Updated dependencies [4fdec4f9] - solid-js@1.6.12 ## 1.6.3 ### Patch Changes - e95e95f: Bug fixes and testing changelog - Updated dependencies [e95e95f] - solid-js@1.6.3 ================================================ FILE: packages/solid-element/README.md ================================================ # Solid Element [![Build Status](https://github.com/solidjs/solid/workflows/Solid%20CI/badge.svg)](https://github.com/solidjs/solid/actions/workflows/main-ci.yml) [![NPM Version](https://img.shields.io/npm/v/solid-element.svg?style=flat)](https://www.npmjs.com/package/solid-element) ![](https://img.shields.io/librariesio/release/npm/solid-element) ![](https://img.shields.io/npm/dm/solid-element.svg?style=flat) This library extends [Solid](https://github.com/solidjs/solid) by adding Custom Web Components and extensions to manage modular behaviors and composition. It uses [Component Register](https://github.com/ryansolid/component-register) to create Web Components and its composed mixin pattern to construct modular re-usable behaviors. This allows your code to available as simple HTML elements for library interop and to leverage Shadow DOM style isolation. Solid already supports binding to Web Components so this fills the gap allowing full modular applications to be built out of nested Web Components. Component Register makes use of the V1 Standards and on top of being compatible with the common webcomponent.js polyfills, has a solution for Polyfilling Shadow DOM CSS using the ShadyCSS Parser from Polymer in a generic framework agnostic way (unlike the ShadyCSS package). ## Example [See here](./sample.jsx) for an example of a webcomponent created by `solid-element`. ## Installation ```sh npm i solid-element solid-js babel-preset-solid ``` ## Custom Elements The simplest way to create a Web Component is to use the `customElement` method. The arguments of `customElement` are: 1) custom element tag (e.g. `'my-component'`) 2) (optional) Default prop values (e.g. `{someProp: 'one', otherProp: 'two'}`). Props without default values will be ignored by the customElement. 3) the Solid template function. The arguments of this function are state wrapped props as the first argument, and the underlying element as the 2nd (e.g. `(props, { element }) => { solid code here }`) ```jsx import { customElement } from 'solid-element'; customElement('my-component', {someProp: 'one', otherProp: 'two'}, (props, { element }) => { // ... Solid code }) ``` Props get assigned as element properties and hyphenated attributes. This exposes the component that can be used in HTML/JSX as: ```html ``` This is all you need to get started with Solid Element. A shadow DOM is used by default for style isolation. If you want to disable the shadow DOM, you can do it with `noShadowDOM()` like this: ```jsx import { customElement, noShadowDOM } from 'solid-element'; customElement('my-component', {someProp: 'one', otherProp: 'two'}, (props, { element }) => { noShadowDOM(); // ... Solid code }) ``` ## Examples [Web Component Todos](https://wc-todo.firebaseapp.com/) Simple Todos Comparison ## Hot Module Replacement (new) Solid Element exposes Component Register's Hot Module Replacement solution for Webpack and Parcel. It does not preserve state, swapping Components that are changed and their descendants. This approach is simple but predictable. It works by indicating the component to be Hot Replaced with the `hot` method in your file. ```js import { customElement, hot } from 'solid-element'; hot(module, 'my-component'); ``` This is a new feature that is actively seeking feedback. Read more: [Component Register](https://github.com/ryansolid/component-register#hot-module-replacement-new) There is also a webpack loader that handles adding this automatically. Check out [Component Register Loader](https://github.com/ryansolid/component-register-loader) ## withSolid Under the hood the customElement method is using Component Register's mixins to create our Custom Element. So this library also provides the way to do so directly if you wish to mixin your own functionality. It all starts by using the register HOC which upgrades your class or method to a WebComponent. It is always the start of the chain. ```jsx import { register } from 'component-register'; /* register(tag, defaultProps) */ register('my-component', {someProp: 'one', otherProp: 'two'})((props, options) => // .... ) ``` Component Register exposes a convenient compose method (a reduce right) that makes it easier compose multiple mixins. From there we can use withSolid mixin to basically produce the Component method above. However, now you are able to add more HOC mixins in the middle to add additional behavior in your components. ```jsx import { register, compose } from 'component-register'; import { withSolid } from 'solid-element'; /* withSolid */ compose( register('my-component'), withSolid )((props, options) => // .... ) ``` ================================================ FILE: packages/solid-element/package.json ================================================ { "name": "solid-element", "description": "Webcomponents wrapper for Solid", "author": "Ryan Carniato", "license": "MIT", "version": "1.9.1", "homepage": "https://github.com/solidjs/solid/blob/main/packages/solid-element#readme", "type": "module", "main": "dist/index.js", "module": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist" ], "sideEffects": false, "scripts": { "clean": "rimraf dist/", "build": "pnpm run clean && tsc" }, "dependencies": { "component-register": "^0.8.7" }, "peerDependencies": { "solid-js": "^1.9.11" }, "devDependencies": { "solid-js": "workspace:*" } } ================================================ FILE: packages/solid-element/sample.jsx ================================================ import { createSignal } from "solid-js"; import { customElement } from "solid-element"; const style = `div * { font-size: 200%; } span { width: 4rem; display: inline-block; text-align: center; } button { width: 4rem; height: 4rem; border: none; border-radius: 10px; background-color: seagreen; color: white; }`; customElement("my-counter", () => { const [count, setCount] = createSignal(0); return (
{count}
); }); ================================================ FILE: packages/solid-element/src/index.ts ================================================ import { register, ComponentType as mComponentType, ICustomElement, FunctionComponent, ComponentOptions, PropsDefinitionInput } from "component-register"; export { hot, getCurrentElement, noShadowDOM } from "component-register"; export type ComponentType = mComponentType; 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(v) { set(() => v); } }); } return props as T; } function lookupContext(el: ICustomElement & { _$owner?: any }) { if (el.assignedSlot && el.assignedSlot._$owner) return el.assignedSlot._$owner; let next: Element & { _$owner?: any } = el.parentNode; while ( next && !next._$owner && !(next.assignedSlot && (next.assignedSlot as Element & { _$owner?: any })._$owner) ) next = next.parentNode as Element; return next && next.assignedSlot ? (next.assignedSlot as Element & { _$owner?: any })._$owner : el._$owner; } function withSolid(ComponentType: ComponentType): ComponentType { return (rawProps: T, options: ComponentOptions) => { const { element } = options as { element: ICustomElement & { _$owner?: any }; }; return createRoot((dispose: Function) => { const props = createProps(rawProps); 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); }, lookupContext(element)); }; } function customElement( tag: string, ComponentType: ComponentType ): CustomElementConstructor; function customElement( tag: string, props: PropsDefinitionInput, ComponentType: ComponentType ): CustomElementConstructor; function customElement( tag: string, props: PropsDefinitionInput | ComponentType, ComponentType?: ComponentType ): CustomElementConstructor { if (arguments.length === 2) { ComponentType = props as ComponentType; props = {} as PropsDefinitionInput; } return register(tag, props as PropsDefinitionInput)(withSolid(ComponentType!)); } export { withSolid, customElement }; ================================================ FILE: packages/solid-element/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./dist", "emitDeclarationOnly": false }, "include": [ "./src" ] } ================================================ FILE: packages/solid-ssr/CHANGELOG.md ================================================ # solid-ssr ## 1.7.2 ### Patch Changes - e660e5a3: add prettier code format in git-commit-hook ## 1.7.1 ### Patch Changes - 91110701: fix element/test mismatch issues #1684, #1697, #1707 fix solid-ssr types add missing JSX types #1690 fix firefox iframe #1688 ## 1.7.0 ### Minor Changes - bee730cb: update solid ssr to type module ## 1.7.0-beta.0 ### Minor Changes - bee730cb: update solid ssr to type module ## 1.6.3 ### Patch Changes - e95e95f: Bug fixes and testing changelog ================================================ FILE: packages/solid-ssr/README.md ================================================ # `solid-ssr` This library provides tools to help with SSR. So far it's a simple Static Generator. But will add more tools in the future. ## solid-ssr/static This is a simple runner that renders files and writes them to disk. It exports a single export that can be used in either CJS or ESM. ```js renderStatic( PAGES.map(p => ({ entry: pathToServer, output: path.join(pathToPublic, `${p}.html`), url: `/${p}` })) ); ``` Each entry expects 3 values: * entry: path to the server entry point you will be using to render the page * output: path to the location of the html file you wish to write * url: the url that will be passed on the faux request object Entry files should be async functions that return the html string in the form: ```js export default async function(req) { return "My Page" } ``` ## Examples 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). There are 4 examples all using the same shared source. It is an isomorphically routed tab navigation using Suspense, Lazy Components, and Data Fetching. They are just compiled differently and have slightly different server entry points. 1. ssr Uses standard synchronous SSR. Renders what it can synchronously on the server and hydrates top level in the client. Async data is fetched and rendered in the client without hydration. 2. stream Similar to `ssr` except using streams. Data is loaded on the server and sent along the stream so the client can render it. Again, only initial HTML is hydrated. Slightly speeds up data loading, so total page load time is reduced. 3. async Resolves everything reactively on server before sending HTML and serialized data to the client. Whole page is hydrated, but all loading states are avoided. Total load time is similar to streaming, but time to first paint is blocked by how long it takes to load data. 4. ssg Using async compiler configuration to statically generate pages. Removes the time it takes to render real time. Examples run on http://localhost:8080/. To run them (in this case ssr): ``` lerna run build:example:ssr --stream lerna run start:example:ssr --stream ``` |folder/example name|ssr|async|stream|ssg| |--- |--- |--- |--- |--- | |Parallels|[JAMStack]("https://jamstack.org/what-is-jamstack/")|Next/Nuxt/Sveltekit|[Marko streaming](https://tech.ebayinc.com/engineering/async-fragments-rediscovering-progressive-html-rendering-with-marko/)|Static site generators| |When is the data queried?|Client|Server, at request time|Server, at request time|Server, at build-time| |Render strategy|Elements outside the suspense boundary are rendered on the server and sent initially, then hydrated on the client. Everything that depends on data is in the bundle and rendered client-side|All necessary nodes are rendered on the server, then hydrated on the client. Data is serialized, sent along with the page, and reused on the client as necessary.|The page is rendered with placeholders for elements that depend on data. These are replaced with the correct nodes as more of the stream loads.|Same as async, but the rendering is done ahead of time| |Loading indicators / suspense fallbacks|Top-level suspense fallback (the _Loading…_ span) is sent as part of the HTML. The _Loading Info…_ span is part of the Profile component, in the bundle.|None. The suspense fallback never gets shown because the suspense is resolved in the server.|Both loading indicators are included in the HTML, along with the scripts that replace them. On a slow connection, the HTML gets to the browser at the same time, and you don’t see any loading indicators.|Same as async| |Server-side render function used|renderToString|renderToStringAsync|renderToStream (1.3)|renderToStringAsync| ================================================ FILE: packages/solid-ssr/examples/.gitignore ================================================ **/public/ **/lib/ ================================================ FILE: packages/solid-ssr/examples/async/index.js ================================================ import express from "express"; import url from "url"; import { renderToStringAsync } from "solid-js/web"; import App from "../shared/src/components/App"; const app = express(); const port = 3000; app.use(express.static(url.fileURLToPath(new URL("../public", import.meta.url)))); app.get("*", async (req, res) => { let result; try { result = await renderToStringAsync(() => ); } catch (err) { console.error(err); } finally { res.send(result); } }); app.listen(port, () => console.log(`Example app listening on port ${port}!`)); ================================================ FILE: packages/solid-ssr/examples/async/rollup.config.js ================================================ import nodeResolve from "@rollup/plugin-node-resolve"; import common from "@rollup/plugin-commonjs"; import babel from "@rollup/plugin-babel"; import copy from "rollup-plugin-copy"; export default [ { input: "examples/async/index.js", output: [ { dir: "examples/async/lib", format: "esm" } ], preserveEntrySignatures: false, external: ["solid-js", "solid-js/web", "path", "express"], plugins: [ nodeResolve({ preferBuiltins: true, exportConditions: ["solid", "node"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "ssr", hydratable: true }]] }), common() ] }, { input: "examples/shared/src/index.js", output: [ { dir: "examples/async/public/js", format: "esm" } ], preserveEntrySignatures: false, plugins: [ nodeResolve({ exportConditions: ["solid"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "dom", hydratable: true }]] }), common(), copy({ targets: [ { src: ["examples/shared/static/*"], dest: "examples/async/public" } ] }) ] } ]; ================================================ FILE: packages/solid-ssr/examples/shared/src/components/App.js ================================================ import { useContext, lazy, ErrorBoundary } from "solid-js"; import { HydrationScript } from "solid-js/web"; import { Link, RouteHOC, RouterContext } from "../router"; // import stub as main package to allowing fetch as you load import Profile from "./Profile"; const Home = lazy(() => import("./Home")); const Settings = lazy(() => import("./Settings")); const App = RouteHOC(() => { const [, pending, { matches }] = useContext(RouterContext); return ( 🔥 Solid SSR 🔥
  • Home
  • Profile
  • Settings
{ return ( <>

Error: {err.message}

); }} > Loading... } >
); }); export default App; ================================================ FILE: packages/solid-ssr/examples/shared/src/components/Home.js ================================================ import { createSignal, onCleanup, onMount } from "solid-js"; const Home = () => { const [s, set] = createSignal(0); onMount(() => { const t = setInterval(() => set(s() + 1), 100); onCleanup(() => clearInterval(t)); }); return ( <>

Welcome to this Simple Routing Example

Click the links in the Navigation above to load different routes.

{s()} ); }; export default Home; ================================================ FILE: packages/solid-ssr/examples/shared/src/components/Profile/Profile.js ================================================ const Profile = props => ( <>

{props.user?.firstName}'s Profile

This section could be about you.

Loading Info...}>
    {fact =>
  • {fact}
  • }
); export default Profile; ================================================ FILE: packages/solid-ssr/examples/shared/src/components/Profile/index.js ================================================ import { createResource, lazy } from "solid-js"; const Profile = lazy(() => import("./Profile")); // this component lazy loads data and code in parallel export default () => { const [user] = createResource(() => { // simulate data loading console.log("LOAD USER"); return new Promise(res => { setTimeout(() => res({ firstName: "Jon", lastName: "Snow" }), 400); }); }), [info] = createResource( user, () => { // 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 ); }); }, { initialValue: [] } ); return ; }; ================================================ FILE: packages/solid-ssr/examples/shared/src/components/Settings.js ================================================ import { createUniqueId, createSignal } from "solid-js"; const Settings = () => { const [text, setText] = createSignal("Hi"); const id = createUniqueId(); return ( <>

Settings

All that configuration you never really ever want to look at.

setText(e.currentTarget.value)} />

{text()}

); }; export default Settings; ================================================ FILE: packages/solid-ssr/examples/shared/src/index.js ================================================ import { hydrate } from "solid-js/web"; import App from "./components/App"; // entry point for browser hydrate(() => , document); ================================================ FILE: packages/solid-ssr/examples/shared/src/router.js ================================================ import { createSignal, createContext, useContext, useTransition } from "solid-js"; import { isServer } from "solid-js/web"; // Super simplistic pushstate router that matches on absolute paths const RouterContext = createContext(); function RouteHOC(Comp) { return (props = {}) => { const [location, setLocation] = createSignal( (props.url ? props.url : window.location.pathname).slice(1) || "index" ), matches = match => match === (location() || "index"), [pending, start] = useTransition(); !isServer && (window.onpopstate = () => setLocation(window.location.pathname.slice(1))); return ( start(() => setLocation(v)), matches }]} > ); }; } const Link = props => { const [, , { setLocation }] = useContext(RouterContext); const navigate = e => { if (e) e.preventDefault(); window.history.pushState("", "", `/${props.path}`); setLocation(props.path); }; return (
{props.children} ); }; export { RouteHOC, RouterContext, Link }; ================================================ FILE: packages/solid-ssr/examples/shared/static/styles.css ================================================ body { background-color: #eee; } #app { border-radius: 3px; border: 1px solid #e5e5e5; margin: 15px; background-color: white; } .tab { width: 100%; padding: 25px; font-family: sans-serif; color: #444; } .tab.pending { transition: opacity 0.2s; transition-delay: 0.1s; transition-timing-function: ease-in; opacity: 0.6; } ul.inline { list-style: none; padding: 0; margin-bottom: 0; -webkit-margin-before: 0; -webkit-margin-after: 0; -webkit-margin-start: 0px; -webkit-margin-end: 0px; -webkit-padding-start: 0px; } ul.inline li { display: inline-block; margin-left: 0; border-bottom: 2px solid #eee; transition: all 0.5s; font-family: sans-serif; font-weight: 300; cursor: pointer; color: #aaa; } ul.inline li.selected { border-bottom: 2px solid #337ab7; color: #444; } ul.inline a { display: block; padding: 10px; } a.link { text-decoration: none; color: black; } .loader { opacity: 0; color: #aaa; font-size: 16px; font-weight: 600; animation: fadeInAnimation ease .3s; animation-delay: .2s; animation-iteration-count: 1; animation-fill-mode: forwards; } @keyframes fadeInAnimation { 0% { opacity: 0; } 100% { opacity: 1; } } ================================================ FILE: packages/solid-ssr/examples/ssg/export.js ================================================ import path from "path"; import url from "url"; import renderStatic from "../../static/index.js"; const PAGES = ["index", "profile", "settings"]; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); const pathToServer = path.resolve(__dirname, "lib/index.js"); const pathToPublic = path.resolve(__dirname, "public"); renderStatic( PAGES.map(p => ({ entry: pathToServer, output: path.join(pathToPublic, `${p}.html`), url: `/${p}` })) ); ================================================ FILE: packages/solid-ssr/examples/ssg/index.js ================================================ import { renderToStringAsync } from "solid-js/web"; import App from "../shared/src/components/App"; // entry point for server render export default async req => { return await renderToStringAsync(() => ); }; ================================================ FILE: packages/solid-ssr/examples/ssg/rollup.config.js ================================================ import nodeResolve from "@rollup/plugin-node-resolve"; import common from "@rollup/plugin-commonjs"; import babel from "@rollup/plugin-babel"; import copy from "rollup-plugin-copy"; export default [ { input: "examples/ssg/index.js", output: [ { dir: "examples/ssg/lib", format: "esm" } ], external: ["solid-js", "solid-js/web"], plugins: [ nodeResolve({ preferBuiltins: true, exportConditions: ["solid", "node"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "ssr", hydratable: true }]] }), common() ] }, { input: "examples/shared/src/index.js", output: [ { dir: "examples/ssg/public/js", format: "esm" } ], preserveEntrySignatures: false, plugins: [ nodeResolve({ exportConditions: ["solid"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "dom", hydratable: true }]] }), common(), copy({ targets: [ { src: ["examples/shared/static/*"], dest: "examples/ssg/public" } ] }) ] } ]; ================================================ FILE: packages/solid-ssr/examples/ssr/index.js ================================================ import express from "express"; import url from "url"; import { renderToString } from "solid-js/web"; import App from "../shared/src/components/App"; const app = express(); const port = 3000; app.use(express.static(url.fileURLToPath(new URL("../public", import.meta.url)))); app.get("*", (req, res) => { let html; try { html = renderToString(() => ); } catch (err) { console.error(err); } finally { res.send(html); } }); app.listen(port, () => console.log(`Example app listening on port ${port}!`)); ================================================ FILE: packages/solid-ssr/examples/ssr/rollup.config.js ================================================ import nodeResolve from "@rollup/plugin-node-resolve"; import common from "@rollup/plugin-commonjs"; import babel from "@rollup/plugin-babel"; import copy from "rollup-plugin-copy"; export default [ { input: "examples/ssr/index.js", output: [ { dir: "examples/ssr/lib", format: "esm" } ], external: ["solid-js", "solid-js/web", "path", "express", "stream"], plugins: [ nodeResolve({ preferBuiltins: true, exportConditions: ["solid", "node"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "ssr", hydratable: true }]] }), common() ], preserveEntrySignatures: false }, { input: "examples/shared/src/index.js", output: [ { dir: "examples/ssr/public/js", format: "esm" } ], preserveEntrySignatures: false, plugins: [ nodeResolve({ exportConditions: ["solid"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "dom", hydratable: true }]] }), common(), copy({ targets: [ { src: ["examples/shared/static/*"], dest: "examples/ssr/public" } ] }) ] } ]; ================================================ FILE: packages/solid-ssr/examples/stream/index.js ================================================ import express from "express"; import url from "url"; import { renderToStream } from "solid-js/web"; import App from "../shared/src/components/App"; const app = express(); const port = 3000; app.use(express.static(url.fileURLToPath(new URL("../public", import.meta.url)))); app.get("*", (req, res) => renderToStream(() => ).pipe(res)); app.listen(port, () => console.log(`Example app listening on port ${port}!`)); ================================================ FILE: packages/solid-ssr/examples/stream/rollup.config.js ================================================ import nodeResolve from "@rollup/plugin-node-resolve"; import common from "@rollup/plugin-commonjs"; import babel from "@rollup/plugin-babel"; import copy from "rollup-plugin-copy"; export default [ { input: "examples/stream/index.js", preserveEntrySignatures: false, output: [ { dir: "examples/stream/lib", format: "esm" } ], external: ["solid-js", "solid-js/web", "path", "express", "stream"], plugins: [ nodeResolve({ preferBuiltins: true, exportConditions: ["solid", "node"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "ssr", hydratable: true }]] }), common() ] }, { input: "examples/shared/src/index.js", output: [ { dir: "examples/stream/public/js", format: "esm" } ], preserveEntrySignatures: false, plugins: [ nodeResolve({ exportConditions: ["solid"] }), babel({ babelHelpers: "bundled", presets: [["solid", { generate: "dom", hydratable: true }]] }), common(), copy({ targets: [ { src: ["examples/shared/static/*"], dest: "examples/stream/public" } ] }) ] } ]; ================================================ FILE: packages/solid-ssr/package.json ================================================ { "name": "solid-ssr", "description": "Utilities to help with SSR", "version": "1.7.2", "author": "Ryan Carniato", "license": "MIT", "type": "module", "repository": { "type": "git", "url": "https://github.com/solidjs/solid/blob/main/packages/solid-ssr" }, "exports": { "./static": { "require": "./static/index.cjs", "import": "./static/index.js" } }, "files": [ "static" ], "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 --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 examples/ssr/lib/index.js", "build:example:stream": "rollup -c examples/stream/rollup.config.js", "start:example:stream": "node examples/stream/lib/index.js", "clean": "rimraf examples/**/lib/ examples/**/public/" }, "devDependencies": { "@babel/core": "^7.21.3", "@rollup/plugin-babel": "6.0.3", "@rollup/plugin-commonjs": "24.0.1", "@rollup/plugin-node-resolve": "15.0.1", "babel-preset-solid": "workspace:*", "express": "^4.20.0", "rollup": "^3.20.0", "rollup-plugin-copy": "^3.4.0", "solid-js": "workspace:*" } } ================================================ FILE: packages/solid-ssr/static/index.cjs ================================================ const path = require("path"); const execFile = require("util").promisify(require("child_process").execFile); const pathToRunner = path.resolve(__dirname, "writeToDisk.cjs"); async function run({ 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); } module.exports = async function renderStatic(config) { if (Array.isArray(config)) { await Promise.all(config.map(run)); } else await run(config); }; ================================================ FILE: packages/solid-ssr/static/index.d.ts ================================================ export type StaticConfig = { entry: string; output: string; url: string }; export default function renderStatic(config: StaticConfig | StaticConfig[]): Promise; ================================================ FILE: packages/solid-ssr/static/index.js ================================================ import { resolve } from "path"; import { fileURLToPath } from "url"; import { execFile } from "child_process"; import { promisify } from "util"; const exec = promisify(execFile); const __dirname = fileURLToPath(new URL(".", import.meta.url)); const pathToRunner = resolve(__dirname, "writeToDisk.js"); async function run({ entry, output, url }) { const { stdout, stderr } = await exec("node", [ pathToRunner, entry, output, url, "--trace-warnings" ]); if (stdout.length) console.log(stdout); if (stderr.length) console.log(stderr); } export default async function renderStatic(config) { if (Array.isArray(config)) { await Promise.all(config.map(run)); } else await run(config); } ================================================ FILE: packages/solid-ssr/static/writeToDisk.cjs ================================================ const fs = require("fs"); const path = require("path"); const server = require(process.argv[2]); async function write() { const res = await server({ url: process.argv[4] }); fs.mkdir(path.dirname(process.argv[3]), {recursive: true}, () => fs.writeFile(process.argv[3], res, () => process.exit(0)) ); } write(); ================================================ FILE: packages/solid-ssr/static/writeToDisk.js ================================================ import { dirname, join } from "path"; import { writeFile, mkdir } from "fs"; async function write() { const server = (await import(join("file://", process.argv[2]))).default; const res = await server({ url: process.argv[4] }); mkdir(dirname(process.argv[3]), { recursive: true }, () => writeFile(process.argv[3], res, () => process.exit(0)) ); } write(); ================================================ FILE: packages/test-integration/CHANGELOG.md ================================================ # test-integration ## 1.9.11 ### Patch Changes - Updated dependencies [6628d9f] - solid-js@1.9.11 ## 1.9.10 ### Patch Changes - Updated dependencies [2270ae9] - Updated dependencies [94d87f1] - Updated dependencies [3114302] - Updated dependencies [6c92555] - solid-js@1.9.10 - babel-preset-solid@1.9.10 ## 1.9.9 ### Patch Changes - Updated dependencies [f59ee48] - Updated dependencies [62c5a98] - Updated dependencies [62c5a98] - Updated dependencies [c07887c] - solid-js@1.9.9 - babel-preset-solid@1.9.9 ## 1.9.8 ### Patch Changes - Updated dependencies [09a9c1d] - Updated dependencies [472c007] - Updated dependencies [3d3207d] - Updated dependencies [2cd810f] - Updated dependencies [cbff564] - Updated dependencies [e056eab] - Updated dependencies [bdba4dc] - solid-js@1.9.8 - babel-preset-solid@1.9.8 ## 1.9.7 ### Patch Changes - Updated dependencies [4cd7eb1] - solid-js@1.9.7 ## 1.9.6 ### Patch Changes - Updated dependencies [362e99f] - Updated dependencies [8356213] - Updated dependencies [c65faec] - Updated dependencies [6380b01] - solid-js@1.9.6 - babel-preset-solid@1.9.6 ## 1.9.5 ### Patch Changes - Updated dependencies [86ae8a9] - Updated dependencies [89e016d] - Updated dependencies [9431b88] - Updated dependencies [35266c1] - Updated dependencies [0eab77d] - Updated dependencies [fff8aed] - Updated dependencies [f9ef621] - solid-js@1.9.5 - babel-preset-solid@1.9.5 ## 1.9.4 ### Patch Changes - Updated dependencies [b93956f] - Updated dependencies [199dd69] - Updated dependencies [7f9cd3d] - Updated dependencies [32aa744] - solid-js@1.9.4 ## 1.9.3 ### Patch Changes - Updated dependencies [bb6ce8b] - Updated dependencies [9b70a15] - solid-js@1.9.3 - babel-preset-solid@1.9.3 ## 1.9.2 ### Patch Changes - Updated dependencies [22aff14] - Updated dependencies [e2e2a03] - babel-preset-solid@1.9.2 - solid-js@1.9.2 ## 1.9.1 ### Patch Changes - Updated dependencies [fb67b687] - Updated dependencies [7ecf92d3] - solid-js@1.9.1 ## 1.9.0 ### Minor Changes - 2a3a1980: update dom-expressions - Improved Custom Element/Shadow DOM traversal - @olivercoad - Better heuristic to determine when to importNode - @titoBouzout - handleEvent syntax to allow custom event properties when not delegated - @titoBouzout - support for bool: attribute namespace - @titoBouzout - add "is" as detection for custom element - @titoBouzout - fix missing exports in different envs - @trusktr - better hydration mismatch errors - @ryansolid - improved HTML validation of JSX partials - @titoBouzout ### Patch Changes - Updated dependencies [4f8597dc] - Updated dependencies [120bf06d] - Updated dependencies [80b09589] - Updated dependencies [2a3a1980] - Updated dependencies [51bec61a] - solid-js@1.9.0 - babel-preset-solid@1.9.0 ## 1.8.23 ### Patch Changes - Updated dependencies [bc20a4ce] - Updated dependencies [9697c94b] - Updated dependencies [9e192d7e] - Updated dependencies [379293d9] - Updated dependencies [73c00927] - Updated dependencies [e4b2c668] - Updated dependencies [94929afa] - solid-js@1.8.23 ## 1.8.22 ### Patch Changes - Updated dependencies [f8ae663c] - Updated dependencies [19d0295f] - Updated dependencies [26128ec0] - solid-js@1.8.22 - babel-preset-solid@1.8.22 ## 1.8.21 ### Patch Changes - Updated dependencies [a036a63a] - solid-js@1.8.21 ## 1.8.20 ### Patch Changes - Updated dependencies [c8fe58e9] - Updated dependencies [80dd2769] - solid-js@1.8.20 ## 1.8.19 ### Patch Changes - Updated dependencies [3fc015c2] - Updated dependencies [f909c1c1] - Updated dependencies [816a5c61] - Updated dependencies [424a31a3] - solid-js@1.8.19 - babel-preset-solid@1.8.19 ## 1.8.18 ### Patch Changes - Updated dependencies [6693b56f] - Updated dependencies [a8c2a8f3] - babel-preset-solid@1.8.18 - solid-js@1.8.18 ## 1.8.17 ### Patch Changes - Updated dependencies [72c5381d] - Updated dependencies [e065e475] - babel-preset-solid@1.8.17 - solid-js@1.8.17 ## 1.8.16 ### Patch Changes - Updated dependencies [8de75a47] - Updated dependencies [071cd42f] - Updated dependencies [3212f74d] - solid-js@1.8.16 - babel-preset-solid@1.8.16 ## 1.8.15 ### Patch Changes - Updated dependencies [829af663] - Updated dependencies [4ee461dc] - solid-js@1.8.15 - babel-preset-solid@1.8.15 ## 1.8.14 ### Patch Changes - Updated dependencies [4b76be80] - solid-js@1.8.14 ## 1.8.13 ### Patch Changes - Updated dependencies [3ac8210c] - solid-js@1.8.13 ## 1.8.12 ### Patch Changes - Updated dependencies [aba5de08] - Updated dependencies [85b26c36] - solid-js@1.8.12 - babel-preset-solid@1.8.12 ## 1.8.11 ### Patch Changes - Updated dependencies [1ec67f15] - solid-js@1.8.11 ## 1.8.10 ### Patch Changes - Updated dependencies [169d23b4] - solid-js@1.8.10 ## 1.8.9 ### Patch Changes - Updated dependencies [80d4830f] - Updated dependencies [918586fb] - Updated dependencies [71bea784] - Updated dependencies [b0862d39] - Updated dependencies [cbc8d3ee] - babel-preset-solid@1.8.9 - solid-js@1.8.9 ## 1.8.8 ### Patch Changes - Updated dependencies [40b5d78d] - Updated dependencies [968e2cc9] - Updated dependencies [292aba41] - Updated dependencies [7e5667ab] - Updated dependencies [8d2de12f] - Updated dependencies [b887587a] - solid-js@1.8.8 - babel-preset-solid@1.8.8 ## 1.8.7 ### Patch Changes - Updated dependencies [22667bbc] - Updated dependencies [e09a3cc3] - solid-js@1.8.7 ## 1.8.6 ### Patch Changes - Updated dependencies [2b320376] - Updated dependencies [fb7f4bc1] - Updated dependencies [b092368c] - Updated dependencies [54e1aecf] - solid-js@1.8.6 - babel-preset-solid@1.8.6 ## 1.8.5 ### Patch Changes - Updated dependencies [80ca972f] - solid-js@1.8.5 ## 1.8.4 ### Patch Changes - Updated dependencies [cf0542a4] - Updated dependencies [3f3a3396] - babel-preset-solid@1.8.4 - solid-js@1.8.4 ## 1.8.3 ### Patch Changes - Updated dependencies [1f0226e1] - solid-js@1.8.3 ## 1.8.2 ### Patch Changes - Updated dependencies [b632dfd5] - Updated dependencies [dd492c5e] - Updated dependencies [4968fe26] - solid-js@1.8.2 - babel-preset-solid@1.8.2 ## 1.8.1 ### Patch Changes - Updated dependencies [0b9b71aa] - solid-js@1.8.1 ## 1.8.0 ### Patch Changes - Updated dependencies [2c087cbb] - Updated dependencies [2c087cbb] - Updated dependencies [2c087cbb] - Updated dependencies [2c087cbb] - Updated dependencies [2c087cbb] - Updated dependencies [2c087cbb] - babel-preset-solid@1.8.0 - solid-js@1.8.0 ## 1.8.0-beta.2 ### Patch Changes - Updated dependencies [e3a97d28] - Updated dependencies [d797a143] - babel-preset-solid@1.8.0-beta.2 - solid-js@1.8.0-beta.2 ## 1.8.0-beta.1 ### Patch Changes - Updated dependencies [f6d511db] - Updated dependencies [af625dd3] - babel-preset-solid@1.8.0-beta.1 - solid-js@1.8.0-beta.1 ## 1.8.0-beta.0 ### Patch Changes - Updated dependencies [d8e0e8e8] - Updated dependencies [bf09b838] - babel-preset-solid@1.8.0-beta.0 - solid-js@1.8.0-beta.0 ## 1.7.12 ### Patch Changes - Updated dependencies [12eb1552] - Updated dependencies [13b1fa6e] - Updated dependencies [10ac07af] - Updated dependencies [8b49110b] - solid-js@1.7.12 - babel-preset-solid@1.7.12 ## 1.7.11 ### Patch Changes - Updated dependencies [26740b88] - solid-js@1.7.11 ## 1.7.10 ### Patch Changes - Updated dependencies [5ed448ae] - Updated dependencies [7dd1f413] - Updated dependencies [c2008f02] - Updated dependencies [792e7dea] - solid-js@1.7.10 ## 1.7.9 ### Patch Changes - Updated dependencies [44a2bf0b] - Updated dependencies [6cd10c73] - Updated dependencies [6c9879c9] - Updated dependencies [039cf60d] - Updated dependencies [852f4c76] - solid-js@1.7.9 ## 1.7.8 ### Patch Changes - Updated dependencies [efd23186] - Updated dependencies [51074fab] - Updated dependencies [fe6f03f9] - solid-js@1.7.8 ## 1.7.7 ### Patch Changes - Updated dependencies [c4cbfd3c] - Updated dependencies [0100bd12] - Updated dependencies [46e5e787] - Updated dependencies [8ba0e80a] - Updated dependencies [e660e5a3] - Updated dependencies [93d44d45] - solid-js@1.7.7 - babel-preset-solid@1.7.7 ## 1.7.6 ### Patch Changes - Updated dependencies [83c99d51] - Updated dependencies [f99dd044] - Updated dependencies [88493691] - Updated dependencies [514ef679] - Updated dependencies [20261537] - Updated dependencies [194f93c7] - solid-js@1.7.6 ## 1.7.5 ### Patch Changes - Updated dependencies [5288cfa8] - Updated dependencies [8852c199] - solid-js@1.7.5 ## 1.7.4 ### Patch Changes - Updated dependencies [1b5ea076] - Updated dependencies [91110701] - solid-js@1.7.4 - babel-preset-solid@1.7.4 ## 1.7.3 ### Patch Changes - Updated dependencies [655f0b7e] - Updated dependencies [8ce2c47b] - babel-preset-solid@1.7.3 - solid-js@1.7.3 ## 1.7.2 ### Patch Changes - Updated dependencies [27994dc9] - Updated dependencies [699d88eb] - Updated dependencies [dfec6883] - solid-js@1.7.2 - babel-preset-solid@1.7.2 ## 1.7.1 ### Patch Changes - Updated dependencies [ba024813] - Updated dependencies [d4087fe7] - solid-js@1.7.1 - babel-preset-solid@1.7.1 ## 1.7.0 ### Patch Changes - Updated dependencies [6b77d9ed] - Updated dependencies [503b6328] - Updated dependencies [41ca6522] - Updated dependencies [840933b8] - Updated dependencies [86c32279] - Updated dependencies [f7dc355f] - Updated dependencies [940e5745] - Updated dependencies [cb6a383d] - Updated dependencies [3de9432c] - Updated dependencies [608b3c3a] - Updated dependencies [a382c0c5] - Updated dependencies [2cb6f3d6] - Updated dependencies [24469762] - Updated dependencies [2b80f706] - Updated dependencies [8d0877e4] - Updated dependencies [5545d3ee] - Updated dependencies [0dc8e365] - Updated dependencies [4929530b] - Updated dependencies [71c40af6] - Updated dependencies [6a4fe46c] - Updated dependencies [5d671b89] - Updated dependencies [74f00e15] - Updated dependencies [23c157ac] - solid-js@1.7.0 - babel-preset-solid@1.7.0 ## 1.7.0-beta.5 ### Patch Changes - Updated dependencies [a382c0c5] - Updated dependencies [0dc8e365] - babel-preset-solid@1.7.0-beta.5 - solid-js@1.7.0-beta.5 ## 1.7.0-beta.4 ### Patch Changes - Updated dependencies [cb6a383d] - Updated dependencies [3de9432c] - Updated dependencies [2cb6f3d6] - Updated dependencies [23c157ac] - solid-js@1.7.0-beta.4 - babel-preset-solid@1.7.0-beta.4 ## 1.7.0-beta.3 ### Patch Changes - Updated dependencies [41ca6522] - babel-preset-solid@1.7.0-beta.3 - solid-js@1.7.0-beta.3 ## 1.7.0-beta.2 ### Patch Changes - Updated dependencies [940e5745] - babel-preset-solid@1.7.0-beta.2 - solid-js@1.7.0-beta.2 ## 1.7.0-beta.1 ### Patch Changes - Updated dependencies [6b77d9ed] - Updated dependencies [608b3c3a] - Updated dependencies [24469762] - Updated dependencies [2b80f706] - Updated dependencies [8d0877e4] - Updated dependencies [5545d3ee] - Updated dependencies [74f00e15] - solid-js@1.7.0-beta.1 - babel-preset-solid@1.7.0-beta.1 ## 1.7.0-beta.0 ### Patch Changes - Updated dependencies [503b632] - Updated dependencies [86c3227] - Updated dependencies [f7dc355] - Updated dependencies [4929530] - Updated dependencies [71c40af] - Updated dependencies [e245736] - Updated dependencies [6a4fe46] - solid-js@1.7.0-beta.0 - babel-preset-solid@1.7.0-beta.0 ## 1.6.16 ### Patch Changes - 620c7636: Switch test runner from Jest to Vitest - Updated dependencies [d10da016] - Updated dependencies [620c7636] - babel-preset-solid@1.6.16 - solid-js@1.6.16 ## 1.6.15 ### Patch Changes - Updated dependencies [e8448ebd] - Updated dependencies [da83ebda] - solid-js@1.6.15 ## 1.6.14 ### Patch Changes - Updated dependencies [6cceab2f] - solid-js@1.6.14 ## 1.6.13 ### Patch Changes - Updated dependencies [af20f00b] - Updated dependencies [60f8624d] - solid-js@1.6.13 - babel-preset-solid@1.6.13 ## 1.6.12 ### Patch Changes - 676ed331: docs: fix typos - Updated dependencies [e2888c77] - Updated dependencies [676ed331] - Updated dependencies [b8a3ff13] - Updated dependencies [1aff80c6] - Updated dependencies [53db3f0f] - Updated dependencies [47d574a8] - Updated dependencies [e245736f] - Updated dependencies [61d1fe25] - Updated dependencies [081ca06c] - Updated dependencies [4fdec4f9] - solid-js@1.6.12 - babel-preset-solid@1.6.12 ## 1.6.11 ### Patch Changes - Updated dependencies [bfbd002] - Updated dependencies [1ecdea4] - Updated dependencies [91d518a] - Updated dependencies [18e734d] - Updated dependencies [12d458d] - Updated dependencies [4aaa94b] - Updated dependencies [c26f933] - Updated dependencies [6fb3cd8] - Updated dependencies [c5b208c] - solid-js@1.6.11 ## 1.6.10 ### Patch Changes - Updated dependencies [1b32e63] - Updated dependencies [dd879da] - Updated dependencies [d89e791] - Updated dependencies [695d99b] - Updated dependencies [d35a1ca] - Updated dependencies [7ab43a4] - solid-js@1.6.10 - babel-preset-solid@1.6.10 ## 1.6.9 ### Patch Changes - Updated dependencies [a572c12] - Updated dependencies [0ad9859] - Updated dependencies [12629a3] - babel-preset-solid@1.6.9 - solid-js@1.6.9 ## 1.6.8 ### Patch Changes - Updated dependencies [6db2d89] - solid-js@1.6.8 ## 1.6.7 ### Patch Changes - Updated dependencies [c4ac14c] - Updated dependencies [1384496] - Updated dependencies [1dbd5a9] - Updated dependencies [368e508] - Updated dependencies [54f3068] - Updated dependencies [c8edacd] - Updated dependencies [89baf12] - solid-js@1.6.7 - babel-preset-solid@1.6.7 ## 1.6.6 ### Patch Changes - Updated dependencies [a603850] - Updated dependencies [2119211] - Updated dependencies [5a5a72d] - Updated dependencies [5eb575a] - solid-js@1.6.6 - babel-preset-solid@1.6.6 ## 1.6.5 ### Patch Changes - Updated dependencies [50d1304] - Updated dependencies [ee71b16] - solid-js@1.6.5 ## 1.6.4 ### Patch Changes - Updated dependencies [a42a5f6] - solid-js@1.6.4 ## 1.6.3 ### Patch Changes - e95e95f: Bug fixes and testing changelog - Updated dependencies [e95e95f] - babel-preset-solid@1.6.3 - solid-js@1.6.3 ================================================ FILE: packages/test-integration/babel.config.cjs ================================================ module.exports = { env: { test: { presets: [ ["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript" ] } } }; ================================================ FILE: packages/test-integration/package.json ================================================ { "name": "test-integration", "private": true, "scripts": { "test": "npm run test:imports", "test:integrations": "vitest run", "test:imports": "node test-imports.mjs" }, "dependencies": { "babel-preset-solid": "workspace:*", "solid-js": "workspace:*" }, "devDependencies": { "gitly": "^2.2.1", "shelljs": "^0.8.5" }, "version": "1.9.11" } ================================================ FILE: packages/test-integration/test-imports.mjs ================================================ function checkError(error) { // This error happens when missing the type:module field in package.json when it is needed. if ( error instanceof SyntaxError && error.message.includes("Cannot use import statement outside a module") ) { console.error(error); process.exit(1); } // These errors happen if exports are not mapped to files that should be importable. if (["ERR_PACKAGE_PATH_NOT_EXPORTED", "ERR_MODULE_NOT_FOUND"].includes(error.code)) { console.error(error); process.exit(1); } // Any other errors (unless I missed any that should be added to the checks // above) are errors that happen after imported modules are successfully // resolved (f.e. a module was found, but it doesn't export a particular // identifier when running in node vs browser). SO we silence them by not // re-throwing them here as we don't want to fail the test in those cases, // because we're testing only that ESM exports are set up correctly. // Importing `solid-js/h` will fail in node even if modules are resolved // properly, for example. } Promise.all([ import("solid-js").catch(checkError), import("solid-js/dist/solid.js").catch(checkError), import("solid-js/web").catch(checkError), import("solid-js/web/dist/web.js").catch(checkError), import("solid-js/web/dist/server.js").catch(checkError), import("solid-js/h").catch(checkError), import("solid-js/h/dist/h.js").catch(checkError), import("solid-js/html").catch(checkError), import("solid-js/html/dist/html.js").catch(checkError) ]) .then(() => { console.log("ES Module import test passed."); }) .catch(error => { console.error(error); process.exit(1); }); ================================================ FILE: packages/test-integration/tests/downloaded.spec.ts ================================================ import { mkdir, rm, exec } from "shelljs"; import { resolve, dirname, join } from "path"; import { download, extract } from "gitly"; import { existsSync } from "fs"; function makeTestRepo( name: string, install: (dependencies: string[]) => string = npmInstaller, test: string = "npm test" ) { return { name, install, test }; } type TestRepo = ReturnType; function npmInstaller(dependencies: string[]) { return dependencies.map(dep => `npm install --save "${dep}" --ignore-scripts`).join(" && "); } /** a function that tests a package */ async function testPackage(testRepo: TestRepo, dependencies: string[], isSilent = false) { const { name, install, test } = testRepo; const distFolder = resolve(join(__dirname, "fixtures", "downloaded", name)); // download repository if (!packageExists(distFolder)) { const source = await download(name); mkdir("-p", distFolder); await extract(source, distFolder); } // run the tests if (packageExists(distFolder)) { exec(install(dependencies), { fatal: true, cwd: distFolder, silent: isSilent }); exec(test, { fatal: true, cwd: distFolder, silent: isSilent }); return true; } return false; } /** pack a package */ function pack(packageRoot: string) { const packageJson = join(packageRoot, "package.json"); const pkg = require(packageJson); const packedPkg = join(packageRoot, `${pkg.name}-${pkg.version}.tgz`); rm("-rf", packedPkg); exec("npm pack", { cwd: packageRoot, fatal: true }); return packedPkg; } function packageExists(packageRoot: string) { return existsSync(join(packageRoot, "package.json")); } describe("Downloaded tests", () => { // The repositories to run the tests for const testRepos: TestRepo[] = [makeTestRepo("aminya/solid-simple-table")]; const clean = true; let packedSolidPkg: string, packedBabelSolidPkg: string, dependencies: string[]; beforeAll(() => { // Check if solid is built if (!existsSync(resolve(join(__dirname, "../../solid/dist")))) { throw new Error("Solid is not built. Run `npm run build`"); } // clean downloaded packages if (clean) { rm("-rf", resolve(join(__dirname, "./fixtures/downloaded"))); } // package solid and babel-preset-solid packedSolidPkg = pack(resolve(__dirname, "../../solid")); packedBabelSolidPkg = pack(resolve(__dirname, "../../babel-preset-solid")); dependencies = [packedSolidPkg, packedBabelSolidPkg]; }); // run the tests for (const testRepo of testRepos) { test(testRepo.name, async () => { const pass = await testPackage(testRepo, dependencies); expect(pass).toBe(true); }); } afterAll(() => { rm("-rf", packedSolidPkg); rm("-rf", packedBabelSolidPkg); }); }); ================================================ FILE: packages/test-integration/tsconfig.json ================================================ { "extends": "../../tsconfig.test.json", "include": ["./tests"] } ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - 'packages/*' ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "emitDeclarationOnly": true, "declaration": true, "target": "ESNext", "newLine": "LF", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "jsx": "preserve" }, "exclude": ["node_modules"] } ================================================ FILE: tsconfig.test.json ================================================ { "compilerOptions": { "noEmit": true, "target": "ESNext", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "lib": ["dom", "esnext", "dom.iterable"], "jsx": "preserve", // Necessary due to picocolors using weird export syntax. // This can be removed if the following issue gets fixed: // https://github.com/alexeyraspopov/picocolors/issues/50 "allowSyntheticDefaultImports": true, // Allows TS to see the Vitest globals "types": ["vitest/globals"] }, "exclude": ["node_modules"] } ================================================ FILE: turbo.json ================================================ { "$schema": "https://turborepo.org/schema.json", "pipeline": { "build": { "dependsOn": ["^build"], "outputMode": "new-only" }, "solid-js#build": { "outputs": ["dist/**", "**/dist/**"] }, "solid-js#link": { "outputs": [] }, "solid-element#build": { "dependsOn": ["solid-js#types", "solid-js#link"] }, "solid-js#types": { "dependsOn": ["^types"], "outputs": ["types/**", "**/types/**"] }, "coverage": { "outputs": ["coverage/**"] }, "test": { "outputs": [] }, "test-integration#test": { "dependsOn": ["solid-js#build", "solid-js#link"] }, "solid-js#test-types": { "outputs": [], "dependsOn": ["types"] } } }