core-ast-ts
v0.3.0
Published
Shared AST library: parse, visit, query, and generate TS/JS code — pure TypeScript, singleton cache, no native binary
Downloads
155
Maintainers
Readme
core-ast-ts
Shared AST library: parse, visit, query, and generate TS/JS code — pure TypeScript, singleton cache, no native binary.
Plugin system: extend core-ast-ts to parse any file format (Vue SFC, Svelte, Astro, etc.) through a unified cache. Plugins are optional — the core stays lightweight. If a plugin is missing, a clear error tells you exactly what to install.
Install
npm install core-ast-tsCore Design: Plugin Architecture
core-ast-ts uses a plugin system to support file formats beyond JS/TS. The core package only handles .js/.jsx/.ts/.tsx — all other formats are handled by plugins that you register explicitly.
Why plugins?
- Small core:
core-ast-tsdoesn't bundle@vue/compiler-sfc,svelte/compiler, etc. Each format plugin is a separate package you install only when needed. - Unified cache: All formats share the same singleton cache. Parse once, use everywhere.
- Clear errors: If you try to parse a
.vuefile without the vue3 plugin, you get an exact install instruction — not a cryptic parse error.
Usage
import { coreAst } from 'core-ast-ts'
import vue3 from 'core-ast-ts-plugin-vue3'
// Create an AST instance with plugins
const ast = coreAst({ plugins: [vue3()] })
// Parse any file — JS/TS handled by core, .vue handled by plugin
const { ast: program, meta } = ast.get('App.vue', source)
// meta.sfcDescriptor — full Vue SFC descriptor
// meta.componentName — derived from filename
// meta.isSetup — whether <script setup> is used
// JS/TS files work without any plugins
const { ast: jsProgram } = ast.get('app.tsx', source)
// HMR: invalidate on file change
ast.invalidate('App.vue')Plugin interface
Every plugin implements CoreAstPlugin:
interface CoreAstPlugin {
/** Unique name, e.g. 'vue3' */
name: string
/** File extensions this plugin handles, e.g. ['.vue'] */
extensions: string[]
/** Parse source into a ParsedModule with ESTree AST */
parse(source: string, filePath: string, config?: ParseConfig): ParsedModule
}A plugin's parse() must return an ESTree-compatible Program node. Format-specific data (e.g. Vue SFC descriptor) is attached to meta on the returned ParsedModule.
Missing plugin error
If you try to parse a .vue file without the vue3 plugin registered:
[core-ast-ts] Cannot parse "App.vue" — no plugin for ".vue" extension.
Install and register the "core-ast-ts-plugin-vue3" plugin:
npm install core-ast-ts-plugin-vue3
import { coreAst } from 'core-ast-ts'
import vue3 from 'core-ast-ts-plugin-vue3'
const ast = coreAst({ plugins: [vue3()] })Available plugins
| Plugin | Package | Formats |
|--------|---------|---------|
| Vue 3 SFC | core-ast-ts-plugin-vue3 | .vue |
| Svelte 5 | core-ast-ts-plugin-svelte | .svelte |
| Astro | core-ast-ts-plugin-astro | .astro (async — use getAsync()) |
Async plugins
Some compilers (e.g. @astrojs/compiler) have async parse APIs. Use AsyncCoreAstPlugin and ast.getAsync():
import type { AsyncCoreAstPlugin } from 'core-ast-ts'
import astro from 'core-ast-ts-plugin-astro'
const ast = coreAst({ plugins: [astro()] })
// Must use getAsync() for async plugins
const { ast: program, meta } = await ast.getAsync('index.astro', source)If you call ast.get() on an async plugin, a clear error is thrown:
[core-ast-ts] Plugin "astro" has async parse(). Use ast.getAsync() instead of ast.get().Writing a custom plugin
import type { CoreAstPlugin } from 'core-ast-ts'
import { parse as parseJsTs } from 'core-ast-ts'
const myPlugin: CoreAstPlugin = {
name: 'custom',
extensions: ['.custom'],
parse(source, filePath, config) {
// Convert your format to JS, then parse with core
const jsSource = transformCustomToJs(source)
const parsed = parseJsTs(jsSource, config)
return {
ast: parsed.ast,
source,
meta: { originalFormat: 'custom' },
}
},
}
const ast = coreAst({ plugins: [myPlugin] })Ecosystem
These FarmFE plugins use core-ast-ts internally:
| Plugin | Package | Uses |
|--------|---------|------|
| Compile-time tricks | farm-plugin-tricks | Singleton API (get, invalidate, visitWithAncestors) |
| Vue 3 page router | farm-plugin-vue3-pagerouter | Plugin system (coreAst() + core-ast-ts-plugin-vue3) |
| Selective compilation | farm-plugin-selective | Singleton API + magic-string |
| Compile-time stdlib | farm-plugin-comptime-stdlib | Singleton API + magic-string |
| Static optimization | farm-plugin-static-opt | Singleton API + magic-string |
| Negotiated obfuscation | farm-plugin-obfuscate | Singleton API + javascript-obfuscator |
| Function overloading | farm-plugin-overload | Singleton API + magic-string |
| Functional Vue components | farm-plugin-vue-reaction | Plugin system + core-ast-ts-plugin-vue3 |
Legacy API (still supported)
The original singleton cache API still works for JS/TS files:
import { get, invalidate } from 'core-ast-ts'
const { ast } = get('app.tsx', source)
invalidate('app.tsx')For multi-format support, use the coreAst() API instead.
Full API Reference
Cache (singleton — JS/TS only)
import { get, getCached, invalidate, invalidateAll } from 'core-ast-ts'
const { ast } = get('app.tsx', source) // parse + cache
const cached = getCached('app.tsx') // cached only
invalidate('app.tsx') // HMR
invalidateAll() // clear allCoreAst (plugin-aware cache)
import { coreAst } from 'core-ast-ts'
import vue3 from 'core-ast-ts-plugin-vue3'
const ast = coreAst({ plugins: [vue3()] })
ast.get('App.vue', source) // parse + cache (any format)
ast.getCached('App.vue') // cached only
ast.invalidate('App.vue') // HMR
ast.invalidateAll() // clear all
ast.registerPlugin(plugin) // add plugin later
ast.hasPlugin('vue3') // check if registered
ast.pluginNames() // list allParse
import { parse } from 'core-ast-ts'
const { ast, source } = parse('export function hello(name: string) { return `Hello, ${name}!` }', {
jsx: true,
sourceType: 'module',
})Visit
import { collectImports, collectExports, visitWithAncestors } from 'core-ast-ts'
const imports = collectImports(ast)
const exports = collectExports(ast)
// Walk with ancestor chain
visitWithAncestors(ast, {
CallExpression(node, ancestors) {
// node + full ancestor chain
},
})Query
import { ModuleQuery } from 'core-ast-ts'
const query = new ModuleQuery(ast)
query.findFunctions() // all function declarations
query.findComponents() // PascalCase functions (React components)
query.findCalls('compileTime') // call sites of a function
query.importsFrom('react') // check if module imports from 'react'Transform
import { createTransformer, replaceNode, removeNode, prependPureMarker } from 'core-ast-ts'
const { magic, result } = createTransformer(source, 'app.tsx')
prependPureMarker(magic, functionNode.start)
const { content, sourceMap, changed } = result()Codegen
import { generateCode } from 'core-ast-ts'
const output = generateCode(ast, { compact: true })License
MIT
