SourceLoom builds LoomBundling the Loom language server

Part VIII, Chapter 3

Part VIII, Chapter 3: Bundling the Loom language server

Package: packages/loom-lang/vite.config.ts

Language: TypeScript

Raw source

Bundling the Loom language server

Vite bundles the language server entry, src/index.ts, into a single dist/loom-server.js. The bundle targets Node 20 in CommonJS form, the shape a Node host loads it as.

The build

Some of the server's dependencies — the vscode-*-languageservice packages and jsonc-parser — ship only UMD bundles, the older module format that rolldown cannot follow statically. The umd2esm plugin rewrites each such import to its ESM sibling so the bundler can inline it. This is the same trick volarjs/starter uses.

vscode-markdown-languageservice, which ProseLanguage's Markdown service pulls in, imports vscode-uri by its default binding, which that package's ES-module build does not provide. An alias resolves vscode-uri to its UMD build instead, where the bundler's CommonJS interop supplies one.

TypeScript
import { defineConfig } from 'vite'
import { builtinModules, createRequire } from 'node:module'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'

const here = dirname(fileURLToPath(import.meta.url))
const requireFromHere = createRequire(import.meta.url)

const nodeBuiltins = [...builtinModules, ...builtinModules.map((m) => `node:${m}`)]

const umd2esm = {
  name: 'umd2esm',
  enforce: 'pre' as const,
  resolveId(source: string, importer: string | undefined) {
    if (/^(vscode-.*-languageservice|jsonc-parser)/.test(source)) {
      const fromDir = importer ? dirname(importer) : here
      const resolved = requireFromHere.resolve(source, { paths: [fromDir] })
      return resolved.replace(/\/umd\//, '/esm/').replace(/\\umd\\/g, '\\esm\\')
    }
    return null
  },
}

Node's built-in modules stay external — they are present in every Node runtime — and everything else is inlined. The single entry builds to dist/loom-server.js.

TypeScript
export default defineConfig({
  plugins: [umd2esm],
  resolve: {
    conditions: ['node'],
    mainFields: ['main', 'module'],
    alias: {
      'vscode-uri': requireFromHere
        .resolve('vscode-uri')
        .replace(/esm[\\/]index\.mjs$/, 'umd/index.js'),
    },
  },
  build: {
    target: 'node20',
    outDir: 'dist',
    emptyOutDir: true,
    sourcemap: true,
    minify: false,
    lib: {
      entry: resolve(here, 'src/index.ts'),
      formats: ['cjs'],
      fileName: () => 'loom-server.js',
    },
    rollupOptions: { external: nodeBuiltins },
  },
})
Loom