├── tests
├── fixtures
│ ├── virtual.ts
│ ├── external.ts
│ ├── mapped.ts
│ ├── data.json
│ ├── simple.ts
│ ├── jsx.tsx
│ ├── mime-db.ts
│ ├── node.ts
│ ├── npm.ts
│ ├── platform.ts
│ ├── preact.ts
│ ├── https.ts
│ ├── jsr.ts
│ ├── with-json.ts
│ ├── with-text.ts
│ ├── with-bytes.ts
│ └── env-vars.ts
├── external.test.ts
└── bundle.test.ts
├── src
├── mod.ts
└── plugin.ts
├── .github
└── workflows
│ ├── publish.yml
│ └── ci.yml
├── deno.json
├── README.md
├── LICENSE
└── deno.lock
/tests/fixtures/virtual.ts:
--------------------------------------------------------------------------------
1 | import "foo";
2 |
--------------------------------------------------------------------------------
/tests/fixtures/external.ts:
--------------------------------------------------------------------------------
1 | import "mapped";
2 |
--------------------------------------------------------------------------------
/tests/fixtures/mapped.ts:
--------------------------------------------------------------------------------
1 | import "mapped";
2 |
--------------------------------------------------------------------------------
/tests/fixtures/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "foo": "it works"
3 | }
4 |
--------------------------------------------------------------------------------
/src/mod.ts:
--------------------------------------------------------------------------------
1 | export { denoPlugin, type DenoPluginOptions } from "./plugin.ts";
2 |
--------------------------------------------------------------------------------
/tests/fixtures/simple.ts:
--------------------------------------------------------------------------------
1 | // deno-lint-ignore no-console
2 | console.log("hey");
3 |
--------------------------------------------------------------------------------
/tests/fixtures/jsx.tsx:
--------------------------------------------------------------------------------
1 | // deno-lint-ignore no-console
2 | console.log(
it works
);
3 |
--------------------------------------------------------------------------------
/tests/fixtures/mime-db.ts:
--------------------------------------------------------------------------------
1 | import * as mime from "mime-db";
2 | // deno-lint-ignore no-console
3 | console.log(mime);
4 |
--------------------------------------------------------------------------------
/tests/fixtures/node.ts:
--------------------------------------------------------------------------------
1 | import * as path from "node:path";
2 | // deno-lint-ignore no-console
3 | console.log(path);
4 |
--------------------------------------------------------------------------------
/tests/fixtures/npm.ts:
--------------------------------------------------------------------------------
1 | import { h } from "npm:preact";
2 | // deno-lint-ignore no-console
3 | console.log(h("div", null));
4 |
--------------------------------------------------------------------------------
/tests/fixtures/platform.ts:
--------------------------------------------------------------------------------
1 | import * as fflate from "fflate";
2 | // deno-lint-ignore no-console
3 | console.log(fflate);
4 |
--------------------------------------------------------------------------------
/tests/fixtures/preact.ts:
--------------------------------------------------------------------------------
1 | import { h } from "preact";
2 | // deno-lint-ignore no-console
3 | console.log(h("div", null));
4 |
--------------------------------------------------------------------------------
/tests/fixtures/https.ts:
--------------------------------------------------------------------------------
1 | import { h } from "https://esm.sh/preact";
2 | // deno-lint-ignore no-console
3 | console.log(h("div", null));
4 |
--------------------------------------------------------------------------------
/tests/fixtures/jsr.ts:
--------------------------------------------------------------------------------
1 | import * as foo from "jsr:@marvinh-test/fresh-island";
2 | // deno-lint-ignore no-console
3 | console.log(foo);
4 |
--------------------------------------------------------------------------------
/tests/fixtures/with-json.ts:
--------------------------------------------------------------------------------
1 | import json from "./data.json" with { type: "json" };
2 | // deno-lint-ignore no-console
3 | console.log(json);
4 |
--------------------------------------------------------------------------------
/tests/fixtures/with-text.ts:
--------------------------------------------------------------------------------
1 | import json from "./data.json" with { type: "text" };
2 | // deno-lint-ignore no-console
3 | console.log(json);
4 |
--------------------------------------------------------------------------------
/tests/fixtures/with-bytes.ts:
--------------------------------------------------------------------------------
1 | import buffer from "./data.json" with { type: "bytes" };
2 | // deno-lint-ignore no-console
3 | console.log(new TextDecoder().decode(buffer));
4 |
--------------------------------------------------------------------------------
/tests/fixtures/env-vars.ts:
--------------------------------------------------------------------------------
1 | export const deno = Deno.env.get("TEST_PUBLIC_FOO");
2 | export const deno2 = Deno.env.get("TEST_PUBLIC_FOO");
3 | // deno-lint-ignore no-process-global
4 | export const node = process.env.TEST_PUBLIC_FOO;
5 |
--------------------------------------------------------------------------------
/tests/external.test.ts:
--------------------------------------------------------------------------------
1 | import { expect } from "@std/expect";
2 | import { externalToRegex } from "../src/plugin.ts";
3 |
4 | Deno.test("externalToRegex", () => {
5 | expect(externalToRegex("foo")).toEqual(/^foo$/);
6 | expect(externalToRegex("/foo*")).toEqual(/^\/foo.*$/);
7 | expect(externalToRegex("*.png")).toEqual(/^.*\.png$/);
8 | });
9 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: read
13 | id-token: write # The OIDC ID token is used for authentication with JSR.
14 | steps:
15 | - uses: actions/checkout@v4
16 | - run: npx jsr publish
17 |
--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@deno/esbuild-plugin",
3 | "unstable": ["raw-imports"],
4 | "version": "1.2.1",
5 | "license": "MIT",
6 | "exports": "./src/mod.ts",
7 | "compilerOptions": {
8 | "jsx": "react-jsx",
9 | "jsxImportSource": "preact"
10 | },
11 | "imports": {
12 | "@deno/loader": "jsr:@deno/loader@^0.3.10",
13 | "@std/expect": "jsr:@std/expect@^1.0.16",
14 | "@std/path": "jsr:@std/path@^1.1.1",
15 | "esbuild": "npm:esbuild@^0.25.5",
16 | "fflate": "npm:fflate@^0.8.2",
17 | "mime-db": "npm:mime-db@^1.54.0",
18 | "preact": "npm:preact@^10.26.8",
19 | "mapped": "./tests/fixtures/simple.ts"
20 | },
21 | "lint": {
22 | "rules": {
23 | "include": ["no-console"],
24 | "exclude": ["no-import-prefix", "no-unversioned-import"]
25 | }
26 | },
27 | "publish": {
28 | "exclude": [".github/", "tests/"]
29 | },
30 | "exclude": ["./tests/fixtures/virtual.ts"]
31 | }
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deno esbuild plugin
2 |
3 | This package adds support for Deno-style resolution and loading to
4 | [`esbuild`](https://esbuild.github.io/). It's based on
5 | [`@deno/loader`](https://jsr.io/@deno/loader).
6 |
7 | The key difference to
8 | [`@luca/esbuild-deno-loader`](https://jsr.io/@luca/esbuild-deno-loader) is that
9 | leverages a WASM build of the same Rust crates that Deno itself uses for
10 | resolving and loading modules.
11 |
12 | It supports the following specifiers:
13 |
14 | - `file:`
15 | - `data:`
16 | - `npm:`
17 | - `jsr:`
18 | - `http:` + `https:`
19 |
20 | ## Usage
21 |
22 | 1. Install this package
23 | 2. Import it and add it to the `esbuild` config.
24 |
25 | ```ts
26 | import * as esbuild from "esbuild";
27 | import { denoPlugin } from "@deno/esbuild-plugin";
28 |
29 | await esbuild.build({
30 | entryPoints: ["app.js"],
31 | bundle: true,
32 | outfile: "out.js",
33 | plugins: [denoPlugin()],
34 | });
35 | ```
36 |
37 | ## License
38 |
39 | MIT, see the [LICENSE file](./LICENSE).
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 the Deno authors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | test:
11 | runs-on: ${{ matrix.os }}
12 |
13 | strategy:
14 | fail-fast: false
15 | matrix:
16 | os: [macOS-latest, windows-latest, ubuntu-latest]
17 | include:
18 | - os: ubuntu-latest
19 | cache_path: ~/.cache/deno/
20 | - os: macos-latest
21 | cache_path: ~/Library/Caches/deno/
22 | - os: windows-latest
23 | cache_path: ~\AppData\Local\deno\
24 |
25 | steps:
26 | - name: Checkout repo
27 | uses: actions/checkout@v4
28 |
29 | - name: Setup Deno
30 | uses: denoland/setup-deno@v2
31 | with:
32 | cache: true
33 | deno-version: 2.x
34 |
35 | - name: Verify formatting
36 | if: startsWith(matrix.os, 'ubuntu')
37 | run: deno fmt --check
38 |
39 | - name: Run linter
40 | if: startsWith(matrix.os, 'ubuntu')
41 | run: deno lint
42 |
43 | - name: Type check
44 | run: deno check
45 |
46 | - name: Spell-check
47 | if: startsWith(matrix.os, 'ubuntu')
48 | uses: crate-ci/typos@master
49 |
50 | - name: Run tests
51 | run: deno test -A
52 |
--------------------------------------------------------------------------------
/src/plugin.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MediaType,
3 | RequestedModuleType,
4 | ResolutionMode,
5 | Workspace,
6 | type WorkspaceOptions,
7 | } from "@deno/loader";
8 | import type {
9 | Loader,
10 | OnLoadArgs,
11 | OnLoadResult,
12 | OnResolveArgs,
13 | OnResolveResult,
14 | Platform,
15 | Plugin,
16 | } from "esbuild";
17 | import * as path from "@std/path";
18 | import { isBuiltin } from "node:module";
19 |
20 | export interface DenoPluginOptions {
21 | /** Show debugging logs */
22 | debug?: boolean;
23 | /** Use this path to a `deno.json` instead of auto-discovering it. */
24 | configPath?: string;
25 | /** Don't transpile files when loading them */
26 | noTranspile?: boolean;
27 | /** Keep JSX as is, instead of transpiling it according to compilerOptions. */
28 | preserveJsx?: boolean;
29 | /**
30 | * Prefix for public environment variables that should be inlined during
31 | * bundling.
32 | * @example `FRESH_PUBLIC_`
33 | */
34 | publicEnvVarPrefix?: string;
35 | }
36 |
37 | /**
38 | * Create an instance of the Deno plugin for esbuild
39 | */
40 | export function denoPlugin(options: DenoPluginOptions = {}): Plugin {
41 | return {
42 | name: "deno",
43 | async setup(ctx) {
44 | const workspace = new Workspace({
45 | debug: options.debug,
46 | configPath: options.configPath,
47 | nodeConditions: ctx.initialOptions.conditions,
48 | noTranspile: options.noTranspile,
49 | preserveJsx: options.preserveJsx,
50 | platform: getPlatform(ctx.initialOptions.platform),
51 | });
52 |
53 | const loader = await workspace.createLoader();
54 |
55 | ctx.onDispose(() => {
56 | loader[Symbol.dispose]?.();
57 | });
58 |
59 | const externals = (ctx.initialOptions.external ?? []).map((item) =>
60 | externalToRegex(item)
61 | );
62 |
63 | const onResolve = async (
64 | args: OnResolveArgs,
65 | ): Promise => {
66 | if (
67 | isBuiltin(args.path) || externals.some((reg) => reg.test(args.path))
68 | ) {
69 | return {
70 | path: args.path,
71 | external: true,
72 | };
73 | }
74 | const kind =
75 | args.kind === "require-call" || args.kind === "require-resolve"
76 | ? ResolutionMode.Require
77 | : ResolutionMode.Import;
78 |
79 | try {
80 | const res = await loader.resolve(args.path, args.importer, kind);
81 |
82 | let namespace: string | undefined;
83 | if (res.startsWith("file:")) {
84 | namespace = "file";
85 | } else if (res.startsWith("http:")) {
86 | namespace = "http";
87 | } else if (res.startsWith("https:")) {
88 | namespace = "https";
89 | } else if (res.startsWith("npm:")) {
90 | namespace = "npm";
91 | } else if (res.startsWith("jsr:")) {
92 | namespace = "jsr";
93 | }
94 |
95 | const resolved = res.startsWith("file:")
96 | ? path.fromFileUrl(res)
97 | : res;
98 |
99 | return {
100 | path: resolved,
101 | namespace,
102 | };
103 | } catch (err) {
104 | const couldNotResolveReg =
105 | /not a dependency and not in import map|Relative import path ".*?" not prefixed with/;
106 |
107 | if (err instanceof Error && couldNotResolveReg.test(err.message)) {
108 | return null;
109 | }
110 |
111 | throw err;
112 | }
113 | };
114 |
115 | // Esbuild doesn't detect namespaces in entrypoints. We need
116 | // a catchall resolver for that.
117 | ctx.onResolve({ filter: /.*/ }, onResolve);
118 | ctx.onResolve({ filter: /.*/, namespace: "file" }, onResolve);
119 | ctx.onResolve({ filter: /.*/, namespace: "http" }, onResolve);
120 | ctx.onResolve({ filter: /.*/, namespace: "https" }, onResolve);
121 | ctx.onResolve({ filter: /.*/, namespace: "data" }, onResolve);
122 | ctx.onResolve({ filter: /.*/, namespace: "npm" }, onResolve);
123 | ctx.onResolve({ filter: /.*/, namespace: "jsr" }, onResolve);
124 |
125 | const onLoad = async (
126 | args: OnLoadArgs,
127 | ): Promise => {
128 | const url =
129 | args.path.startsWith("http:") || args.path.startsWith("https:") ||
130 | args.path.startsWith("npm:") || args.path.startsWith("jsr:")
131 | ? args.path
132 | : path.toFileUrl(args.path).toString();
133 |
134 | const moduleType = getModuleType(args.path, args.with);
135 | const res = await loader.load(url, moduleType);
136 |
137 | if (res.kind === "external") {
138 | return null;
139 | }
140 |
141 | const esbuildLoader = mediaToLoader(res.mediaType);
142 |
143 | const envPrefix = options.publicEnvVarPrefix;
144 | if (
145 | envPrefix &&
146 | moduleType === RequestedModuleType.Default
147 | ) {
148 | let code = new TextDecoder().decode(res.code);
149 |
150 | code = code.replaceAll(
151 | /Deno\.env\.get\(["']([^)]+)['"]\)|process\.env\.([\w_-]+)/g,
152 | (m, name, processName) => {
153 | if (name !== undefined && name.startsWith(envPrefix)) {
154 | return JSON.stringify(Deno.env.get(name));
155 | }
156 | if (
157 | processName !== undefined && processName.startsWith(envPrefix)
158 | ) {
159 | return JSON.stringify(Deno.env.get(processName));
160 | }
161 | return m;
162 | },
163 | );
164 |
165 | return {
166 | contents: code,
167 | loader: esbuildLoader,
168 | };
169 | }
170 |
171 | return {
172 | contents: res.code,
173 | loader: esbuildLoader,
174 | };
175 | };
176 | ctx.onLoad({ filter: /.*/, namespace: "file" }, onLoad);
177 | ctx.onLoad({ filter: /.*/, namespace: "jsr" }, onLoad);
178 | ctx.onLoad({ filter: /.*/, namespace: "npm" }, onLoad);
179 | ctx.onLoad({ filter: /.*/, namespace: "http" }, onLoad);
180 | ctx.onLoad({ filter: /.*/, namespace: "https" }, onLoad);
181 | ctx.onLoad({ filter: /.*/, namespace: "data" }, onLoad);
182 | },
183 | };
184 | }
185 |
186 | function mediaToLoader(type: MediaType): Loader {
187 | switch (type) {
188 | case MediaType.Jsx:
189 | return "jsx";
190 | case MediaType.JavaScript:
191 | case MediaType.Mjs:
192 | case MediaType.Cjs:
193 | return "js";
194 | case MediaType.TypeScript:
195 | case MediaType.Mts:
196 | case MediaType.Dmts:
197 | case MediaType.Dcts:
198 | return "ts";
199 | case MediaType.Tsx:
200 | return "tsx";
201 | case MediaType.Css:
202 | return "css";
203 | case MediaType.Json:
204 | return "json";
205 | case MediaType.Html:
206 | return "default";
207 | case MediaType.Sql:
208 | return "default";
209 | case MediaType.Wasm:
210 | return "binary";
211 | case MediaType.SourceMap:
212 | return "json";
213 | case MediaType.Unknown:
214 | return "default";
215 | default:
216 | return "default";
217 | }
218 | }
219 |
220 | function getPlatform(
221 | platform: Platform | undefined,
222 | ): WorkspaceOptions["platform"] {
223 | switch (platform) {
224 | case "browser":
225 | return "browser";
226 | case "node":
227 | return "node";
228 | case "neutral":
229 | default:
230 | return undefined;
231 | }
232 | }
233 |
234 | function getModuleType(
235 | file: string,
236 | withArgs: Record,
237 | ): RequestedModuleType {
238 | switch (withArgs.type) {
239 | case "text":
240 | return RequestedModuleType.Text;
241 | case "bytes":
242 | return RequestedModuleType.Bytes;
243 | case "json":
244 | return RequestedModuleType.Json;
245 | default:
246 | if (file.endsWith(".json")) {
247 | return RequestedModuleType.Json;
248 | }
249 | return RequestedModuleType.Default;
250 | }
251 | }
252 |
253 | // For some reason esbuild passes external specifiers to plugins.
254 | // See: https://esbuild.github.io/api/#external
255 | export function externalToRegex(external: string): RegExp {
256 | return new RegExp(
257 | "^" + external.replace(/[-/\\^$+?.()|[\]{}]/g, "\\$&").replace(
258 | /\*/g,
259 | ".*",
260 | ) + "$",
261 | );
262 | }
263 |
--------------------------------------------------------------------------------
/tests/bundle.test.ts:
--------------------------------------------------------------------------------
1 | import { expect } from "@std/expect";
2 | import { build, type BuildOptions } from "esbuild";
3 | import { denoPlugin } from "@deno/esbuild-plugin";
4 | import * as path from "@std/path";
5 |
6 | async function testEsbuild(options: {
7 | jsx?: BuildOptions["jsx"];
8 | jsxDev?: BuildOptions["jsxDev"];
9 | jsxImportSource?: BuildOptions["jsxImportSource"];
10 | entryPoints: BuildOptions["entryPoints"];
11 | plugins?: BuildOptions["plugins"];
12 | external?: BuildOptions["external"];
13 | platform?: BuildOptions["platform"];
14 | }) {
15 | const res = await build({
16 | entryPoints: options.entryPoints,
17 | write: false,
18 | format: "esm",
19 | bundle: true,
20 | jsx: options.jsx ?? "automatic",
21 | jsxDev: options.jsxDev ?? undefined,
22 | jsxImportSource: options.jsxImportSource ?? "preact",
23 | plugins: [denoPlugin(), ...(options.plugins ?? [])],
24 | external: options.external,
25 | platform: options.platform,
26 | });
27 |
28 | expect(res.errors).toEqual([]);
29 | expect(res.warnings).toEqual([]);
30 | expect(res.outputFiles.length).toEqual(1);
31 |
32 | return res;
33 | }
34 |
35 | function getFixture(name: string) {
36 | return path.join(import.meta.dirname!, "fixtures", name);
37 | }
38 |
39 | Deno.test({
40 | name: "entrypoints - string array",
41 | fn: async () => {
42 | await testEsbuild({
43 | entryPoints: [getFixture("simple.ts")],
44 | });
45 | },
46 | sanitizeResources: false,
47 | sanitizeOps: false,
48 | });
49 |
50 | Deno.test({
51 | name: "entrypoints - object array",
52 | fn: async () => {
53 | await testEsbuild({
54 | entryPoints: [
55 | {
56 | in: getFixture("simple.ts"),
57 | out: "foo",
58 | },
59 | ],
60 | });
61 | },
62 | sanitizeResources: false,
63 | sanitizeOps: false,
64 | });
65 |
66 | Deno.test({
67 | name: "entrypoints - record",
68 | fn: async () => {
69 | await testEsbuild({
70 | entryPoints: {
71 | foo: getFixture("simple.ts"),
72 | },
73 | });
74 | },
75 | sanitizeResources: false,
76 | sanitizeOps: false,
77 | });
78 |
79 | Deno.test({
80 | name: "resolves/loads - mapped",
81 | fn: async () => {
82 | await testEsbuild({
83 | entryPoints: [getFixture("preact.ts")],
84 | });
85 | },
86 | sanitizeResources: false,
87 | sanitizeOps: false,
88 | });
89 |
90 | Deno.test({
91 | name: "resolves/loads - https:",
92 | fn: async () => {
93 | await testEsbuild({
94 | entryPoints: [getFixture("https.ts")],
95 | });
96 | },
97 | sanitizeResources: false,
98 | sanitizeOps: false,
99 | });
100 |
101 | Deno.test({
102 | name: "resolves/loads - npm:",
103 | fn: async () => {
104 | await testEsbuild({
105 | entryPoints: [getFixture("npm.ts")],
106 | });
107 | },
108 | sanitizeResources: false,
109 | sanitizeOps: false,
110 | });
111 |
112 | Deno.test({
113 | name: "resolves/loads - jsr:",
114 | fn: async () => {
115 | await testEsbuild({
116 | entryPoints: [getFixture("jsr.ts")],
117 | });
118 | },
119 | sanitizeResources: false,
120 | sanitizeOps: false,
121 | });
122 |
123 | Deno.test({
124 | name: "resolves/loads - node:",
125 | fn: async () => {
126 | await testEsbuild({
127 | entryPoints: [getFixture("node.ts")],
128 | });
129 | },
130 | sanitizeResources: false,
131 | sanitizeOps: false,
132 | });
133 |
134 | Deno.test({
135 | name: "resolves/loads - file:",
136 | fn: async () => {
137 | await testEsbuild({
138 | entryPoints: [path.toFileUrl(getFixture("simple.ts")).href],
139 | });
140 | },
141 | sanitizeResources: false,
142 | sanitizeOps: false,
143 | });
144 |
145 | Deno.test({
146 | name: "resolves/loads - tsx",
147 | fn: async () => {
148 | const res = await testEsbuild({
149 | entryPoints: [path.toFileUrl(getFixture("jsx.tsx")).href],
150 | });
151 |
152 | expect(res.outputFiles[0].text).toContain('"div"');
153 | },
154 | sanitizeResources: false,
155 | sanitizeOps: false,
156 | });
157 |
158 | Deno.test({
159 | name: "plugins can participate in resolution",
160 | fn: async () => {
161 | const res = await testEsbuild({
162 | entryPoints: [getFixture("mapped.ts")],
163 | plugins: [
164 | {
165 | name: "test",
166 | setup(ctx) {
167 | ctx.onResolve({ filter: /mapped$/ }, () => {
168 | return {
169 | path: getFixture("simple.ts"),
170 | namespace: "test-internal",
171 | };
172 | });
173 |
174 | ctx.onLoad({ filter: /.*/, namespace: "test-internal" }, () => {
175 | return {
176 | contents: "hey",
177 | };
178 | });
179 | },
180 | },
181 | ],
182 | });
183 |
184 | expect(res.outputFiles[0].text).toContain("hey");
185 | },
186 | sanitizeResources: false,
187 | sanitizeOps: false,
188 | });
189 |
190 | Deno.test({
191 | name: "jsx import source",
192 | fn: async () => {
193 | const res = await testEsbuild({
194 | jsx: "automatic",
195 | jsxDev: true,
196 | jsxImportSource: "preact",
197 | entryPoints: [getFixture("jsx.tsx")],
198 | });
199 |
200 | expect(res.outputFiles[0].text).toContain("it works");
201 | },
202 | sanitizeResources: false,
203 | sanitizeOps: false,
204 | });
205 |
206 | Deno.test({
207 | name: "with json",
208 | fn: async () => {
209 | const res = await testEsbuild({
210 | entryPoints: [getFixture("with-json.ts")],
211 | });
212 |
213 | expect(res.outputFiles[0].text).toContain("it works");
214 | },
215 | sanitizeResources: false,
216 | sanitizeOps: false,
217 | });
218 |
219 | Deno.test({
220 | name: "with text",
221 | fn: async () => {
222 | const res = await testEsbuild({
223 | entryPoints: [getFixture("with-text.ts")],
224 | });
225 |
226 | expect(res.outputFiles[0].text).toContain("it works");
227 | },
228 | sanitizeResources: false,
229 | sanitizeOps: false,
230 | });
231 |
232 | Deno.test({
233 | name: "with bytes",
234 | fn: async () => {
235 | const res = await testEsbuild({
236 | entryPoints: [getFixture("with-bytes.ts")],
237 | });
238 |
239 | expect(res.outputFiles[0].text).toContain("it works");
240 | },
241 | sanitizeResources: false,
242 | sanitizeOps: false,
243 | });
244 |
245 | Deno.test({
246 | name: "keep external specifiers",
247 | fn: async () => {
248 | const res = await testEsbuild({
249 | entryPoints: [getFixture("external.ts")],
250 | external: ["mapped"],
251 | });
252 |
253 | expect(res.outputFiles[0].text).toContain(`import "mapped"`);
254 | },
255 | sanitizeResources: false,
256 | sanitizeOps: false,
257 | });
258 |
259 | Deno.test({
260 | name: "ignore modules it cannot resolve",
261 | fn: async () => {
262 | const res = await testEsbuild({
263 | entryPoints: [":::"],
264 | plugins: [
265 | {
266 | name: ":::",
267 | setup(build) {
268 | build.onResolve({ filter: /:::/ }, () => {
269 | return {
270 | path: getFixture("simple.ts"),
271 | };
272 | });
273 | },
274 | },
275 | ],
276 | });
277 |
278 | expect(res.outputFiles[0].text).toContain("hey");
279 | },
280 | sanitizeResources: false,
281 | sanitizeOps: false,
282 | });
283 |
284 | Deno.test({
285 | name: "respects 'platform: browser'",
286 | fn: async () => {
287 | const res = await testEsbuild({
288 | entryPoints: [getFixture("platform.ts")],
289 | platform: "browser",
290 | });
291 |
292 | expect(res.outputFiles[0].text).not.toContain("worker_threads");
293 | },
294 | sanitizeResources: false,
295 | sanitizeOps: false,
296 | });
297 |
298 | Deno.test({
299 | name: "bundle json require in npm module",
300 | fn: async () => {
301 | // This threw previously.
302 | await testEsbuild({
303 | entryPoints: [getFixture("mime-db.ts")],
304 | });
305 | },
306 | sanitizeResources: false,
307 | sanitizeOps: false,
308 | });
309 |
310 | Deno.test({
311 | name: "support public env vars",
312 | fn: async () => {
313 | Deno.env.set("TEST_PUBLIC_FOO", "foo-public-var");
314 | const res = await build({
315 | entryPoints: [getFixture("env-vars.ts")],
316 | write: false,
317 | format: "esm",
318 | bundle: true,
319 | plugins: [denoPlugin({ publicEnvVarPrefix: "TEST_PUBLIC_" })],
320 | });
321 |
322 | const code = res.outputFiles[0].text;
323 |
324 | const binString = String.fromCodePoint(...res.outputFiles[0].contents);
325 | const data = btoa(binString);
326 |
327 | expect(code).not.toContain("Deno.env");
328 | expect(code).not.toContain("process.env");
329 |
330 | const dataURL = `data:text/javascript;base64,${data}`;
331 |
332 | // then import that as new JS module, which will fail.
333 | const mod = await import(dataURL);
334 |
335 | expect(mod.deno).toEqual("foo-public-var");
336 | expect(mod.deno2).toEqual("foo-public-var");
337 | expect(mod.node).toEqual("foo-public-var");
338 | },
339 | sanitizeResources: false,
340 | sanitizeOps: false,
341 | });
342 |
--------------------------------------------------------------------------------
/deno.lock:
--------------------------------------------------------------------------------
1 | {
2 | "version": "5",
3 | "specifiers": {
4 | "jsr:@deno/loader@~0.3.10": "0.3.10",
5 | "jsr:@marvinh-test/fresh-island@*": "0.0.1",
6 | "jsr:@std/assert@1": "1.0.13",
7 | "jsr:@std/assert@^1.0.13": "1.0.13",
8 | "jsr:@std/expect@^1.0.16": "1.0.16",
9 | "jsr:@std/internal@^1.0.6": "1.0.10",
10 | "jsr:@std/internal@^1.0.7": "1.0.10",
11 | "jsr:@std/internal@^1.0.9": "1.0.10",
12 | "jsr:@std/path@^1.1.1": "1.1.1",
13 | "npm:@preact/signals@^1.2.3": "1.3.2_preact@10.26.8",
14 | "npm:@types/node@*": "22.15.15",
15 | "npm:esbuild@~0.25.5": "0.25.5",
16 | "npm:fflate@~0.8.2": "0.8.2",
17 | "npm:mime-db@^1.54.0": "1.54.0",
18 | "npm:preact@*": "10.26.8",
19 | "npm:preact@^10.22.0": "10.26.8",
20 | "npm:preact@^10.26.8": "10.26.8"
21 | },
22 | "jsr": {
23 | "@deno/loader@0.3.10": {
24 | "integrity": "a9c0aa44a0499e7fecef52c29fbc206c1c8f8946388f25d9d0789a23313bfd43"
25 | },
26 | "@marvinh-test/fresh-island@0.0.1": {
27 | "integrity": "890f2595e60b1aaeaa8d73c6ad2c1247d4c5b895387df230f7f3b2a4da29b585",
28 | "dependencies": [
29 | "npm:@preact/signals",
30 | "npm:preact@^10.22.0",
31 | "npm:preact@^10.26.8"
32 | ]
33 | },
34 | "@std/assert@1.0.13": {
35 | "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29",
36 | "dependencies": [
37 | "jsr:@std/internal@^1.0.6"
38 | ]
39 | },
40 | "@std/expect@1.0.16": {
41 | "integrity": "ceeef6dda21f256a5f0f083fcc0eaca175428b523359a9b1d9b3a1df11cc7391",
42 | "dependencies": [
43 | "jsr:@std/assert@^1.0.13",
44 | "jsr:@std/internal@^1.0.7"
45 | ]
46 | },
47 | "@std/internal@1.0.8": {
48 | "integrity": "fc66e846d8d38a47cffd274d80d2ca3f0de71040f855783724bb6b87f60891f5"
49 | },
50 | "@std/internal@1.0.9": {
51 | "integrity": "bdfb97f83e4db7a13e8faab26fb1958d1b80cc64366501af78a0aee151696eb8"
52 | },
53 | "@std/internal@1.0.10": {
54 | "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7"
55 | },
56 | "@std/path@1.1.1": {
57 | "integrity": "fe00026bd3a7e6a27f73709b83c607798be40e20c81dde655ce34052fd82ec76",
58 | "dependencies": [
59 | "jsr:@std/internal@^1.0.9"
60 | ]
61 | }
62 | },
63 | "npm": {
64 | "@esbuild/aix-ppc64@0.25.5": {
65 | "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
66 | "os": ["aix"],
67 | "cpu": ["ppc64"]
68 | },
69 | "@esbuild/android-arm64@0.25.5": {
70 | "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
71 | "os": ["android"],
72 | "cpu": ["arm64"]
73 | },
74 | "@esbuild/android-arm@0.25.5": {
75 | "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
76 | "os": ["android"],
77 | "cpu": ["arm"]
78 | },
79 | "@esbuild/android-x64@0.25.5": {
80 | "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
81 | "os": ["android"],
82 | "cpu": ["x64"]
83 | },
84 | "@esbuild/darwin-arm64@0.25.5": {
85 | "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
86 | "os": ["darwin"],
87 | "cpu": ["arm64"]
88 | },
89 | "@esbuild/darwin-x64@0.25.5": {
90 | "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
91 | "os": ["darwin"],
92 | "cpu": ["x64"]
93 | },
94 | "@esbuild/freebsd-arm64@0.25.5": {
95 | "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
96 | "os": ["freebsd"],
97 | "cpu": ["arm64"]
98 | },
99 | "@esbuild/freebsd-x64@0.25.5": {
100 | "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
101 | "os": ["freebsd"],
102 | "cpu": ["x64"]
103 | },
104 | "@esbuild/linux-arm64@0.25.5": {
105 | "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
106 | "os": ["linux"],
107 | "cpu": ["arm64"]
108 | },
109 | "@esbuild/linux-arm@0.25.5": {
110 | "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
111 | "os": ["linux"],
112 | "cpu": ["arm"]
113 | },
114 | "@esbuild/linux-ia32@0.25.5": {
115 | "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
116 | "os": ["linux"],
117 | "cpu": ["ia32"]
118 | },
119 | "@esbuild/linux-loong64@0.25.5": {
120 | "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
121 | "os": ["linux"],
122 | "cpu": ["loong64"]
123 | },
124 | "@esbuild/linux-mips64el@0.25.5": {
125 | "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
126 | "os": ["linux"],
127 | "cpu": ["mips64el"]
128 | },
129 | "@esbuild/linux-ppc64@0.25.5": {
130 | "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
131 | "os": ["linux"],
132 | "cpu": ["ppc64"]
133 | },
134 | "@esbuild/linux-riscv64@0.25.5": {
135 | "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
136 | "os": ["linux"],
137 | "cpu": ["riscv64"]
138 | },
139 | "@esbuild/linux-s390x@0.25.5": {
140 | "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
141 | "os": ["linux"],
142 | "cpu": ["s390x"]
143 | },
144 | "@esbuild/linux-x64@0.25.5": {
145 | "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
146 | "os": ["linux"],
147 | "cpu": ["x64"]
148 | },
149 | "@esbuild/netbsd-arm64@0.25.5": {
150 | "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
151 | "os": ["netbsd"],
152 | "cpu": ["arm64"]
153 | },
154 | "@esbuild/netbsd-x64@0.25.5": {
155 | "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
156 | "os": ["netbsd"],
157 | "cpu": ["x64"]
158 | },
159 | "@esbuild/openbsd-arm64@0.25.5": {
160 | "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
161 | "os": ["openbsd"],
162 | "cpu": ["arm64"]
163 | },
164 | "@esbuild/openbsd-x64@0.25.5": {
165 | "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
166 | "os": ["openbsd"],
167 | "cpu": ["x64"]
168 | },
169 | "@esbuild/sunos-x64@0.25.5": {
170 | "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
171 | "os": ["sunos"],
172 | "cpu": ["x64"]
173 | },
174 | "@esbuild/win32-arm64@0.25.5": {
175 | "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
176 | "os": ["win32"],
177 | "cpu": ["arm64"]
178 | },
179 | "@esbuild/win32-ia32@0.25.5": {
180 | "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
181 | "os": ["win32"],
182 | "cpu": ["ia32"]
183 | },
184 | "@esbuild/win32-x64@0.25.5": {
185 | "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
186 | "os": ["win32"],
187 | "cpu": ["x64"]
188 | },
189 | "@preact/signals-core@1.9.0": {
190 | "integrity": "sha512-uUgFHJLWxb33rfCtb1g+1e3Rg7Jl5EALhGTHlQ5Y0w37OF+fdidYdYEE6crbpUOYDOjlmelIWf0ulXr1ggfUkg=="
191 | },
192 | "@preact/signals@1.3.2_preact@10.26.8": {
193 | "integrity": "sha512-naxcJgUJ6BTOROJ7C3QML7KvwKwCXQJYTc5L/b0eEsdYgPB6SxwoQ1vDGcS0Q7GVjAenVq/tXrybVdFShHYZWg==",
194 | "dependencies": [
195 | "@preact/signals-core",
196 | "preact"
197 | ]
198 | },
199 | "@types/node@22.15.15": {
200 | "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==",
201 | "dependencies": [
202 | "undici-types"
203 | ]
204 | },
205 | "esbuild@0.25.5": {
206 | "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
207 | "optionalDependencies": [
208 | "@esbuild/aix-ppc64",
209 | "@esbuild/android-arm",
210 | "@esbuild/android-arm64",
211 | "@esbuild/android-x64",
212 | "@esbuild/darwin-arm64",
213 | "@esbuild/darwin-x64",
214 | "@esbuild/freebsd-arm64",
215 | "@esbuild/freebsd-x64",
216 | "@esbuild/linux-arm",
217 | "@esbuild/linux-arm64",
218 | "@esbuild/linux-ia32",
219 | "@esbuild/linux-loong64",
220 | "@esbuild/linux-mips64el",
221 | "@esbuild/linux-ppc64",
222 | "@esbuild/linux-riscv64",
223 | "@esbuild/linux-s390x",
224 | "@esbuild/linux-x64",
225 | "@esbuild/netbsd-arm64",
226 | "@esbuild/netbsd-x64",
227 | "@esbuild/openbsd-arm64",
228 | "@esbuild/openbsd-x64",
229 | "@esbuild/sunos-x64",
230 | "@esbuild/win32-arm64",
231 | "@esbuild/win32-ia32",
232 | "@esbuild/win32-x64"
233 | ],
234 | "scripts": true,
235 | "bin": true
236 | },
237 | "fflate@0.8.2": {
238 | "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
239 | },
240 | "mime-db@1.54.0": {
241 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="
242 | },
243 | "preact@10.26.8": {
244 | "integrity": "sha512-1nMfdFjucm5hKvq0IClqZwK4FJkGXhRrQstOQ3P4vp8HxKrJEMFcY6RdBRVTdfQS/UlnX6gfbPuTvaqx/bDoeQ=="
245 | },
246 | "undici-types@6.21.0": {
247 | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
248 | }
249 | },
250 | "redirects": {
251 | "https://esm.sh/preact": "https://esm.sh/preact@10.26.8"
252 | },
253 | "remote": {
254 | "https://esm.sh/preact@10.26.8": "f6c6195e67293b6df707b53dd3075ca149604e1ba989c7832179b3c1b7e8a302",
255 | "https://esm.sh/preact@10.26.8/denonext/preact.mjs": "15cb76ed3f64c60ee55efae71db9561133cbb37af609921ddef445c4a543fad2"
256 | },
257 | "workspace": {
258 | "dependencies": [
259 | "jsr:@deno/loader@~0.3.10",
260 | "jsr:@std/expect@^1.0.16",
261 | "jsr:@std/path@^1.1.1",
262 | "npm:esbuild@~0.25.5",
263 | "npm:fflate@~0.8.2",
264 | "npm:mime-db@^1.54.0",
265 | "npm:preact@^10.26.8"
266 | ]
267 | }
268 | }
269 |
--------------------------------------------------------------------------------