868 lines
17 KiB
Markdown
868 lines
17 KiB
Markdown
# WRNexus Language Support
|
|
|
|
For AI tools that support Model Context Protocol, run `wrnexus mcp <app-dir>` (or
|
|
`bunx wrnexus-mcp --root=<app-dir>`). The same read-only framework context works from VS Code,
|
|
JetBrains IDEs, Neovim and standalone MCP clients.
|
|
|
|
Complete VS Code language support for [WRNexus](https://wrnexusjs.dev) `.wrn` files.
|
|
|
|
WRNexus is an SSR-first, Bun-powered full-stack framework with reactive components, layouts, colocated APIs, browser functions, lifecycle hooks, state watchers, client-side navigation, and reusable UI components.
|
|
|
|
Typed declarations work across props, state, and functions:
|
|
|
|
```wrn
|
|
component UserCard {
|
|
types {
|
|
interface User { id: string; name: string }
|
|
}
|
|
props {
|
|
user: User
|
|
compact: boolean = false
|
|
}
|
|
state selected: boolean = false
|
|
functions {
|
|
function select(user: User): void {
|
|
selected = true
|
|
}
|
|
}
|
|
view { <article>{user.name}</article> }
|
|
}
|
|
```
|
|
|
|
The extension shows declared prop types and reports missing required props and incompatible values. Typed prop and state expressions passed into nested components retain their declared type.
|
|
|
|
Top-level imports are supported before `page`, `component`, or `layout`. Imported
|
|
helpers can be used in server-rendered prop expressions:
|
|
|
|
```wrn
|
|
import { appUrl } from "@wrnexus/helpers";
|
|
|
|
layout PublicLayout {
|
|
view {
|
|
<PublicHeader signInHref="{appUrl('sso', '/sign-in')}" />
|
|
}
|
|
}
|
|
```
|
|
|
|
## Installation
|
|
|
|
Search for **WRNexus Language Support** in the VS Code Extensions view or run:
|
|
|
|
```text
|
|
ext install wrnexus.wrnexus
|
|
```
|
|
|
|
For a local build:
|
|
|
|
1. Package the extension:
|
|
|
|
```bash
|
|
bun run package
|
|
```
|
|
|
|
2. Open the VS Code Command Palette.
|
|
3. Select **Extensions: Install from VSIX**.
|
|
4. Choose the generated `wrnexus-<version>.vsix` file.
|
|
|
|
You can also install it from the terminal:
|
|
|
|
```bash
|
|
code --install-extension ./wrnexus-0.2.7.vsix --force
|
|
```
|
|
|
|
Reload VS Code after installation:
|
|
|
|
```text
|
|
Developer: Reload Window
|
|
```
|
|
|
|
## Features
|
|
|
|
### Syntax highlighting
|
|
|
|
The extension provides highlighting for WRNexus declarations and embedded languages.
|
|
|
|
Supported top-level declarations:
|
|
|
|
```wrn
|
|
page HomePage {
|
|
}
|
|
|
|
component UserCard {
|
|
}
|
|
|
|
layout PublicLayout {
|
|
}
|
|
```
|
|
|
|
Supported WRNexus blocks include:
|
|
|
|
```wrn
|
|
props {
|
|
}
|
|
|
|
state count = 0
|
|
|
|
seo {
|
|
}
|
|
|
|
functions {
|
|
}
|
|
|
|
lifecycle {
|
|
}
|
|
|
|
watch count {
|
|
}
|
|
|
|
view {
|
|
}
|
|
|
|
style {
|
|
}
|
|
|
|
api GET /api/users {
|
|
}
|
|
|
|
ssr {
|
|
}
|
|
|
|
client {
|
|
}
|
|
|
|
realtime chat {
|
|
}
|
|
```
|
|
|
|
Put `// @required` immediately above a prop when callers must provide it while
|
|
retaining a typed runtime fallback. `propName = undefined` is also treated as
|
|
required when no fallback is appropriate. The editor shows required props first
|
|
in completion lists and reports an error when a component mount omits one. Prop
|
|
types are inferred from defaults, and string options used in component comparisons
|
|
are offered as completion choices.
|
|
|
|
Embedded language highlighting:
|
|
|
|
- `view { ... }` uses HTML highlighting.
|
|
- `style { ... }` uses CSS highlighting.
|
|
- `functions { ... }` uses TypeScript highlighting.
|
|
- Lifecycle hook bodies use TypeScript highlighting.
|
|
- Watcher bodies use TypeScript highlighting.
|
|
- API, SSR, client, and realtime bodies use TypeScript highlighting.
|
|
- `{expression}` interpolation uses TypeScript expression highlighting.
|
|
|
|
### Pages
|
|
|
|
Create route pages with layouts, SEO metadata, state, APIs, and views:
|
|
|
|
```wrn
|
|
page UserPage {
|
|
layout = "public"
|
|
|
|
state id = ctx.params.id
|
|
|
|
seo {
|
|
title = "User profile"
|
|
description = "View user information"
|
|
}
|
|
|
|
view {
|
|
<main>
|
|
<h1>User {id}</h1>
|
|
</main>
|
|
}
|
|
}
|
|
```
|
|
|
|
Dynamic route parameters are available through:
|
|
|
|
```wrn
|
|
state id = ctx.params.id
|
|
```
|
|
|
|
For a route such as:
|
|
|
|
```text
|
|
pages/users/[id].wrn
|
|
```
|
|
|
|
autocomplete suggests:
|
|
|
|
```text
|
|
id
|
|
ctx.params.id
|
|
```
|
|
|
|
### Components
|
|
|
|
Create reusable reactive components:
|
|
|
|
```wrn
|
|
component Counter {
|
|
state count = 0
|
|
|
|
view {
|
|
<button @click="count++">
|
|
Count: {count}
|
|
</button>
|
|
}
|
|
}
|
|
```
|
|
|
|
Components support:
|
|
|
|
- props
|
|
- reactive state
|
|
- browser functions
|
|
- lifecycle hooks
|
|
- state watchers
|
|
- event handlers
|
|
- conditional classes
|
|
- reactive attributes
|
|
- reactive loops
|
|
- conditional visibility
|
|
|
|
### Layouts
|
|
|
|
Create reusable layouts:
|
|
|
|
```wrn
|
|
layout PublicLayout {
|
|
view {
|
|
<header>
|
|
<nav>Navigation</nav>
|
|
</header>
|
|
|
|
<main>
|
|
{content}
|
|
</main>
|
|
}
|
|
}
|
|
```
|
|
|
|
Layouts can also use state, props, functions, lifecycle hooks, and watchers.
|
|
|
|
## Browser functions
|
|
|
|
Functions declared inside a component or layout are available to browser events, lifecycle hooks, and watchers.
|
|
|
|
```wrn
|
|
component Counter {
|
|
state count = 0
|
|
|
|
functions {
|
|
function increment() {
|
|
count += 1
|
|
}
|
|
|
|
function reset() {
|
|
count = 0
|
|
}
|
|
}
|
|
|
|
view {
|
|
<button @click="increment()">
|
|
Increment
|
|
</button>
|
|
|
|
<button @click="reset()">
|
|
Reset
|
|
</button>
|
|
|
|
<p>{count}</p>
|
|
}
|
|
}
|
|
```
|
|
|
|
Functions may use browser APIs:
|
|
|
|
```wrn
|
|
functions {
|
|
function scrollToTop() {
|
|
window.scrollTo({
|
|
top: 0,
|
|
behavior: "smooth"
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
Asynchronous functions are also supported:
|
|
|
|
```wrn
|
|
functions {
|
|
async function loadUsers() {
|
|
const response = await fetch("/api/users")
|
|
const users = await response.json()
|
|
}
|
|
}
|
|
```
|
|
|
|
## Lifecycle hooks
|
|
|
|
Components and layouts support three lifecycle hooks:
|
|
|
|
```wrn
|
|
lifecycle {
|
|
mount {
|
|
}
|
|
|
|
update {
|
|
}
|
|
|
|
unmount {
|
|
}
|
|
}
|
|
```
|
|
|
|
### `mount`
|
|
|
|
Runs once after the component is connected to the page and hydrated.
|
|
|
|
```wrn
|
|
lifecycle {
|
|
mount {
|
|
console.log("Component mounted")
|
|
}
|
|
}
|
|
```
|
|
|
|
Use it to:
|
|
|
|
- read browser state
|
|
- initialize third-party libraries
|
|
- register global event listeners
|
|
- start timers
|
|
- perform initial browser-only work
|
|
|
|
### `update`
|
|
|
|
Runs after reactive state changes.
|
|
|
|
Multiple synchronous state changes are batched into one update cycle.
|
|
|
|
```wrn
|
|
lifecycle {
|
|
update {
|
|
console.log("Component state updated")
|
|
}
|
|
}
|
|
```
|
|
|
|
### `unmount`
|
|
|
|
Runs before the component is removed.
|
|
|
|
```wrn
|
|
lifecycle {
|
|
unmount {
|
|
console.log("Component removed")
|
|
}
|
|
}
|
|
```
|
|
|
|
Use it to:
|
|
|
|
- remove global event listeners
|
|
- clear timers
|
|
- disconnect observers
|
|
- release browser resources
|
|
- clean up third-party libraries
|
|
|
|
Lifecycle cleanup also runs during WRNexus client-side navigation.
|
|
|
|
## State watchers
|
|
|
|
Watch one declared state value:
|
|
|
|
```wrn
|
|
component Menu {
|
|
state open = false
|
|
|
|
watch open {
|
|
console.log("Current value:", value)
|
|
console.log("Previous value:", previous)
|
|
}
|
|
|
|
view {
|
|
<button @click="open = !open">
|
|
Toggle menu
|
|
</button>
|
|
}
|
|
}
|
|
```
|
|
|
|
Watcher variables:
|
|
|
|
| Variable | Description |
|
|
| ---------- | -------------------- |
|
|
| `value` | Current state value |
|
|
| `previous` | Previous state value |
|
|
|
|
A watcher runs only when its declared state changes.
|
|
|
|
Invalid watcher declarations are reported:
|
|
|
|
```wrn
|
|
watch missingState {
|
|
}
|
|
```
|
|
|
|
The editor reports that `missingState` has not been declared.
|
|
|
|
## Window and document events
|
|
|
|
WRNexus supports events attached directly to `window` and `document`.
|
|
|
|
### Window events
|
|
|
|
```wrn
|
|
<div @window:scroll="updateScrollPosition()">
|
|
</div>
|
|
```
|
|
|
|
```wrn
|
|
<div @window:resize="updateViewportSize()">
|
|
</div>
|
|
```
|
|
|
|
```wrn
|
|
<div @window:keydown="handleKeyboardShortcut()">
|
|
</div>
|
|
```
|
|
|
|
### Document events
|
|
|
|
```wrn
|
|
<div @document:click="closeMenu()">
|
|
</div>
|
|
```
|
|
|
|
```wrn
|
|
<div @document:visibilitychange="handleVisibilityChange()">
|
|
</div>
|
|
```
|
|
|
|
Global event listeners created manually inside `mount` should be removed inside `unmount`.
|
|
|
|
## Complete BackToTop example
|
|
|
|
```wrn
|
|
component BackToTop {
|
|
state visible = false
|
|
|
|
functions {
|
|
function updateBackToTopVisibility() {
|
|
visible = window.scrollY > 500
|
|
}
|
|
|
|
function scrollToTop() {
|
|
window.scrollTo({
|
|
top: 0,
|
|
behavior: "smooth"
|
|
})
|
|
}
|
|
}
|
|
|
|
lifecycle {
|
|
mount {
|
|
updateBackToTopVisibility()
|
|
|
|
window.addEventListener(
|
|
"scroll",
|
|
updateBackToTopVisibility,
|
|
{ passive: true }
|
|
)
|
|
}
|
|
|
|
unmount {
|
|
window.removeEventListener(
|
|
"scroll",
|
|
updateBackToTopVisibility
|
|
)
|
|
}
|
|
}
|
|
|
|
watch visible {
|
|
console.log(
|
|
"BackToTop visibility changed:",
|
|
value,
|
|
previous
|
|
)
|
|
}
|
|
|
|
view {
|
|
<button
|
|
type="button"
|
|
@click="scrollToTop()"
|
|
aria-label="Back to top"
|
|
class="fixed bottom-5 right-5"
|
|
class:pointer-events-none="!visible"
|
|
class:translate-y-4="!visible"
|
|
class:opacity-0="!visible"
|
|
class:pointer-events-auto="visible"
|
|
class:translate-y-0="visible"
|
|
class:opacity-100="visible"
|
|
>
|
|
Back to top
|
|
</button>
|
|
}
|
|
}
|
|
```
|
|
|
|
## Conditional classes
|
|
|
|
Use `class:<class-name>` to toggle CSS classes reactively:
|
|
|
|
```wrn
|
|
<button
|
|
class:opacity-100="visible"
|
|
class:pointer-events-auto="visible"
|
|
class:opacity-0="!visible"
|
|
class:pointer-events-none="!visible"
|
|
>
|
|
Action
|
|
</button>
|
|
```
|
|
|
|
The expression is evaluated whenever its reactive dependencies change.
|
|
|
|
## Event bindings
|
|
|
|
Standard element events:
|
|
|
|
```wrn
|
|
<button @click="submitForm()">
|
|
Submit
|
|
</button>
|
|
```
|
|
|
|
```wrn
|
|
<input @input="query = event.target.value" />
|
|
```
|
|
|
|
```wrn
|
|
<form @submit="handleSubmit()">
|
|
</form>
|
|
```
|
|
|
|
Supported completion suggestions include:
|
|
|
|
- `@click`
|
|
- `@input`
|
|
- `@change`
|
|
- `@submit`
|
|
- `@focus`
|
|
- `@blur`
|
|
- `@keydown`
|
|
- `@keyup`
|
|
- `@mouseenter`
|
|
- `@mouseleave`
|
|
- `@window:scroll`
|
|
- `@window:resize`
|
|
- `@window:keydown`
|
|
- `@document:click`
|
|
- `@document:visibilitychange`
|
|
|
|
## Reactive directives
|
|
|
|
### Conditional visibility
|
|
|
|
```wrn
|
|
<div data-show="open">
|
|
Visible when open is true
|
|
</div>
|
|
```
|
|
|
|
### Reactive loops
|
|
|
|
```wrn
|
|
<ul>
|
|
<li data-for="user in users">
|
|
{user.name}
|
|
</li>
|
|
</ul>
|
|
```
|
|
|
|
### Reactive text
|
|
|
|
```wrn
|
|
<span data-text="message"></span>
|
|
```
|
|
|
|
### Dynamic attributes
|
|
|
|
```wrn
|
|
<button disabled="{loading}">
|
|
Save
|
|
</button>
|
|
```
|
|
|
|
## Diagnostics
|
|
|
|
The extension provides two diagnostic layers.
|
|
|
|
### Editor diagnostics
|
|
|
|
Fast local validation checks:
|
|
|
|
- invalid root declarations
|
|
- unknown page, component, or layout members
|
|
- unbalanced braces, brackets, and parentheses
|
|
- unclosed strings
|
|
- invalid HTML closing tags
|
|
- missing HTML closing tags
|
|
- invalid lifecycle hooks
|
|
- duplicate lifecycle hooks
|
|
- duplicate lifecycle blocks
|
|
- undeclared watched states
|
|
- missing watcher bodies
|
|
- invalid layout usage
|
|
- missing view blocks
|
|
|
|
### Compiler diagnostics
|
|
|
|
The extension bundles the real WRNexus compiler and validates `.wrn` files as you type.
|
|
|
|
Compiler errors are attached to the reported source offset whenever available.
|
|
|
|
Disable diagnostics with:
|
|
|
|
```json
|
|
"wrnexus.diagnostics.enable": false
|
|
```
|
|
|
|
## Formatting
|
|
|
|
The built-in formatter supports:
|
|
|
|
- page, component, and layout indentation
|
|
- functions and nested function bodies
|
|
- lifecycle blocks
|
|
- watcher blocks
|
|
- multiline function calls
|
|
- object literals
|
|
- HTML elements
|
|
- multiline HTML attributes
|
|
- conditional class directives
|
|
- window and document events
|
|
- CSS blocks
|
|
- API blocks
|
|
|
|
Format a document using:
|
|
|
|
```text
|
|
Shift+Alt+F
|
|
```
|
|
|
|
Enable format-on-save:
|
|
|
|
```json
|
|
"[wrn]": {
|
|
"editor.defaultFormatter": "wrnexus.wrnexus",
|
|
"editor.formatOnSave": true
|
|
}
|
|
```
|
|
|
|
Disable the formatter:
|
|
|
|
```json
|
|
"wrnexus.format.enable": false
|
|
```
|
|
|
|
Configure preferred line width:
|
|
|
|
```json
|
|
"wrnexus.formatting.printWidth": 100
|
|
```
|
|
|
|
## Autocomplete
|
|
|
|
Autocomplete is available for:
|
|
|
|
- pages
|
|
- components
|
|
- layouts
|
|
- state declarations
|
|
- props
|
|
- views
|
|
- SEO metadata
|
|
- functions
|
|
- lifecycle hooks
|
|
- watchers
|
|
- declared state names
|
|
- declared props
|
|
- declared functions
|
|
- watcher variables
|
|
- route parameters
|
|
- context values
|
|
- standard events
|
|
- window events
|
|
- document events
|
|
- reactive attributes
|
|
- conditional classes
|
|
|
|
After typing:
|
|
|
|
```wrn
|
|
watch
|
|
```
|
|
|
|
the extension suggests declared state names.
|
|
|
|
Inside a watcher, it suggests:
|
|
|
|
```text
|
|
value
|
|
previous
|
|
```
|
|
|
|
Declared component functions are suggested as callable snippets:
|
|
|
|
```text
|
|
updateVisibility()
|
|
scrollToTop()
|
|
```
|
|
|
|
## Go to definition
|
|
|
|
Use Ctrl+Click or **Go to Definition** for supported WRNexus symbols.
|
|
|
|
The extension can navigate to component declarations and locally declared functions when supported by the definition provider.
|
|
|
|
## Snippets
|
|
|
|
Useful snippet prefixes include:
|
|
|
|
| Prefix | Purpose |
|
|
| ------------------------ | ------------------------------------- |
|
|
| `wrn-page` | Create a page |
|
|
| `wrn-dynamic-page` | Create a dynamic route page |
|
|
| `wrn-component` | Create a component |
|
|
| `wrn-reactive-component` | Create a reactive component |
|
|
| `wrn-layout` | Create a layout |
|
|
| `wrn-state` | Declare state |
|
|
| `wrn-functions` | Create a functions block |
|
|
| `wrn-function` | Create a function |
|
|
| `wrn-async-function` | Create an async function |
|
|
| `wrn-lifecycle` | Create all lifecycle hooks |
|
|
| `wrn-mount` | Create a mount hook |
|
|
| `wrn-update` | Create an update hook |
|
|
| `wrn-unmount` | Create an unmount hook |
|
|
| `wrn-watch` | Create a watcher |
|
|
| `wrn-window-scroll` | Add a window scroll event |
|
|
| `wrn-window-resize` | Add a window resize event |
|
|
| `wrn-document-event` | Add a document event |
|
|
| `wrn-class-if` | Add a conditional class |
|
|
| `wrn-class-toggle` | Toggle two classes |
|
|
| `wrn-back-to-top` | Create a complete BackToTop component |
|
|
|
|
## Settings
|
|
|
|
| Setting | Default | Description |
|
|
| ------------------------------- | ------- | -------------------------------------- |
|
|
| `wrnexus.diagnostics.enable` | `true` | Enable editor and compiler diagnostics |
|
|
| `wrnexus.format.enable` | `true` | Enable the built-in formatter |
|
|
| `wrnexus.formatting.printWidth` | `100` | Preferred formatter line width |
|
|
|
|
Recommended VS Code configuration:
|
|
|
|
```json
|
|
{
|
|
"[wrn]": {
|
|
"editor.defaultFormatter": "wrnexus.wrnexus",
|
|
"editor.formatOnSave": true,
|
|
"editor.tabSize": 4,
|
|
"editor.insertSpaces": true,
|
|
"editor.snippetSuggestions": "top"
|
|
},
|
|
"wrnexus.diagnostics.enable": true,
|
|
"wrnexus.format.enable": true,
|
|
"wrnexus.formatting.printWidth": 100
|
|
}
|
|
```
|
|
|
|
## Troubleshooting language detection
|
|
|
|
Files ending in `.wrn` should show `WRNexus` in the VS Code status bar. If an
|
|
older workspace setting maps `*.wrn` to the legacy `wrn` language ID, replace
|
|
it with:
|
|
|
|
```json
|
|
"files.associations": {
|
|
"*.wrn": "wrn"
|
|
}
|
|
```
|
|
|
|
Then run **Developer: Reload Window**. The extension also repairs open `.wrn`
|
|
documents that VS Code classified as Plain Text or as the legacy `wrn`
|
|
language.
|
|
|
|
## Privacy
|
|
|
|
The extension does not collect telemetry.
|
|
|
|
It does not send source code, diagnostics, formatting data, or usage information over the network.
|
|
|
|
Syntax highlighting, formatting, autocomplete, snippets, definition navigation, and diagnostics run locally.
|
|
|
|
## Development
|
|
|
|
Install dependencies:
|
|
|
|
```bash
|
|
bun install
|
|
```
|
|
|
|
Rebuild the bundled compiler:
|
|
|
|
```bash
|
|
bun run build:compiler
|
|
```
|
|
|
|
Validate the extension:
|
|
|
|
```bash
|
|
bun run check
|
|
```
|
|
|
|
Package a VSIX:
|
|
|
|
```bash
|
|
bun run package
|
|
```
|
|
|
|
This creates:
|
|
|
|
```text
|
|
wrnexus-<version>.vsix
|
|
```
|
|
|
|
Install the local package:
|
|
|
|
```bash
|
|
code --install-extension ./wrnexus-<version>.vsix --force
|
|
```
|
|
|
|
Open the extension folder in VS Code and press <kbd>F5</kbd> to launch an Extension Development Host.
|
|
|
|
After changing the parser or compiler, always rebuild:
|
|
|
|
```bash
|
|
bun run build:compiler
|
|
```
|
|
|
|
Without the compiler bundle, syntax highlighting, snippets, formatting, and autocomplete continue to work, but compiler diagnostics are unavailable.
|
|
|
|
See the [Marketplace publishing guide](https://git.workroot.in/WorkRoot/WRNexusJS/src/branch/main/editors/vscode/PUBLISHING.md) for publishing instructions.
|
|
The extension now runs the same bundled `@wrnexus/language-server` used by other
|
|
editors. Diagnostics, accessibility checks, formatting, completion, hover,
|
|
definitions, references, rename, symbols, and quick fixes therefore share the
|
|
canonical parser. Set `wrnexus.languageServer.enable` to `false` only to use the
|
|
legacy in-extension fallback providers.
|
|
|
|
The extension activates only after a WRN document is used. Workspace indexing ignores dependency,
|
|
generated, build, cache, and coverage directories; applies file/count/size limits; and reuses a
|
|
short-lived bounded index. Type diagnostics are debounced while typing to prevent repeated
|
|
TypeScript compiler graphs from causing memory spikes.
|