Skip to content

Package

remix/ui-hmr

Hot module replacement transforms for Remix UI components, with integrations for Node and remix/assets.

ui-hmr rewrites supported Remix UI component modules so they can use the standard import.meta.hot APIs provided by packages like assets and node-hmr.

Features

  • Stable Components - Keep component identity stable while swapping implementations
  • Remount Fallbacks - Mark components stale when their setup scope changes
  • Node Module Hooks - Transform server component modules through Node's module customization hooks API
  • Assets Loader - Transform component modules for the browser through remix/assets

Installation

npm i remix

Usage

Use remix/ui-hmr/node as a Node import hook for server modules:

node --import remix/node-tsx --import remix/ui-hmr/node ./server.ts

Use uiHmr() from remix/ui-hmr/assets with remix/assets for browser modules:

import { createAssetServer } from 'remix/assets'
import { uiHmr } from 'remix/ui-hmr/assets'

let isDevelopment = process.env.NODE_ENV === 'development'

let assetServer = createAssetServer({
  basePath: '/assets',
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
  allowPackages: ['remix'],
  denyFiles: ['app/**/*.test.*'],
  hmr: isDevelopment
    ? {
        channel: async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel(),
        moduleImporter: 'remix/multiple-import-maps-polyfill',
      }
    : undefined,
  scripts: {
    loaders: isDevelopment ? [uiHmr()] : undefined,
  },
  watch: isDevelopment,
})

HMR appends mappings for updated modules to the document in additional <script type="importmap"> elements. The module importer allows browsers without native support for multiple import maps to load these updates.

Direct Transforms

Use the direct transform APIs when you are writing your own loader, Node module hooks, or build integration.

import { transformComponentsForBrowser } from 'remix/ui-hmr'

let result = transformComponentsForBrowser(source, {
  importSource: 'remix',
  moduleUrl: '/assets/app/routes.tsx',
})

if (result.transformed) {
  console.log(result.componentNames)
}

transformComponentsForBrowser(source, options) rewrites browser component modules and emits import.meta.hot.accept() code for browser updates. The direct transform uses importSource to derive imports for the UI refresh runtime and browser HMR runtime.

import { transformComponentsForServer } from 'remix/ui-hmr'

let result = transformComponentsForServer(source, {
  importSource: 'remix',
  moduleUrl: 'file:///app/routes.tsx',
})

transformComponentsForServer(source, options) rewrites server component modules and registers updated component implementations for the current module URL. The direct transform uses importSource to derive imports for the server HMR runtime.

Both transforms return:

type ComponentsHmrTransformResult = {
  code: string
  componentNames: string[]
  map: string | null
  transformed: boolean
}

Pass sourceMap: true to generate a source map.

Use importSource to define where injected imports come from, typically either remix or @remix-run:

transformComponentsForBrowser(source, {
  importSource: 'remix',
  moduleUrl: '/assets/app/routes.tsx',
})

transformComponentsForServer(source, {
  importSource: 'remix',
  moduleUrl: 'file:///app/routes.tsx',
})

importSource: 'remix' generates imports from remix/ui and remix/ui-hmr. importSource: '@remix-run' generates imports from @remix-run/ui and @remix-run/ui-hmr. Custom import sources follow the same nested import layout.

  • assets - Runs loaders while compiling assets
  • node-hmr - Provides the server-side import.meta.hot runtime
  • ui - Component APIs transformed by ui-hmr

License

See LICENSE