diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index 1dc13383..ea603b6e 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -1,5 +1,4 @@ { - "buildCommand": "codesandbox-ci", "sandboxes": ["/.codesandbox/sandbox"], "node": "18" } diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 61701270..e1383f22 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,25 +1,35 @@ -name: "Install Project" +name: "Setup the project" description: "Installs node, npm, and dependencies" runs: using: "composite" steps: - - name: Use Node.js + - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: lts/* registry-url: "https://registry.npmjs.org" - - name: Cache Dependencies - id: node-modules-cache + - name: Get npm cache directory + id: npm-cache-dir + shell: bash + run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT} + + - name: Cache dependencies uses: actions/cache@v4 + id: npm-cache with: - path: node_modules - key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }} + path: ${{ steps.npm-cache-dir.outputs.dir }} + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | - ${{ runner.os }}-node-modules- + ${{ runner.os }}-node- + + - name: Install dependencies + if: steps.node-modules-cache.outputs.cache-hit != 'true' + shell: bash + run: npm ci --ignore-scripts --no-audit --no-fund - - name: Install Dependencies + - name: Rebuild binaries if: steps.node-modules-cache.outputs.cache-hit != 'true' shell: bash - run: npm ci + run: npm rebuild diff --git a/.github/workflows/handle-release-branch-push.yml b/.github/workflows/handle-release-branch-push.yml index b21a0017..5767c949 100644 --- a/.github/workflows/handle-release-branch-push.yml +++ b/.github/workflows/handle-release-branch-push.yml @@ -2,22 +2,45 @@ name: Handle Release Branch Push on: push: - branches: - - 'alpha' - - 'beta' - - 'main' jobs: - release: + verify: + name: Verify runs-on: ubuntu-latest + strategy: + matrix: + script: + # - name: Typecheck + # command: test:types + - name: Lint + command: test:lint + - name: Unit tests + command: test + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + - name: ${{ matrix.script.name }} + run: npm run ${{ matrix.script.command }} + + publish: + name: Publish + needs: + - verify + if: contains(fromJson('["refs/heads/alpha", "refs/heads/beta", "refs/heads/main"]'), github.ref) + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Publish Release + - name: Publish release uses: ./.github/actions/publish-release with: branchName: ${{ github.head_ref || github.ref_name }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index e3ef5c8e..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: CI - -on: - push: - -jobs: - verify: - name: CI - runs-on: ubuntu-latest - strategy: - matrix: - script: - # - name: Typecheck - # command: test:types - - name: Lint - command: test:lint - - name: Unit tests - command: test - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - - run: npm ci --ignore-scripts --no-audit --no-fund - - run: npm rebuild - - run: npm run ${{ matrix.script.command }} diff --git a/README.md b/README.md index 34f38a1b..0860493d 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ To add to an existing React application, just install the dependencies: #### Install Pixi React Dependencies ```bash -npm install pixi.js@^8.2.1 @pixi/react +npm install pixi.js@^8.2.1 @pixi/react@beta ``` #### Pixie React Usage @@ -189,7 +189,7 @@ Pixi React supports custom components via the `extend` API. For example, you can import { extend } from '@pixi/react' import { Viewport } from 'pixi-viewport' -extend({ viewport }) +extend({ Viewport }) const MyComponent = () => { @@ -219,19 +219,23 @@ declare global { #### `useApp` -`useApp` allows access to the parent `PIXI.Application` created by the `` component. This hook _will not work_ outside of an `` component. Additionally, the parent application is passed via [React Context](https://react.dev/reference/react/useContext). This means `useApp` will only work appropriately in _child components_, and not directly in the component that contains the `` component. +**DEPRECATED.** Use `useApplication` hook instead. -For example, the following example `useApp` **will not** be able to access the parent application: +#### `useApplication` + +`useApplication` allows access to the parent `PIXI.Application` created by the `` component. This hook _will not work_ outside of an `` component. Additionally, the parent application is passed via [React Context](https://react.dev/reference/react/useContext). This means `useApplication` will only work appropriately in _child components_, and in the same component that creates the ``. + +For example, the following example `useApplication` **will not** be able to access the parent application: ```jsx import { Application, - useApp, + useApplication, } from '@pixi/react' const ParentComponent = () => { // This will cause an invariant violation. - const app = useApp() + const { app } = useApplication() return ( @@ -239,16 +243,16 @@ const ParentComponent = () => { } ``` -Here's a working example where `useApp` **will** be able to access the parent application: +Here's a working example where `useApplication` **will** be able to access the parent application: ```jsx import { Application, - useApp, + useApplication, } from '@pixi/react' const ChildComponent = () => { - const app = useApp() + const { app } = useApplication() console.log(app) @@ -357,7 +361,7 @@ const MyComponent = () => { } ``` -`useTick` optionally takes a boolean as a second argument. Setting this boolean to `false` will cause the callback to be disabled until the argument is set to true again. +`useTick` optionally takes an options object. This allows control of all [`ticker.add`](https://pixijs.download/release/docs/ticker.Ticker.html#add) options, as well as adding the `isEnabled` option. Setting `isEnabled` to `false` will cause the callback to be disabled until the argument is changed to true again. ```jsx import { useState } from 'react' @@ -373,3 +377,34 @@ const MyComponent = () => { ) } ``` + +> [!CAUTION] +> The callback passed to `useTick` **is not memoised**. This can cause issues where your callback is being removed and added back to the ticker on every frame if you're mutating state in a component where `useTick` is using a non-memoised function. For example, this issue would affect the component below because we are mutating the state, causing the component to re-render constantly: +> ```jsx +> import { useState } from 'react' +> import { useTick } from '@pixi/react' +> +> const MyComponent = () => { +> const [count, setCount] = useState(0) +> +> useTick(() => setCount(previousCount => previousCount + 1)) +> +> return null +> } +> ``` +> This issue can be solved by memoising the callback passed to `useTick`: +> ```jsx +> import { +> useCallback, +> useState, +> } from 'react' +> import { useTick } from '@pixi/react' +> +> const MyComponent = () => { +> const [count, setCount] = useState(0) +> +> const updateCount = useCallback(() => setCount(previousCount => previousCount + 1), []) +> +> useTick(updateCount) +> } +> ``` diff --git a/src/components/Application.ts b/src/components/Application.ts index 4327d9db..b53baa8d 100644 --- a/src/components/Application.ts +++ b/src/components/Application.ts @@ -97,7 +97,7 @@ export const ApplicationFunction: ForwardRefRenderFunction { - const canvasElement = canvasRef.current as HTMLCanvasElement; + const canvasElement = canvasRef.current; if (canvasElement) { diff --git a/src/components/Context.ts b/src/components/Context.ts index 47c5785c..820767fa 100644 --- a/src/components/Context.ts +++ b/src/components/Context.ts @@ -1,8 +1,8 @@ import { createContext } from 'react'; -import type { InternalState } from '../typedefs/InternalState.ts'; +import type { ApplicationState } from '../typedefs/ApplicationState.ts'; -export const Context = createContext>({}); +export const Context = createContext({} as ApplicationState); export const ContextProvider = Context.Provider; export const ContextConsumer = Context.Consumer; diff --git a/src/core/createRoot.ts b/src/core/createRoot.ts index d62a7033..15400fbb 100644 --- a/src/core/createRoot.ts +++ b/src/core/createRoot.ts @@ -10,20 +10,34 @@ import { roots } from './roots.ts'; import type { ApplicationOptions } from 'pixi.js'; import type { ReactNode } from 'react'; +import type { ApplicationState } from '../typedefs/ApplicationState.ts'; +import type { CreateRootOptions } from '../typedefs/CreateRootOptions.ts'; import type { HostConfig } from '../typedefs/HostConfig.ts'; import type { InternalState } from '../typedefs/InternalState.ts'; /** Creates a new root for a Pixi React app. */ export function createRoot( + /** @description The DOM node which will serve as the root for this tree. */ target: HTMLElement | HTMLCanvasElement, - options: Partial = {}, + + /** @description Options to configure the tree. */ + options: CreateRootOptions = {}, + + /** + * @deprecated + * @description Callback to be fired when the application finishes initializing. + */ onInit?: (app: Application) => void, ) { // Check against mistaken use of createRoot let root = roots.get(target); + let applicationState = (root?.applicationState ?? { + isInitialised: false, + isInitialising: false, + }) as ApplicationState; - const state = Object.assign((root?.state ?? {}), options) as InternalState; + const internalState = root?.internalState ?? {} as InternalState; if (root) { @@ -31,12 +45,12 @@ export function createRoot( } else { - state.app = new Application(); - state.rootContainer = prepareInstance(state.app.stage) as HostConfig['containerInstance']; + applicationState.app = new Application(); + internalState.rootContainer = prepareInstance(applicationState.app.stage) as HostConfig['containerInstance']; } const fiber = root?.fiber ?? reconciler.createContainer( - state.rootContainer, + internalState.rootContainer, ConcurrentRoot, null, false, @@ -67,15 +81,17 @@ export function createRoot( applicationOptions: ApplicationOptions, ) => { - if (!state.app.renderer && !state.isInitialising) + if (!applicationState.app.renderer && !applicationState.isInitialised && !applicationState.isInitialising) { - state.isInitialising = true; - await state.app.init({ + applicationState.isInitialising = true; + await applicationState.app.init({ ...applicationOptions, canvas, }); - onInit?.(state.app); - state.isInitialising = false; + applicationState.isInitialising = false; + applicationState.isInitialised = true; + applicationState = { ...applicationState }; + (options.onInit ?? onInit)?.(applicationState.app); } Object.entries(applicationOptions).forEach(([key, value]) => @@ -91,24 +107,25 @@ export function createRoot( } // @ts-expect-error Typescript doesn't realise it, but we're already verifying that this isn't a readonly key. - state.app[typedKey] = value; + applicationState.app[typedKey] = value; }); // Update fiber and expose Pixi.js state to children reconciler.updateContainer( - createElement(ContextProvider, { value: state }, children), + createElement(ContextProvider, { value: applicationState }, children), fiber, null, - () => undefined + () => undefined, ); - return state.app; + return applicationState.app; }; root = { + applicationState, fiber, + internalState, render, - state, }; roots.set(canvas, root); diff --git a/src/helpers/applyProps.ts b/src/helpers/applyProps.ts index 6526294e..aec8c0d1 100644 --- a/src/helpers/applyProps.ts +++ b/src/helpers/applyProps.ts @@ -21,7 +21,7 @@ import type { } from 'pixi.js'; import type { DiffSet } from '../typedefs/DiffSet.ts'; import type { HostConfig } from '../typedefs/HostConfig.ts'; -import type { NodeState } from '../typedefs/NodeState.ts'; +import type { InstanceState } from '../typedefs/InstanceState.ts'; const DEFAULT = '__default'; const DEFAULTS_CONTAINERS = new Map(); @@ -53,7 +53,7 @@ export function applyProps( { const { // eslint-disable-next-line @typescript-eslint/no-unused-vars - __pixireact: instanceState = {} as NodeState, + __pixireact: instanceState = {} as InstanceState, ...instanceProps } = instance; diff --git a/src/helpers/prepareInstance.ts b/src/helpers/prepareInstance.ts index 1362d1b5..3645e6e1 100644 --- a/src/helpers/prepareInstance.ts +++ b/src/helpers/prepareInstance.ts @@ -3,12 +3,12 @@ import type { Filter, } from 'pixi.js'; import type { HostConfig } from '../typedefs/HostConfig.ts'; -import type { NodeState } from '../typedefs/NodeState.ts'; +import type { InstanceState } from '../typedefs/InstanceState.ts'; /** Create the instance with the provided sate and attach the component to it. */ export function prepareInstance( component: T, - state: Partial = {}, + state: Partial = {}, ) { const instance = component as HostConfig['instance']; diff --git a/src/hooks/useApp.ts b/src/hooks/useApp.ts index ff489bf2..6691b126 100644 --- a/src/hooks/useApp.ts +++ b/src/hooks/useApp.ts @@ -3,6 +3,10 @@ import { useContext } from 'react'; import { Context } from '../components/Context.ts'; import { invariant } from '../helpers/invariant'; +/** + * @deprecated + * @description Retrieves the nearest Pixi.js Application from the Pixi React context. + */ export function useApp() { const { app } = useContext(Context); diff --git a/src/hooks/useApplication.ts b/src/hooks/useApplication.ts new file mode 100644 index 00000000..cea75e7a --- /dev/null +++ b/src/hooks/useApplication.ts @@ -0,0 +1,21 @@ +import { Application } from 'pixi.js'; +import { useContext } from 'react'; +import { Context } from '../components/Context.ts'; +import { invariant } from '../helpers/invariant'; + +/** + * @description Retrieves the nearest Pixi.js Application from the Pixi React context. + */ +export function useApplication() +{ + const appContext = useContext(Context); + + invariant( + appContext.app instanceof Application, + 'No Context found with `%s`. Make sure to wrap component with `%s`', + 'Application', + 'AppProvider' + ); + + return appContext; +} diff --git a/src/hooks/useTick.ts b/src/hooks/useTick.ts index 28d955b6..7a08c9e2 100644 --- a/src/hooks/useTick.ts +++ b/src/hooks/useTick.ts @@ -1,24 +1,33 @@ import { invariant } from '../helpers/invariant.ts'; -import { useApp } from './useApp.ts'; +import { useApplication } from './useApplication.ts'; import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect.ts'; import type { TickerCallback } from 'pixi.js'; -import type { TickCallbackOptions } from '../typedefs/TickCallbackOptions.ts'; +import type { UseTickOptions } from '../typedefs/UseTickOptions.ts'; /** Attaches a callback to the application's Ticker. */ -function useTick( +export function useTick( /** @description The function to be called on each tick. */ - options: TickerCallback | TickCallbackOptions, - /** @description Whether this callback is currently enabled. */ + options: TickerCallback | UseTickOptions, + + /** + * @deprecated + * @description Whether this callback is currently enabled. + */ enabled = true, ) { - const app = useApp(); + const { + app, + isInitialised, + } = useApplication(); let callback; let context: any; + let isEnabled = enabled; + let priority: number | undefined; if (typeof options === 'function') @@ -29,6 +38,7 @@ function useTick( { callback = options.callback; context = options.context; + isEnabled = options.isEnabled; priority = options.priority; } @@ -37,15 +47,14 @@ function useTick( // eslint-disable-next-line consistent-return useIsomorphicLayoutEffect(() => { - const ticker = app?.ticker; - - if (ticker) + if (isInitialised) { - const wasEnabled = enabled; + const ticker = app?.ticker; + const wasEnabled = isEnabled; const previousContext = context; const previousCallback = callback; - if (enabled && ticker) + if (isEnabled && ticker) { ticker.add(callback, context, priority); } @@ -61,9 +70,7 @@ function useTick( }, [ callback, context, - enabled, + isEnabled, priority, ]); } - -export { useTick }; diff --git a/src/index.ts b/src/index.ts index 5f1f4bc9..16f05b4b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,16 @@ +console.warn(` +Be aware that you are using a beta version of Pixi React. +- Things may be broken. +- Things may (but shouldn't) change. +- All functionality that's deprecated in the beta version WILL BE REMOVED for the production release. +`); + export { Application } from './components/Application.ts'; export { createRoot } from './core/createRoot.ts'; export * from './global.ts'; export { extend } from './helpers/extend.ts'; export { useApp } from './hooks/useApp.ts'; +export { useApplication } from './hooks/useApplication.ts'; export { useAsset } from './hooks/useAsset.ts'; export { useExtend } from './hooks/useExtend.ts'; export { useTick } from './hooks/useTick.ts'; diff --git a/src/typedefs/ApplicationState.ts b/src/typedefs/ApplicationState.ts new file mode 100644 index 00000000..6c56d912 --- /dev/null +++ b/src/typedefs/ApplicationState.ts @@ -0,0 +1,8 @@ +import type { Application } from 'pixi.js'; + +export interface ApplicationState +{ + app: Application; + isInitialised: boolean; + isInitialising: boolean; +} diff --git a/src/typedefs/CreateRootOptions.ts b/src/typedefs/CreateRootOptions.ts new file mode 100644 index 00000000..b46df018 --- /dev/null +++ b/src/typedefs/CreateRootOptions.ts @@ -0,0 +1,7 @@ +import type { Application } from 'pixi.js'; + +export interface CreateRootOptions +{ + /** @description Callback to be fired when the application finishes initializing. */ + onInit?: (app: Application) => void +} diff --git a/src/typedefs/NodeState.ts b/src/typedefs/InstanceState.ts similarity index 89% rename from src/typedefs/NodeState.ts rename to src/typedefs/InstanceState.ts index 13b83a12..12559282 100644 --- a/src/typedefs/NodeState.ts +++ b/src/typedefs/InstanceState.ts @@ -1,7 +1,7 @@ import type { Filter } from 'pixi.js'; import type { HostConfig } from './HostConfig.ts'; -export interface NodeState +export interface InstanceState { autoRemovedBeforeAppend?: boolean; filters: Filter[], diff --git a/src/typedefs/InternalState.ts b/src/typedefs/InternalState.ts index 1f3bc333..981df500 100644 --- a/src/typedefs/InternalState.ts +++ b/src/typedefs/InternalState.ts @@ -1,11 +1,7 @@ -import type { Application } from 'pixi.js'; import type { HostConfig } from './HostConfig.ts'; export interface InternalState { - app: Application; canvas?: HTMLCanvasElement; - debug?: boolean; - isInitialising?: boolean; rootContainer: HostConfig['containerInstance']; } diff --git a/src/typedefs/PixiReactNode.ts b/src/typedefs/PixiReactNode.ts index 26e586ae..322af1c7 100644 --- a/src/typedefs/PixiReactNode.ts +++ b/src/typedefs/PixiReactNode.ts @@ -10,7 +10,7 @@ import type { PixiToReactEventPropNames } from '../constants/EventPropNames.ts'; import type { ConstructorOptions } from './ConstructorOptions.ts'; import type { DrawCallback } from './DrawCallback.ts'; import type { EventHandlers } from './EventHandlers.ts'; -import type { NodeState } from './NodeState.ts'; +import type { InstanceState } from './InstanceState.ts'; import type { PixiReactChildNode } from './PixiReactChildNode.ts'; export interface BaseNodeProps any = typeof Container> @@ -27,7 +27,7 @@ export interface BaseNodeProps any = typeof Cont export interface NodeProps any = typeof Container> extends BaseNodeProps { - __pixireact: NodeState, + __pixireact: InstanceState, parent?: PixiReactNode; } diff --git a/src/typedefs/Root.ts b/src/typedefs/Root.ts index 4bed624e..23f1c19e 100644 --- a/src/typedefs/Root.ts +++ b/src/typedefs/Root.ts @@ -1,11 +1,13 @@ import type { Container } from 'pixi.js'; import type { Fiber } from 'react-reconciler'; +import type { ApplicationState } from './ApplicationState.ts'; import type { InternalState } from './InternalState.ts'; export interface Root { - fiber: Fiber - root?: Container - render: (...args: any[]) => any - state: InternalState + fiber: Fiber; + root?: Container; + render: (...args: any[]) => any; + applicationState: ApplicationState; + internalState: InternalState; } diff --git a/src/typedefs/TickCallbackOptions.ts b/src/typedefs/UseTickOptions.ts similarity index 73% rename from src/typedefs/TickCallbackOptions.ts rename to src/typedefs/UseTickOptions.ts index 7f447e2d..895fd551 100644 --- a/src/typedefs/TickCallbackOptions.ts +++ b/src/typedefs/UseTickOptions.ts @@ -1,6 +1,6 @@ import type { TickerCallback } from 'pixi.js'; -export interface TickCallbackOptions +export interface UseTickOptions { /** @description The function to be called on each tick. */ callback: TickerCallback @@ -8,6 +8,9 @@ export interface TickCallbackOptions /** @description The value of `this` within the callback. */ context?: T + /** @description Whether this callback is currently enabled. */ + isEnabled: true, + /** @description The priority of this callback compared to other callbacks on the ticker. */ priority?: number } diff --git a/test/unit/core/createRoot.test.ts b/test/unit/core/createRoot.test.ts index daf7a747..bb5ac4a7 100644 --- a/test/unit/core/createRoot.test.ts +++ b/test/unit/core/createRoot.test.ts @@ -1,7 +1,4 @@ -import { - Application, - Container, -} from 'pixi.js'; +import { Application } from 'pixi.js'; import { describe, expect, @@ -20,11 +17,10 @@ describe('createRoot', () => expect(root) .to.have.property('render') .and.to.be.a('function'); - expect(root).to.have.property('state'); - expect(root.state).to.have.property('rootContainer'); - // expect(root.state.app).to.be.instanceOf(Application) - expect(root.state.app).to.have.all.keys(Object.keys(new Application())); - // expect(root.state.rootContainer).to.be.instanceOf(Container) - expect(root.state.rootContainer).to.have.all.keys(Object.keys(new Container()).concat('__pixireact')); + expect(root).to.have.property('applicationState'); + expect(root).to.have.property('internalState'); + expect(root.internalState).to.have.property('rootContainer'); + expect(root.applicationState.app).to.be.instanceOf(Application); + expect(root.internalState.rootContainer).to.equal(root.applicationState.app.stage); }); }); diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index 3ec4f75c..dd6ab7fb 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -3,43 +3,64 @@ import { expect, it, } from 'vitest'; +import { Application } from '../../src/components/Application.ts'; +import { createRoot } from '../../src/core/createRoot.ts'; +import { extend } from '../../src/helpers/extend.ts'; +import { useApp } from '../../src/hooks/useApp.ts'; +import { useApplication } from '../../src/hooks/useApplication.ts'; +import { useAsset } from '../../src/hooks/useAsset.ts'; +import { useExtend } from '../../src/hooks/useExtend.ts'; +import { useTick } from '../../src/hooks/useTick.ts'; import * as PixiReact from '../../src/index.ts'; describe('exports', () => { it('exports the `` component', () => { - expect(PixiReact).to.have.property('Application'); + expect(PixiReact).to.have.property('Application') + .and.to.equal(Application); }); it('exports the `createRoot()` function', () => { - expect(PixiReact).to.have.property('createRoot'); + expect(PixiReact).to.have.property('createRoot') + .and.to.equal(createRoot); }); it('exports the `extend()` function', () => { - expect(PixiReact).to.have.property('extend'); + expect(PixiReact).to.have.property('extend') + .and.to.equal(extend); }); it('exports the `useApp()` hook', () => { - expect(PixiReact).to.have.property('useApp'); + expect(PixiReact).to.have.property('useApp') + .and.to.equal(useApp); + }); + + it('exports the `useApplication()` hook', () => + { + expect(PixiReact).to.have.property('useApplication') + .and.to.equal(useApplication); }); it('exports the `useAsset()` hook', () => { - expect(PixiReact).to.have.property('useAsset'); + expect(PixiReact).to.have.property('useAsset') + .and.to.equal(useAsset); }); it('exports the `useExtend()` hook', () => { - expect(PixiReact).to.have.property('useExtend'); + expect(PixiReact).to.have.property('useExtend') + .and.to.equal(useExtend); }); it('exports the `useTick()` hook', () => { - expect(PixiReact).to.have.property('useTick'); + expect(PixiReact).to.have.property('useTick') + .and.to.equal(useTick); }); it('doesn\'t export extraneous keys', () => @@ -49,6 +70,7 @@ describe('exports', () => 'createRoot', 'extend', 'useApp', + 'useApplication', 'useAsset', 'useExtend', 'useTick',