Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
atellmer committed Nov 29, 2022
2 parents 2d49142 + fdf02d3 commit d31a9da
Show file tree
Hide file tree
Showing 17 changed files with 770 additions and 33 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import {
useContext,
useEffect,
useLayoutEffect,
useInsertionEffect,
useError,
useRef,
useId,
Expand All @@ -99,6 +100,7 @@ import {
useReducer,
useReactiveState,
useDeferredValue,
useSyncExternalStore,
} from '@dark-engine/core';
import { render, createRoot, createPortal, useStyle } from '@dark-engine/platform-browser';
```
Expand Down Expand Up @@ -584,6 +586,20 @@ useLayoutEffect(() => {
}, []);
```

#### useInsertionEffect

```tsx
import { useInsertionEffect } from '@dark-engine/core';
```

The signature is identical to useEffect, but it fires synchronously before all DOM mutations. Use this to inject styles into the DOM before reading layout in useLayoutEffect. This hook does not have access to refs and cannot call render. Useful for css-in-js libraries.

```tsx
useInsertionEffect(() => {
// add style tags to head
}, []);
```

<a name="optimization"></a>
## Performance optimization

Expand Down Expand Up @@ -1009,6 +1025,22 @@ const Checkbox = createComponent(() => {
});
```

#### useSyncExternalStore

The hook is useful for synchronizing render states with an external state management library such as Redux.

```tsx
import { useSyncExternalStore } from '@dark-engine/core';
```

```tsx
const App = createComponent(() => {
const state = useSyncExternalStore(store.subscribe, store.getState); // redux store

return <div>{state.isFetching ? 'loading...' : 'ola! 🤪'}</div>;
});
```

Thanks everyone!

# LICENSE
Expand Down
44 changes: 34 additions & 10 deletions packages/core/src/fiber/fiber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import {
rootStore,
effectsStore,
layoutEffectsStore,
insertionEffectsStore,
isLayoutEffectsZone,
isInsertionEffectsZone,
} from '../scope';
import { type ComponentFactory, detectIsComponentFactory, getComponentFactoryKey } from '../component';
import {
Expand All @@ -39,6 +41,7 @@ import { PARTIAL_UPDATE } from '../constants';
import { type NativeElement, type Hook, EffectTag, cloneTagMap } from './types';
import { hasEffects } from '../use-effect';
import { hasLayoutEffects } from '../use-layout-effect';
import { hasInsertionEffects } from '../use-insertion-effect';
import { walkFiber } from '../walk';
import { unmountFiber } from '../unmount';
import { Text } from '../view';
Expand All @@ -56,9 +59,10 @@ class Fiber<N = NativeElement> {
public provider: Map<Context, ContextProviderValue>;
public transposition: boolean;
public mountedToHost: boolean;
public portalHost: boolean;
public effectHost: boolean;
public layoutEffectHost: boolean;
public insertionEffectHost: boolean;
public portalHost: boolean;
public childrenCount: number;
public marker: string;
public isUsed: boolean;
Expand All @@ -79,19 +83,20 @@ class Fiber<N = NativeElement> {
this.provider = options.provider || null;
this.transposition = !detectIsUndefined(options.transposition) ? options.transposition : false;
this.mountedToHost = !detectIsUndefined(options.mountedToHost) || false;
this.portalHost = !detectIsUndefined(options.portalHost) ? options.portalHost : false;
this.effectHost = !detectIsUndefined(options.effectHost) ? options.effectHost : false;
this.layoutEffectHost = !detectIsUndefined(options.layoutEffectHost) ? options.layoutEffectHost : false;
this.insertionEffectHost = !detectIsUndefined(options.insertionEffectHost) ? options.insertionEffectHost : false;
this.portalHost = !detectIsUndefined(options.portalHost) ? options.portalHost : false;
this.childrenCount = options.childrenCount || 0;
this.marker = options.marker || '';
this.idx = options.idx || 0;
this.isUsed = options.isUsed || false;
this.batched = options.batched || null;
}

public markPortalHost() {
this.portalHost = true;
this.parent && !this.parent.portalHost && this.parent.markPortalHost();
public markMountedToHost() {
this.mountedToHost = true;
this.parent && !this.parent.mountedToHost && this.parent.markMountedToHost();
}

public markEffectHost() {
Expand All @@ -104,9 +109,14 @@ class Fiber<N = NativeElement> {
this.parent && !this.parent.layoutEffectHost && this.parent.markLayoutEffectHost();
}

public markMountedToHost() {
this.mountedToHost = true;
this.parent && !this.parent.mountedToHost && this.parent.markMountedToHost();
public markInsertionEffectHost() {
this.insertionEffectHost = true;
this.parent && !this.parent.insertionEffectHost && this.parent.markInsertionEffectHost();
}

public markPortalHost() {
this.portalHost = true;
this.parent && !this.parent.portalHost && this.parent.markPortalHost();
}

public setError(error: Error) {
Expand Down Expand Up @@ -549,6 +559,10 @@ function performMemo(options: PerformMemoOptions) {
nextFiber = nextFiber.nextSibling;
}

if (memoFiber.mountedToHost) {
fiber.markMountedToHost();
}

if (memoFiber.effectHost) {
fiber.markEffectHost();
}
Expand All @@ -557,8 +571,8 @@ function performMemo(options: PerformMemoOptions) {
fiber.markLayoutEffectHost();
}

if (memoFiber.mountedToHost) {
fiber.markMountedToHost();
if (memoFiber.insertionEffectHost) {
fiber.markInsertionEffectHost();
}

if (memoFiber.portalHost) {
Expand Down Expand Up @@ -610,6 +624,10 @@ function pertformInstance(options: PerformInstanceOptions) {
fiber.markLayoutEffectHost();
}

if (hasInsertionEffects(fiber)) {
fiber.markInsertionEffectHost();
}

if (platform.detectIsPortal(performedInstance)) {
fiber.markPortalHost();
}
Expand Down Expand Up @@ -845,6 +863,11 @@ function hasChildrenProp(element: DarkElementInstance): element is TagVirtualNod

function commitChanges() {
const wipFiber = wipRootStore.get();
const insertionEffects = insertionEffectsStore.get();

isInsertionEffectsZone.set(true);
insertionEffects.forEach(fn => fn());
isInsertionEffectsZone.set(false);

commitWork(wipFiber.child, () => {
const layoutEffects = layoutEffectsStore.get();
Expand All @@ -859,6 +882,7 @@ function commitChanges() {
});

wipRootStore.set(null); // important order
insertionEffectsStore.reset();
layoutEffectsStore.reset();
effectsStore.reset();

Expand Down
32 changes: 17 additions & 15 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,28 @@ export * from './ref';
export * from './scope';
export * from './shared';
export * from './suspense';
export * from './use-callback';
export * from './use-context';
export * from './use-deferred-value';
export { useCallback } from './use-callback';
export { useContext } from './use-context';
export { useDeferredValue } from './use-deferred-value';
export { useEffect } from './use-effect';
export * from './use-error';
export * from './use-event';
export * from './use-imperative-handle';
export { useLayoutEffect } from './use-layout-effect';
export * from './use-memo';
export * from './use-reducer';
export * from './use-ref';
export * from './use-update';
export * from './use-state';
export * from './use-reactive-state';
export * from './use-id';
export * from './view';
export * from './constants';
export { useInsertionEffect } from './use-insertion-effect';
export { useError } from './use-error';
export { useEvent } from './use-event';
export { useImperativeHandle } from './use-imperative-handle';
export { useMemo } from './use-memo';
export { useReducer } from './use-reducer';
export { useRef } from './use-ref';
export { useUpdate } from './use-update';
export { useState } from './use-state';
export { useReactiveState } from './use-reactive-state';
export { useId } from './use-id';
export { useSyncExternalStore } from './use-sync-external-store';
export { walkFiber } from './walk';
export { unmountRoot } from './unmount';
export { batch } from './batch';
export * from './view';
export * from './constants';

export const version = process.env.VERSION;

Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/scope/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ class Store<N = unknown> {
public componentFiber: Fiber = null;
public effects: Array<() => void> = [];
public layoutEffects: Array<() => void> = [];
public insertionEffects: Array<() => void> = [];
public isLayoutEffectsZone = false;
public isInserionEffectsZone = false;
public isUpdateHookZone = false;
public isBatchZone = false;
public trackUpdate: (nativeElement: N) => void | undefined;
Expand Down Expand Up @@ -126,11 +128,22 @@ const layoutEffectsStore = {
add: (effect: () => void) => store.get().layoutEffects.push(effect),
};

const insertionEffectsStore = {
get: () => store.get().insertionEffects,
reset: () => (store.get().insertionEffects = []),
add: (effect: () => void) => store.get().insertionEffects.push(effect),
};

const isLayoutEffectsZone = {
get: () => store.get()?.isLayoutEffectsZone || false,
set: (value: boolean) => (store.get().isLayoutEffectsZone = value),
};

const isInsertionEffectsZone = {
get: (id?: number) => store.get(id)?.isInserionEffectsZone || false,
set: (value: boolean) => (store.get().isInserionEffectsZone = value),
};

const isUpdateHookZone = {
get: () => store.get()?.isUpdateHookZone || false,
set: (value: boolean) => (store.get().isUpdateHookZone = value),
Expand All @@ -153,7 +166,9 @@ export {
fiberMountStore,
effectsStore,
layoutEffectsStore,
insertionEffectsStore,
isLayoutEffectsZone,
isInsertionEffectsZone,
isUpdateHookZone,
isBatchZone,
};
15 changes: 11 additions & 4 deletions packages/core/src/unmount/unmount.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { createRoot } from '@dark-engine/platform-browser';
import { h } from '../element';
import { createComponent } from '../component';
import { useInsertionEffect } from '../use-insertion-effect';
import { useLayoutEffect } from '../use-layout-effect';
import { useEffect } from '../use-effect';

Expand All @@ -14,10 +15,14 @@ beforeEach(() => {
});

describe('[unmount]', () => {
test('clears all effects and unmounts root node correctly', () => {
test('clears all effects correctly', () => {
const dropFn = jest.fn();

const Child = createComponent(() => {
useInsertionEffect(() => {
return () => dropFn();
}, []);

useLayoutEffect(() => {
return () => dropFn();
}, []);
Expand All @@ -30,6 +35,10 @@ describe('[unmount]', () => {
});

const App = createComponent(() => {
useInsertionEffect(() => {
return () => dropFn();
}, []);

useLayoutEffect(() => {
return () => dropFn();
}, []);
Expand All @@ -52,8 +61,6 @@ describe('[unmount]', () => {
root.render(App());
jest.runAllTimers();
root.unmount();
expect(dropFn).toBeCalledTimes(8);
expect(host.innerHTML).toBe('');
expect(root.unmount).not.toThrowError();
expect(dropFn).toBeCalledTimes(12);
});
});
5 changes: 4 additions & 1 deletion packages/core/src/unmount/unmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ import { platform } from '../platform';
import { detectIsComponentFactory } from '../component';
import { dropEffects } from '../use-effect';
import { dropLayoutEffects } from '../use-layout-effect';
import { dropInsertionEffects } from '../use-insertion-effect';
import { walkFiber } from '../walk';
import { detectIsUndefined } from '../helpers';
import { currentRootStore, eventsStore, rootStore } from '../scope';

function unmountFiber(fiber: Fiber) {
if (!fiber.effectHost && !fiber.layoutEffectHost && !fiber.portalHost) return;
if (!fiber.insertionEffectHost && !fiber.layoutEffectHost && !fiber.effectHost && !fiber.portalHost) return;

walkFiber(fiber, ({ nextFiber, isReturn, stop }) => {
if (nextFiber === fiber.nextSibling || fiber.transposition) return stop();

if (!isReturn && detectIsComponentFactory(nextFiber.instance)) {
// important order
nextFiber.insertionEffectHost && dropInsertionEffects(nextFiber.hook);
nextFiber.layoutEffectHost && dropLayoutEffects(nextFiber.hook);
nextFiber.effectHost && dropEffects(nextFiber.hook);
nextFiber.portalHost && platform.unmountPortal(nextFiber);
Expand Down
47 changes: 47 additions & 0 deletions packages/core/src/use-effect/use-effect.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { render } from '@dark-engine/platform-browser';
import { h } from '../element';
import { createComponent } from '../component';
import { useUpdate } from '../use-update';
import { useEffect } from './use-effect';

let host: HTMLElement = null;
Expand Down Expand Up @@ -251,4 +252,50 @@ describe('[use-effect]', () => {
expect(effectFn1.mock.invocationCallOrder[2]).toBeLessThan(effectFn2.mock.invocationCallOrder[2]);
expect(dropFn1.mock.invocationCallOrder[1]).toBeLessThan(dropFn2.mock.invocationCallOrder[1]);
});

test('can call render #1', () => {
const mockFn = jest.fn();

const render$ = (props = {}) => {
render(App(props), host);
};

const App = createComponent(() => {
useEffect(() => {
render$();
}, []);

mockFn();

return null;
});

render$();
jest.runAllTimers();
expect(mockFn).toBeCalledTimes(2);
});

test('can call render #2', () => {
const mockFn = jest.fn();

const render$ = (props = {}) => {
render(App(props), host);
};

const App = createComponent(() => {
const update = useUpdate();

useEffect(() => {
update();
}, []);

mockFn();

return null;
});

render$();
jest.runAllTimers();
expect(mockFn).toBeCalledTimes(2);
});
});
Loading

0 comments on commit d31a9da

Please sign in to comment.