keb
keb•14mo ago

Using esbuild with import maps

Hello 👋 I'm attempting to migrate one of my front-end projects to using Deno for tooling instead of Node. The setup is very simple; my frontend code lives in src/ and should be bundled by esbuild and output to dist/. I'd like to use import maps to keep my front-end code easy to read, so for example, in my frontend code I have an import that looks like import { m } from 'umai'; and then in my deno.json file I have:
"imports": {
"std/": "https://deno.land/std@0.201.0/",
"umai": "https://deno.land/x/umai@0.2.4/index.js",
"esbuild": "https://deno.land/x/esbuild@v0.19.3/mod.js"
}
"imports": {
"std/": "https://deno.land/std@0.201.0/",
"umai": "https://deno.land/x/umai@0.2.4/index.js",
"esbuild": "https://deno.land/x/esbuild@v0.19.3/mod.js"
}
However, when I try running my esbuild build script, I get this error:
✘ [ERROR] Could not resolve "umai"

src/Apps.js:1:26:
1 │ import { m } from 'umai';
╵ ~~~~~~
✘ [ERROR] Could not resolve "umai"

src/Apps.js:1:26:
1 │ import { m } from 'umai';
╵ ~~~~~~
Which seems to imply that while Deno can resolve import maps, esbuild cannot. Has anyone figure out a way around this?
12 Replies
keb
keb•14mo ago
My best guess is that I'll need to implement an esbuild plugin that will resolve the paths at build time to their ESM url counterparts So like rewrite umai -> https://deno.land/x/umai@0.2.4/index.js at build time. Although I'm not sure if that would even work 🤔 I wish Deno just took care of this somehow gonna give it a shot and report back OK yeah esbuild doesn't like that. I guess because it doesn't want HTTP urls as paths
Plugin "import-map" returned a non-absolute path: https://deno.land/x/umai@0.2.4/index.js (set a namespace if this is not a file path)

src/components.js:1:18:
1 │ import { m } from 'umai';
╵ ~~~~~~
Plugin "import-map" returned a non-absolute path: https://deno.land/x/umai@0.2.4/index.js (set a namespace if this is not a file path)

src/components.js:1:18:
1 │ import { m } from 'umai';
╵ ~~~~~~
Deno AI Helper
Deno AI Helper•14mo ago
Based on the documentation, Deno does support import maps which allow you to use bare specifiers like "umai" in your code. However, it seems like esbuild, the tool you're using to bundle your code, is not able to resolve these bare specifiers using Deno's import map. The documentation does not provide a direct solution for this issue. It's important to note that the output of deno bundle is intended for consumption in Deno and not for use in a web browser or other runtimes. If you wish to bundle for the web, the documentation recommends other solutions such as esbuild, but it does not provide specific guidance on how to use esbuild with Deno's import maps. As a general advice (which is not in the documentation), you might need to configure esbuild to understand Deno's import maps, or use a plugin if one exists. If esbuild does not support Deno's import maps, you might need to use the full URLs in your import statements when you're bundling your code with esbuild. Remember to always refer to the official esbuild and Deno documentation or their respective communities for the most accurate and up-to-date information. 🦕
ioB
ioB•14mo ago
please send code I’m assuming you’re using esbuild_deno_loader? If so there’s a config option that you can set to handle this for you
keb
keb•14mo ago
I was using only esbuild. I'm not near my PC right now, but will send when I have the chance. Is there a way to use esbuild with deno without esbuild_deno_loader while still using third party packages in your source?
ioB
ioB•14mo ago
unless you’re willing to recreate the plugin, unfortunately not the default loader doesn’t work with http imports, and it definitely doesn’t work with import maps
keb
keb•14mo ago
that's a bummer I'll take a look at the loader. Thanks for clarifying.
ioB
ioB•14mo ago
definitely a bummer hopefully the esbuild folks take deno support more seriously in the future
NDH
NDH•14mo ago
This has always worked for me. Even when a deps.ts includes external http imports.
export * as esbuild from "https://deno.land/x/esbuild@v0.17.11/mod.js";
export { denoPlugin } from "https://deno.land/x/esbuild_deno_loader@0.6.0/mod.ts";

const buildConfig = {
Out: "dist",
FileName: "bundle.js",
Entry: ["./src/main.ts"],
Minify: false
}

/**
* builds and bundles an entrypoint into a single ESM output.
* @param {Config} cfg - the configuration to build from, object that contains:
* - Out: string - the folder to place the bundle in (defaults to 'dist')
* - Entry: string[] - the entry points to build from (defaults to ["./src/main.ts"])
* - Minify: boolean - whether or not to minify the bundle
*/
export async function buildIt( buildConfig = {
Out: "dist",
FileName: "bundle.js",
Entry: ["./src/main.ts"],
Minify: false
} ) {
console.log(`Bundling ${cfg.Entry} to ${cfg.Out} - minified = ${cfg.Minify}`)
await esbuild.build({
// @ts-ignore: outdated types
plugins: [denoPlugin({})],
entryPoints: cfg.Entry,
outfile: cfg.Out,
bundle: true,
minify: cfg.Minify,
keepNames: true,
banner: { js: '// deno-lint-ignore-file' },
format: "esm"
}).catch((e) => console.info(e));
esbuild.stop();
}
export * as esbuild from "https://deno.land/x/esbuild@v0.17.11/mod.js";
export { denoPlugin } from "https://deno.land/x/esbuild_deno_loader@0.6.0/mod.ts";

const buildConfig = {
Out: "dist",
FileName: "bundle.js",
Entry: ["./src/main.ts"],
Minify: false
}

/**
* builds and bundles an entrypoint into a single ESM output.
* @param {Config} cfg - the configuration to build from, object that contains:
* - Out: string - the folder to place the bundle in (defaults to 'dist')
* - Entry: string[] - the entry points to build from (defaults to ["./src/main.ts"])
* - Minify: boolean - whether or not to minify the bundle
*/
export async function buildIt( buildConfig = {
Out: "dist",
FileName: "bundle.js",
Entry: ["./src/main.ts"],
Minify: false
} ) {
console.log(`Bundling ${cfg.Entry} to ${cfg.Out} - minified = ${cfg.Minify}`)
await esbuild.build({
// @ts-ignore: outdated types
plugins: [denoPlugin({})],
entryPoints: cfg.Entry,
outfile: cfg.Out,
bundle: true,
minify: cfg.Minify,
keepNames: true,
banner: { js: '// deno-lint-ignore-file' },
format: "esm"
}).catch((e) => console.info(e));
esbuild.stop();
}
You can see the application of it in my dev-server Hot! https://github.com/nhrones/Hot
keb
keb•14mo ago
it looks like main.ts doesn't include any external dependencies in your example
NDH
NDH•14mo ago
The following was used as a test. Note that the external https dependency was bundled properly!
// src/main.ts

import { VERSION }from "https://deno.land/std@0.201.0/version.ts";
console.log('ver. ', VERSION)
// src/main.ts

import { VERSION }from "https://deno.land/std@0.201.0/version.ts";
console.log('ver. ', VERSION)
bundles to --
// /dist/bundle.js

// deno-lint-ignore-file

// https://deno.land/std@0.201.0/version.ts
var VERSION = "0.201.0";

// src/main.ts
console.log("ver. ", VERSION);
// /dist/bundle.js

// deno-lint-ignore-file

// https://deno.land/std@0.201.0/version.ts
var VERSION = "0.201.0";

// src/main.ts
console.log("ver. ", VERSION);
keb
keb•14mo ago
interestingggg I'll give that a shot
NDH
NDH•14mo ago
All of my personal projects use either Hot, or another utility I use called BuildIt. Most of them have many external dependencies. I've never had an issue. These tools are Deno-installed locally. I only need to call them on the command line when needed. If I need to bundle a lib, I just type build on the terminal in the root. If I'm working on a web-app, I just type Hot in the root. I've even bundled Deno-Desktop apps that include native code with this utility. My DWM-ReactiveUI framework creates nice little desktop apps using only Deno-TS code.