惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Azure Blog
Microsoft Azure Blog
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
V
V2EX
PCI Perspectives
PCI Perspectives
Latest news
Latest news
博客园 - 三生石上(FineUI控件)
C
CERT Recently Published Vulnerability Notes
W
WeLiveSecurity
Last Week in AI
Last Week in AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Palo Alto Networks Blog
T
The Exploit Database - CXSecurity.com
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
WordPress大学
WordPress大学
V
Vulnerabilities – Threatpost
H
Heimdal Security Blog
Attack and Defense Labs
Attack and Defense Labs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
博客园 - 叶小钗
V
Visual Studio Blog
Jina AI
Jina AI
P
Proofpoint News Feed
罗磊的独立博客
SecWiki News
SecWiki News
J
Java Code Geeks
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
L
LINUX DO - 热门话题
Security Archives - TechRepublic
Security Archives - TechRepublic
The Hacker News
The Hacker News
Hugging Face - Blog
Hugging Face - Blog
N
News and Events Feed by Topic
NISL@THU
NISL@THU
T
Tailwind CSS Blog
T
Tenable Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Recent Announcements
Recent Announcements
H
Hacker News: Front Page
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Tor Project blog
宝玉的分享
宝玉的分享
Help Net Security
Help Net Security
S
Security Affairs
Microsoft Security Blog
Microsoft Security Blog
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
G
GRAHAM CLULEY

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
One PR to a parser unlocked prerendering in Brisa
Aral Roca · 2026-04-24 · via DEV Community

I built a JavaScript framework called Brisa. The kind of framework that needs to parse every single source file your app contains; analyze imports, detect server vs. client components, inject macros, transform JSX. All of that happens at the AST level.

Before Brisa, I was already maintaining next-translate, an i18n library for Next.js. For the plugin that auto-injects locale loaders into pages, I used the TypeScript compiler API. It worked. It was also painfully slow; ts.createProgram() for every page file at build time, full type-checker instantiation, lib resolution. We had to add noResolve: true and noLib: true just to make it bearable. The parser was doing ten times more work than we needed because all we wanted was the AST, not the types.

When I started building Brisa, I knew I needed something faster. Something that gave me an ESTree-compliant AST without the overhead of a full compiler. That's how I found Meriyah.

Why I chose Meriyah over everything else

Meriyah is written entirely in JavaScript. No native bindings. No WASM loading step. No compilation step. Just parseScript(code, { jsx: true, module: true, next: true }) and you get back an ESTree AST in microseconds.

For Brisa's build pipeline, that speed difference compounds. Every source file in a Brisa project passes through Meriyah. The parser runs inside AST().parseCodeToAST(), which first transpiles via Bun's transpiler and then feeds the result to Meriyah. The output is a standard ESTree Program node that I can traverse, modify, and regenerate with astring.

But here's where it got interesting. Brisa has a feature called renderOn that lets you prerender components at build time. You write this in your page:

<SomeComponent renderOn="build" foo="bar" />

Enter fullscreen mode Exit fullscreen mode

And at build time, the AST transform detects renderOn="build", replaces the JSX with a __prerender__macro() call, and injects this import at the top of the file:

import { __prerender__macro } from 'brisa/macros' with { type: 'macro' };

Enter fullscreen mode Exit fullscreen mode

That with { type: 'macro' } is an import attribute that tells Bun's bundler to resolve the import at compile time. The component gets rendered during the build, and the result is injected as static HTML. The user writes renderOn="build", but under the hood the framework constructs ImportDeclaration and ImportAttribute AST nodes by hand and regenerates the code.

The problem: Meriyah didn't support import attributes when I started using it. So I contributed a PR to add the feature. That PR landed, and Brisa's entire prerender pipeline could work end to end.

Going from "the parser can't handle my syntax" to "I'll fix the parser itself" is the kind of thing that only happens when you deeply understand how ASTs work.

The inspiration

AST Explorer exists, and it's great. I use it regularly. It's the reference tool for exploring ASTs. I wanted to build something similar as part of Kitmul; my own version of an AST visualizer with parser selection, interactive tree view, and support for the parsers I use daily.

The AST Visualizer does exactly this. Paste JavaScript, pick your parser (Acorn, Meriyah, or SWC), and get an interactive tree or raw JSON. Everything runs locally in your browser.

The AST Visualizer showing a JavaScript function parsed with Acorn into an interactive tree view; Monaco editor on the left, collapsible AST nodes on the right, with parser and view mode selectors in the toolbar

The parser choice matters because each one produces a slightly different AST:

  • Acorn follows the ESTree spec strictly. It's the parser that ESLint uses internally. If you're writing ESLint rules, this is the tree your rule will traverse.
  • Meriyah also follows ESTree, but adds JSX support and bleeding-edge features via the next: true flag. It's the parser I chose for Brisa because it's fast, lightweight, and written in pure JS.
  • SWC is a Rust-based compiler that runs via WASM in the browser. Its AST uses a different structure; Module instead of Program, span objects instead of start/end positions. If you're working with Next.js or Turbopack internals, this is the AST you're dealing with.

Switching between parsers and seeing how the same code produces different trees is one of the fastest ways to understand parser differences. Try parsing const x = 42; with all three and compare the node types.

Three things the tree teaches you that docs don't

next-translate bundle size comparison; the i18n library for Next.js where I first dealt with AST parsing via the TypeScript compiler API

1. Expressions vs. statements are visible.

Every JavaScript developer hears "expression vs. statement" at some point. Few can articulate the difference until they see it in a tree. Consider:

x = 5;

Enter fullscreen mode Exit fullscreen mode

The AST shows an ExpressionStatement wrapping an AssignmentExpression. The expression is the x = 5 part. The statement is the semicolon-terminated wrapper that makes it a standalone line. This distinction is why if (x = 5) is legal JavaScript; the assignment is an expression, and expressions are valid inside conditions.

2. Operator precedence becomes structural.

Parse 2 + 3 * 4 and you'll see:

BinaryExpression (operator: "+")
  ├─ left: Literal (2)
  └─ right: BinaryExpression (operator: "*")
           ├─ left: Literal (3)
           └─ right: Literal (4)

Enter fullscreen mode Exit fullscreen mode

The multiplication is nested inside the addition's right operand. That's not a formatting choice; that's the parser encoding precedence into structure. The deeper node evaluates first. Parentheses change the tree structure, not some invisible priority flag.

3. Import attributes reveal how renderOn="build" works.

Parse this with Meriyah:

import { __prerender__macro } from 'brisa/macros' with { type: 'macro' };

Enter fullscreen mode Exit fullscreen mode

The ImportDeclaration node gets an attributes array containing ImportAttribute nodes. Each attribute has a key and a value, both Literal nodes. This is the import that Brisa's build pipeline injects when it finds renderOn="build" on a component. The with { type: 'macro' } tells Bun to resolve the function at compile time. Without seeing the tree, you'd never guess that with { type: 'macro' } becomes a nested array of attribute objects.

Real use cases from building frameworks

I keep hearing "ASTs are for compiler people." No. ASTs are for anyone who writes tools that operate on code. Here's where I've actually used AST knowledge:

Framework build pipelines. In Brisa, every source file is parsed to an AST, analyzed for imports, transformed (macro injection, server/client separation, i18n processing), and regenerated as code. The central function is AST('tsx').parseCodeToAST(code), which returns an ESTree Program node. Without understanding the tree, I couldn't write a single one of those transforms.

Prerender macro injection via renderOn="build". When Brisa encounters <Foo renderOn="build" />, the AST transform constructs ImportAttribute nodes by hand to inject import {__prerender__macro} from 'brisa/macros' with { type: "macro" }. There's a quirk: Meriyah uses value on Literal nodes where astring expects name. That's an actual comment in the Brisa source code: // This astring is looking for "name", but meriyah "value". You only discover that kind of thing by staring at trees.

i18n loader injection. In next-translate-plugin, the Webpack loader uses ts.createProgram() to parse each page and detect its exports. It needs to know whether the page has getStaticProps, getServerSideProps, or a default export, so it can inject the right locale loader. The TypeScript AST uses SyntaxKind enums instead of string-based types, which is a different mental model from ESTree. Seeing both trees side by side clarifies the difference instantly.

Import path resolution. Brisa resolves relative imports to absolute paths at build time. The transform walks ImportDeclaration nodes, reads the source.value string, resolves it against the file system, and replaces it. This is 30 lines of code once you understand that ImportDeclaration.source is a Literal node with a value property.

The search feature saves more time than you'd expect

The visualizer includes a search bar that filters nodes by type, name, or value. Type "Identifier" and every identifier in the tree highlights. Type "import" and you find every import-related node instantly.

This sounds trivial until you're debugging a framework transform and you need to find every ImportDeclaration in a 200-line file. Scrolling through an expanded tree is slow. Searching for ImportDeclaration and seeing exactly where they sit in the hierarchy is fast.

Comparing parsers side by side

Feature Acorn Meriyah SWC
Language JavaScript JavaScript Rust (WASM in browser)
Spec ESTree ESTree SWC AST
JSX support No Yes Yes
Import attributes No Yes Yes
Speed Fast Very fast Fast (after WASM load)
Bundle size ~120KB ~320KB ~14MB (WASM)
Used by ESLint Brisa Next.js, Turbopack

The point isn't that one parser is better. Each one has trade-offs. Acorn is the standard. Meriyah is the fast, feature-rich option. SWC is the heavyweight that handles everything but requires loading 14MB of WASM. The AST Visualizer lets you switch between all three and see the differences.

The AST Visualizer tool showing parser selection between Acorn, Meriyah, and SWC with tree and JSON view modes

Five code snippets worth exploring

Paste these into the AST Visualizer and try each parser:

1. Arrow function with implicit return:

const add = (a, b) => a + b;

Enter fullscreen mode Exit fullscreen mode

Notice how the ArrowFunctionExpression has expression: true and the body is a BinaryExpression, not a BlockStatement. That boolean flag is how tools distinguish => x from => { return x; }.

2. Import attributes (use Meriyah or SWC):

import { __prerender__macro } from 'brisa/macros' with { type: 'macro' };

Enter fullscreen mode Exit fullscreen mode

With Meriyah, the ImportDeclaration gets an attributes array with ImportAttribute nodes. With Acorn, this syntax will throw a parse error. That's exactly the kind of parser difference that matters in practice; Brisa's build pipeline depends on it.

3. Optional chaining:

const value = obj?.nested?.deep?.property;

Enter fullscreen mode Exit fullscreen mode

Each ?. creates a ChainExpression wrapping MemberExpression nodes with optional: true. The chain itself is a single node, not nested optionals.

4. Async/await:

async function fetchData() {
  const response = await fetch('/api/data');
  return response.json();
}

Enter fullscreen mode Exit fullscreen mode

The FunctionDeclaration has async: true. The await keyword creates an AwaitExpression wrapping the CallExpression. This is why you can't use await outside an async function; the parser enforces the nesting.

5. Destructuring with defaults:

const { a = 1, b: { c = 2 } = {} } = config;

Enter fullscreen mode Exit fullscreen mode

This generates a deeply nested tree. The defaults create AssignmentPattern nodes. The nested destructuring puts an ObjectPattern inside a Property value. This is the kind of structure where a tree view is worth a thousand words.

Privacy

All three parsers run entirely in your browser. Acorn and Meriyah are JavaScript libraries that execute client-side. SWC loads a WASM binary from a local file. No code is transmitted to any server. No analytics track what you paste. If you're parsing proprietary source code, nothing leaves your device.

If you're working with code that needs other kinds of analysis, the Visualizers & Logic Tools collection includes graph visualizers, truth table generators, and regex tools that pair well with AST work. For tracking your learning sessions, the Pomodoro Timer with built-in focus music is surprisingly effective for problem sets.

The actual takeaway

ASTs aren't magic. They're trees. Every piece of code you've ever written has a tree representation that a parser produces in milliseconds. The gap between "I've heard of ASTs" and "I can build a framework's compiler pipeline" is mostly about seeing enough trees that the patterns become obvious.

I went from struggling with the TypeScript compiler API in next-translate to contributing parser features to Meriyah for Brisa. The turning point wasn't reading more documentation. It was seeing enough ASTs that the node types became second nature.

The AST Visualizer won't teach you compiler theory. It'll teach you what the parser sees when it reads your code. For writing framework internals, build tools, codemods, and ESLint rules, that's the only thing that matters.


The AST Visualizer is free, private, and runs entirely in your browser. No signup, no install, no data leaves your device. Part of the Visualizers & Logic Tools collection on Kitmul.