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,
    }),  
  ],
})
Profile picture of Tobias Kraze
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)