← Back to all roasts Dec 10, 2025

VUE.JS CORE: A ROAST

Ah, Vue.js. The framework that said "React is too corporate, Angular is too Google" and decided to become the indie darling of JavaScript frameworks. 8,662 commits. 146,314 lines of TypeScript. One man's vision. 3,806 of those commits are from Evan You himself. That's 44% of all commits. This isn't a framework. It's a diary. Let me show you what I found when I dug into this "production-ready" codebase that powers millions of applications worldwide. Spoiler: It's TODOs. It's TODOs all the way down.

BIT #1: THE TODO ARCHAEOLOGY

packages/runtime-core/src/renderer.ts:1237 // TODO handle self-defined fallback if (!initialVNode.el) { git blame shows: 5ae7380b4a (Evan You 2020-09-15) // TODO handle self-defined fallback September 15th, 2020. FOUR YEARS AGO. This TODO is older than some JavaScript frameworks. It's been through COVID, two US presidents, and three Vue major versions. It's not a TODO anymore. It's a monument. packages/runtime-core/src/compat/global.ts:553 // TODO hydration render(vnode, container, namespace) git blame: 53b8127a9c (Evan You 2021-04-05) // TODO hydration Three and a half years. "TODO hydration" in a framework literally FAMOUS for its server-side rendering. It's like a bakery with a note that says "TODO: learn to make bread." packages/compiler-core/src/errors.ts:101-102 // placed here to preserve order for the current minor // TODO adjust order in 3.5 X_V_BIND_INVALID_SAME_NAME_ARGUMENT, git blame: 2b0a706dc7 (Evan You 2024-02-08) // TODO adjust order in 3.5 We're on Vue 3.5.25. THEY'RE IN 3.5. The TODO says "adjust order in 3.5." Evan literally wrote a reminder to do something in version 3.5, and then... released version 3.5. Without doing it. This is like writing "clean room before guests arrive" and then opening the door while the note is still taped to it. packages/reactivity/src/effect.ts:152 run(): T { // TODO cleanupEffect git blame: 05eb4e0fef (Evan You 2024-02-25) // TODO cleanupEffect The REACTIVITY SYSTEM has an incomplete TODO about CLEANUP. The thing that manages memory and prevents leaks... has a note that says "TODO" next to the word "cleanup." Sleep well tonight, Vue developers.

BIT #2: THE VUE 2 COMPATIBILITY LAYER

Vue 3 came out in September 2020. It's been over 5 years. Let's look at what they're STILL shipping: packages/runtime-core/src/compat/ 2,696 lines of code Files: attrsFallthrough.ts compatConfig.ts (642 lines) component.ts componentAsync.ts componentFunctional.ts componentVModel.ts customDirective.ts data.ts global.ts (665 lines) globalConfig.ts instance.ts instanceChildren.ts instanceEventEmitter.ts instanceListeners.ts props.ts renderFn.ts renderHelpers.ts 147 commits. Just for the compat layer. That's more commits than some entire npm packages have in their lifetime. packages/runtime-core/src/compat/compatConfig.ts:12-66 export enum DeprecationTypes { GLOBAL_MOUNT = 'GLOBAL_MOUNT', GLOBAL_MOUNT_CONTAINER = 'GLOBAL_MOUNT_CONTAINER', GLOBAL_EXTEND = 'GLOBAL_EXTEND', GLOBAL_PROTOTYPE = 'GLOBAL_PROTOTYPE', GLOBAL_SET = 'GLOBAL_SET', GLOBAL_DELETE = 'GLOBAL_DELETE', GLOBAL_OBSERVABLE = 'GLOBAL_OBSERVABLE', GLOBAL_PRIVATE_UTIL = 'GLOBAL_PRIVATE_UTIL', CONFIG_SILENT = 'CONFIG_SILENT', CONFIG_DEVTOOLS = 'CONFIG_DEVTOOLS', CONFIG_KEY_CODES = 'CONFIG_KEY_CODES', CONFIG_PRODUCTION_TIP = 'CONFIG_PRODUCTION_TIP', CONFIG_IGNORED_ELEMENTS = 'CONFIG_IGNORED_ELEMENTS', CONFIG_WHITESPACE = 'CONFIG_WHITESPACE', CONFIG_OPTION_MERGE_STRATS = 'CONFIG_OPTION_MERGE_STRATS', INSTANCE_SET = 'INSTANCE_SET', INSTANCE_DELETE = 'INSTANCE_DELETE', INSTANCE_DESTROY = 'INSTANCE_DESTROY', INSTANCE_EVENT_EMITTER = 'INSTANCE_EVENT_EMITTER', INSTANCE_EVENT_HOOKS = 'INSTANCE_EVENT_HOOKS', INSTANCE_CHILDREN = 'INSTANCE_CHILDREN', INSTANCE_LISTENERS = 'INSTANCE_LISTENERS', INSTANCE_SCOPED_SLOTS = 'INSTANCE_SCOPED_SLOTS', INSTANCE_ATTRS_CLASS_STYLE = 'INSTANCE_ATTRS_CLASS_STYLE', OPTIONS_DATA_FN = 'OPTIONS_DATA_FN', OPTIONS_DATA_MERGE = 'OPTIONS_DATA_MERGE', OPTIONS_BEFORE_DESTROY = 'OPTIONS_BEFORE_DESTROY', OPTIONS_DESTROYED = 'OPTIONS_DESTROYED', WATCH_ARRAY = 'WATCH_ARRAY', PROPS_DEFAULT_THIS = 'PROPS_DEFAULT_THIS', V_ON_KEYCODE_MODIFIER = 'V_ON_KEYCODE_MODIFIER', CUSTOM_DIR = 'CUSTOM_DIR', ATTR_FALSE_VALUE = 'ATTR_FALSE_VALUE', ATTR_ENUMERATED_COERCION = 'ATTR_ENUMERATED_COERCION', TRANSITION_CLASSES = 'TRANSITION_CLASSES', TRANSITION_GROUP_ROOT = 'TRANSITION_GROUP_ROOT', COMPONENT_ASYNC = 'COMPONENT_ASYNC', COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL', COMPONENT_V_MODEL = 'COMPONENT_V_MODEL', RENDER_FUNCTION = 'RENDER_FUNCTION', FILTERS = 'FILTERS', PRIVATE_APIS = 'PRIVATE_APIS', } 43 different deprecation types. FORTY-THREE ways things can be deprecated. Vue 3 didn't replace Vue 2. It's carrying Vue 2's corpse on its back like a weekend at Bernie's situation. packages/runtime-core/src/compat/global.ts:181 Vue.version = `2.6.14-compat:${__VERSION__}` Read that version string. "2.6.14-compat" Vue 3 literally identifies itself as Vue 2.6.14 in compat mode. It's a framework with an identity crisis. "I'm Vue 3!" "No wait, I'm Vue 2.6.14!" "Actually I'm both!" This is the JavaScript framework equivalent of wearing your ex's hoodie.

BIT #3: THE REVERT CHRONICLES

Let me show you the commit history. Brace yourself. e596378e0 2024-09-10 fix: Revert "fix: Revert "fix(reactivity): self-referencing computed should refresh"" Read that commit message. OUT LOUD. "fix: Revert 'fix: Revert 'fix(reactivity)'" They fixed something. Then they reverted the fix. Then they reverted the revert of the fix. That's THREE layers of contradiction for ONE feature. This isn't version control. This is a time paradox. 7e3b3bb2a 2024-09-06 fix: Revert "fix(Transition): handle KeepAlive child..." 311655352 2024-09-05 fix(Transition): handle KeepAlive child unmount... ONE DAY. They shipped a fix and reverted it ONE DAY LATER. The Transition component has been in Vue since the beginning. Years of development. And they still can't figure out how to unmount a child without breaking everything. More reverts: 11ec51aa5 2025-11-07 Revert "fix(compiler-core): correctly handle ts type assertions..." 6b68f7267 2025-09-24 Revert "fix(hmr): prevent __VUE_HMR_RUNTIME__ from being overwritten..." cf20ee6fd 2025-07-07 Revert "feat: Vapor mode (#12359)" 19f23b180 2025-05-29 Revert "fix(compiler-sfc): add scoping tag to trailing universal selector..." 42f879fca 2025-05-27 Revert "fix(compiler-sfc): add error handling for defineModel()..." 4974e5dab 2025-01-23 Revert "Revert "chore: cache buildSlots result"" 74e8bb44c 2025-01-23 Revert "chore: cache buildSlots result" Look at January 23rd, 2025. Same day. TWO reverts. They reverted something, then reverted the revert. On the SAME DAY. I've seen cleaner git histories in coding bootcamp projects. The best part? "Vapor mode" - the big new feature - was committed and then COMPLETELY REVERTED in July 2025: cf20ee6fd 2025-07-07 Revert "feat: Vapor mode (#12359)" Then re-added in December: bfe5ce309 feat: Vapor mode (#12359) They added a major feature. Deleted it entirely. Then added it again 5 months later. The same PR number. #12359. This is software development by Ctrl+Z.

BIT #4: TYPESCRIPT'S GREATEST ENEMY

Vue.js: "We rewrote everything in TypeScript for better type safety!" Also Vue.js: - 516 occurrences of any across 100+ files - 298 occurrences of as any type casts - 379 occurrences of @ts-ignore / @ts-expect-error / eslint-disable That's over 1,100 places where they gave up on type safety. packages/runtime-core/src/component.ts:238 export interface ClassComponent { new (...args: any[]): ComponentPublicInstance<any, any, any, any, any> __vccOpts: ComponentOptions } "any, any, any, any, any" Five anys in a row. This type definition has more any than a pirate movie. packages-private/dts-test/defineComponent.test-d.tsx has 115 @ts-expect-error annotations. 115. In a TYPE TEST file. The file that's supposed to TEST THE TYPES has 115 places where they're telling TypeScript to shut up. "We're testing our types!" "How's it going?" "We expect 115 errors!" packages/reactivity/src/computed.ts:127 } else if (__DEV__) { // TODO warn } The computed property - the CORE of Vue's reactivity - has a TODO that just says "warn". Warn about WHAT? We'll never know. It's been there since February 2024. 205 total @internal or @deprecated markers across 31 files. That's 205 things in the PUBLIC API that are either: - Not meant to be used ("@internal") - Shouldn't be used anymore ("@deprecated") If 205 parts of your API aren't meant to be used... maybe don't export them?

BIT #5: THE WIP GRAVEYARD

Here's a sample of commits from Evan You himself: 71262384d wip: save 761b1617a wip: save 80e6ea873 wip: save 4aaa69ae4 wip: save 41379c4d9 wip: save 799004696 wip: save 0a8322bf0 wip: save "wip: save" "wip: save" "wip: save" This man has 3,806 commits to this repo and he's using git like it's a floppy disk. "wip: save" isn't a commit message. It's a nervous tic. More WIP commits from the history: 1d87fc80e wip: hydrate vapor async component (#13976) 5e29c98e5 wip: force hydrate v-bind with .prop modifiers 4131beedc wip: force hydrate prop 12e41691c wip: hydrate vapor teleport (#13864) 87190a289 wip: use rolldown a30e97861 wip: inject block anchors in ssr vnode-based slot 065064011 wip: create anchor for DynamicFragment when necessary 6454a295d wip: special handing anchors in ssr slot vnode fallback bd5d341ff wip: process component as a single element during hydration These aren't on feature branches. These are on MAIN. Work in progress commits on the main branch of a framework used by millions. "Is Vue 3 stable?" "Well, the main branch has 30+ commits that say 'wip: save'..." Here's my favorite sequence: 4974e5dab 2025-01-23 Revert "Revert "chore: cache buildSlots result"" 74e8bb44c 2025-01-23 Revert "chore: cache buildSlots result" Same day. Same feature. Reverted, then un-reverted. The slot caching feature had an existential crisis and resolved it within 24 hours. Character development.

BIT #6: THE 30 MILLION ELEMENT TEST

packages/vue/__tests__/e2e/Transition.spec.ts:3357 setup: () => { // Big arrays kick GC earlier const test = ref([...Array(30_000_000)].map((_, i) => ({ i }))) // TODO: Use a different TypeScript env for testing // @ts-expect-error - Custom property and same lib as runtime is used window.__REF__ = new WeakRef(test) return { test } }, THIRTY. MILLION. ELEMENTS. To test a transition component, they create an array with 30 million objects. Each object has one property. That's 30 million objects, each saying { i: 0 }, { i: 1 }, { i: 2 }... all the way to { i: 29999999 }. The comment says "Big arrays kick GC earlier." You know what else kicks GC earlier? SMALLER ARRAYS. Maybe 1,000? Maybe 10,000? But no. THIRTY MILLION. And they do it TWICE: packages/vue/__tests__/e2e/Transition.spec.ts:3412 setup: () => { // Big arrays kick GC earlier const test = ref([...Array(30_000_000)].map((_, i) => ({ i }))) // TODO: Use a different TypeScript env for testing // @ts-expect-error - Custom property and same lib as runtime is used window.__REF__ = new WeakRef(test) Same test. Same 30 million elements. Same TODO about using a different TypeScript environment. This file alone is 3,463 lines. It's longer than some entire applications. The Transition component tests file has: - 6 @ts-expect-error annotations - Tests that allocate 60 million objects total - A comment that says "TODO: Use a different TypeScript env" The entire test suite for Vue's transition system is held together by suppressed errors and brute-force memory allocation.

BIT #7: THE TYPO TIMELINE

Commits dedicated entirely to fixing typos: 1de93d819 2025-11-04 daiwei - chore: fix typo 2dbe30177 2025-10-09 abeer0 - chore: fix typo (#13973) c16f8a94c 2025-10-04 wangergou - chore: fix typo. (#13948) 63279661e 2025-09-01 wangergou - chore: fix typo (#13833) cde15b07b 2025-08-21 wuyangfan - chore: fix typo 5ab938db5 2025-07-15 daiwei - chore: fix typo 762fae4b5 2025-06-04 yamabuki - fix(types): typo of `vOnce` and `vSlot` d9bd436b1 2025-05-27 Arpit Jain - chore: fix typos f556c925a 2025-05-07 edison - chore: fix typo (#13290) c97cc4cdb 2025-03-29 Leedom - chore: fix typo (#13117) 11c053a54 2024-12-16 LiquidAss... - fix(typos): fix comments referencing... 7dbab278d 2024-08-20 Tycho - chore: fix typo (#11663) 139548e0e 2024-08-07 sq800 - chore: fix typo (#11535) 917c0631c 2024-08-06 edison - chore: fix typo (#11522) 00341e8d6 2024-06-24 Tycho - chore: fix typo (#11195) 5a382b7a1 2024-06-17 Snoppy - chore: fix typo [skip ci] (#11154) 70773d009 2024-06-11 Micha Huhn - docs: fix typo (#11105) 3a0b463a2 2024-05-30 Haoqun Jiang - chore: fix typo (DistrubuteRef -> DistributeRef) a653a8c12 2024-04-01 Wes Cook - chore: fix typo (#10621) 7866070bd 2024-01-13 Carlos R. - chore: fix typo 20+ commits just fixing typos. These aren't documentation typos. These are CODE typos. In a TypeScript codebase. With a compiler. "DistrubuteRef -> DistributeRef" They misspelled DISTRIBUTE. In a type name. And it shipped. These typos made it through: - Code review - CI/CD pipeline - TypeScript compilation - MULTIPLE releases And nobody noticed until someone went "wait... that's not how you spell distribute." This is what happens when you have 3,806 commits from one person. Eventually you stop reading the code reviews because it's all from the same guy. My personal favorite: Someone with the handle "LiquidAssContainer" fixed typos in December 2024. I have no further comment. The username speaks for itself.

BIT #8: THE 1,289 LINE COMPONENT FILE

packages/runtime-core/src/component.ts - 1,289 lines This single file contains: - 57 @internal markers - 28 uses of any - The entire component initialization system - About 40 different type definitions - The setup context - The render context - The public instance proxy - Multiple compatibility layers It's not a file. It's a novella. And it's not even the worst one: packages/vue/__tests__/e2e/Transition.spec.ts 3,463 lines packages/compiler-core/__tests__/parse.spec.ts 3,461 lines packages/runtime-core/__tests__/hydration.spec.ts 2,541 lines packages/runtime-core/src/renderer.ts 2,579 lines packages/compiler-sfc/src/script/resolveType.ts 2,075 lines packages/runtime-core/__tests__/apiWatch.spec.ts 2,064 lines The test file for parsing is 3,461 lines. The thing it's testing - the parser itself - is only 1,079 lines. The tests are THREE TIMES longer than the code. The renderer.ts file - the CORE of Vue's rendering system - is 2,579 lines. That's one file. Responsible for: - Creating virtual DOM - Patching the real DOM - Handling lifecycle hooks - Managing component instances - Suspense boundaries - Teleports - Transitions - And about 50 other things When one file does everything, no file does anything well.

BIT #9: THE VAPOR MODE SAGA

Vapor Mode. Vue's answer to Solid.js. The next generation of reactivity. Let's look at its commit history: 2696f14e1 2025-03-12 wip(vapor): fix insertion for vdom interop e5e4d295b 2025-03-12 wip(vapor): test cases 2a76b52d7 2025-03-11 wip(vapor): fix children gen for dynamic with anchor f6d7b9019 2025-03-11 wip(vapor): corresponding runtime behavior for if/for 972257474 2025-03-11 wip(vapor): adjust children and block generation e3a33e609 2025-03-10 wip(vapor): component hydration a2415de7b 2025-03-09 wip(vapor): text hydration tests e9d912a18 2025-03-07 wip(vapor): more hydration 64270ae1b 2025-02-12 wip(vapor): basic hydration c6fe9f941 2025-02-12 wip(vapor): new impl + test for vapor custom directive a2fa0db99 2025-02-11 wip(vapor): improve node traversal codegen 11383ae72 2025-02-09 wip(vapor): fix v-for update check 69422d50d 2025-02-08 wip(vapor): optimize event handling 8549a243a 2025-02-08 wip(vapor): custom directives 23939d09c 2025-02-07 wip(vapor): vapor slots in vdom bcd2eb7fd 2025-02-07 wip(vapor): fix component unmount 99d70ddd3 2025-02-06 wip(vapor): vdom slots in vapor component a770a83de 2025-02-05 wip(vapor): support vapor component as root be5c2a2f5 2025-02-04 wip: vdom in vapor hmr reload 19 "wip(vapor)" commits in 5 weeks. All of them work-in-progress. The future of Vue is being developed through a series of incomplete commits that all admit they're not finished. July 7th, 2025: cf20ee6fd Revert "feat: Vapor mode (#12359)" They REVERTED the entire Vapor Mode feature. Gone. Deleted. 12,359 PRs of work, undone with one commit. Then, December 2025: bfe5ce309 feat: Vapor mode (#12359) Same PR number. They re-added it. The exact same feature they deleted 5 months earlier. Vapor Mode has died and been resurrected more times than a soap opera character. Current state of Vapor Mode commits: 2a76b52d7 wip(vapor): fix children gen for dynamic with anchor insertion "fix children gen for dynamic with anchor insertion" I've read this commit message 10 times. I still don't know what it means. But I DO know it starts with "wip" and it's in production.

THE FINAL VERDICT

Vue.js Core. 8,662 commits of history. 146,314 lines of TypeScript. 3,806 commits from one person (44%). What did we find? - TODOs from 2020 still waiting to be done - A "TODO adjust order in 3.5" that wasn't adjusted... in version 3.5 - 2,696 lines of Vue 2 compatibility code, 4 years after Vue 3 released - A version string that says "2.6.14-compat" because Vue 3 can't let go - "fix: Revert 'fix: Revert 'fix'" in the commit history - 516 uses of any type (in a TypeScript codebase) - 298 as any casts (giving up on types) - 379 eslint-disable/@ts-ignore annotations (giving up entirely) - Test files that allocate 60 million objects - "wip: save" commits on the main branch - 20+ commits just fixing typos that made it through review - A 3,463 line test file - Vapor Mode: added, deleted, re-added The bottom line: Vue.js is a framework built by one extremely talented developer who has been coding at the speed of thought for 6+ years while the rest of the team tries to keep up. It's a monument to what one person can accomplish. It's also a warning about what happens when one person accomplishes too much. Every "TODO" is a promise Evan made to himself that he forgot. Every "wip: save" is a moment he was coding faster than he could think. Every as any is a place where TypeScript asked a question and he said "not now." And you know what? It still works. Millions of apps. Billions of users. Running on a codebase with 4-year-old TODOs and commit messages that say "wip: save." That's not a roast. That's respect wrapped in horror.