Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update SSR, Events, Spreads, New Resource API, Children Helper, and More #328

Merged
merged 15 commits into from
Feb 4, 2021
Merged
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ coverage/
types/

packages/solid/src/jsx.d.ts
packages/solid/web/server-async/asyncSSR.d.ts
packages/solid/web/server/syncSSR.d.ts
packages/solid/web/src/runtime.d.ts
packages/solid/web/server/server.d.ts
packages/solid/web/src/client.d.ts
64 changes: 64 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,69 @@
# Changelog

## 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 `<Switch>` can now have dynamic children like `<For>` 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.
Expand Down
36 changes: 16 additions & 20 deletions documentation/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

This is the smallest and most primitive reactive atom used to track a single value. By default signals always notify on setting a value. You can have it only notify on changes if you pass true to the second parameter. Or a custom comparator can be passed in to indicate whether the values should be considered equal and listeners not notified.

### `createState(initValue): [state, setState]`
### `createMemo(prev => <code>, initialValue, boolean | comparatorFn): getValueFn`

Creates a new State proxy object and setState pair. State only triggers update on values changing. Tracking is done by intercepting property access and automatically tracks deep nesting via proxy.
Creates a readonly derived signal that recalculates it's value whenever the executed codes dependencies update. By default memos always notify on updating a value. You can have it only notify on changes if you pass true to the second parameter. Or a custom comparator can be passed in to indicate whether the values should be considered equal and listeners not notified.

### `createEffect(prev => <code>, initialValue): void`

Creates a new computation that automatically tracks dependencies and runs after each render where a dependency has changed. Ideal for using `ref`s and managing other side effects. 2nd argument is the initial value.

### `onMount(() => <code>)`

Expand All @@ -16,17 +20,9 @@ Registers a method that runs after initial render and elements have been mounted

Registers a cleanup method that executes on disposal or recalculation of the current context. Can be used in components or computations.

### `createMemo(prev => <code>, initialValue, boolean | comparatorFn): getValueFn`

Creates a readonly signal that recalculates it's value whenever the executed codes dependencies update. By default memos always notify on updating a value. You can have it only notify on changes if you pass true to the second parameter. Or a custom comparator can be passed in to indicate whether the values should be considered equal and listeners not notified.

### `createComputed(prev => <code>, initialValue): void`

Creates a new computation that automatically tracks dependencies and runs immediately. Use this to write to other reactive primitives or to reactively trigger async data loading. 2nd argument is the initial value.

### `createEffect(prev => <code>, initialValue): void`
### `createState(initValue): [state, setState]`

Creates a new computation that automatically tracks dependencies and runs after each render where a dependency has changed. Ideal for using `ref`s and managing other side effects. 2nd argument is the initial value.
Creates a new State proxy object and setState pair. State only triggers update on values changing. Tracking is done by intercepting property access and automatically tracks deep nesting via proxy.

### `createContext(defaultContext): Context`

Expand Down Expand Up @@ -68,6 +64,10 @@ Creates a new mutable State proxy object. State only triggers update on values c

Creates memo that only notifies downstream changes when the browser is idle. `timeoutMs` is the maximum time to wait before forcing the update.

### `createComputed(prev => <code>, initialValue): void`

Creates a new computation that automatically tracks dependencies and runs immediately. Use this to write to other reactive primitives or to reactively trigger async data loading before render. 2nd argument is the initial value.

### `createRenderEffect(prev => <code>, initialValue): void`

Creates a new computation that automatically tracks dependencies and runs during the render phase as DOM elements are created and updated but not necessarily connected. All internal DOM updates happen at this time.
Expand All @@ -76,13 +76,9 @@ Creates a new computation that automatically tracks dependencies and runs during

Creates a conditional signal that only notifies subscribers when entering or exiting their key matching the value. Useful for delegated selection state.

### `createResource(initialValue, options: { name }): [getValueFn, loadFn]`

Creates a new resource signal that can hold an async resource. Resources when read while loading trigger Suspense. The `loadFn` takes a Promise whose resolved value is set in the resource.

### `createResourceState(initialValue, options: { name }): [state, loadState, setState]`
### `createResource(key, fetcher, initialValue): getValueFn`

Creates a new Resource State object. Similar to normal state except each immediate property is a resource.
Creates a new resource signal that can hold an async resource. Resources when read while loading trigger Suspense. The `fetcher` is a function that accepts key and returns a Promise whose resolved value is set in the resource.

### `lazy(() => <Promise>): Component`

Expand All @@ -92,9 +88,9 @@ Used to lazy load components to allow for things like code splitting and Suspens

Used to batch async updates deferring commit until all async processes are complete.

### `assignProps(target, ...sources): target`
### `mergeProps(...sources): target`

A reactive object `assign` method. Useful for setting default props for components in case caller doesn't provide them. Or cloning the props object including reactive properties.
A reactive object `merge` method. Useful for setting default props for components in case caller doesn't provide them. Or cloning the props object including reactive properties.

### `splitProps(props, ...keyArrays): [...splitProps]`

Expand Down
6 changes: 3 additions & 3 deletions documentation/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ To help maintain reactivity Solid has a couple prop helpers:

```jsx
// default props
props = assignProps({}, { name: "Smith" }, props);
props = mergeProps({ name: "Smith" }, props);

// clone props
const newProps = assignProps({}, props);
const newProps = mergeProps(props);

// merge props
assignProps(props, otherProps);
props = mergeProps(props, otherProps);

// split props into multiple props objects
const [local, others] = splitProps(props, ["className"])
Expand Down
74 changes: 20 additions & 54 deletions documentation/reactivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Solid's data management is built off a set of flexible reactive primitives which are responsible for all the updates. It takes a very similar approach to MobX or Vue except it never trades its granularity for a VDOM. Dependencies are automatically tracked when you access your reactive values in your Effects and JSX View code.

Solid has a number of reactive primitives but the main 2 are Signals, and State. Ultimately you will need to understand both to write effective Solid code.
Solid's primitives come in the form of `create` calls that often return tuples, where generally the first element is a readable primitive and the second is a setter. It is common to refer to only the readable part by the primitive name.

Signals hold simple values that you view as atomic immutable cells that consist of a getter and setter. These are ideal for simple local component values. They are called signals as they act as tiny streams that wire your application together.
Here is a basic auto incrementing counter that is updating based on setting the `count` signal.

```jsx
import { createSignal, onCleanup } from "solid-js";
Expand All @@ -21,76 +21,45 @@ const App = () => {
render(() => <App />, document.getElementById("app"));
```

> **For React Users:** This looks like React Hooks, but it is very different. There are no Hook rules, or concern about stale closures because your Component only runs once. It is only the "Hooks" that re-execute. So they always have the latest.

Solid's state object are deeply nested reactive data trees useful for global stores, model caches, and 3rd party immutable data interopt. They have a much more powerful setter that allows to specify nested changes and use value and function forms for updates.

They can be used in Components as well and is the go to choice when data gets more complicated (nested).

```jsx
import { createState, onCleanup } from "solid-js";
import { render } from "solid-js/web";

const App = () => {
const [state, setState] = createState({
user: {
firstName: "John",
lastName: "Smith",
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
});

return (
<div onClick={() => setState("user", "lastName", value => value + "!")}>
{state.user.fullName}
</div>
);
};

render(() => <App />, document.getElementById("app"));
```

Remember if you destructure or spread a state object reactivity is lost. However, unlike Vue we don't separate our `setup` from our view code so there is little concern about transforming or transfering these reactive atoms around. Just access the properties where you need them.

With Solid State and Context API you really don't need 3rd party global stores. These proxies are optimized part of the reactive system and lend to creating controlled unidirectional patterns.
> **For React Users:** This looks like React Hooks, but it is very different. There are no Hook rules, or concern about stale closures because your Component only runs once. It is only the "Hooks" that re-execute. So they always have the latest references.

## Signals

Signals are the glue that hold the library together. They are a simple primitive that contain values that change over time. With Signals you can track all sorts of changes from various sources in your applications. You can update a Signal manually or from any Async source.
Signals are the glue that hold the library together. They are a simple primitive that contain values that change over time. With Signals you can track all sorts of changes from various sources in your applications. They are not tied to any specific component and can be used wherever whenever.

```js
import { createSignal, onCleanup } from "solid-js";

function useTick(delay) {
function createTick(delay) {
const [getCount, setCount] = createSignal(0),
handle = setInterval(() => setCount(getCount() + 1), delay);
onCleanup(() => clearInterval(handle));
return getCount;
}
```

## Accessors Reactive Scope
## Reactive Scope and Tracking

Signals are special functions that when executed return their value. In addition they are trackable when executed under a reactive scope. This means that when their value is read (executed) the currently executing reactive scope is now subscribed to the Signal and will re-execute whenever the Signal is updated.

This mechanism is based on the executing function's scope so Signals reads can be composed and nested as many levels as desired. By wrapping a Signal read in a thunk `() => signal()` you have effectively created a derived signal that can be tracked as well. The same holds true for accessing state. Want to use state as a signal just wrap it in a function:
This method of tracking wraps the execution stack so Signals can be accessed any number of levels deep. In so, by wrapping a Signal read in a thunk `() => signal()` you have effectively created a derived signal that can be tracked as well. The same holds true for accessing props or Solid's reactive State proxies. Want to use state as a signal just wrap it in a function:

```js
// I can be tracked
// I can be tracked later
const firstName = () => state.user.firstName;

return <div>{firstName()}</div>;
```

These accessors are just functions that can be tracked and return a value. No additional primitive or method is needed for them to work as Signals in their own right. However, you need another primitive to create that reactive scope:
These are just functions that can be tracked and return a value. No additional primitive or method is needed for them to work as Signals in their own right. This is because Signals are readonly. Any pure function that wraps a signal is also a Signal.

However, you need another primitive to actually execute the work and track the these signals.

## Computations

A computation is calculation over a function execution that automatically and dynamically tracks any child signals that are accessed during that execution. A computation goes through a cycle on execution where it releases its previous execution's dependencies, then executes grabbing the current dependencies.

There are 3 main computations used by Solid: Memos which are pure and designed to cache values until their reactivity forces re-evaluation, Computeds which are designed to write to other signals, and Effects which are intended to produce side effects after rendering.
There are 2 main types of computations. Those that are pure and meant to derive a value called Memos and those that update the outside world and produce side effects, aptly called Effects.

```js
import { createSignal, createEffect, createMemo } from "solid-js";
Expand All @@ -106,7 +75,7 @@ setCount(count() + 1);

Effects are what allow the DOM to stay up to date. While you don't see them, everytime you write an expression in the JSX(code between the parenthesis `{}`), the compiler is wrapping it in a function and passing it to a `createEffect` call.

Memos allow us to store and access values without re-evaluating them until their dependencies change.
Memos allow us to store and access values without re-evaluating them until their dependencies change. They are very similar to derived Signals mentioned above except they only re-evaluate when their dependencies change and return the last cached value on read.

Keep in mind memos are only necessary if you wish to prevent re-evaluation when the value is read. Useful for expensive operations like DOM Node creation. Any example with a memo could also just be a function and effectively be the same without caching as it's just another signal.

Expand All @@ -126,6 +95,7 @@ setCount(count() + 1);
Memos also pass the previous value on each execution. This is useful for reducing operations (obligatory Redux in a couple lines example):

```js
// reducer
const reducer = (state, action = {}) => {
switch (action.type) {
case "LIST/ADD":
Expand All @@ -135,9 +105,12 @@ const reducer = (state, action = {}) => {
}
};

// initial state
const state = { list: [] };

// redux
const [getAction, dispatch] = createSignal(),
getStore = createMemo(state => reducer(state, getAction()), { list: [] });
getStore = createMemo(state => reducer(state, getAction()), state);

// subscribe and dispatch
createEffect(() => console.log(getStore().list));
Expand Down Expand Up @@ -171,13 +144,6 @@ For convenience Solid provides an `on` operator to set up explict dependencies f
createEffect(on(a, v => console.log(v, b())));
```

Another situation is maybe you want something to run only on mount:

```js
// does not update when props update
createEffect(() => untrack(() => console.log("Mounted with", props.a, props.b)));
```

Solid executes synchronously but sometimes you want to apply multiple changes at once. `batch` allows us to do that without triggering updates multiple times.

```js
Expand All @@ -197,10 +163,10 @@ _Note: Solid's graph is synchronously executed so any starting point that isn't

## Composition

State and Signals combine wonderfully as wrapping a state selector in a function instantly makes it reactive accessor. They encourage composing more sophisticated patterns to fit developer need.
Solid's primitives combine wonderfully. They encourage composing more sophisticated patterns to fit developer need.

```js
// deep reconciled immutable reducer
// Solid's fine-grained exivalent to React's `useReducer` Hook
const useReducer = (reducer, init) => {
const [state, setState] = createState(init),
[getAction, dispatch] = createSignal();
Expand Down