Skip to content

Commit

Permalink
Solid 1.5 (#1176)
Browse files Browse the repository at this point in the history
* new batching, fix #1001, fix #1062

* fix #1019, resource state

* pluggable storage on resource

* Fix createMutable to handle reactivity in case element is undefined

* Added tests

* better fixes, ssr spread improvements

* Add `has` trap for accurate `in` with stores and mutables (#1109)

* Add `has` trap for accurate `in` with stores and mutables

Fixes #1107

* Fix ?. typo

* Remove optional chaining ?.

* fix(types): fix mergeProps type when spreading array as params (#1028)

This fixes the return type of mergeProps when an array is spread as params: `mergeProps(...propsArray)`, `mergeProps(...propsArray, props)`, and `mergeProps(props1, ...propsArray, props2)`. Previously these calls would ignore the type of the array being spread and all props after the spread e.g.:

```ts
const merged = mergeProps({ a: 1 }, ...[{ b: 2 }], { c: 3 }); // { a: 1 }
```

- Allow mergeProps to be called with all manner of spread params and return the correct type.
- As a consequence of the above, mergeProps' type allows calling it with no params.
- Brought back `Simplify`, since it doesn't interfere with generics and improves readability of the result type.
- Simplified and added comments for component.ts type tests.

Additionally:
- Improved types when generic props are present.

Issues:
- This doesn't correctly type spreading multiple arrays however, since typescript doesn't allow two "rest" params: `someFn(...[1], ...["2"], 3)` is inferred as `T = [...(string | number)[], number]` and not `T = [...number[], ...string[], number]`. In this case the union of types in the array is merged into a single type, which may not be entirely accurate.

* Fix `Dynamic` types (#1116)

* Fix `Dynamic` types

* Fix `component` type

* Fix `DynamicProps`, `ComponentProps`; Add `ValidComponent`

* Fix `Dynamic` test for type fix

* Fix `Dynamic` test to use `id` instead of `title` for DOM compatibility

* Fix `component` to be required property

* fix #1112 fix built in functions on proxy failing pass through

* v1.5.0-beta.0

* cleanups on the server

* update and rework build system

* tweak turbo commands

* remove clean command

* update ci

* decrease dependency on symlink

* update lockfile

* more type issues

* fix build

* SSR performance improvement

* fix bug with ssr resolve

* bump

* Add build instructions to CONTRIBUTING (#1136)

* Add pnpm build instructions to CONTRIBUTING

* Fix Turbo link

* fix: add explicit extensions and exports fields

* errors on server while not flushed, many bug fixes

* feat(types): createResource improvements (#1130)

* feat(types): createResource improvements

- Add typable `refetching`/`refetch` with default type `unknown` for backwards compatibility.
- Remove unneeded generics.
- Refactor `createResource` parameters to reduce casting.
- Remove unneeded instances of casting, non-null assertations and `any`.
- Fix `onHydrated` type, removing `refetching`.
- Add `InitializedResource` types for overloads which define `initialValue`.
- Changed `Error` to return `never` instead of `undefined`, since reading resources in such a state throws, so the values will never be used.
- Updated docs slightly.
- Fixed a test slightly (type of an unused parameter from `string` to `unknown`).

Potential Issues:
- Adding the initialized types might slightly break userland functions. However adding them is necessary to differentiate `{ initialValue: undefined }` from omitting `initialValue`, and for cases where `T` includes `undefined` to be typed correctly.
- `store` should not need to accept `undefined` if `initialValue` is provided, but in order to make passing a generic `createSignal`-typed function work without an error, `init` and the type of the signal returned must be the same.
- In various places `undefined` can still appear if a resource errors and refetches. As such `mutate` and `store` also need to handle `undefined` correctly. This might break typing for existing `mutate` calls which are typed without `undefined`.

* test(types): add tests for createResource types

* refactor(types): fix and clean up createResource types

- Add `NoInfer` for options so that only the fetcher is used to infer the type of the resource
- Remove some unneeded casting

* feat(types): export initialized resource types

Co-authored-by: Ryan Carniato <[email protected]>

* simplify batching, reduce createComputed, fix #1122 enable transitions.

* bump

* remove computed from SuspenseList

* Remove computed from createProvider

* Remove a console.log (#1146)

* fixes to suspenselist

* useInitialValue on resources

* fix #1138 dialog type, fix #1147 untrack ref, fix #1151 untrack cleanup

* keyed control flow updates

* bump

* improve createSelector perf

* draft 1.5 changelog

* fix falsy check

* faster asset rendering

* children.toArray

* refactor(types): change `createStore` types for a clearer error message (#1157)

Specifically, this change targets these differences:

- Initializing a store with a generic type which is not restricted to `object` now shows `Argument of type 'T' is not assignable to parameter of type 'object | undefined'. Type 'T' is not assignable to type 'object'` instead of `Argument of type '[T]' is not assignable to parameter of type '{} extends T ? [store?: T | undefined, options?: { name?: string | undefined; } | undefined] : [store: object & T, options?: { name?: string | undefined; } | undefined]'`.

- Initializing a store with a non-generic, non-object type now shows that the expected parameter type is `object | undefined` instead of `never` (or `{} | undefiend` if trying to use `null`).

* `resource.value` and small tweaks to resources

* bump

* experimenting with nodenext

* fix

* delete resource.value, defer to further deliberation

* small naming tweaks

* bump

* update packages

* bump

* docs: add quotes to snippets (#1153)

* better option naming for resource

* add missing deps

* Update Readme (#1137)

* add missing type exports

* bump

* small updates

* untrack JSON.stringify to avoid reactive side-effects on serialization. (#1177)

* untrack JSON.stringify to avoid reactive side-effects on serialization.

* untrack JSON.stringify to avoid reactive side-effects on serialization.

* keep immdiately evaluated module code, below its indirect declared let dependencies.

Co-authored-by: Ryan Carniato <[email protected]>

Co-authored-by: Paolo Ricciuti <[email protected]>
Co-authored-by: Erik Demaine <[email protected]>
Co-authored-by: Xavier Loh <[email protected]>
Co-authored-by: Alexis H. Munsayac <[email protected]>
Co-authored-by: modderme123 <[email protected]>
Co-authored-by: Kirill Mironov <[email protected]>
Co-authored-by: Milo <[email protected]>
Co-authored-by: Seanghay Yath <[email protected]>
Co-authored-by: Mathieu Decaffmeyer <[email protected]>
Co-authored-by: LiQuidProQuo <[email protected]>
  • Loading branch information
11 people authored Aug 26, 2022
1 parent 12b969d commit a209b35
Show file tree
Hide file tree
Showing 92 changed files with 8,470 additions and 35,947 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/main-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: pnpm/[email protected]
with:
version: 7
- uses: actions/setup-node@v2
with:
node-version: 14
cache: 'pnpm'

- name: Installing deps
run: npm install
run: pnpm install

- name: Building
run: npm run build
run: pnpm run build

- name: Testing & Coverage
run: |
npm run test
npm run test:coverage
pnpm run test
pnpm run coverage
- name: Coveralls
uses: coverallsapp/github-action@master
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ lib/
coverage/
types/
.DS_Store
.turbo/

packages/solid/src/jsx.d.ts
packages/solid/web/server/server.d.ts
Expand Down
127 changes: 125 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,125 @@
# Changelog

## 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<T>(value: T): Signal<T> {
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<T>;
}

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 `<Show>` and `<Match>` 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
<Show when={user()} keyed>
<div>{user().name}</div>
</Show>

// callback
<Show when={user()} keyed>
{user => <div>{user.name}</div>}
</Show>
```

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
Expand All @@ -19,6 +139,7 @@ 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.
Expand All @@ -39,7 +160,7 @@ setTodos([...todos, { id: 3, title: "New Todo", done: false }])
<For each={todos}>{todo => <Todo todo={todo} />}</For>;
```

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.
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.

Expand All @@ -50,7 +171,7 @@ Suspense and Transitions are amazingly powerful feature but occasionally you wan
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)
const [resource] = createResource(source, fetcher);

// read it as usual
resource();
Expand Down Expand Up @@ -115,12 +236,14 @@ Writing to a store or mutable within `batch` (including effects) no longer immed
#### 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
Expand Down
11 changes: 11 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,14 @@ Contributing to ecosystem projects is just as important as contributing to Solid
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. `pnpm install` (install all dependencies)
2. `pnpm run build`

You can then run all tests via `pnpm test`.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ This will create a minimal, client-rendered application powered by [Vite](https:
Or you can install the dependencies in your own setup. To use Solid with JSX (_recommended_), run:

```sh
> npm install solid-js babel-preset-solid
> 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:
Expand Down
6 changes: 0 additions & 6 deletions lerna.json

This file was deleted.

Loading

0 comments on commit a209b35

Please sign in to comment.