npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pyreon/native-compiler

v0.53.0

Published

Pyreon Multi-Target Compiler (PMTC): compiles Pyreon JSX to native Swift (SwiftUI) and Kotlin (Jetpack Compose).

Readme

@pyreon/native-compiler

EXPERIMENTAL. The Pyreon Multi-Target Compiler (PMTC) itself. Compiles Pyreon JSX/TSX source to native Swift (SwiftUI) and Kotlin (Jetpack Compose) source — no JS runtime, no bridge, no WebView by default. The output is idiomatic per-platform code driving each platform's own reactive primitives (@State / mutableStateOf) directly. Published to npm — see Native Packages for where this fits among the other five packages behind PMTC.

This package is TypeScript-in, TypeScript-out plumbing: given a source string, it returns a source string. It doesn't walk a directory, write files, or know about Xcode/Gradle — that's @pyreon/native-cli, which wraps this package.

Usage

import { transform } from '@pyreon/native-compiler'

const { code: swiftSource, warnings } = transform(pyreonSource, { target: 'swift' })
const { code: kotlinSource } = transform(pyreonSource, {
  target: 'kotlin',
  filename: 'Counter.tsx',
})

transform(source, options) returns { code: string, warnings: string[] }. options.target is 'swift' | 'kotlin'; options.filename (optional) improves parse-error diagnostics; options.fonts (optional, Record<canonicalName, iOSPostScriptName>) resolves <Text font="Brand"> to .font(.custom(…)) on iOS.

Compiler plugins (experimental API v2)

The shared frontend parses once. A compiler instance then runs every plugin's transformIR, computes the module namespace, runs prepareIR (the built-in chart runtime first), and dispatches the registered backend. Each phase follows plugin registration order. Swift/Kotlin remain built in; transform() uses a fixed default instance with no user plugins.

import { createCompiler, type CompilerPlugin } from '@pyreon/native-compiler'

export const releaseBadge = {
  name: 'release-badge',
  apiVersion: 2,
  transformIR(module) {
    for (const component of module.components) {
      const expr = component.returnExpr
      if (expr.kind === 'jsx-element' && expr.tag === 'ReleaseBadge') {
        component.returnExpr = {
          kind: 'jsx-element',
          tag: 'Text',
          attrs: [],
          children: [{ kind: 'text', value: 'Ready for release' }],
        }
      }
    }
  },
} satisfies CompilerPlugin<never>

const compiler = createCompiler({ plugins: [releaseBadge] })
const result = compiler.transform('export function Badge() { return <ReleaseBadge /> }', {
  target: 'swift',
  filename: 'Badge.tsx',
})

CompilerModule is the typed shared IR. Extensions operate within the existing frontend subset; new TypeScript syntax still requires frontend support. Passes may mutate their input or return a complete replacement. context.source and frozen context.options describe the invocation; context.warn(message) adds an attributed diagnostic. The filename, target and font mapping come from one invocation snapshot, even if a callback changes caller-owned options through a closure. Passes and backend emit(module, context) are synchronous. Load asynchronous resources before createCompiler(). Returning a promise is an error.

Use transformIR for source semantics and prepareIR for external/runtime metadata. Parsed module.imports contains actual import declarations, including side-effect and type imports. Runtime declarations added during preparation do not change synthesized module names. To add a target, put { target: 'your-target', emit(module, context) { return { code, warnings } } } in a plugin's backends. swiftBackend and kotlinBackend are exported for explicit delegation. Distinct target names are required: overriding built-in backends or registering duplicate plugin names/targets is rejected. Unknown targets fail before parsing. compiler.targets lists the frozen registrations.

Registrations and callback references are snapshotted when an instance is created. Custom callbacks receive isolated IR; later passes and emitters cannot mutate plugin-owned cached modules or shared chart declarations. This copying has a cost only when custom callbacks run. Prefer stateless passes; an instance does not reset mutable state captured inside a plugin's own closure. No plugin registry grows across calls. Existing emitter internals still contain scoped state: callbacks run outside emission, and this API does not promise concurrent or recursive emission through the legacy backends.

The plugin protocol and IR are experimental. Pin apiVersion: 2 in each plugin descriptor (or import the immutable NATIVE_COMPILER_PLUGIN_API_V2). Incompatible protocol/IR changes require a new version; additive optional hooks do not. NATIVE_COMPILER_PLUGIN_API_VERSION is deprecated and permanently remains 1, so an old plugin importing that runtime constant cannot silently claim API 2. API 1 plugins fail at load time with the required and supported versions. Update the compiler and first-party package plugins together to the next minor release. Third-party plugins must review the IR migration below before opting into API 2. Test extensions by compiling both target outputs with the real validators below. The CLI's --plugin accepts local ESM modules with a default plugin export; CLI target selection remains iOS/Android.

Services, modules, requires

These optional fields extend the plugin protocol:

  • services — plain service hooks the plugin lowers, keyed by hook name. Each value is a ServiceSpec (a ServiceDescriptor without its hook: a swift initialiser and kotlin lines). createCompiler builds one registry from every plugin's services — @pyreon/hooks' table arrives through the discovered @pyreon/hooks/native-plugin like every other library's lowering (the compiler ships no table and no built-in plugin); two owners for one hook is a load-time error naming both, because silently picking one would make the emit depend on plugin order. The registry is compiler.services and context.services, and it is the one the parser and both emitters read, so a plugin's hook is lowered end to end (on both targets) when it is imported from @pyreon/* or from one of the plugin's modules.
  • elements — JSX element lowerings ({ module, tags, retag?, emit?: { swift?, kotlin? }, styleBase? }; emit.kotlin receives the EmitContext facade and emit.swift a SwiftEmitContext, which adds only the members with no Compose analogue: handlerName, markColorSchemeUsed() and the hostState slot. The string / layout / action / const / read-only colour-scope members (stringAttr, layoutModifiers, action, constExpr, colorScope<T>(), withColorScope(el, emit)) are on the shared EmitContext). A target with no function falls through to the core, so a plugin may lower one target at a time. A (module, tag) pair claimed by two owners is a load-time error naming both. No lowering is built in: @pyreon/elements, @pyreon/coolgrid, @pyreon/charts and the rest ship their plugin in their own package (<pkg>/native-plugin, found through pyreon.native.plugin) and pyreon-native loads it when a source file imports the package.
  • calls + decls — code-shaped lowering: calls maps a hook/function name to a recognizer (call, ctx: ParseContext) => { type, payload? } | undefined (undefined declines and the parser falls through as if the plugin were absent); decls maps that type to a DeclEmitter { swift(decl, ctx), kotlin(decl, ctx) } rendering the plugin's open ext declaration through the EmitContext facade. payload must be JSON (the IR is cloned between passes). A name is claimed under the same rule as a service hook, and two owners for one name — or a name that is also a service — is a load-time error naming both. createChartHandle() is lowered this way by the @pyreon/charts plugin.
  • memberCalls — call EXPRESSIONS: keyed by method name, { swift(call, ctx), kotlin(call, ctx) } => string | undefined lowers <receiver>.<method>(…) when <receiver> is a binding one of the plugin's own decls created (so two plugins may both claim dispatch: the receiver decides). undefined declines. Requires decls. chart.dispatch({...}) is a @pyreon/charts memberCalls entry. Inside a lowering, ctx.expr(e) / ctx.exprAs(type, e) emit a sub-expression, ctx.decls(plugin, type) reads the component's declarations, ctx.state(key, init) is per-component memory, and ctx.deferred(key, fallback?) / ctx.resolveDeferred(key, value) substitute a value that is only known after the component body has emitted (an unresolved token without a fallback is an error naming the key).
  • unlowered — per-module metadata for the "has NO native lowering" warning: { [module]: { advice, supported?: string[] } }. Merged into compiler.registries.unlowered; a module supplied by two plugins is a load-time error, and a plugin's entry wins over the compiler's own hand-maintained one. The @pyreon/charts entry is supplied by the charts plugin.
  • scopes — compile-time colour-scope providers: [{ module, tags, enter(el, { warn, outer }), transparent? }]. <PyreonUI mode> / <ColorModeProvider mode> (and a plugin's own scope-only element, transparent: true) open a scope around their children; enter returns the value a plugin's elements read back with ctx.colorScope<T>(). The core owns the plumbing; the value is the plugin's. One owner per (module, tag).
  • stubs — { swift?(source), kotlin?(source) }: extra type-gate stub text this plugin's emit needs, passed by the caller of the compile gates as ValidateOptions.augment (validateSwiftWithStubs(src, { augment: [plugin.stubs] })). The compiler never reads it.
  • runtimeTypes — type names the plugin's runtime declares (['TooltipContent']), so a helper typed against one resolves instead of warning "can't be resolved". Merged into compiler.registries.runtimeTypes; one name declared by two plugins is a load-time error naming both. Declaring the type in the emitted file stays the plugin's prepareIR.
  • refineParse — ({ components, helperFns }) => void: an IR edit that must land DURING parse, before helper return types are inferred over their parameters (the charts plugin widens a formatter helper's number parameter to Double this way). It mutates the live, call-owned IR; anything that does not need that ordering belongs in transformIR.
  • requires — plugin names that must be loaded. A missing one or a cycle is a load-time error naming the plugins; passes run in requires order (input order otherwise).
  • modules — import specifiers the plugin serves. A hook in services is also claimed when imported from one of them (exact or name/ prefix). A package-owned plugin is activated from the pyreon.native.modules manifest field, because deciding whether to load a module by asking the module defeats the laziness.
  • builtIn — marks a compiler-shipped plugin. createCompiler({ discovered }) lets a discovered plugin with the same name replace it silently; an explicit plugins entry of the same name is still a duplicate error and wins over a discovered one. No plugin ships built in today (BUILT_IN_PLUGINS is empty); the mechanism is kept for a future compiler-shipped plugin.
  • Later additive members (each documented in .agents/guides/multiplatform/README.md, "The plugin protocol"): declCalls / declConstructs (calls / constructions recognized by callee SHAPE, with { signal } / { computed } verdicts), tier2Calls (the standing Tier-2 diagnostic for a call the package does not lower, without claiming its name), receivers, functions, identifiers, memberReads, intrinsics, refModifiers, prepareEmit, propsTypes, runtimeTypes, refineParse, scanModule, requestSources, topLevel + items (slots bindings / models / declarations / data), methodCalls + exprs, callExprs, refineStructs, finishModule, rewriteElement, destructureCalls, componentOnlyCalls, persistence and stubs. DeclEmitter.typing types both the call read (callRead) and the property read (member) of a declaration's container.

SUPPORTED_PLUGIN_API_VERSIONS lists every version this compiler loads.

Registries are instance-owned: each createCompiler builds its own (compiler.registries), so two compilers in one process never leak services or element lowerings into each other. While transform runs they are installed in a scoped slot the parser and emitters read, restored when the call ends even on a throw. parsePyreon(source, filename, { registries }) takes them explicitly.

Testing and verifying a plugin

@pyreon/native-compiler/testing exports testNativePlugin(plugin, source, { target, requireNoWarnings }), which compiles a snippet with only that plugin installed. verifyServiceTypes(plugin, { swiftSources, kotlinSources }) reads each service's leading Swift type and Kotlin remember { Type(…) } / rememberPyreonX(…) name and checks it is declared (class|struct|actor|enum, class|object|fun) in the sources the plugin ships. That closes the phantom-capability class: a type that exists only in a validation stub passes every compile gate and fails the device build. It proves a name exists, not that a signature matches — keep the real-toolchain compile for that.

Compile-validation

Snapshot tests prove "the emit equals what it equalled last time," not "the emit is valid Swift/Kotlin." src/validate.ts closes that gap by piping emitted source through the real language toolchains, at increasing cost/fidelity:

| Function | What it checks | Requires | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | | validateSwift(source) | swiftc -parse — syntax only. Accepts unresolved type references (no SwiftUI stdlib at parse time). | swiftc on PATH | | validateSwiftWithStubs(source) | Real typecheck against hand-written stubs mirroring the SwiftUI/PyreonRuntime surface — the Linux-viable path, since a consumer that generates Pyreon source (the scaffolder, most of all) needs proof the output COMPILES without needing a real Apple SDK. | swiftc on PATH | | validateSwiftTypecheck(source) | Full typecheck against the real Apple SDK. | macOS + Xcode | | validateKotlin(source) | kotlinc against a small hand-written Compose/kotlinx-serialization stub set (no real Jetpack Compose, no Gradle, no Android SDK). | kotlinc on PATH |

Also exported: isSwiftcAvailable(), isSwiftUIAvailable(), isKotlincAvailable() — toolchain-presence checks the CLI's check --typecheck and the test suite use to skip gracefully rather than fail when a toolchain isn't installed.

Project audit — @pyreon/native-compiler/audit

A separate, lightweight entry (it needs only oxc-parser and the web-only package map, not the Swift/Kotlin emitters) holding the multiplatform project audit: auditNative(cwd) scans .tsx files that import @pyreon/primitives for web-only-package-import and native-unsupported-decl hazards, and detectNativePatterns(code, filename?) is the per-snippet form. It backs pyreon doctor --check-native and the MCP validate tool, both of which load it lazily and skip with an install hint when this package is not installed. It reads the SAME WEB_ONLY_PACKAGES set as the parser's import warning (src/web-only-packages.ts, generated from the manifests by scripts/check-multiplatform-tier.ts), so the set exists exactly once. A file oxc cannot parse is skipped, as the compiler would refuse it.

Scope

The subset of TypeScript/JSX this compiler lowers — components, signal/computed/effect, <For>/<Show>, hooks, @pyreon/store/form/query/table/flow/…, HTTP + fetch, WebView bridging, and what it explicitly refuses — is documented in full at PMTC Supported TypeScript, not duplicated here. Multi-Platform (PMTC) covers the architecture and the primitive vocabulary end to end.

useCounter and useToggle lower to component-local state. Components may reuse the same local hook name: each keeps its own hook kind, bounds and reset value, regardless of declaration order or earlier compiler calls. The compiler releases this metadata after each component.

Build / test locally

Pure TypeScript — bun run test runs the compiler's own suite (parse/emit fixtures, native-equivalence checks, the differential fuzzer). The validate.ts tests additionally spawn swiftc/kotlinc when present and skip gracefully otherwise (PYREON_REQUIRE_NATIVE_VALIDATE=1 turns an absent toolchain into a hard failure instead, for CI environments where it's expected to exist).

What to read next

Writing a plugin: @pyreon/native-compiler/plugin-api

A plugin shipped by a library imports from @pyreon/native-compiler/plugin-api and nothing else in this package: the IR types, the EmitContext / ParseContext facades and the plugin protocol types (type-only), NATIVE_COMPILER_PLUGIN_API_V2, forEachExpr, substituteIdentifier, and the pure spelling helpers swiftStr / kotlinStr / swiftIdent / kotlinIdent. Declare the compiler as an optional peer + a dev dependency, point package.json → pyreon.native.plugin at the built entry (default export = the plugin) and list the import specifiers that activate it in pyreon.native.modules. @pyreon/charts is the reference (packages/fundamentals/charts/src/native-plugin/).

State vocabulary, constants and factory components

intrinsicCalls maps package exports onto generic signal or computed operations. Import aliases and foreign bindings follow the ordinary hook ownership rules; state IR, inference and target emission remain compiler operations. The reactivity plugin supplies its package's mapping.

constants(exportedName) returns a compile-time string for named imports from the plugin's modules, or undefined for an unsupported export. Providers are registered before scanners run. A consuming plugin reads scan.constant(node) to get { module, name, value } independently of plugin order. Kinetic consumes the separate kinetic-presets package's motion vocabulary through this seam.

A factory component is a topLevel module item whose emitter supplies element(item, element, context). The compiler looks up the visible item binding; the hook returns ordinary JSX/style IR, or undefined to keep normal rendering. ModuleParseContext.binding(name) distinguishes local bindings and named/default import aliases, and theme supplies static token resolution. Attrs, styler and rocketstyle own their factory syntax and definition payloads this way; the compiler retains generic style and theme rendering. New plugins omit legacyList, which exists only to keep earlier generated hashes stable.

Programmatic callers load each owner explicitly. The former ParseResult.styledComponents, rocketstyleComponents and attrsComponents arrays have been replaced by moduleItems; runtime barrels remain separate from the optional tooling entry.

Statement and pattern calls

statementCalls handles bare setup calls and patternCalls handles calls bound to a destructuring pattern (ctx.pattern). Both use the declaration recognizer contract: return a declaration to accept, null to drop after reporting a diagnostic, or undefined to continue normal parsing. Claims honor the owning plugin's module imports and aliases. A symbol has one owner across all call positions.

ctx.functionDeclaration parses callback parameters and bodies; ctx.initialType supplies initial expression types. In a compiler pass, ctx.inferType(module, expression, component, locals) includes module helpers, constants, component fields and local overrides. Emission uses ctx.argument(value, expectedType) for backend numeric conversions. ctx.typeText(type, { synthesize: false }) spells an opaque target type without creating anonymous Kotlin classes.

A declaration can request shared environment reads through usesColorScheme and usesSizeClass. lifecycleOnly omits declaration text; lifecycle.earlyOrder orders setup contributions before ordinary lifecycle hooks, preserving declaration order within a priority. legacyName: false omits a synthetic binding from a previously nameless declaration's compatibility hash.

DeclEmitter.root wraps the root once for all declarations of its owning type. Its next(indent) callback emits the previous wrapper or canonical root. Wrappers compose in first-declaration order and receive only declarations from the current component. @pyreon/hotkeys uses this to install Compose focus and keyboard handling without compiler-specific shortcut branches.

Store and state-tree plugins

The owning packages supply singleton recognition, typed members, target emitters and runtime marker stubs. The native CLI discovers their native-plugin exports from the app's imports. For a direct compiler instance, load the same plugins explicitly:

import { createCompiler } from '@pyreon/native-compiler'
import storePlugin from '@pyreon/store/native-plugin'
import stateTreePlugin from '@pyreon/state-tree/native-plugin'

const compiler = createCompiler({ discovered: [storePlugin, stateTreePlugin] })
const output = compiler.transform(appSource, { target: 'swift' })

appSource imports the libraries it uses. A bare compiler instance no longer lowers their APIs. Package plugins use generic module receivers, live member type evidence, alias factories and scoped function emission; store/model IR is owned by those packages.

Form lowering is owned by @pyreon/form/native-plugin. Direct compiler users install that plugin; the native CLI discovers it from imports. Plugins can provide native field bindings with DeclEmitter.valueBinding, describe iteration rows with iteration, scope callback receivers/types with EmitContext.withBindings, and consume module-item validators with fieldValidators. These hooks keep library behavior out of the compiler's primitive emitters.

Custom provider element emitters call ctx.withColorScope(el, emit) to render under a registered scope while retaining their own target-specific wrapper. Nested calls and thrown callbacks restore the enclosing scope. @pyreon/ui-core/native-plugin owns PyreonUI rendering; the compiler has no ui-core tag or package cases.

Native validation caches successful capability probes. Interrupted probes fail without persisting toolchain absence and retry on a later call. With PYREON_REQUIRE_NATIVE_VALIDATE=1, source requiring Observation fails if that capability cannot be established; validation never reports that failure as a successful skip. SwiftUI SDK checks still skip on hosts without Apple frameworks.