Using path aliases in esbuild

Posted . Visible to the public. Repeats.

In esbuild, you usually import other files using relative paths:

import './some-related-module'
import `../../utils/some-utility-module`
import `../../../css/some-css.sass`

This is totally fine if you import closely related files, but a bit clunky when you're trying to import some "global" module, like a utility module. When moving a file, your imports also need to change.

To get around this, esbuild support a mechanism first introduced in TypeScript called "path aliases". It works like this:

First, you create a file called jsconfig.json (or tsconfig.json if you do use TypeScript):

{
  "compilerOptions": {
    "baseUrl": "./app/assets",
    "paths": {
      "@/*": ["js/*"],
      "@images/*": ["images/*"],
      "@css/*": ["css/*"],
      "@spec/*": ["../../spec/js/*"]
    }
  },
  "include": []
}

(The include: [] only makes sense if you do not use Typescript. If you leave it out, hq-alias will search all js files in the directory, which could be expensive.)

Then, change your imports to

import './some-related-module'
import '@/utils/some-utility-module'
import '@css/some-css.sass'

That's mostly it. Unfortunately, some parts of the ecosystem will do their own resolving, and you'll have to teach them to use your path aliases.

The alias-hq npm package

The alias-hq Show archive.org snapshot npm package aims to simplify configuring tools that are unaware of your path aliases.

For example, you would configure webpack using

import hq from 'alias-hq'

module.exports = {
  ...
  resolve: {
    alias: hq.get('webpack'),
  },
}

or jest using

// jest.config.js
import hq from 'alias-hq'

module.exports = {
  ...
  moduleNameMapper: hq.get('jest'),
}

Support for SASS

In Sass, your @import statements also use their own resolving. To bring your path aliases to Sass, add this to your esbuild.config.js:

First, a function that rewrites paths, itself using alias-hq:

const path = require('path')
const hq = require('alias-hq')

function escapeStringRegexp(string) {
  return string
    .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
    .replace(/-/g, '\\x2d')
}

const mapAliasedPath = hq.get(({ rootUrl, baseUrl, paths }) => {
  return (importPath) => {
    if (!importPath.includes('@')) {
      return importPath
    }
    const basePath = path.join(rootUrl, baseUrl)
    for (const [aliasedPath, replacements] of Object.entries(paths)) {
      const regexp = new RegExp('(^|.*/)' + escapeStringRegexp(aliasedPath).replace('\\*', '(.*)'))
      importPath = importPath.replace(regexp, path.join(basePath, replacements[0]).replace('*', '$2'))
    }
    return importPath
  }
})

Then add it to your esbuild-sass-plugin config with

require('esbuild').build({
  // ...
  plugins: [
    // ...
    sassPlugin({
      // ...
      importMapper: mapAliasedPath,
    }),  
  ],
})

Support for esbuild-plugin-import-glob

Unfortunately, esbuild-plugin-import-glob Show archive.org snapshot does not support external path resolving at all.

Instead, use the attached variant of the plugin, and then set it up via

const importGlob = require('./lib/esbuild/esbuild-plugin-import-glob') // copy the attached plugin here

require('esbuild').build({
  // ...
  plugins: [
    // ...
    importGlob({
      importMapper: mapAliasedPath,
    }),  
  ],
})
Tobias Kraze
Last edit
Tobias Kraze
License
Source code in this card is licensed under the MIT License.
Posted by Tobias Kraze to makandra dev (2022-12-01 09:07)