Skip to content

Support ESM-style "imports" field aliases in package.json #7385

@lehni

Description

@lehni

Clear and concise description of the problem

As of v14, Node.js now supports defining aliases for ESM imports in the packages.json through the "imports" field. Vite supports rollup-style aliases.

It would be nice if Vite was capable out of the box of reading the "imports" field and translating it correctly to rollup aliases.

Suggested solution

As a proof of concept, I have implemented a version that uses rollup's customResolver feature and works as long as no conditional imports are used. It turned out to be surprisingly simple:

import path from 'path'
import fs from 'fs'
import { defineConfig } from 'vite'
import { findUpSync } from 'find-up'

const pkg = findUpSync('package.json', { cwd: process.cwd() })
const { imports = {} } = JSON.parse(fs.readFileSync(pkg, 'utf8'))

export default defineConfig({
  // ...
  resolve: {
    alias: [
      {
        // Use a custom rollup resolver to emulate ESM-style imports
        // mappings in vite, as read from `package.json` above:
        find: /^#/,
        replacement: '#',
        customResolver(id) {
          for (const [find, replacement] of Object.entries(imports)) {
            const { match, capture } = matchIdentifier(id, find)
            if (match) {
              const replacementPath = path.resolve(replacement)
              id = capture
                ? replacementPath.replace('*', capture)
                : replacementPath
            }
          }
          return id
        }
      }
    ]
  }
})

function matchIdentifier(id, pattern) {
  const regexp = new RegExp(`^${pattern.replace('*', '(.*)')}$`)
  const match = id.match(regexp)
  return {
    match: !!match,
    capture: match?.[1]
  }
}

This works for my code-base with the following imports map:

  "imports": {
    "#config": "./src/config/index.js",
    "#app": "./src/server/app.js",
    "#models": "./src/server/models/index.js",
    "#models/*": "./src/server/models/*.js",
    "#controllers": "./src/server/controllers/index.js",
    "#controllers/*": "./src/server/controllers/*.js",
    "#services": "./src/server/services/index.js",
    "#services/*": "./src/server/services/*.js",
    "#errors": "./src/server/errors/index.js",
    "#utils": "./src/server/utils/index.js",
    "#utils/*": "./src/server/utils/*.js",
    "#features/*": "./src/server/features/*.js",
    "#admin/schema": "./src/admin/schema/index.js",
    "#admin/*": "./src/admin/*.js",
    "#test/*": "./test/*.js"
  }

It shouldn't be too hard to expand this to also support conditional imports.

Alternative

No response

Additional context

No response

Validations

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions