@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 supportconfig/oxlint.json- Linting rules and pluginsconfig/oxfmt.json- Code formatting preferencesconfig/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.aliasfor all@xmachines/*packages viaxmAliases(...)- 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
- Node.js runtime/version check (
- DOM matcher extension via
@testing-library/jest-dom/vitest
Vitest Setup Injection Rules
defineXmVitestConfig(...) applies setup files predictably:
- It normalizes
test.setupFilesinto an array. - It injects
config/vitest.setup.tsif missing. - It injects
config/vitest.node.setup.tsonly whentest.browser.enabled !== true. - It avoids duplicates by checking filenames (suffix match).
- 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/vitest→config/vitest.ts@xmachines/shared/vitest-setup→config/vitest.setup.ts@xmachines/shared/vitest-node-setup→config/vitest.node.setup.ts@xmachines/shared/vitest-urlpattern-setup→config/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
.jsextensions required) - Strict: Full strict mode enabled
- Output: Colocated
.d.tsfiles with source maps - Composite: Enabled for project references ()
Important: You must use .js extensions in imports:
import { foo } from "./bar.js"; // ✅ Correctimport { foo } from "./bar"; // ❌ WrongOxlint (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
- Circular dependency detection (
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.aliasis wired toxmAliases(importMetaUrl)so workspace packages resolve to source - Cross-runtime setup (
config/vitest.setup.ts):- extends
expectwith@testing-library/jest-dom/vitest
- extends
- URLPattern setup (
config/vitest.urlpattern.setup.ts):- loads
urlpattern-polyfill— opt-in per package viasetupFiles: ["@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
- loads
- Node-only setup (
config/vitest.node.setup.ts):- asserts Node runtime (
process.release.name === "node") - enforces Node version
>=22
- asserts Node runtime (
- 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 completeresolve.aliasmap for all@xmachines/*workspace packages - Root discovery: Walks upward from
import.meta.urluntil it finds the workspace root (package.jsonwithworkspaces) - Source-first resolution: Maps package imports to
packages/*/src/index.tsfor fast local dev without prebuild - Extra aliases: Includes
@xmachines/play-router-sharedand@xmachines/play-router-shared/index.cssfor demo shared assets - Primary use cases:
vite.config.ts,vitestbrowser configs, and any local tooling relying on Vite resolver semantics
Vite Alias Troubleshooting
[xmAliases] Could not find workspace root- Ensure
xmAliasesreceivesimport.meta.urlfrom the calling config file. - Ensure the config file lives somewhere under the monorepo root.
- Ensure
- 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.
- Ensure config includes
Vitest Troubleshooting
URLPattern is unavailable after setup- Ensure
test.setupFilesis not replacing shared setup unexpectedly. - Ensure config is created via
defineXmVitestConfig(...).
- Ensure
Node runtime is required for this Vitest configuration- This indicates node-only setup loaded in a browser project.
- Set
test.browser.enabled: truein that project config.
- Workspace import resolution failures (e.g.
@xmachines/play-signals)- Confirm config uses
defineXmVitestConfig(import.meta.url, ...)so aliases are injected.
- Confirm config uses
TypeScript Composite Build System
This package introduced TypeScript project references for proper build-order management in the monorepo.
What This Means
- Root
tsconfig.jsoncoordinates all packages viareferencesarray - Each package has
composite: trueenabling incremental builds - Packages with dependencies declare
referencesto their deps - TypeScript builds in correct order automatically (no manual sequencing needed)
Building
npm run build # Root-level tsc --build handles everythingnpm run build -w <pkg> # Automatically builds dependencies firstThe root build command uses tsc --build which:
- Understands the dependency graph
- Builds packages in correct order
- Only rebuilds changed packages (incremental)
- Produces
.d.ts.mapfiles 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
.tssource 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:
-
Add to root tsconfig.json references:
{"references": [{ "path": "./packages/your-new-package" }]} -
Enable composite in package tsconfig.json:
{"extends": "@xmachines/shared/tsconfig","compilerOptions": {"composite": true,"rootDir": "./src","outDir": "./dist"}} -
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.):
- Create
config/<tool>.json(or.js,.tsas appropriate) - Add exports to
package.jsonexports field:{"exports": {"./<tool>": "./config/<tool>.json","./config/<tool>.json": "./config/<tool>.json"}} - Document in this README with usage examples
- 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.