diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs
index b3295f8..5af6250 100644
--- a/.lintstagedrc.mjs
+++ b/.lintstagedrc.mjs
@@ -24,16 +24,16 @@ function test(files) {
)
const other = Object.entries(packages)
.filter(([key]) => key !== 'babel-plugin-jsx')
- .map(([_, v]) => v)
+ .map(([_, file]) => file).flat(Infinity)
return [
plugin.length
- ? `jest --config packages/${plugin[0][0]}/jest.config.js ${plugin[0][1].join(
+ ? `jest --showConfig --config packages/${plugin[0][0]}/jest.config.js ${plugin[0][1].join(
' '
)}`
: null,
other.length
- ? `jest --config jest.config.js --colors ${other.join(' ')}`
+ ? `jest --showConfig --config jest.config.js --colors ${other.join(' ')}`
: null,
].filter(Boolean)
}
diff --git a/README_EN.md b/README_EN.md
index f954886..48e015d 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -16,9 +16,9 @@
# Gyron
-` Gyron` is a simple zero-dependency responsive framework . Core code size: 。
+`Gyron` is a simple zero-dependency responsive framework . Core code size: 。
-It also has a very good performance performance, details of which can be found in [js-framework-benchmark](https://krausest.github.io/js-framework-benchmark/current.html#eyJmcmFtZXdvcmtzIjpbImtleWVkL2FuZ3VsYXIiLCJrZXllZC9neXJvbiIsImtleWVkL3JlYWN0Iiwibm9uLWtleWVkL2d5cm9uIiwibm9uLWtleWVkL3JlYWN0Il0sImJlbmNobWFya3MiOlsiMDFfcnVuMWsiLCIwMl9yZXBsYWNlMWsiLCIwM191cGRhdGUxMHRoMWtfeDE2IiwiMDRfc2VsZWN0MWsiLCIwNV9zd2FwMWsiLCIwNl9yZW1vdmUtb25lLTFrIiwiMDdfY3JlYXRlMTBrIiwiMDhfY3JlYXRlMWstYWZ0ZXIxa194MiIsIjA5X2NsZWFyMWtfeDgiLCIyMV9yZWFkeS1tZW1vcnkiLCIyMl9ydW4tbWVtb3J5IiwiMjNfdXBkYXRlNS1tZW1vcnkiLCIyNV9ydW4tY2xlYXItbWVtb3J5IiwiMjZfcnVuLTEway1tZW1vcnkiLCIzMV9zdGFydHVwLWNpIiwiMzRfc3RhcnR1cC10b3RhbGJ5dGVzIl0sImRpc3BsYXlNb2RlIjoxLCJjYXRlZ29yaWVzIjpbMSwyLDMsNF19) 提供的结果。
+It also has a very good performance performance, details of which can be found in [js-framework-benchmark](https://krausest.github.io/js-framework-benchmark/current.html#eyJmcmFtZXdvcmtzIjpbImtleWVkL2FuZ3VsYXIiLCJrZXllZC9neXJvbiIsImtleWVkL3JlYWN0Iiwibm9uLWtleWVkL2d5cm9uIiwibm9uLWtleWVkL3JlYWN0Il0sImJlbmNobWFya3MiOlsiMDFfcnVuMWsiLCIwMl9yZXBsYWNlMWsiLCIwM191cGRhdGUxMHRoMWtfeDE2IiwiMDRfc2VsZWN0MWsiLCIwNV9zd2FwMWsiLCIwNl9yZW1vdmUtb25lLTFrIiwiMDdfY3JlYXRlMTBrIiwiMDhfY3JlYXRlMWstYWZ0ZXIxa194MiIsIjA5X2NsZWFyMWtfeDgiLCIyMV9yZWFkeS1tZW1vcnkiLCIyMl9ydW4tbWVtb3J5IiwiMjNfdXBkYXRlNS1tZW1vcnkiLCIyNV9ydW4tY2xlYXItbWVtb3J5IiwiMjZfcnVuLTEway1tZW1vcnkiLCIzMV9zdGFydHVwLWNpIiwiMzRfc3RhcnR1cC10b3RhbGJ5dGVzIl0sImRpc3BsYXlNb2RlIjoxLCJjYXRlZ29yaWVzIjpbMSwyLDMsNF19)
- Readme
- [中文](./README.md)
diff --git a/jest.config.js b/jest.config.js
index 383a387..e1cff7c 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -10,7 +10,7 @@ module.exports = {
: [`${process.cwd()}/tests/**/(*.)+(spec|test).[jt]s?(x)`],
testEnvironment: 'jsdom',
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
- prefix: '../../',
+ prefix: process.env.PACKAGES ? '../../' : '',
}),
collectCoverageFrom: [`./src/**/*.ts`],
globals: {
diff --git a/packages/dom-client/package.json b/packages/dom-client/package.json
index e4ccf70..519011f 100644
--- a/packages/dom-client/package.json
+++ b/packages/dom-client/package.json
@@ -17,7 +17,7 @@
"build:esm": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/esm/index.js --format=esm --platform=node --external:@gyron/* --define:__DEV__=false --define:__WARN__=true",
"build:cjs": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/cjs/index.js --format=cjs --platform=node --external:@gyron/* --define:__DEV__=false --define:__WARN__=true",
"build:dts": "rollup -c ../../rollup.config.js",
- "test": "jest --config=../../jest.config.js"
+ "test": "cross-env PACKAGES=dom-client jest --config=../../jest.config.js"
},
"devDependencies": {
"@gyron/shared": "^0.0.29"
diff --git a/packages/dom-server/package.json b/packages/dom-server/package.json
index 1428288..f8c9349 100644
--- a/packages/dom-server/package.json
+++ b/packages/dom-server/package.json
@@ -17,7 +17,7 @@
"build:esm": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/esm/index.js --format=esm --platform=node --external:@gyron/* --define:__DEV__=false --define:__WARN__=true",
"build:cjs": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/cjs/index.js --format=cjs --platform=node --external:@gyron/* --define:__DEV__=false --define:__WARN__=true",
"build:dts": "rollup -c ../../rollup.config.js",
- "test": "jest --config=../../jest.config.js"
+ "test": "cross-env PACKAGES=dom-server jest --config=../../jest.config.js"
},
"devDependencies": {
"@gyron/shared": "^0.0.29"
diff --git a/packages/jsx-runtime/package.json b/packages/jsx-runtime/package.json
index 1e285ee..c1beb7d 100644
--- a/packages/jsx-runtime/package.json
+++ b/packages/jsx-runtime/package.json
@@ -17,7 +17,7 @@
"build:esm": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/esm/index.js --format=esm --platform=node --external:@gyron/* --define:__DEV__=false --define:__WARN__=true",
"build:cjs": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/cjs/index.js --format=cjs --platform=node --external:@gyron/* --define:__DEV__=false --define:__WARN__=true",
"build:dts": "rollup -c ../../rollup.config.js",
- "test": "jest --config=../../jest.config.js"
+ "test": "cross-env PACKAGES=jsx-runtime jest --config=../../jest.config.js"
},
"dependencies": {
"@gyron/runtime": "^0.0.29",
diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json
index 780402a..5fb758b 100644
--- a/packages/reactivity/package.json
+++ b/packages/reactivity/package.json
@@ -17,7 +17,7 @@
"build:esm": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/esm/index.js --format=esm --platform=node --external:@gyron/*",
"build:cjs": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/cjs/index.js --format=cjs --platform=node --external:@gyron/*",
"build:dts": "rollup -c ../../rollup.config.js",
- "test": "jest --config=../../jest.config.js"
+ "test": "cross-env PACKAGES=reactivity jest --config=../../jest.config.js"
},
"dependencies": {
"@gyron/shared": "^0.0.29"
diff --git a/packages/redux/package.json b/packages/redux/package.json
index 7362055..5134fd4 100644
--- a/packages/redux/package.json
+++ b/packages/redux/package.json
@@ -17,7 +17,7 @@
"build:esm": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/esm/index.js --format=esm --platform=node --external:@gyron/* --external:@reduxjs/toolkit",
"build:cjs": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/cjs/index.js --format=cjs --platform=node --external:@gyron/* --external:@reduxjs/toolkit",
"build:dts": "rollup -c ../../rollup.config.js",
- "test": "jest --config=../../jest.config.js"
+ "test": "cross-env PACKAGES=redux jest --config=../../jest.config.js"
},
"dependencies": {
"@gyron/runtime": "^0.0.29",
diff --git a/packages/router/package.json b/packages/router/package.json
index 3a9da4c..2deebc9 100644
--- a/packages/router/package.json
+++ b/packages/router/package.json
@@ -18,7 +18,7 @@
"build:cjs": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/cjs/index.js --format=cjs --platform=node --external:@gyron/* --external:history --external:path-to-regexp --define:__DEV__=false --define:__WARN__=true",
"build:browser": "esbuild src/index.ts --bundle --sourcemap --outfile=dist/browser/index.js --format=esm --platform=browser --define:__DEV__=false --define:__WARN__=true",
"build:dts": "rollup -c ../../rollup.config.js",
- "test": "jest --config=../../jest.config.js"
+ "test": "cross-env PACKAGES=router jest --config=../../jest.config.js"
},
"dependencies": {
"@gyron/runtime": "^0.0.29",
diff --git a/packages/runtime/package.json b/packages/runtime/package.json
index 0c0dbce..80fc135 100644
--- a/packages/runtime/package.json
+++ b/packages/runtime/package.json
@@ -17,7 +17,7 @@
"build:esm": "esbuild src/index.ts --bundle --sourcemap --outfile=dist/esm/index.js --format=esm --platform=node",
"build:cjs": "esbuild src/index.ts --bundle --sourcemap --outfile=dist/cjs/index.js --format=cjs --platform=node",
"build:dts": "cross-env RESPECT_EXTERNAL=runtime rollup -c ../../rollup.config.js",
- "test": "jest --config=../../jest.config.js"
+ "test": "cross-env PACKAGES=runtime jest --config=../../jest.config.js"
},
"devDependencies": {
"@gyron/dom-client": "^0.0.29",
diff --git a/packages/runtime/src/assert.ts b/packages/runtime/src/assert.ts
index e4009f3..b66e6bc 100644
--- a/packages/runtime/src/assert.ts
+++ b/packages/runtime/src/assert.ts
@@ -1,5 +1,5 @@
import { Component } from './component'
-import { getErrorBoundaryCtx } from './ErrorBoundary'
+import { getErrorBoundaryCtx } from './internal'
import { ErrorType, WarnType, BoundariesHandler } from './boundaries'
export enum InnerCode {
diff --git a/packages/runtime/src/boundaries.ts b/packages/runtime/src/boundaries.ts
index 5ff806c..19927ca 100644
--- a/packages/runtime/src/boundaries.ts
+++ b/packages/runtime/src/boundaries.ts
@@ -1,5 +1,5 @@
import { Component, getCurrentComponent } from './component'
-import { getErrorBoundaryCtx } from './ErrorBoundary'
+import { getErrorBoundaryCtx } from './internal'
export type BoundariesHandlerParamsType = 'Error' | 'Warn'
export type BoundariesHandlerParams = Partial<{
diff --git a/packages/runtime/src/hydrate.ts b/packages/runtime/src/hydrate.ts
index 652defb..6fa6752 100644
--- a/packages/runtime/src/hydrate.ts
+++ b/packages/runtime/src/hydrate.ts
@@ -22,7 +22,7 @@ import {
normalizeChildrenVNode,
normalizeVNodeWithLink,
} from './vnode'
-import { mountComponent, patch } from './render'
+import { mountComponent, patch } from './renderer'
import { setRef } from './ref'
/**
diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts
index 4c603f0..6b04c2b 100644
--- a/packages/runtime/src/index.ts
+++ b/packages/runtime/src/index.ts
@@ -12,8 +12,7 @@ export {
isResponsive,
toRaw,
} from '@gyron/reactivity'
-export { ErrorBoundary } from './ErrorBoundary'
-export { Transition } from './Transition'
+export { Transition, ErrorBoundary } from './internal'
export {
useWatch,
createComponentInstance,
@@ -29,6 +28,7 @@ export {
FC,
} from './component'
export { createInstance, render, createContext } from './instance'
+export { createInstance as createGyron } from './instance'
export {
createVNode,
createVNodeComment,
diff --git a/packages/runtime/src/instance.ts b/packages/runtime/src/instance.ts
index 27a4945..c47c65e 100644
--- a/packages/runtime/src/instance.ts
+++ b/packages/runtime/src/instance.ts
@@ -1,5 +1,5 @@
import type { RenderElement, VNode } from './vnode'
-import { patch, unmount } from './render'
+import { patch, unmount } from './renderer'
import { hydrate } from './hydrate'
import { getUserContainer } from './shared'
diff --git a/packages/runtime/src/ErrorBoundary.ts b/packages/runtime/src/internal/ErrorBoundary.ts
similarity index 83%
rename from packages/runtime/src/ErrorBoundary.ts
rename to packages/runtime/src/internal/ErrorBoundary.ts
index 8d99d54..9d3850d 100644
--- a/packages/runtime/src/ErrorBoundary.ts
+++ b/packages/runtime/src/internal/ErrorBoundary.ts
@@ -1,10 +1,10 @@
import { useReactive } from '@gyron/reactivity'
import { isFunction } from '@gyron/shared'
-import { Component, FC } from './component'
-import { VNode } from './vnode'
-import { h } from './h'
-import { inject, useProvide } from './context'
-import { BoundariesHandler, BoundariesHandlerParams } from './boundaries'
+import { Component, FC } from '../component'
+import { VNode } from '../vnode'
+import { h } from '../h'
+import { inject, useProvide } from '../context'
+import { BoundariesHandler, BoundariesHandlerParams } from '../boundaries'
export interface ErrorBoundaryProps {
fallback: VNode
diff --git a/packages/runtime/src/Transition.ts b/packages/runtime/src/internal/Transition.ts
similarity index 97%
rename from packages/runtime/src/Transition.ts
rename to packages/runtime/src/internal/Transition.ts
index b5da2c6..adda30a 100644
--- a/packages/runtime/src/Transition.ts
+++ b/packages/runtime/src/internal/Transition.ts
@@ -1,9 +1,9 @@
-import { VNode } from './vnode'
-import { FC } from './component'
+import { RenderElement, VNode } from '../vnode'
+import { FC } from '../component'
import { isFunction, isNumber, shouldValue } from '@gyron/shared'
-import { isVNode, isVNodeComment, RenderElement, warn } from '.'
import { Noop } from '@gyron/shared'
-import { InnerCode } from './assert'
+import { InnerCode, warn } from '../assert'
+import { isVNode, isVNodeComment } from '../shared'
interface TransitionPropsNormalize {
cls: {
diff --git a/packages/runtime/src/internal/index.ts b/packages/runtime/src/internal/index.ts
new file mode 100644
index 0000000..347d762
--- /dev/null
+++ b/packages/runtime/src/internal/index.ts
@@ -0,0 +1,2 @@
+export { TransitionHooks, whenTransitionEnd, Transition } from './Transition'
+export { getErrorBoundaryCtx, ErrorBoundary } from './ErrorBoundary'
diff --git a/packages/runtime/src/render.ts b/packages/runtime/src/render.ts
deleted file mode 100644
index 4da3c54..0000000
--- a/packages/runtime/src/render.ts
+++ /dev/null
@@ -1,721 +0,0 @@
-import {
- createComment,
- createElement,
- createText,
- insert,
- isSelectElement,
- mountProps,
- nextSibling,
- patchProps,
- remove,
-} from '@gyron/dom-client'
-import {
- asyncTrackEffect,
- clearTrackEffect,
- createEffect,
-} from '@gyron/reactivity'
-import {
- extend,
- isArray,
- isBoolean,
- isElement,
- isEqual,
- isFunction,
- isObject,
- isPromise,
- keys,
- Noop,
- shouldValue,
-} from '@gyron/shared'
-import { warn } from './assert'
-import {
- Component,
- ComponentSetupFunction,
- createComponentInstance,
- getCacheComponent,
- isAsyncComponent,
- isCacheComponent,
- renderComponent,
- normalizeComponent,
- removeBuiltInProps,
-} from './component'
-import { collectHmrComponent, refreshComponentType } from './hmr'
-import { hydrate } from './hydrate'
-import { invokeLifecycle } from './lifecycle'
-import { setRef } from './ref'
-import { JobPriority, pushQueueJob, SchedulerJob } from './scheduler'
-import { isVNode, isVNodeComponent } from './shared'
-import { SSRMessage } from './ssr'
-import {
- Children,
- Comment,
- Element,
- Fragment,
- mergeVNodeWith,
- normalizeChildrenVNode,
- normalizeVNode,
- RenderElement,
- Text,
- VNode,
-} from './vnode'
-
-function shouldUpdate(result: any) {
- return !(isBoolean(result) && !result)
-}
-
-export function isSameVNodeType(n1: VNode, n2: VNode) {
- return n1.type === n2.type && n1.key === n2.key
-}
-
-function isKeyPatch(n1: VNode[], n2: VNode[]) {
- if (n1 && n2 && n1[0] && n2[0] && isObject(n1[0]) && isObject(n2[0])) {
- return shouldValue(n1[0].key) && shouldValue(n2[0].key)
- }
- return false
-}
-
-function getNextSibling(vnode: VNode) {
- if (vnode.component) {
- return getNextSibling(vnode.component.subTree)
- }
- if (vnode.el || vnode.anchor) {
- return nextSibling(vnode.el || vnode.anchor)
- }
- return null
-}
-
-function mountChildren(
- nodes: VNode[] | Children[],
- container: RenderElement,
- anchor: RenderElement,
- start = 0,
- parentComponent: Component | null = null,
- isSvg: boolean
-) {
- for (let i = start; i < nodes.length; i++) {
- const node = normalizeVNode(nodes[i])
- patch(null, node, container, anchor, parentComponent, isSvg)
- }
-}
-
-function removeInvoke(_el: RenderElement, vnode: VNode, done: Noop) {
- const { transition } = vnode
- const el = _el as Element
- if (transition) {
- transition.onLeave(el, () => {
- remove(el)
- done()
- })
- } else {
- remove(el)
- done()
- }
-}
-
-export function unmount(vnode: VNode) {
- if (!isVNode(vnode)) {
- return null
- }
-
- function reset() {
- vnode.el = null
- }
- const { el, component, children, transition } = vnode
-
- if (component) {
- if (!isCacheComponent(component.type)) {
- component.effect.stop()
- }
- if (component.subTree) {
- unmount(component.subTree)
- }
- invokeLifecycle(component, 'destroyed')
- if (component.$el) {
- removeInvoke(component.$el, vnode, reset)
- if (!isCacheComponent(component.type)) {
- component.$el = null
- }
- }
- component.destroyed = true
- component.mounted = false
- } else {
- if (!transition) {
- if (isArray(children) && children.length > 0) {
- unmountChildren(children as VNode[])
- } else {
- unmount(children as VNode)
- }
- }
- if (isElement(el)) {
- removeInvoke(el, vnode, reset)
- }
- }
-}
-
-function unmountChildren(c1: VNode[], start = 0) {
- for (let i = start; i < c1.length; i++) {
- unmount(c1[i])
- }
-}
-
-function patchNonKeyed(
- c1: VNode[],
- c2: VNode[],
- container: RenderElement,
- anchor: RenderElement,
- parentComponent: Component,
- isSvg: boolean
-) {
- const c1length = c1.length
- const c2length = c2.length
- const minLength = Math.min(c1length, c2length)
- for (let i = 0; i < minLength; i++) {
- const prevChild = c1[i]
- const nextChild = c2[i]
- patch(prevChild, nextChild, container, anchor, parentComponent)
- }
- if (c1length > c2length) {
- unmountChildren(c1, minLength)
- } else {
- mountChildren(c2, container, anchor, minLength, parentComponent, isSvg)
- }
-}
-
-function patchKeyed(
- c1: VNode[],
- c2: VNode[],
- container: RenderElement,
- anchor: RenderElement | null,
- parentComponent: Component | null,
- isSvg: boolean
-) {
- const o1: Record<
- string | symbol,
- VNode & { index: number; inserted: boolean }
- > = c1.reduce((nodeMap, node, index) => {
- nodeMap[node.key] = extend(node, { index })
- return nodeMap
- }, {})
-
- const e2 = c2.length
- let i = 0
- while (i < e2) {
- const c2n = c2[i]
- const c1n = o1[c2n.key]
- if (c1n) {
- // 1, find the same key value of the node, and then inserted into the corresponding location. (Do not delete add, move directly)
- const el = mergeVNodeWith(c2n, c1n).el
- if (c1n.index !== i) {
- // insert to new position when node order is changed
- const anchor = container.childNodes[i]
- if (el !== anchor.nextSibling) {
- insert(el, container, anchor.nextSibling)
- }
- }
- // update props after migration is complete
- // element update attribute
- if (!isEqual(c1n.props, c2n.props)) {
- const isComponent = isVNodeComponent(c2n) && isVNodeComponent(c1n)
- if (isComponent) {
- patchComponent(c1n, c2n, container, anchor, parentComponent)
- } else {
- patchProps(
- el as HTMLElement,
- c1n,
- extend({}, c2n, { props: removeBuiltInProps(c2n.props) })
- )
- }
- }
- // 1, end
-
- if (c1n.children || c2n.children) {
- // 2, update the nodes with the same key value, including the child nodes.
- // sub-level nodes need to be patched again
- patchChildren(c1n, c2n, container, anchor, parentComponent, isSvg)
- // 2, end
- }
-
- // mark nodes that have been moved and do not need to be uninstalled in the third step
- c1n.inserted = true
- } else {
- patch(null, c2n, container, anchor, parentComponent, isSvg)
- }
- i++
- }
-
- for (const node of Object.values(o1)) {
- if (!node.inserted) {
- // 3, uninstall the old nodes that are not used.
- unmount(node)
- // 3, end
- }
- }
-}
-
-function patchChildren(
- n1: VNode,
- n2: VNode,
- container: RenderElement,
- anchor: RenderElement,
- parentComponent: Component,
- isSvg: boolean
-) {
- if (isFunction(n1.children) && isFunction(n2.children)) {
- // when the child nodes are all functions, they should be called by the parent node.
- // {count => }
- return
- }
-
- const c1memo = n1.props.memo
- const c2memo = n2.props.memo
- if (isArray(c1memo) && isArray(c2memo)) {
- const index = c1memo.findIndex((item, index) => {
- return c2memo[index] !== item
- })
- if (index < 0) {
- n2.children = n1.children
- return
- }
- }
-
- const c1 = n1.children as VNode[]
- const c2: VNode[] = (n2.children = normalizeChildrenVNode(n2))
-
- if (c1?.length || c2?.length) {
- if (isKeyPatch(c1, c2)) {
- const el = (n2.el = n1.el)
- patchKeyed(c1, c2, el || container, anchor, parentComponent, isSvg)
- } else {
- // if the fragment node does not have a dom instance, use the container
- const el = (n2.el = n1.el)
- if (c1) {
- patchNonKeyed(c1, c2, el || container, anchor, parentComponent, isSvg)
- } else {
- mountChildren(c2, el || container, anchor, 0, parentComponent, isSvg)
- }
- }
- }
-}
-
-function mountElement(
- vnode: VNode,
- container: RenderElement,
- anchor: RenderElement,
- parentComponent: Component,
- isSvg: boolean
-) {
- const { tag, is, transition } = vnode
- const el = (vnode.el = createElement(tag, isSvg, is) as RenderElement)
- el.__vnode__ = vnode
-
- if (vnode.props.ref) {
- setRef(el, vnode.props.ref)
- }
-
- const props = removeBuiltInProps(vnode.props)
- if (shouldValue(keys(props))) {
- mountProps(el as HTMLElement, extend({}, vnode, { props: props }))
- }
-
- if (shouldValue(vnode.children)) {
- vnode.children = normalizeChildrenVNode(vnode)
- mountChildren(vnode.children, vnode.el, anchor, 0, parentComponent, isSvg)
- }
-
- insert(el, container, anchor)
-
- if (transition) {
- transition.onActive(el as Element)
- }
-}
-
-function patchElement(
- n1: VNode,
- n2: VNode,
- container: RenderElement,
- anchor: RenderElement,
- parentComponent: Component,
- isSvg: boolean
-) {
- const el = (n2.el = n1.el) as Element
- if (el.nodeName === n2.tag.toLocaleUpperCase()) {
- if (!isEqual(n1.props, n2.props) || isSelectElement(n2)) {
- patchProps(
- el,
- n1,
- extend({}, n2, { props: removeBuiltInProps(n2.props) })
- )
- }
- if (n1.children || n2.children) {
- patchChildren(n1, n2, container, anchor, parentComponent, isSvg)
- }
- } else {
- anchor = getNextSibling(n1)
- unmount(n1)
- patch(null, n2, container, anchor, parentComponent, isSvg)
- }
-}
-
-function patchSubTree(component: Component, prevTree: VNode, nextTree: VNode) {
- component.subTree = nextTree
- if (component.mounted) {
- const { anchor } = prevTree
- component.subTree.anchor = anchor
- patch(prevTree, nextTree, component.$parent, anchor, component)
- // onAfterUpdate
- invokeLifecycle(component, 'afterUpdates')
- component.$el = nextTree.el
- } else {
- // mount
- patch(null, nextTree, component.$parent, component.vnode.anchor, component)
- // after the render is complete, set el to the vnode for comparison
- // dummy ? :
- component.vnode.el = nextTree.el
- component.$el = nextTree.el
- component.mounted = true
- // onAfterMount
- component.effect.allowEffect = true
- invokeLifecycle(component, 'afterMounts')
- component.effect.allowEffect = false
- }
-}
-
-function updateComponentEffect(
- component: Component,
- ssrMessage: SSRMessage = null
-) {
- if (component.mounted) {
- // if the onBeforeUpdate callback function returns falsy
- // no update of the component is performed
- if (
- shouldUpdate(invokeLifecycle(component, 'beforeUpdates')) &&
- shouldUpdate(!component.props.static)
- ) {
- if (__DEV__) {
- refreshComponentType(component.vnode, component)
- }
-
- const prevTree = component.subTree
- const nextTree = renderComponent(component)
- if (isPromise(nextTree)) {
- warn(
- 'Asynchronous components without wrapping are not supported, please use FCA wrapping',
- component,
- 'UpdateComponent'
- )
- } else {
- patchSubTree(component, prevTree, nextTree)
- }
- }
- } else if (!component.destroyed) {
- if (component.vnode.el) {
- function hydrateSubTree() {
- const nextTree = renderComponent(component)
- component.subTree = nextTree as VNode
- hydrate(component.vnode.el, component.subTree, component, ssrMessage)
-
- component.mounted = true
- // onAfterMount
- invokeLifecycle(component, 'afterMounts')
- }
- // asynchronous component rendering in ssr mode
- if (isAsyncComponent(component.vnode.type)) {
- component.vnode.type.__loader(component.props, component).then(() => {
- if (!component.destroyed) {
- asyncTrackEffect(component.effect)
- hydrateSubTree()
- clearTrackEffect()
- }
- })
- } else {
- hydrateSubTree()
- }
- } else {
- const nextTree = renderComponent(component)
- if (isPromise(nextTree)) {
- warn(
- 'Asynchronous components without wrapping are not supported, please use FCA wrapping',
- component,
- 'SetupPatch'
- )
- } else {
- nextTree.transition ||= component.vnode.transition
- patchSubTree(component, null, nextTree)
- }
- }
- }
-}
-
-function renderComponentEffect(
- component: Component,
- ssrMessage: SSRMessage = null
-) {
- const effect = (component.effect = createEffect(
- updateComponentEffect.bind(null, component, ssrMessage),
- () => pushQueueJob(component.update)
- ))
-
- const update = (component.update = effect.run.bind(effect) as SchedulerJob)
- update.id = component.uid
- update.component = component
- update.priority = JobPriority.NORMAL
- update()
-}
-
-export function mountComponent(
- vnode: VNode,
- container: RenderElement,
- anchor: RenderElement,
- parentComponent: Component,
- ssrMessage: SSRMessage = null
-) {
- vnode.anchor = anchor
-
- const component = (vnode.component = createComponentInstance(
- vnode,
- parentComponent
- ))
- component.$parent = container
-
- // if (vnode.transition) {
- // const { innerCache, transitionLeaving } = vnode.transition.state
- // const innerVNode = innerCache.get(vnode.type)
- // if (innerVNode && transitionLeaving) {
- // const subTree = getVNodeWithComponent(innerVNode)
- // // when a component is wrapped by a Transition, mark the component as active
- // component.mounted = true
- // component.subTree = subTree
- // }
- // }
-
- if (__DEV__ && (component.type as any).__hmr_id) {
- refreshComponentType(vnode, component)
-
- const parentId: string = parentComponent
- ? (parentComponent.type as any).__hmr_id
- : null
- collectHmrComponent((component.type as any).__hmr_id, parentId, component)
- }
-
- if (component.props.ref) {
- setRef(component.exposed, component.props.ref)
- }
-
- renderComponentEffect(component, ssrMessage)
-}
-
-function patchComponent(
- n1: VNode,
- n2: VNode,
- container: RenderElement,
- anchor: RenderElement,
- parentComponent: Component
-) {
- const component = (n2.component = n1.component)
- if (component) {
- normalizeComponent(n2, component, parentComponent)
- if (isCacheComponent(n1.component.type)) {
- if (!isEqual(n1.props, n2.props)) {
- component.update()
- }
- } else {
- component.update()
- }
- } else {
- if (__WARN__) {
- console.warn('Component update exception', n1)
- }
- mountComponent(n2, container, anchor, parentComponent)
- }
-}
-
-function enterComponent(
- n1: VNode | null,
- n2: VNode,
- container: RenderElement,
- anchor: RenderElement,
- parentComponent: Component
-) {
- if (n1 === null) {
- // clear the element of the next vnode to prevent access to the SSR hydrate logic.
- n2.el = null
- if (isCacheComponent(n2.type)) {
- // update the DOM with the locally cached component state when a local component cache is found
- const component = getCacheComponent(n2.type)
- component.destroyed = false
- component.mounted = true
- component.vnode = n2
- component.$parent = container
- if (isEqual(removeBuiltInProps(component.props), n2.props)) {
- patch(null, component.subTree, container, anchor, parentComponent)
- } else {
- mountComponent(n2, container, anchor, parentComponent)
- }
- } else {
- mountComponent(n2, container, anchor, parentComponent)
- }
- } else {
- n2.anchor ||= n1.anchor
- patchComponent(n1, n2, container, anchor, parentComponent)
- }
-}
-
-function transitionMove(
- n1: VNode,
- n2: VNode,
- container: RenderElement,
- anchor: RenderElement,
- parentComponent: Component,
- isSvg: boolean
-) {
- const { transition } = n1
- const el = n1.el as Element
- transition.onLeaveFinish(el)
- unmount(n1)
- patch(
- null,
- n2,
- container,
- anchor || getNextSibling(n1),
- parentComponent,
- isSvg
- )
-}
-
-function enterElement(
- n1: VNode | null,
- n2: VNode,
- container: RenderElement,
- anchor: RenderElement,
- parentComponent: Component,
- isSvg: boolean
-) {
- isSvg = isSvg || n2.tag === 'svg'
-
- if (n1 === null) {
- mountElement(n2, container, anchor, parentComponent, isSvg)
- } else if (!n2.props.static) {
- if (n1.transition) {
- transitionMove(n1, n2, container, anchor, parentComponent, isSvg)
- } else {
- patchElement(n1, n2, container, anchor, parentComponent, isSvg)
- }
- }
-}
-
-function enterFragment(
- n1: VNode | null,
- n2: VNode,
- container: RenderElement,
- anchor: RenderElement,
- parentComponent: Component,
- isSvg: boolean
-) {
- const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : createText(''))
- if (n1 === null) {
- n2.anchor = fragmentEndAnchor
- insert(fragmentEndAnchor, container, anchor)
-
- n2.children = normalizeChildrenVNode(n2)
- mountChildren(
- n2.children,
- container,
- fragmentEndAnchor,
- 0,
- parentComponent,
- isSvg
- )
- } else {
- patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent, isSvg)
- }
-}
-
-function enterComment(
- n1: VNode | null,
- n2: VNode,
- container: RenderElement,
- anchor: RenderElement
-) {
- if (n1 === null) {
- const comment = createComment(
- (n2.children as string) || ''
- ) as RenderElement
- comment.__vnode__ = n2
-
- n2.el = comment as RenderElement
- insert(comment, container, anchor)
- } else {
- n2.el = n1.el
- n2.el.__vnode__ = n2
- }
-}
-
-function enterText(
- n1: VNode | null,
- n2: VNode,
- container: RenderElement,
- anchor: RenderElement
-) {
- if (n1 === null || !n1.el) {
- // when hydrating the code, since there is no empty text node on the server side, you need to execute mountText
- const textNode = createText(n2.children as string) as RenderElement
-
- textNode.__vnode__ = n2
- n2.el = textNode
-
- insert(textNode, container, anchor)
- } else {
- const el = (n2.el = n1.el)
-
- const c1 = '' + n1.children
- const c2 = '' + n2.children
-
- if (c1 !== c2) {
- el.textContent = c2
- }
- }
-}
-
-export function patch(
- n1: VNode | null,
- n2: VNode,
- container: RenderElement,
- anchor: RenderElement | null = null,
- parentComponent: Component | null = null,
- isSvg = false
-) {
- if (!container) {
- throw new Error(
- 'The parent element is not found when updating, please check the code.'
- )
- }
-
- if (n1 && !isSameVNodeType(n1, n2)) {
- anchor = getNextSibling(n1)
- unmount(n1)
- n1 = null
- }
-
- switch (n2.type) {
- case Text:
- enterText(n1, n2, container, anchor)
- break
- case Comment:
- enterComment(n1, n2, container, anchor)
- break
- case Element:
- enterElement(n1, n2, container, anchor, parentComponent, isSvg)
- break
- case Fragment:
- enterFragment(n1, n2, container, anchor, parentComponent, isSvg)
- break
- default:
- enterComponent(
- n1 as VNode,
- n2 as VNode,
- container,
- anchor,
- parentComponent
- )
- }
-}
diff --git a/packages/runtime/src/renderer/comment.ts b/packages/runtime/src/renderer/comment.ts
new file mode 100644
index 0000000..00e8c87
--- /dev/null
+++ b/packages/runtime/src/renderer/comment.ts
@@ -0,0 +1,22 @@
+import { createComment, insert } from '@gyron/dom-client'
+import { RenderElement, VNode } from '../vnode'
+
+export function enterComment(
+ n1: VNode | null,
+ n2: VNode,
+ container: RenderElement,
+ anchor: RenderElement
+) {
+ if (n1 === null) {
+ const comment = createComment(
+ (n2.children as string) || ''
+ ) as RenderElement
+ comment.__vnode__ = n2
+
+ n2.el = comment as RenderElement
+ insert(comment, container, anchor)
+ } else {
+ n2.el = n1.el
+ n2.el.__vnode__ = n2
+ }
+}
diff --git a/packages/runtime/src/renderer/component.ts b/packages/runtime/src/renderer/component.ts
new file mode 100644
index 0000000..63c8380
--- /dev/null
+++ b/packages/runtime/src/renderer/component.ts
@@ -0,0 +1,223 @@
+import { isBoolean, isEqual, isPromise } from '@gyron/shared'
+import { VNode, RenderElement } from '../vnode'
+import {
+ isCacheComponent,
+ getCacheComponent,
+ ComponentSetupFunction,
+ Component,
+ removeBuiltInProps,
+ normalizeComponent,
+ createComponentInstance,
+ isAsyncComponent,
+ renderComponent,
+} from '../component'
+import { patch } from '.'
+import { SSRMessage } from '../ssr'
+import { refreshComponentType, collectHmrComponent } from '../hmr'
+import { setRef } from '../ref'
+import {
+ asyncTrackEffect,
+ clearTrackEffect,
+ createEffect,
+} from '@gyron/reactivity'
+import { JobPriority, pushQueueJob, SchedulerJob } from '../scheduler'
+import { warn } from '../assert'
+import { hydrate } from '../hydrate'
+import { invokeLifecycle } from '../lifecycle'
+
+function shouldUpdate(result: any) {
+ return !(isBoolean(result) && !result)
+}
+
+function patchSubTree(component: Component, prevTree: VNode, nextTree: VNode) {
+ component.subTree = nextTree
+ if (component.mounted) {
+ const { anchor } = prevTree
+ component.subTree.anchor = anchor
+ patch(prevTree, nextTree, component.$parent, anchor, component)
+ // onAfterUpdate
+ invokeLifecycle(component, 'afterUpdates')
+ component.$el = nextTree.el
+ } else {
+ // mount
+ patch(null, nextTree, component.$parent, component.vnode.anchor, component)
+ // after the render is complete, set el to the vnode for comparison
+ // dummy ? :
+ component.vnode.el = nextTree.el
+ component.$el = nextTree.el
+ component.mounted = true
+ // onAfterMount
+ component.effect.allowEffect = true
+ invokeLifecycle(component, 'afterMounts')
+ component.effect.allowEffect = false
+ }
+}
+
+function updateComponentEffect(
+ component: Component,
+ ssrMessage: SSRMessage = null
+) {
+ if (component.mounted) {
+ // if the onBeforeUpdate callback function returns falsy
+ // no update of the component is performed
+ if (
+ shouldUpdate(invokeLifecycle(component, 'beforeUpdates')) &&
+ shouldUpdate(!component.props.static)
+ ) {
+ if (__DEV__) {
+ refreshComponentType(component.vnode, component)
+ }
+
+ const prevTree = component.subTree
+ const nextTree = renderComponent(component)
+ if (isPromise(nextTree)) {
+ warn(
+ 'Asynchronous components without wrapping are not supported, please use FCA wrapping',
+ component,
+ 'UpdateComponent'
+ )
+ } else {
+ patchSubTree(component, prevTree, nextTree)
+ }
+ }
+ } else if (!component.destroyed) {
+ if (component.vnode.el) {
+ function hydrateSubTree() {
+ const nextTree = renderComponent(component)
+ component.subTree = nextTree as VNode
+ hydrate(component.vnode.el, component.subTree, component, ssrMessage)
+
+ component.mounted = true
+ // onAfterMount
+ invokeLifecycle(component, 'afterMounts')
+ }
+ // asynchronous component rendering in ssr mode
+ if (isAsyncComponent(component.vnode.type)) {
+ component.vnode.type.__loader(component.props, component).then(() => {
+ if (!component.destroyed) {
+ asyncTrackEffect(component.effect)
+ hydrateSubTree()
+ clearTrackEffect()
+ }
+ })
+ } else {
+ hydrateSubTree()
+ }
+ } else {
+ const nextTree = renderComponent(component)
+ if (isPromise(nextTree)) {
+ warn(
+ 'Asynchronous components without wrapping are not supported, please use FCA wrapping',
+ component,
+ 'SetupPatch'
+ )
+ } else {
+ nextTree.transition ||= component.vnode.transition
+ patchSubTree(component, null, nextTree)
+ }
+ }
+ }
+}
+
+function renderComponentEffect(
+ component: Component,
+ ssrMessage: SSRMessage = null
+) {
+ const effect = (component.effect = createEffect(
+ updateComponentEffect.bind(null, component, ssrMessage),
+ () => pushQueueJob(component.update)
+ ))
+
+ const update = (component.update = effect.run.bind(effect) as SchedulerJob)
+ update.id = component.uid
+ update.component = component
+ update.priority = JobPriority.NORMAL
+ update()
+}
+
+export function mountComponent(
+ vnode: VNode,
+ container: RenderElement,
+ anchor: RenderElement,
+ parentComponent: Component,
+ ssrMessage: SSRMessage = null
+) {
+ vnode.anchor = anchor
+
+ const component = (vnode.component = createComponentInstance(
+ vnode,
+ parentComponent
+ ))
+ component.$parent = container
+
+ if (__DEV__ && (component.type as any).__hmr_id) {
+ refreshComponentType(vnode, component)
+
+ const parentId: string = parentComponent
+ ? (parentComponent.type as any).__hmr_id
+ : null
+ collectHmrComponent((component.type as any).__hmr_id, parentId, component)
+ }
+
+ if (component.props.ref) {
+ setRef(component.exposed, component.props.ref)
+ }
+
+ renderComponentEffect(component, ssrMessage)
+}
+
+export function patchComponent(
+ n1: VNode,
+ n2: VNode,
+ container: RenderElement,
+ anchor: RenderElement,
+ parentComponent: Component
+) {
+ const component = (n2.component = n1.component)
+ if (component) {
+ normalizeComponent(n2, component, parentComponent)
+ if (isCacheComponent(n1.component.type)) {
+ if (!isEqual(n1.props, n2.props)) {
+ component.update()
+ }
+ } else {
+ component.update()
+ }
+ } else {
+ if (__WARN__) {
+ console.warn('Component update exception', n1)
+ }
+ mountComponent(n2, container, anchor, parentComponent)
+ }
+}
+
+export function enterComponent(
+ n1: VNode | null,
+ n2: VNode,
+ container: RenderElement,
+ anchor: RenderElement,
+ parentComponent: Component
+) {
+ if (n1 === null) {
+ // clear the element of the next vnode to prevent access to the SSR hydrate logic.
+ n2.el = null
+ if (isCacheComponent(n2.type)) {
+ // update the DOM with the locally cached component state when a local component cache is found
+ const component = getCacheComponent(n2.type)
+ component.destroyed = false
+ component.mounted = true
+ component.vnode = n2
+ component.$parent = container
+ if (isEqual(removeBuiltInProps(component.props), n2.props)) {
+ patch(null, component.subTree, container, anchor, parentComponent)
+ } else {
+ mountComponent(n2, container, anchor, parentComponent)
+ }
+ } else {
+ mountComponent(n2, container, anchor, parentComponent)
+ }
+ } else {
+ n2.anchor ||= n1.anchor
+ patchComponent(n1, n2, container, anchor, parentComponent)
+ }
+}
diff --git a/packages/runtime/src/renderer/element.ts b/packages/runtime/src/renderer/element.ts
new file mode 100644
index 0000000..a4546d6
--- /dev/null
+++ b/packages/runtime/src/renderer/element.ts
@@ -0,0 +1,281 @@
+import {
+ createElement,
+ insert,
+ isSelectElement,
+ mountProps,
+ patchProps,
+} from '@gyron/dom-client'
+import {
+ shouldValue,
+ keys,
+ extend,
+ isEqual,
+ isArray,
+ isFunction,
+ isObject,
+} from '@gyron/shared'
+import { isVNodeComponent } from '..'
+import { Component, removeBuiltInProps } from '../component'
+import { setRef } from '../ref'
+import { patch } from '.'
+import {
+ mergeVNodeWith,
+ normalizeChildrenVNode,
+ RenderElement,
+ VNode,
+} from '../vnode'
+import { patchComponent } from './component'
+import {
+ getNextSibling,
+ mountChildren,
+ unmount,
+ unmountChildren,
+} from './shared'
+
+function transitionMove(
+ n1: VNode,
+ n2: VNode,
+ container: RenderElement,
+ anchor: RenderElement,
+ parentComponent: Component,
+ isSvg: boolean
+) {
+ const { transition } = n1
+ const el = n1.el as Element
+ transition.onLeaveFinish(el)
+ unmount(n1)
+ patch(
+ null,
+ n2,
+ container,
+ anchor || getNextSibling(n1),
+ parentComponent,
+ isSvg
+ )
+}
+
+function mountElement(
+ vnode: VNode,
+ container: RenderElement,
+ anchor: RenderElement,
+ parentComponent: Component,
+ isSvg: boolean
+) {
+ const { tag, is, transition } = vnode
+ const el = (vnode.el = createElement(tag, isSvg, is) as RenderElement)
+ el.__vnode__ = vnode
+
+ if (vnode.props.ref) {
+ setRef(el, vnode.props.ref)
+ }
+
+ const props = removeBuiltInProps(vnode.props)
+ if (shouldValue(keys(props))) {
+ mountProps(el as HTMLElement, extend({}, vnode, { props: props }))
+ }
+
+ if (shouldValue(vnode.children)) {
+ vnode.children = normalizeChildrenVNode(vnode)
+ mountChildren(vnode.children, vnode.el, anchor, 0, parentComponent, isSvg)
+ }
+
+ insert(el, container, anchor)
+
+ if (transition) {
+ transition.onActive(el as Element)
+ }
+}
+
+function isKeyPatch(n1: VNode[], n2: VNode[]) {
+ if (n1 && n2 && n1[0] && n2[0] && isObject(n1[0]) && isObject(n2[0])) {
+ return shouldValue(n1[0].key) && shouldValue(n2[0].key)
+ }
+ return false
+}
+
+function patchKeyed(
+ c1: VNode[],
+ c2: VNode[],
+ container: RenderElement,
+ anchor: RenderElement | null,
+ parentComponent: Component | null,
+ isSvg: boolean
+) {
+ const o1: Record<
+ string | symbol,
+ VNode & { index: number; inserted: boolean }
+ > = c1.reduce((nodeMap, node, index) => {
+ nodeMap[node.key] = extend(node, { index })
+ return nodeMap
+ }, {})
+
+ const e2 = c2.length
+ let i = 0
+ while (i < e2) {
+ const c2n = c2[i]
+ const c1n = o1[c2n.key]
+ if (c1n) {
+ // 1, find the same key value of the node, and then inserted into the corresponding location. (Do not delete add, move directly)
+ const el = mergeVNodeWith(c2n, c1n).el
+ if (c1n.index !== i) {
+ // insert to new position when node order is changed
+ const anchor = container.childNodes[i]
+ if (el !== anchor.nextSibling) {
+ insert(el, container, anchor.nextSibling)
+ }
+ }
+ // update props after migration is complete
+ // element update attribute
+ if (!isEqual(c1n.props, c2n.props)) {
+ const isComponent = isVNodeComponent(c2n) && isVNodeComponent(c1n)
+ if (isComponent) {
+ patchComponent(c1n, c2n, container, anchor, parentComponent)
+ } else {
+ patchProps(
+ el as HTMLElement,
+ c1n,
+ extend({}, c2n, { props: removeBuiltInProps(c2n.props) })
+ )
+ }
+ }
+ // 1, end
+
+ if (c1n.children || c2n.children) {
+ // 2, update the nodes with the same key value, including the child nodes.
+ // sub-level nodes need to be patched again
+ patchChildren(c1n, c2n, container, anchor, parentComponent, isSvg)
+ // 2, end
+ }
+
+ // mark nodes that have been moved and do not need to be uninstalled in the third step
+ c1n.inserted = true
+ } else {
+ patch(null, c2n, container, anchor, parentComponent, isSvg)
+ }
+ i++
+ }
+
+ for (const node of Object.values(o1)) {
+ if (!node.inserted) {
+ // 3, uninstall the old nodes that are not used.
+ unmount(node)
+ // 3, end
+ }
+ }
+}
+
+function patchNonKeyed(
+ c1: VNode[],
+ c2: VNode[],
+ container: RenderElement,
+ anchor: RenderElement,
+ parentComponent: Component,
+ isSvg: boolean
+) {
+ const c1length = c1.length
+ const c2length = c2.length
+ const minLength = Math.min(c1length, c2length)
+ for (let i = 0; i < minLength; i++) {
+ const prevChild = c1[i]
+ const nextChild = c2[i]
+ patch(prevChild, nextChild, container, anchor, parentComponent)
+ }
+ if (c1length > c2length) {
+ unmountChildren(c1, minLength)
+ } else {
+ mountChildren(c2, container, anchor, minLength, parentComponent, isSvg)
+ }
+}
+
+export function patchChildren(
+ n1: VNode,
+ n2: VNode,
+ container: RenderElement,
+ anchor: RenderElement,
+ parentComponent: Component,
+ isSvg: boolean
+) {
+ if (isFunction(n1.children) && isFunction(n2.children)) {
+ // when the child nodes are all functions, they should be called by the parent node.
+ // {count => }
+ return
+ }
+
+ const c1memo = n1.props.memo
+ const c2memo = n2.props.memo
+ if (isArray(c1memo) && isArray(c2memo)) {
+ const index = c1memo.findIndex((item, index) => {
+ return c2memo[index] !== item
+ })
+ if (index < 0) {
+ n2.children = n1.children
+ return
+ }
+ }
+
+ const c1 = n1.children as VNode[]
+ const c2: VNode[] = (n2.children = normalizeChildrenVNode(n2))
+
+ if (c1?.length || c2?.length) {
+ if (isKeyPatch(c1, c2)) {
+ const el = (n2.el = n1.el)
+ patchKeyed(c1, c2, el || container, anchor, parentComponent, isSvg)
+ } else {
+ // if the fragment node does not have a dom instance, use the container
+ const el = (n2.el = n1.el)
+ if (c1) {
+ patchNonKeyed(c1, c2, el || container, anchor, parentComponent, isSvg)
+ } else {
+ mountChildren(c2, el || container, anchor, 0, parentComponent, isSvg)
+ }
+ }
+ }
+}
+
+function patchElement(
+ n1: VNode,
+ n2: VNode,
+ container: RenderElement,
+ anchor: RenderElement,
+ parentComponent: Component,
+ isSvg: boolean
+) {
+ const el = (n2.el = n1.el) as Element
+ if (el.nodeName === n2.tag.toLocaleUpperCase()) {
+ if (!isEqual(n1.props, n2.props) || isSelectElement(n2)) {
+ patchProps(
+ el,
+ n1,
+ extend({}, n2, { props: removeBuiltInProps(n2.props) })
+ )
+ }
+ if (n1.children || n2.children) {
+ patchChildren(n1, n2, container, anchor, parentComponent, isSvg)
+ }
+ } else {
+ anchor = getNextSibling(n1)
+ unmount(n1)
+ patch(null, n2, container, anchor, parentComponent, isSvg)
+ }
+}
+
+export function enterElement(
+ n1: VNode | null,
+ n2: VNode,
+ container: RenderElement,
+ anchor: RenderElement,
+ parentComponent: Component,
+ isSvg: boolean
+) {
+ isSvg = isSvg || n2.tag === 'svg'
+
+ if (n1 === null) {
+ mountElement(n2, container, anchor, parentComponent, isSvg)
+ } else if (!n2.props.static) {
+ if (n1.transition) {
+ transitionMove(n1, n2, container, anchor, parentComponent, isSvg)
+ } else {
+ patchElement(n1, n2, container, anchor, parentComponent, isSvg)
+ }
+ }
+}
diff --git a/packages/runtime/src/renderer/fragment.ts b/packages/runtime/src/renderer/fragment.ts
new file mode 100644
index 0000000..c20df62
--- /dev/null
+++ b/packages/runtime/src/renderer/fragment.ts
@@ -0,0 +1,31 @@
+import { createText, insert } from '@gyron/dom-client'
+import { VNode, RenderElement, Component, normalizeChildrenVNode } from '..'
+import { patchChildren } from './element'
+import { mountChildren } from './shared'
+
+export function enterFragment(
+ n1: VNode | null,
+ n2: VNode,
+ container: RenderElement,
+ anchor: RenderElement,
+ parentComponent: Component,
+ isSvg: boolean
+) {
+ const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : createText(''))
+ if (n1 === null) {
+ n2.anchor = fragmentEndAnchor
+ insert(fragmentEndAnchor, container, anchor)
+
+ n2.children = normalizeChildrenVNode(n2)
+ mountChildren(
+ n2.children,
+ container,
+ fragmentEndAnchor,
+ 0,
+ parentComponent,
+ isSvg
+ )
+ } else {
+ patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent, isSvg)
+ }
+}
diff --git a/packages/runtime/src/renderer/index.ts b/packages/runtime/src/renderer/index.ts
new file mode 100644
index 0000000..b9c18fe
--- /dev/null
+++ b/packages/runtime/src/renderer/index.ts
@@ -0,0 +1,65 @@
+import { unmount, getNextSibling } from './shared'
+import { enterText } from './text'
+import { enterFragment } from './fragment'
+import { enterElement } from './element'
+import { enterComment } from './comment'
+import { enterComponent, mountComponent } from './component'
+import {
+ VNode,
+ RenderElement,
+ Fragment,
+ Text,
+ Comment,
+ Element,
+} from '../vnode'
+import { Component, ComponentSetupFunction } from '../component'
+
+export { unmount, mountComponent }
+
+export function isSameVNodeType(n1: VNode, n2: VNode) {
+ return n1.type === n2.type && n1.key === n2.key
+}
+
+export function patch(
+ n1: VNode | null,
+ n2: VNode,
+ container: RenderElement,
+ anchor: RenderElement | null = null,
+ parentComponent: Component | null = null,
+ isSvg = false
+) {
+ if (!container) {
+ throw new Error(
+ 'The parent element is not found when updating, please check the code.'
+ )
+ }
+
+ if (n1 && !isSameVNodeType(n1, n2)) {
+ anchor = getNextSibling(n1)
+ unmount(n1)
+ n1 = null
+ }
+
+ switch (n2.type) {
+ case Text:
+ enterText(n1, n2, container, anchor)
+ break
+ case Comment:
+ enterComment(n1, n2, container, anchor)
+ break
+ case Element:
+ enterElement(n1, n2, container, anchor, parentComponent, isSvg)
+ break
+ case Fragment:
+ enterFragment(n1, n2, container, anchor, parentComponent, isSvg)
+ break
+ default:
+ enterComponent(
+ n1 as VNode,
+ n2 as VNode,
+ container,
+ anchor,
+ parentComponent
+ )
+ }
+}
diff --git a/packages/runtime/src/renderer/shared.ts b/packages/runtime/src/renderer/shared.ts
new file mode 100644
index 0000000..9938c07
--- /dev/null
+++ b/packages/runtime/src/renderer/shared.ts
@@ -0,0 +1,91 @@
+import { isArray, isElement, Noop } from '@gyron/shared'
+import { isVNode } from '../shared'
+import { Component, isCacheComponent } from '../component'
+import { invokeLifecycle } from '../lifecycle'
+import { Children, normalizeVNode, RenderElement, VNode } from '../vnode'
+import { nextSibling, remove } from '@gyron/dom-client'
+import { patch } from '.'
+
+function removeInvoke(_el: RenderElement, vnode: VNode, done: Noop) {
+ const { transition } = vnode
+ const el = _el as Element
+ if (transition) {
+ transition.onLeave(el, () => {
+ remove(el)
+ done()
+ })
+ } else {
+ remove(el)
+ done()
+ }
+}
+
+export function unmountChildren(c1: VNode[], start = 0) {
+ for (let i = start; i < c1.length; i++) {
+ unmount(c1[i])
+ }
+}
+
+export function mountChildren(
+ nodes: VNode[] | Children[],
+ container: RenderElement,
+ anchor: RenderElement,
+ start = 0,
+ parentComponent: Component | null = null,
+ isSvg: boolean
+) {
+ for (let i = start; i < nodes.length; i++) {
+ const node = normalizeVNode(nodes[i])
+ patch(null, node, container, anchor, parentComponent, isSvg)
+ }
+}
+
+export function getNextSibling(vnode: VNode) {
+ if (vnode.component) {
+ return getNextSibling(vnode.component.subTree)
+ }
+ if (vnode.el || vnode.anchor) {
+ return nextSibling(vnode.el || vnode.anchor)
+ }
+ return null
+}
+
+export function unmount(vnode: VNode) {
+ if (!isVNode(vnode)) {
+ return null
+ }
+
+ function reset() {
+ vnode.el = null
+ }
+ const { el, component, children, transition } = vnode
+
+ if (component) {
+ if (!isCacheComponent(component.type)) {
+ component.effect.stop()
+ }
+ if (component.subTree) {
+ unmount(component.subTree)
+ }
+ invokeLifecycle(component, 'destroyed')
+ if (component.$el) {
+ removeInvoke(component.$el, vnode, reset)
+ if (!isCacheComponent(component.type)) {
+ component.$el = null
+ }
+ }
+ component.destroyed = true
+ component.mounted = false
+ } else {
+ if (!transition) {
+ if (isArray(children) && children.length > 0) {
+ unmountChildren(children as VNode[])
+ } else {
+ unmount(children as VNode)
+ }
+ }
+ if (isElement(el)) {
+ removeInvoke(el, vnode, reset)
+ }
+ }
+}
diff --git a/packages/runtime/src/renderer/text.ts b/packages/runtime/src/renderer/text.ts
new file mode 100644
index 0000000..3024786
--- /dev/null
+++ b/packages/runtime/src/renderer/text.ts
@@ -0,0 +1,28 @@
+import { createText, insert } from '@gyron/dom-client'
+import { RenderElement, VNode } from '../vnode'
+
+export function enterText(
+ n1: VNode | null,
+ n2: VNode,
+ container: RenderElement,
+ anchor: RenderElement
+) {
+ if (n1 === null || !n1.el) {
+ // when hydrating the code, since there is no empty text node on the server side, you need to execute mountText
+ const textNode = createText(n2.children as string) as RenderElement
+
+ textNode.__vnode__ = n2
+ n2.el = textNode
+
+ insert(textNode, container, anchor)
+ } else {
+ const el = (n2.el = n1.el)
+
+ const c1 = '' + n1.children
+ const c2 = '' + n2.children
+
+ if (c1 !== c2) {
+ el.textContent = c2
+ }
+ }
+}
diff --git a/packages/runtime/src/vnode.ts b/packages/runtime/src/vnode.ts
index 5e80665..8552ae2 100644
--- a/packages/runtime/src/vnode.ts
+++ b/packages/runtime/src/vnode.ts
@@ -15,7 +15,7 @@ import {
ComponentSetupFunction,
} from './component'
import { UserRef } from './ref'
-import { TransitionHooks } from './Transition'
+import { TransitionHooks } from './internal'
export const Gyron = Symbol('gyron')
export const Text = Symbol('gyron.text')
diff --git a/packages/runtime/tests/handler.spec.ts b/packages/runtime/tests/handler.spec.ts
index a7a5e91..c43a67b 100644
--- a/packages/runtime/tests/handler.spec.ts
+++ b/packages/runtime/tests/handler.spec.ts
@@ -9,7 +9,7 @@ import {
getCurrentComponent,
manualWarnHandler,
} from '../src'
-import { ErrorBoundary } from '../src/ErrorBoundary'
+import { ErrorBoundary } from '../src/internal'
describe('Handler Error', () => {
const container = document.createElement('div')
diff --git a/packages/runtime/tests/transition.spec.ts b/packages/runtime/tests/transition.spec.ts
index ba1526d..c6d234f 100644
--- a/packages/runtime/tests/transition.spec.ts
+++ b/packages/runtime/tests/transition.spec.ts
@@ -1,6 +1,6 @@
import { sleep, sleepWithRequestFrame } from '@gyron/shared'
import { createInstance, h, nextRender, Transition } from '../src'
-import { whenTransitionEnd } from '../src/Transition'
+import { whenTransitionEnd } from '../src/internal'
describe('transition component', () => {
const container = document.createElement('div')
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 5233ea5..b1c9c2b 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -17,6 +17,6 @@
"build:esm": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/esm/index.js --format=esm --platform=node",
"build:cjs": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/cjs/index.js --format=cjs --platform=node",
"build:dts": "rollup -c ../../rollup.config.js",
- "test": "jest --config=../../jest.config.js"
+ "test": "cross-env PACKAGES=shared jest --config=../../jest.config.js"
}
}
diff --git a/packages/sync/package.json b/packages/sync/package.json
index 45ac908..a6609e6 100644
--- a/packages/sync/package.json
+++ b/packages/sync/package.json
@@ -17,7 +17,7 @@
"build:esm": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/esm/index.js --format=esm --external:@gyron/* --platform=node",
"build:cjs": "esbuild src/index.ts --bundle --sourcemap --minify --outfile=dist/cjs/index.js --format=cjs --external:@gyron/* --platform=node",
"build:dts": "rollup -c ../../rollup.config.js",
- "test": "jest --config=../../jest.config.js"
+ "test": "cross-env PACKAGES=sync jest --config=../../jest.config.js"
},
"devDependencies": {
"@gyron/shared": "^0.0.29",