Skip to content

Instantly share code, notes, and snippets.

View johnsoncodehk's full-sized avatar

Johnson Chu johnsoncodehk

View GitHub Profile
@johnsoncodehk
johnsoncodehk / CLAUDE.shareable.md
Last active June 9, 2026 15:44
Working principles for an AI coding agent (Claude Code CLAUDE.md) — decide by correctness not ROI; fix bugs at the architectural root, not the symptom

To adopt: copy the block below into your ~/.claude/CLAUDE.md.

## How to decide what to do

Judge every piece of work by whether it **should** be done — is it correct, is the current state wrong or inconsistent, does it serve the goal — and **never by ROI, cost, effort, or "is it worth it."** Do not label a known-wrong thing "low-value," "marginal," "an edge case," or "not worth it" to justify leaving it unfixed; reasoning by ROI is exactly what keeps work mediocre. ("The reference / competitor also gets it wrong" is a *gap* argument, not a correctness one — it never makes a wrong thing acceptable.)

The only valid reason to stop short of doing the right thing is that it **provably cannot** be done — a demonstrated limit of the model or tools, not an assumed or cost-based one. "Hard," "heavy," "expensive," or "a lot of work" is never a reason to stop; "proven impossible / blocked" is. When unsure which it is, find out — try it, measure it, prove it — before deciding, and never declare a limi
@johnsoncodehk
johnsoncodehk / tsslint_3.1_eslint_rules_at_16x.md
Last active May 1, 2026 03:24
TSSLint 3.1: running ESLint rules at 16× speed

Running ESLint rules at 16× speed in TSSLint

On Dify web/ (5860 .ts / .tsx files), running one type-aware rule:

Wall (min of 3) Peak RSS
ESLint Linter 25 s 7.0 GB
TSSLint 3.1 1.5 s 3.75 GB

Native linters (Rust's Oxlint and the like) hitting 10× is normal — that's the cross-runtime gap. JS-vs-JS usually lands at 1.5-3×. This 16× is the same V8, the same ESLint rule source, the same ESTree shape — the gap is all architecture. 3.1 is a full rewrite of @tsslint/compat-eslint (the compatibility layer that lets stock ESLint rules run inside TSSLint). Bench: tsslint-dify-bench.

These are some notes on the performance work that went into alien-signals. I'm sharing them not as a definitive guide, but as a log of a few key discoveries. The hope is that some of these findings might be useful to others tackling similar problems in high-performance JavaScript.

The Origin: Push-Pull-Push

My journey into the depths of reactivity performance began with Vue. I was trying to solve a specific problem in Vue 3.4: even if a computed's value didn't change, it would still trigger downstream computations and effects. This seemed inefficient. My attempt to fix this resulted in a pull request (vuejs/core#5912) that, after a year of discussions, was eventually merged. This PR introduced the Push-Pull-Push model to Vue 3.4, a model also adopted by libraries like reactivity.

For a time, I thought this was near-perfect. Then I saw the plans for Vue 3.5, which adopted a doubly-linked list but also moved to a pure pull-based model. I was still convinced

import type config = require('@tsslint/config');
import path = require('node:path');
export function create(): config.Rule {
return ({ typescript: ts, sourceFile, reportError, languageServiceHost }) => {
const { noEmit } = languageServiceHost.getCompilationSettings();
if (noEmit) {
return;
}
import { createReactiveSystem, Dependency, Link, Subscriber, SubscriberFlags } from 'alien-signals/esm';
import { ReactiveFramework } from "../util/reactiveFramework";
let toCleanup: (() => void)[] = [];
export const alienSignalsStaticDeps: ReactiveFramework = {
type: "pure",
name: "alien-signals (static deps)",
signal: (initial) => {
const data = signal(initial);

Volar 2.0: The Rewrite

I'm Johnson, the author of Volar (now Vue Language Tools). We released 2.0 in March 2024. This is a look back at what we changed, why, and what it cost.

Why we needed to rewrite

Vetur and Volar v1 implemented Vue's IDE support through the Language Server Protocol. For small and medium projects this worked fine. For larger projects it broke down, and the reason was always the same: memory.

TypeScript Server and the Vue Language Server each kept their own copy of the project's TS AST — every file, every .d.ts in node_modules. When a project pulled in thousands of .d.ts files (common for anything with a big dependency tree), the two processes together could exhaust available memory.

import type { SFCParseResult, VueLanguagePlugin } from '../types';
import { parse } from '../utils/parseSfc';
const jsxWrapper = ['<script setup lang="jsx">\n', '\n</script>'];
const tsxWrapper = ['<script setup lang="tsx">\n', '\n</script>'];
const plugin: VueLanguagePlugin = _ctx => {
return {
import type { Rule } from '@tsslint/config';
export function create(): Rule {
return ({ typescript: ts, sourceFile, reportWarning }) => {
ts.forEachChild(sourceFile, function cb(node) {
if (
ts.isCallExpression(node) &&
ts.isIdentifier(node.expression) &&
node.expression.text === 'alert'
) {
import type { Rule } from '@tsslint/config';
export function create(): Rule {
return ({ typescript: ts, sourceFile, languageService, reportWarning }) => {
ts.forEachChild(sourceFile, function walk(node) {
if (ts.isNonNullExpression(node)) {
const typeChecker = languageService.getProgram()!.getTypeChecker();
const type = typeChecker.getTypeAtLocation(node.expression);
if (
typeChecker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
/**
*
* @type {import('@tsslint/config').Rule}
*/
const noConsoleRule = ({ typescript: ts, sourceFile, reportWarning }) => {
ts.forEachChild(sourceFile, function walk(node) {
if (
ts.isPropertyAccessExpression(node) &&
ts.isIdentifier(node.expression) &&
node.expression.text === 'console'