├── .github ├── renovate.json └── workflows │ └── ci.yml ├── .gitignore ├── .node-version ├── README.md ├── fixtures ├── UserSettings.tsx ├── parser.ts ├── renderer.ts └── table.tsx ├── memory.sh ├── output └── .gitkeep ├── package.json ├── pnpm-lock.yaml ├── src ├── id.bench.ts ├── memory │ ├── babel.js │ ├── oxc.js │ └── swc.js └── transform.bench.ts └── tsconfig.json /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>Boshen/renovate"], 4 | "rangeStrategy": "bump", 5 | "schedule": ["at any time"] 6 | } 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | types: [opened, synchronize] 7 | push: 8 | branches: 9 | - main 10 | 11 | permissions: {} 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 15 | cancel-in-progress: ${{ github.ref_name != 'main' }} 16 | 17 | jobs: 18 | benchmark: 19 | name: Benchmark 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: taiki-e/checkout-action@b13d20b7cda4e2f325ef19895128f7ff735c0b3d # v1.3.1 23 | - uses: oxc-project/setup-node@fdbf0dfd334c4e6d56ceeb77d91c76339c2a0885 # v1.0.4 24 | - run: pnpm install 25 | - run: pnpm run bench id 26 | - run: pnpm run bench transform 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | output/*.js 3 | .idea/ 4 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bench Oxc, Swc, and Babel Transformer 2 | 3 | ## Summary 4 | 5 | - Transform: Oxc is 3x - 5x faster than SWC, uses 20% less memory, and has smaller package size (2 MB vs SWC's 37 MB). 6 | - Transform: Oxc is 20x - 50x faster than Babel, uses 70% less memory, and is 19 MB smaller, with only 2 npm packages to install vs Babel's 170. 7 | - React development + React Refresh: Oxc is 5x faster than SWC, 50x faster than Babel. 8 | - TS isolated declarations `.d.ts` emit: Oxc is 40x faster than TSC on typical files, 20x faster on larger files. 9 | 10 | ## Transform / Transpile 11 | 12 | Oxc is 3x - 5x faster than swc, and 20x - 50x faster than Babel. 13 | 14 | React development + refresh is 6x faster than swc and 20x - 70x faster than Babel. 15 | 16 | ### GitHub Actions `ubuntu-latest` 17 | 18 | ``` 19 | oxc - src/transform.bench.ts > UserSettings.tsx (sourceMap: false, reactDev: false, target: esnext) 20 | 4.18x faster than swc 21 | 49.04x faster than babel 22 | oxc - src/transform.bench.ts > UserSettings.tsx (sourceMap: false, reactDev: false, target: es2015) 23 | 5.71x faster than swc 24 | 46.49x faster than babel 25 | oxc - src/transform.bench.ts > UserSettings.tsx (sourceMap: true, reactDev: false, target: esnext) 26 | 4.23x faster than swc 27 | 40.18x faster than babel 28 | oxc - src/transform.bench.ts > UserSettings.tsx (sourceMap: false, reactDev: true, target: esnext) 29 | 5.41x faster than swc 30 | 64.39x faster than babel 31 | oxc - src/transform.bench.ts > UserSettings.tsx (sourceMap: true, reactDev: true, target: esnext) 32 | 5.17x faster than swc 33 | 53.43x faster than babel 34 | oxc - src/transform.bench.ts > parser.ts (sourceMap: false, reactDev: false, target: esnext) 35 | 3.13x faster than swc 36 | 34.59x faster than babel 37 | oxc - src/transform.bench.ts > parser.ts (sourceMap: false, reactDev: false, target: es2015) 38 | 3.51x faster than swc 39 | 31.86x faster than babel 40 | oxc - src/transform.bench.ts > parser.ts (sourceMap: true, reactDev: false, target: esnext) 41 | 3.03x faster than swc 42 | 26.98x faster than babel 43 | oxc - src/transform.bench.ts > renderer.ts (sourceMap: false, reactDev: false, target: esnext) 44 | 3.21x faster than swc 45 | 23.76x faster than babel 46 | oxc - src/transform.bench.ts > renderer.ts (sourceMap: false, reactDev: false, target: es2015) 47 | 3.62x faster than swc 48 | 22.93x faster than babel 49 | oxc - src/transform.bench.ts > renderer.ts (sourceMap: true, reactDev: false, target: esnext) 50 | 3.11x faster than swc 51 | 18.77x faster than babel 52 | oxc - src/transform.bench.ts > table.tsx (sourceMap: false, reactDev: false, target: esnext) 53 | 3.74x faster than swc 54 | 30.57x faster than babel 55 | oxc - src/transform.bench.ts > table.tsx (sourceMap: false, reactDev: false, target: es2015) 56 | 4.32x faster than swc 57 | 30.96x faster than babel 58 | oxc - src/transform.bench.ts > table.tsx (sourceMap: true, reactDev: false, target: esnext) 59 | 3.59x faster than swc 60 | 26.30x faster than babel 61 | oxc - src/transform.bench.ts > table.tsx (sourceMap: false, reactDev: true, target: esnext) 62 | 4.71x faster than swc 63 | 46.03x faster than babel 64 | oxc - src/transform.bench.ts > table.tsx (sourceMap: true, reactDev: true, target: esnext) 65 | 4.45x faster than swc 66 | 37.05x faster than babel 67 | ``` 68 | 69 | ## Isolated Declarations DTS Emit 70 | 71 | Oxc is 45x faster than `tsc` on ordinary files, and 20x faster on larger files. 72 | 73 | ### GitHub Actions `ubuntu-latest` 74 | 75 | ``` 76 | oxc - src/id.bench.ts > UserSettings.tsx 77 | 44.45x faster than tsc 78 | 79 | oxc - src/id.bench.ts > parser.ts 80 | 21.16x faster than tsc 81 | 82 | oxc - src/id.bench.ts > renderer.ts 83 | 21.70x faster than tsc 84 | 85 | oxc - src/id.bench.ts > table.tsx 86 | 7.99x faster than tsc 87 | ``` 88 | 89 | 90 | ### Memory Usage 91 | 92 | On `parser.ts` by using `/usr/bin/time -alh node`: 93 | 94 | | | Max RSS | 95 | | --- | ------- | 96 | | oxc | 57 MB | 97 | | swc | 74 MB | 98 | | babel | 180 MB | 99 | 100 | ## Package size 101 | 102 | For package download size, oxc downloads 2 packages for around a total of 2MB. 103 | 104 | | Package | Size | 105 | | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | 106 | | `@oxc-transform/binding-darwin-arm64` | [1.95 MB](https://www.npmjs.com/package/@oxc-transform/binding-darwin-arm64) | 107 | | `@swc/core-darwin-arm64` | [37.5 MB](https://www.npmjs.com/package/@swc/core-darwin-arm64) | 108 | | `@babel/core` + `@babel/preset-env` + `@babel/preset-react` + `@babel/preset-typescript` | [21MB and 170 packages](https://www.npmjs.com/package/@oxc-transform/binding-darwin-arm64) | 109 | 110 | ## Fixtures 111 | 112 | * [TypeScript/src/compiler/parser.ts](https://github.com/microsoft/TypeScript/blob/3ad0f752482f5e846dc35a69572ccb43311826c0/src/compiler/parser.ts) - an atypical large file with 10777 lines. 113 | * [vuejs/core/packages/runtime-core/src/renderer.ts](https://github.com/vuejs/core/blob/cb34b28a4a9bf868be4785b001c526163eda342e/packages/runtime-core/src/renderer.ts) - somewhat large library file with 2550 lines. 114 | * [AFFiNE/packages/frontend/core/src/components/affine/page-properties/table.tsx](https://github.com/toeverything/AFFiNE/blob/a9b29d24f1f6e5563e43a11b5cbcfb30c9981d25/packages/frontend/core/src/components/affine/page-properties/table.tsx) - a tsx file with 1118 lines. 115 | * [cal.com/apps/web/components/getting-started/steps-views/UserSettings.tsx](https://github.com/calcom/cal.com/blob/20729b3a4e62c52f49419d2c3b30225f0c7a5936/apps/web/components/getting-started/steps-views/UserSettings.tsx) - a typical 124 lines of tsx code. 116 | 117 | ### NOTE: 118 | 119 | Babel's code generator deoptimised the styling for large files and reports: 120 | 121 | > [BABEL] Note: The code generator has deoptimised the styling of parser.ts as it exceeds the max of 500KB. 122 | 123 | I intended to benchmark `checker.ts` from tsc, but Babel failed to parse: 124 | 125 | ``` 126 | TypeError: Duplicate declaration "SymbolLinks" 127 | 1425 | })); 128 | 1426 | 129 | > 1427 | const SymbolLinks = class implements SymbolLinks { 130 | | ^^^^^^^^^^^ 131 | 1428 | declare _symbolLinksBrand: any; 132 | 1429 | }; 133 | 1430 | 134 | ``` 135 | -------------------------------------------------------------------------------- /fixtures/UserSettings.tsx: -------------------------------------------------------------------------------- 1 | import { zodResolver } from "@hookform/resolvers/zod"; 2 | import { useEffect } from "react"; 3 | import { useForm } from "react-hook-form"; 4 | import { z } from "zod"; 5 | 6 | import dayjs from "@calcom/dayjs"; 7 | import { useTimePreferences } from "@calcom/features/bookings/lib"; 8 | import { FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants"; 9 | import { useLocale } from "@calcom/lib/hooks/useLocale"; 10 | import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; 11 | import { trpc } from "@calcom/trpc/react"; 12 | import { Button, TimezoneSelect, Icon, Input } from "@calcom/ui"; 13 | 14 | import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability"; 15 | 16 | interface IUserSettingsProps { 17 | nextStep: () => void; 18 | hideUsername?: boolean; 19 | } 20 | 21 | const UserSettings = (props: IUserSettingsProps) => { 22 | const { nextStep } = props; 23 | const [user] = trpc.viewer.me.useSuspenseQuery(); 24 | const { t } = useLocale(); 25 | const { setTimezone: setSelectedTimeZone, timezone: selectedTimeZone } = useTimePreferences(); 26 | const telemetry = useTelemetry(); 27 | const userSettingsSchema = z.object({ 28 | name: z 29 | .string() 30 | .min(1) 31 | .max(FULL_NAME_LENGTH_MAX_LIMIT, { 32 | message: t("max_limit_allowed_hint", { limit: FULL_NAME_LENGTH_MAX_LIMIT }), 33 | }), 34 | }); 35 | const { 36 | register, 37 | handleSubmit, 38 | formState: { errors }, 39 | } = useForm>({ 40 | defaultValues: { 41 | name: user?.name || "", 42 | }, 43 | reValidateMode: "onChange", 44 | resolver: zodResolver(userSettingsSchema), 45 | }); 46 | 47 | useEffect(() => { 48 | telemetry.event(telemetryEventTypes.onboardingStarted); 49 | }, [telemetry]); 50 | 51 | const utils = trpc.useUtils(); 52 | const onSuccess = async () => { 53 | await utils.viewer.me.invalidate(); 54 | nextStep(); 55 | }; 56 | const mutation = trpc.viewer.updateProfile.useMutation({ 57 | onSuccess: onSuccess, 58 | }); 59 | 60 | const onSubmit = handleSubmit((data) => { 61 | mutation.mutate({ 62 | name: data.name, 63 | timeZone: selectedTimeZone, 64 | }); 65 | }); 66 | 67 | return ( 68 |
69 |
70 | {/* Username textfield: when not coming from signup */} 71 | {!props.hideUsername && } 72 | 73 | {/* Full name textfield */} 74 |
75 | 78 | 88 | {errors.name && ( 89 |

90 | {errors.name.message} 91 |

92 | )} 93 |
94 | {/* Timezone select field */} 95 |
96 | 99 | 100 | setSelectedTimeZone(value)} 104 | className="mt-2 w-full rounded-md text-sm" 105 | /> 106 | 107 |

108 | {t("current_time")} {dayjs().tz(selectedTimeZone).format("LT").toString().toLowerCase()} 109 |

110 |
111 |
112 | 120 |
121 | ); 122 | }; 123 | 124 | export { UserSettings }; 125 | -------------------------------------------------------------------------------- /fixtures/renderer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Comment, 3 | Fragment, 4 | Static, 5 | Text, 6 | type VNode, 7 | type VNodeArrayChildren, 8 | type VNodeHook, 9 | type VNodeProps, 10 | cloneIfMounted, 11 | createVNode, 12 | invokeVNodeHook, 13 | isSameVNodeType, 14 | normalizeVNode, 15 | } from './vnode' 16 | import { 17 | type ComponentInternalInstance, 18 | type ComponentOptions, 19 | type Data, 20 | type LifecycleHook, 21 | createComponentInstance, 22 | setupComponent, 23 | } from './component' 24 | import { 25 | filterSingleRoot, 26 | renderComponentRoot, 27 | shouldUpdateComponent, 28 | updateHOCHostEl, 29 | } from './componentRenderUtils' 30 | import { 31 | EMPTY_ARR, 32 | EMPTY_OBJ, 33 | NOOP, 34 | PatchFlags, 35 | ShapeFlags, 36 | def, 37 | getGlobalThis, 38 | invokeArrayFns, 39 | isArray, 40 | isReservedProp, 41 | } from '@vue/shared' 42 | import { 43 | type SchedulerJob, 44 | SchedulerJobFlags, 45 | type SchedulerJobs, 46 | flushPostFlushCbs, 47 | flushPreFlushCbs, 48 | queueJob, 49 | queuePostFlushCb, 50 | } from './scheduler' 51 | import { 52 | EffectFlags, 53 | ReactiveEffect, 54 | pauseTracking, 55 | resetTracking, 56 | } from '@vue/reactivity' 57 | import { updateProps } from './componentProps' 58 | import { updateSlots } from './componentSlots' 59 | import { popWarningContext, pushWarningContext, warn } from './warning' 60 | import { type CreateAppFunction, createAppAPI } from './apiCreateApp' 61 | import { setRef } from './rendererTemplateRef' 62 | import { 63 | type SuspenseBoundary, 64 | type SuspenseImpl, 65 | isSuspense, 66 | queueEffectWithSuspense, 67 | } from './components/Suspense' 68 | import { 69 | TeleportEndKey, 70 | type TeleportImpl, 71 | type TeleportVNode, 72 | } from './components/Teleport' 73 | import { type KeepAliveContext, isKeepAlive } from './components/KeepAlive' 74 | import { isHmrUpdating, registerHMR, unregisterHMR } from './hmr' 75 | import { type RootHydrateFunction, createHydrationFunctions } from './hydration' 76 | import { invokeDirectiveHook } from './directives' 77 | import { endMeasure, startMeasure } from './profiling' 78 | import { 79 | devtoolsComponentAdded, 80 | devtoolsComponentRemoved, 81 | devtoolsComponentUpdated, 82 | setDevtoolsHook, 83 | } from './devtools' 84 | import { initFeatureFlags } from './featureFlags' 85 | import { isAsyncWrapper } from './apiAsyncComponent' 86 | import { isCompatEnabled } from './compat/compatConfig' 87 | import { DeprecationTypes } from './compat/compatConfig' 88 | import type { TransitionHooks } from './components/BaseTransition' 89 | 90 | export interface Renderer { 91 | render: RootRenderFunction 92 | createApp: CreateAppFunction 93 | } 94 | 95 | export interface HydrationRenderer extends Renderer { 96 | hydrate: RootHydrateFunction 97 | } 98 | 99 | export type ElementNamespace = 'svg' | 'mathml' | undefined 100 | 101 | export type RootRenderFunction = ( 102 | vnode: VNode | null, 103 | container: HostElement, 104 | namespace?: ElementNamespace, 105 | ) => void 106 | 107 | export interface RendererOptions< 108 | HostNode = RendererNode, 109 | HostElement = RendererElement, 110 | > { 111 | patchProp( 112 | el: HostElement, 113 | key: string, 114 | prevValue: any, 115 | nextValue: any, 116 | namespace?: ElementNamespace, 117 | parentComponent?: ComponentInternalInstance | null, 118 | ): void 119 | insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void 120 | remove(el: HostNode): void 121 | createElement( 122 | type: string, 123 | namespace?: ElementNamespace, 124 | isCustomizedBuiltIn?: string, 125 | vnodeProps?: (VNodeProps & { [key: string]: any }) | null, 126 | ): HostElement 127 | createText(text: string): HostNode 128 | createComment(text: string): HostNode 129 | setText(node: HostNode, text: string): void 130 | setElementText(node: HostElement, text: string): void 131 | parentNode(node: HostNode): HostElement | null 132 | nextSibling(node: HostNode): HostNode | null 133 | querySelector?(selector: string): HostElement | null 134 | setScopeId?(el: HostElement, id: string): void 135 | cloneNode?(node: HostNode): HostNode 136 | insertStaticContent?( 137 | content: string, 138 | parent: HostElement, 139 | anchor: HostNode | null, 140 | namespace: ElementNamespace, 141 | start?: HostNode | null, 142 | end?: HostNode | null, 143 | ): [HostNode, HostNode] 144 | } 145 | 146 | // Renderer Node can technically be any object in the context of core renderer 147 | // logic - they are never directly operated on and always passed to the node op 148 | // functions provided via options, so the internal constraint is really just 149 | // a generic object. 150 | export interface RendererNode { 151 | [key: string | symbol]: any 152 | } 153 | 154 | export interface RendererElement extends RendererNode {} 155 | 156 | // An object exposing the internals of a renderer, passed to tree-shakeable 157 | // features so that they can be decoupled from this file. Keys are shortened 158 | // to optimize bundle size. 159 | export interface RendererInternals< 160 | HostNode = RendererNode, 161 | HostElement = RendererElement, 162 | > { 163 | p: PatchFn 164 | um: UnmountFn 165 | r: RemoveFn 166 | m: MoveFn 167 | mt: MountComponentFn 168 | mc: MountChildrenFn 169 | pc: PatchChildrenFn 170 | pbc: PatchBlockChildrenFn 171 | n: NextFn 172 | o: RendererOptions 173 | } 174 | 175 | // These functions are created inside a closure and therefore their types cannot 176 | // be directly exported. In order to avoid maintaining function signatures in 177 | // two places, we declare them once here and use them inside the closure. 178 | type PatchFn = ( 179 | n1: VNode | null, // null means this is a mount 180 | n2: VNode, 181 | container: RendererElement, 182 | anchor?: RendererNode | null, 183 | parentComponent?: ComponentInternalInstance | null, 184 | parentSuspense?: SuspenseBoundary | null, 185 | namespace?: ElementNamespace, 186 | slotScopeIds?: string[] | null, 187 | optimized?: boolean, 188 | ) => void 189 | 190 | type MountChildrenFn = ( 191 | children: VNodeArrayChildren, 192 | container: RendererElement, 193 | anchor: RendererNode | null, 194 | parentComponent: ComponentInternalInstance | null, 195 | parentSuspense: SuspenseBoundary | null, 196 | namespace: ElementNamespace, 197 | slotScopeIds: string[] | null, 198 | optimized: boolean, 199 | start?: number, 200 | ) => void 201 | 202 | type PatchChildrenFn = ( 203 | n1: VNode | null, 204 | n2: VNode, 205 | container: RendererElement, 206 | anchor: RendererNode | null, 207 | parentComponent: ComponentInternalInstance | null, 208 | parentSuspense: SuspenseBoundary | null, 209 | namespace: ElementNamespace, 210 | slotScopeIds: string[] | null, 211 | optimized: boolean, 212 | ) => void 213 | 214 | type PatchBlockChildrenFn = ( 215 | oldChildren: VNode[], 216 | newChildren: VNode[], 217 | fallbackContainer: RendererElement, 218 | parentComponent: ComponentInternalInstance | null, 219 | parentSuspense: SuspenseBoundary | null, 220 | namespace: ElementNamespace, 221 | slotScopeIds: string[] | null, 222 | ) => void 223 | 224 | type MoveFn = ( 225 | vnode: VNode, 226 | container: RendererElement, 227 | anchor: RendererNode | null, 228 | type: MoveType, 229 | parentSuspense?: SuspenseBoundary | null, 230 | ) => void 231 | 232 | type NextFn = (vnode: VNode) => RendererNode | null 233 | 234 | type UnmountFn = ( 235 | vnode: VNode, 236 | parentComponent: ComponentInternalInstance | null, 237 | parentSuspense: SuspenseBoundary | null, 238 | doRemove?: boolean, 239 | optimized?: boolean, 240 | ) => void 241 | 242 | type RemoveFn = (vnode: VNode) => void 243 | 244 | type UnmountChildrenFn = ( 245 | children: VNode[], 246 | parentComponent: ComponentInternalInstance | null, 247 | parentSuspense: SuspenseBoundary | null, 248 | doRemove?: boolean, 249 | optimized?: boolean, 250 | start?: number, 251 | ) => void 252 | 253 | export type MountComponentFn = ( 254 | initialVNode: VNode, 255 | container: RendererElement, 256 | anchor: RendererNode | null, 257 | parentComponent: ComponentInternalInstance | null, 258 | parentSuspense: SuspenseBoundary | null, 259 | namespace: ElementNamespace, 260 | optimized: boolean, 261 | ) => void 262 | 263 | type ProcessTextOrCommentFn = ( 264 | n1: VNode | null, 265 | n2: VNode, 266 | container: RendererElement, 267 | anchor: RendererNode | null, 268 | ) => void 269 | 270 | export type SetupRenderEffectFn = ( 271 | instance: ComponentInternalInstance, 272 | initialVNode: VNode, 273 | container: RendererElement, 274 | anchor: RendererNode | null, 275 | parentSuspense: SuspenseBoundary | null, 276 | namespace: ElementNamespace, 277 | optimized: boolean, 278 | ) => void 279 | 280 | export enum MoveType { 281 | ENTER, 282 | LEAVE, 283 | REORDER, 284 | } 285 | 286 | export const queuePostRenderEffect: ( 287 | fn: SchedulerJobs, 288 | suspense: SuspenseBoundary | null, 289 | ) => void = __FEATURE_SUSPENSE__ 290 | ? __TEST__ 291 | ? // vitest can't seem to handle eager circular dependency 292 | (fn: Function | Function[], suspense: SuspenseBoundary | null) => 293 | queueEffectWithSuspense(fn, suspense) 294 | : queueEffectWithSuspense 295 | : queuePostFlushCb 296 | 297 | /** 298 | * The createRenderer function accepts two generic arguments: 299 | * HostNode and HostElement, corresponding to Node and Element types in the 300 | * host environment. For example, for runtime-dom, HostNode would be the DOM 301 | * `Node` interface and HostElement would be the DOM `Element` interface. 302 | * 303 | * Custom renderers can pass in the platform specific types like this: 304 | * 305 | * ``` js 306 | * const { render, createApp } = createRenderer({ 307 | * patchProp, 308 | * ...nodeOps 309 | * }) 310 | * ``` 311 | */ 312 | export function createRenderer< 313 | HostNode = RendererNode, 314 | HostElement = RendererElement, 315 | >(options: RendererOptions): Renderer { 316 | return baseCreateRenderer(options) 317 | } 318 | 319 | // Separate API for creating hydration-enabled renderer. 320 | // Hydration logic is only used when calling this function, making it 321 | // tree-shakable. 322 | export function createHydrationRenderer( 323 | options: RendererOptions, 324 | ): HydrationRenderer { 325 | return baseCreateRenderer(options, createHydrationFunctions) 326 | } 327 | 328 | // overload 1: no hydration 329 | function baseCreateRenderer< 330 | HostNode = RendererNode, 331 | HostElement = RendererElement, 332 | >(options: RendererOptions): Renderer 333 | 334 | // overload 2: with hydration 335 | function baseCreateRenderer( 336 | options: RendererOptions, 337 | createHydrationFns: typeof createHydrationFunctions, 338 | ): HydrationRenderer 339 | 340 | // implementation 341 | function baseCreateRenderer( 342 | options: RendererOptions, 343 | createHydrationFns?: typeof createHydrationFunctions, 344 | ): any { 345 | // compile-time feature flags check 346 | if (__ESM_BUNDLER__ && !__TEST__) { 347 | initFeatureFlags() 348 | } 349 | 350 | const target = getGlobalThis() 351 | target.__VUE__ = true 352 | if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { 353 | setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__, target) 354 | } 355 | 356 | const { 357 | insert: hostInsert, 358 | remove: hostRemove, 359 | patchProp: hostPatchProp, 360 | createElement: hostCreateElement, 361 | createText: hostCreateText, 362 | createComment: hostCreateComment, 363 | setText: hostSetText, 364 | setElementText: hostSetElementText, 365 | parentNode: hostParentNode, 366 | nextSibling: hostNextSibling, 367 | setScopeId: hostSetScopeId = NOOP, 368 | insertStaticContent: hostInsertStaticContent, 369 | } = options 370 | 371 | // Note: functions inside this closure should use `const xxx = () => {}` 372 | // style in order to prevent being inlined by minifiers. 373 | const patch: PatchFn = ( 374 | n1, 375 | n2, 376 | container, 377 | anchor = null, 378 | parentComponent = null, 379 | parentSuspense = null, 380 | namespace = undefined, 381 | slotScopeIds = null, 382 | optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren, 383 | ) => { 384 | if (n1 === n2) { 385 | return 386 | } 387 | 388 | // patching & not same type, unmount old tree 389 | if (n1 && !isSameVNodeType(n1, n2)) { 390 | anchor = getNextHostNode(n1) 391 | unmount(n1, parentComponent, parentSuspense, true) 392 | n1 = null 393 | } 394 | 395 | if (n2.patchFlag === PatchFlags.BAIL) { 396 | optimized = false 397 | n2.dynamicChildren = null 398 | } 399 | 400 | const { type, ref, shapeFlag } = n2 401 | switch (type) { 402 | case Text: 403 | processText(n1, n2, container, anchor) 404 | break 405 | case Comment: 406 | processCommentNode(n1, n2, container, anchor) 407 | break 408 | case Static: 409 | if (n1 == null) { 410 | mountStaticNode(n2, container, anchor, namespace) 411 | } else if (__DEV__) { 412 | patchStaticNode(n1, n2, container, namespace) 413 | } 414 | break 415 | case Fragment: 416 | processFragment( 417 | n1, 418 | n2, 419 | container, 420 | anchor, 421 | parentComponent, 422 | parentSuspense, 423 | namespace, 424 | slotScopeIds, 425 | optimized, 426 | ) 427 | break 428 | default: 429 | if (shapeFlag & ShapeFlags.ELEMENT) { 430 | processElement( 431 | n1, 432 | n2, 433 | container, 434 | anchor, 435 | parentComponent, 436 | parentSuspense, 437 | namespace, 438 | slotScopeIds, 439 | optimized, 440 | ) 441 | } else if (shapeFlag & ShapeFlags.COMPONENT) { 442 | processComponent( 443 | n1, 444 | n2, 445 | container, 446 | anchor, 447 | parentComponent, 448 | parentSuspense, 449 | namespace, 450 | slotScopeIds, 451 | optimized, 452 | ) 453 | } else if (shapeFlag & ShapeFlags.TELEPORT) { 454 | ;(type as typeof TeleportImpl).process( 455 | n1 as TeleportVNode, 456 | n2 as TeleportVNode, 457 | container, 458 | anchor, 459 | parentComponent, 460 | parentSuspense, 461 | namespace, 462 | slotScopeIds, 463 | optimized, 464 | internals, 465 | ) 466 | } else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { 467 | ;(type as typeof SuspenseImpl).process( 468 | n1, 469 | n2, 470 | container, 471 | anchor, 472 | parentComponent, 473 | parentSuspense, 474 | namespace, 475 | slotScopeIds, 476 | optimized, 477 | internals, 478 | ) 479 | } else if (__DEV__) { 480 | warn('Invalid VNode type:', type, `(${typeof type})`) 481 | } 482 | } 483 | 484 | // set ref 485 | if (ref != null && parentComponent) { 486 | setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2) 487 | } 488 | } 489 | 490 | const processText: ProcessTextOrCommentFn = (n1, n2, container, anchor) => { 491 | if (n1 == null) { 492 | hostInsert( 493 | (n2.el = hostCreateText(n2.children as string)), 494 | container, 495 | anchor, 496 | ) 497 | } else { 498 | const el = (n2.el = n1.el!) 499 | if (n2.children !== n1.children) { 500 | hostSetText(el, n2.children as string) 501 | } 502 | } 503 | } 504 | 505 | const processCommentNode: ProcessTextOrCommentFn = ( 506 | n1, 507 | n2, 508 | container, 509 | anchor, 510 | ) => { 511 | if (n1 == null) { 512 | hostInsert( 513 | (n2.el = hostCreateComment((n2.children as string) || '')), 514 | container, 515 | anchor, 516 | ) 517 | } else { 518 | // there's no support for dynamic comments 519 | n2.el = n1.el 520 | } 521 | } 522 | 523 | const mountStaticNode = ( 524 | n2: VNode, 525 | container: RendererElement, 526 | anchor: RendererNode | null, 527 | namespace: ElementNamespace, 528 | ) => { 529 | // static nodes are only present when used with compiler-dom/runtime-dom 530 | // which guarantees presence of hostInsertStaticContent. 531 | ;[n2.el, n2.anchor] = hostInsertStaticContent!( 532 | n2.children as string, 533 | container, 534 | anchor, 535 | namespace, 536 | n2.el, 537 | n2.anchor, 538 | ) 539 | } 540 | 541 | /** 542 | * Dev / HMR only 543 | */ 544 | const patchStaticNode = ( 545 | n1: VNode, 546 | n2: VNode, 547 | container: RendererElement, 548 | namespace: ElementNamespace, 549 | ) => { 550 | // static nodes are only patched during dev for HMR 551 | if (n2.children !== n1.children) { 552 | const anchor = hostNextSibling(n1.anchor!) 553 | // remove existing 554 | removeStaticNode(n1) 555 | // insert new 556 | ;[n2.el, n2.anchor] = hostInsertStaticContent!( 557 | n2.children as string, 558 | container, 559 | anchor, 560 | namespace, 561 | ) 562 | } else { 563 | n2.el = n1.el 564 | n2.anchor = n1.anchor 565 | } 566 | } 567 | 568 | const moveStaticNode = ( 569 | { el, anchor }: VNode, 570 | container: RendererElement, 571 | nextSibling: RendererNode | null, 572 | ) => { 573 | let next 574 | while (el && el !== anchor) { 575 | next = hostNextSibling(el) 576 | hostInsert(el, container, nextSibling) 577 | el = next 578 | } 579 | hostInsert(anchor!, container, nextSibling) 580 | } 581 | 582 | const removeStaticNode = ({ el, anchor }: VNode) => { 583 | let next 584 | while (el && el !== anchor) { 585 | next = hostNextSibling(el) 586 | hostRemove(el) 587 | el = next 588 | } 589 | hostRemove(anchor!) 590 | } 591 | 592 | const processElement = ( 593 | n1: VNode | null, 594 | n2: VNode, 595 | container: RendererElement, 596 | anchor: RendererNode | null, 597 | parentComponent: ComponentInternalInstance | null, 598 | parentSuspense: SuspenseBoundary | null, 599 | namespace: ElementNamespace, 600 | slotScopeIds: string[] | null, 601 | optimized: boolean, 602 | ) => { 603 | if (n2.type === 'svg') { 604 | namespace = 'svg' 605 | } else if (n2.type === 'math') { 606 | namespace = 'mathml' 607 | } 608 | 609 | if (n1 == null) { 610 | mountElement( 611 | n2, 612 | container, 613 | anchor, 614 | parentComponent, 615 | parentSuspense, 616 | namespace, 617 | slotScopeIds, 618 | optimized, 619 | ) 620 | } else { 621 | patchElement( 622 | n1, 623 | n2, 624 | parentComponent, 625 | parentSuspense, 626 | namespace, 627 | slotScopeIds, 628 | optimized, 629 | ) 630 | } 631 | } 632 | 633 | const mountElement = ( 634 | vnode: VNode, 635 | container: RendererElement, 636 | anchor: RendererNode | null, 637 | parentComponent: ComponentInternalInstance | null, 638 | parentSuspense: SuspenseBoundary | null, 639 | namespace: ElementNamespace, 640 | slotScopeIds: string[] | null, 641 | optimized: boolean, 642 | ) => { 643 | let el: RendererElement 644 | let vnodeHook: VNodeHook | undefined | null 645 | const { props, shapeFlag, transition, dirs } = vnode 646 | 647 | el = vnode.el = hostCreateElement( 648 | vnode.type as string, 649 | namespace, 650 | props && props.is, 651 | props, 652 | ) 653 | 654 | // mount children first, since some props may rely on child content 655 | // being already rendered, e.g. `