Skip to content

@xmachines/shared

Documentation / @xmachines/shared

Shared TypeScript, linting, and formatting configurations for XMachines monorepo

Centralized config enabling composite build system with project references.

Overview

@xmachines/shared provides shared configuration files for all XMachines packages, establishing consistent TypeScript compilation, linting rules, formatting, and Vitest setup across the monorepo.

Enhancement: Added TypeScript composite build system with project references enabling correct build order, incremental compilation, and IDE source navigation via declaration maps.

Installation

This package is automatically available to all workspace packages via npm workspaces.

Contents

Configuration files in config/ directory:

  • config/tsconfig.json - TypeScript compiler settings with composite project support
  • config/oxlint.json - Linting rules and plugins
  • config/oxfmt.json - Code formatting preferences
  • config/vitest.ts - Shared Vitest config helper (defineXmVitestConfig)
  • config/vitest.setup.ts - Shared cross-runtime Vitest setup (polyfills, jest-dom matchers)
  • config/vitest.node.setup.ts - Node-only Vitest setup (Node runtime/version preflight)
  • config/vite-aliases.ts - Workspace alias mapping for @xmachines/*

Usage

TypeScript Configuration

Recommended (short alias):

{
"extends": "@xmachines/shared/tsconfig",
"compilerOptions": {
"composite": true,
"rootDir": "./src",
"outDir": "./dist"
}
}

Alternative (full path):

{
"extends": "@xmachines/shared/config/tsconfig.json",
"compilerOptions": {
"composite": true,
"rootDir": "./src",
"outDir": "./dist"
}
}

Note: Both import styles work. The short alias is recommended for cleaner config files.

Oxlint Configuration

Recommended:

{
"extends": ["@xmachines/shared/oxlint"]
}

Oxfmt Configuration

Recommended:

{
"extends": ["@xmachines/shared/oxfmt"]
}

Vite Alias Configuration

Use the shared alias helper in Vite and Vitest configs so workspace packages resolve to source files without requiring dist/ builds:

import { defineConfig } from "vite";
import { xmAliases } from "@xmachines/shared/vite-aliases";
export default defineConfig({
resolve: {
alias: xmAliases(import.meta.url),
},
});

This is especially important after npm run clean, where package exports targeting dist/ are temporarily unavailable until rebuild.

Vitest Configuration

Use the shared helper so package configs inherit common aliasing and runtime setup:

import { defineXmVitestConfig } from "@xmachines/shared/vitest";
export default defineXmVitestConfig(import.meta.url, {
test: {
environment: "node",
include: ["test/**/*.test.ts"],
exclude: ["node_modules/**"],
},
});

Browser project example:

import { defineXmVitestConfig } from "@xmachines/shared/vitest";
import { playwright } from "@vitest/browser-playwright";
export default defineXmVitestConfig(import.meta.url, {
test: {
name: "my-demo-browser",
browser: {
enabled: true,
provider: playwright(),
headless: true,
instances: [{ browser: "chromium" }],
},
include: ["test/browser/**/*.browser.test.ts"],
exclude: ["node_modules/**"],
},
});

What the shared Vitest layer provides automatically:

  • resolve.alias for all @xmachines/* packages via xmAliases(...)
  • Shared setup injection (config/vitest.setup.ts) unless already present
  • Node setup injection (config/vitest.node.setup.ts) for non-browser projects
  • Runtime preflight:
    • Node.js runtime/version check (>=22) in node setup
  • DOM matcher extension via @testing-library/jest-dom/vitest

Vitest Setup Injection Rules

defineXmVitestConfig(...) applies setup files predictably:

  1. It normalizes test.setupFiles into an array.
  2. It injects config/vitest.setup.ts if missing.
  3. It injects config/vitest.node.setup.ts only when test.browser.enabled !== true.
  4. It avoids duplicates by checking filenames (suffix match).
  5. Injected setup files run before user-provided setup files.

This gives a safe default for Node package tests while keeping browser projects compatible.

Vitest Exports

@xmachines/shared exports these Vitest entry points:

  • @xmachines/shared/vitestconfig/vitest.ts
  • @xmachines/shared/vitest-setupconfig/vitest.setup.ts
  • @xmachines/shared/vitest-node-setupconfig/vitest.node.setup.ts
  • @xmachines/shared/vitest-urlpattern-setupconfig/vitest.urlpattern.setup.ts

Use @xmachines/shared/vitest for package config files; direct setup exports are for advanced customization only.

vitest-urlpattern-setup must be added explicitly to setupFiles in packages that exercise URLPattern-based route matching (e.g. play-router and its adapter packages). It mirrors what consumers must do in production on Node < 24 or older browsers.

Configuration Details

TypeScript (config/tsconfig.json)

  • Target: ESNext (Node.js 22+)
  • Module: NodeNext (ESM with .js extensions required)
  • Strict: Full strict mode enabled
  • Output: Colocated .d.ts files with source maps
  • Composite: Enabled for project references ()

Important: You must use .js extensions in imports:

import { foo } from "./bar.js"; // ✅ Correct
import { foo } from "./bar"; // ❌ Wrong

Oxlint (config/oxlint.json)

  • Plugins: TypeScript, Unicorn, Import
  • Categories: Correctness (error), Suspicious (warn), Performance (warn)
  • Key Rules:
    • Circular dependency detection (import/no-cycle)
    • Unused variables (allow _ prefix)
    • TypeScript-specific checks

Oxfmt (config/oxfmt.json)

  • Line Width: 100 characters
  • Indentation: Tabs (4 spaces)
  • Style: Double quotes, semicolons, trailing commas
  • Final Newline: Always insert

Vitest (config/vitest.ts, config/vitest.setup.ts, config/vitest.node.setup.ts, config/vitest.urlpattern.setup.ts)

  • Helper API: defineXmVitestConfig(importMetaUrl, overrides) merges shared defaults with package-specific Vitest config
  • Auto-aliasing: resolve.alias is wired to xmAliases(importMetaUrl) so workspace packages resolve to source
  • Cross-runtime setup (config/vitest.setup.ts):
    • extends expect with @testing-library/jest-dom/vitest
  • URLPattern setup (config/vitest.urlpattern.setup.ts):
    • loads urlpattern-polyfill — opt-in per package via setupFiles: ["@xmachines/shared/vitest-urlpattern-setup"]
    • required for packages exercising URLPattern-based route matching (play-router and adapters)
    • mirrors what consumers must do in production on Node < 24 or older browsers
  • Node-only setup (config/vitest.node.setup.ts):
    • asserts Node runtime (process.release.name === "node")
    • enforces Node version >=22
  • Browser behavior: Browser projects do not receive node-only setup injection
  • Override model: Package-level config still controls test environment, includes/excludes, plugins, coverage, and browser provider

Vite Aliases (config/vite-aliases.ts)

  • Helper API: xmAliases(importMetaUrl) returns a complete resolve.alias map for all @xmachines/* workspace packages
  • Root discovery: Walks upward from import.meta.url until it finds the workspace root (package.json with workspaces)
  • Source-first resolution: Maps package imports to packages/*/src/index.ts for fast local dev without prebuild
  • Extra aliases: Includes @xmachines/play-router-shared and @xmachines/play-router-shared/index.css for demo shared assets
  • Primary use cases: vite.config.ts, vitest browser configs, and any local tooling relying on Vite resolver semantics

Vite Alias Troubleshooting

  • [xmAliases] Could not find workspace root
    • Ensure xmAliases receives import.meta.url from the calling config file.
    • Ensure the config file lives somewhere under the monorepo root.
  • Workspace package import resolves to missing dist/ files
    • Ensure config includes resolve.alias: xmAliases(import.meta.url).
    • Re-run after cleaning caches when lockfile/deps changed.

Vitest Troubleshooting

  • URLPattern is unavailable after setup
    • Ensure test.setupFiles is not replacing shared setup unexpectedly.
    • Ensure config is created via defineXmVitestConfig(...).
  • Node runtime is required for this Vitest configuration
    • This indicates node-only setup loaded in a browser project.
    • Set test.browser.enabled: true in that project config.
  • Workspace import resolution failures (e.g. @xmachines/play-signals)
    • Confirm config uses defineXmVitestConfig(import.meta.url, ...) so aliases are injected.

TypeScript Composite Build System

This package introduced TypeScript project references for proper build-order management in the monorepo.

What This Means

  • Root tsconfig.json coordinates all packages via references array
  • Each package has composite: true enabling incremental builds
  • Packages with dependencies declare references to their deps
  • TypeScript builds in correct order automatically (no manual sequencing needed)

Building

Terminal window
npm run build # Root-level tsc --build handles everything
npm run build -w <pkg> # Automatically builds dependencies first

The root build command uses tsc --build which:

  • Understands the dependency graph
  • Builds packages in correct order
  • Only rebuilds changed packages (incremental)
  • Produces .d.ts.map files for IDE navigation

Declaration Maps

The base config enables declaration maps (declarationMap: true) for better IDE experience in composite projects.

When TypeScript compiles with declarationMap: true, it produces .d.ts.map files that map type definitions back to source. This enables:

  • Go to Definition jumps to .ts source files (not compiled .d.ts)
  • Rename refactoring works across package boundaries
  • Error messages reference source locations

Cost: Slightly larger dist/ output (~10% more)
Benefit: Dramatically better IDE experience

Project References

Packages with internal dependencies should declare references in their tsconfig.json:

{
"extends": "@xmachines/shared/tsconfig",
"compilerOptions": {
"composite": true,
"rootDir": "./src",
"outDir": "./dist"
},
"references": [{ "path": "../dependency-package" }]
}

The root tsconfig.json maintains the full package reference graph, enabling TypeScript to:

  • Build packages in correct dependency order
  • Support incremental builds (only rebuild what changed)
  • Enable cross-package type checking and navigation

Adding New Packages

When creating a new package:

  1. Add to root tsconfig.json references:

    {
    "references": [{ "path": "./packages/your-new-package" }]
    }
  2. Enable composite in package tsconfig.json:

    {
    "extends": "@xmachines/shared/tsconfig",
    "compilerOptions": {
    "composite": true,
    "rootDir": "./src",
    "outDir": "./dist"
    }
    }
  3. Declare dependencies (if any):

    {
    "references": [{ "path": "../dependency-package" }]
    }

IDE Benefits

With declarationMap: true in the base config:

  • Go to Definition jumps to source files (not compiled .d.ts)
  • Real-time errors across package boundaries
  • Refactoring works across the entire monorepo

No build required for IDE features - TypeScript understands the source graph directly.

See AGENTS.md (section: TypeScript Composite Build System) for complete composite build documentation.

Adding New Configurations

To add a new shared configuration (e.g., for Jest, Vitest, etc.):

  1. Create config/<tool>.json (or .js, .ts as appropriate)
  2. Add exports to package.json exports field:
    {
    "exports": {
    "./<tool>": "./config/<tool>.json",
    "./config/<tool>.json": "./config/<tool>.json"
    }
    }
  3. Document in this README with usage examples
  4. Note whether the tool supports package exports or requires relative paths

License

Copyright (c) 2016 Mikael Karon. All rights reserved.

This work is licensed under the terms of the MIT license.
For a copy, see https://opensource.org/licenses/MIT.

Functions