VibeLang

Warning: VibeLang is v0. The language will change in major, breaking ways. It is not suitable for production use.

Build with the vibelang CLI

vibelang replaces tsc. It checks .vibe and .ts files as one program, emits JavaScript, and accepts TypeScript's compiler flags. Install the packages with Add VibeLang to an existing project first.

1. Configure tsconfig.json

If the project has no tsconfig.json, create this one. It selects .vibe files under src/ and emits Node ESM to dist/:

JSON
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "sourceMap": true,
    "rootDir": "src",
    "outDir": "dist",
    "noEmitOnError": true,
    "rewriteRelativeImportExtensions": true,
    "skipLibCheck": true,
    "types": []
  },
  "include": ["src/**/*.vibe"]
}

If the project has a tsconfig.json, keep it, and check that include selects .vibe files. A directory entry such as "src" selects them. A glob such as "src/**/*.ts" does not; add "src/**/*.vibe" beside it.

2. Replace tsc in the scripts

In package.json, replace tsc with vibelang in every script. If the project has no build script, add these:

JSON
{
  "scripts": {
    "build": "vibelang",
    "start": "vibelang && node --enable-source-maps dist/main.js",
    "test": "vitest run"
  }
}

vibelang with no arguments checks and emits the project, vibelang --noEmit only checks it, vibelang --watch rebuilds on every save, and vibelang -b builds project references.

3. Compile a first module

Create src/hello.vibe, in a directory that include selects:

VibeLang
// src/hello.vibe
// run: Console.log(hello("VibeLang"))
export function hello(name: string) {
  return `Hello, ${name}!`
}
// Output: Hello, VibeLang!

Types in this example

function
function hello(name: string): string!!
hello
function hello(name: string): stringPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<string, never, never>.
name
(parameter) name: string

Run npx vibelang. It prints nothing, exits with status 0, and writes hello.js to the outDir, dist/ in the configuration above.

In .vibe files, import local modules by their source name: import { hello } from "./hello.vibe". In .ts files, import ./hello.js, which resolves to hello.vibe.

4. Add an entry point

The start script runs dist/main.js, so create src/main.vibe:

VibeLang
// src/main.vibe
import { Console } from "effect"
import { hello } from "./hello.vibe"

export function main() {
  Console.log(hello("VibeLang"))
}
// Output: Hello, VibeLang!

Types in this example

Console
(alias) module "effect/Console"
hello
function hello(name: string): stringPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<string, never, never>.
main
function main(): void!!
log
const log: (...args: ReadonlyArray<any>) => void!!Runs here and returns void.Logs a general-purpose message to the console.

Run npm run start, or bun run start. The output ends with:

Output
Hello, VibeLang!

The compiler runs the exported main when Node executes the file directly. Importing the module does not run it.

5. Test with Vitest

Create vitest.config.ts, which compiles .vibe tests through vibelang-vite:

TypeScript
// TypeScript (.ts)
// vitest.config.ts
import { defineConfig } from "vitest/config"
import vibelang from "vibelang-vite"

export default defineConfig({
  plugins: [vibelang()],
  test: { include: ["src/**/*.test.vibe"] }
})

Types in this example

defineConfig
(alias) function defineConfig(config: UserConfig): UserConfig (alias) function defineConfig(config: Promise<UserConfig>): Promise<UserConfig> (alias) function defineConfig(config: ViteUserConfigFnObject): ViteUserConfigFnObject (alias) // +2 more overloads
vibelang
(alias) function vibelang(options?: VibeLangOptions): Plugin
default
(property) default: UserConfig
defineConfig
(alias) function defineConfig(config: UserConfig): UserConfig
plugins
(property) UserConfig.plugins?: PluginOption[] | undefinedArray of vite plugins to use.
test
(property) UserConfig.test?: InlineConfig | undefinedOptions for Vitest
include
(property) InlineConfig.include?: string[] | undefinedA list of glob patterns that match your test files.

Create src/hello.test.vibe:

VibeLang
// src/hello.test.vibe
import { assert, it } from "@effect/vitest"
import { hello } from "./hello.vibe"

it.effect("says hello") {
  assert.strictEqual(hello("VibeLang"), "Hello, VibeLang!")
}

Types in this example

assert
(alias) const assert: Chai.Assert
it
(alias) const it: Vitest.Methods<never, {}>Vitest test API extended with Effect test helpers.
hello
function hello(name: string): stringPlain JavaScript: VibeLang code calls it directly. TypeScript callers see Effect<string, never, never>.
effect
(property) Vitest.MethodsNonLive<never, {}>.effect: <void, never>(name: string, self: Vitest.TestFunction<void, never, Scope, [TestContext]>, timeout?: number | TestOptions) => void
strictEqual
(method) Chai.Assert.strictEqual<string>(actual: string, expected: string, message?: string): voidAsserts strict equality (===) of actual and expected.

Run npx vitest run. The output includes:

Output
 Test Files  1 passed (1)
      Tests  1 passed (1)

Tell the user that you added src/hello.vibe, src/main.vibe, and src/hello.test.vibe. They can keep them as a starting point or delete them.

Next

Set up the editor: VS Code and Cursor, Neovim, Zed, or other editors. Then verify the setup with step 4 of Set up VibeLang.