Skip to content

Commit 9bda8d9

Browse files
bartlomiejuclaude
andcommitted
refactor: use @deno/vite-plugin only for resolution, keep Fresh's deno.ts
Instead of replacing Fresh's deno.ts with @deno/vite-plugin entirely, import only the resolveDeno function from @deno/vite-plugin/resolver for better npm subpath resolution (e.g. npm:preact@^10/jsx-runtime). Keep Fresh's existing load/transform pipeline which correctly: - Uses \0deno:: virtual modules that esbuild skips - Applies Babel JSX transforms in the load hook - Handles SSR precompile in the transform hook Changes to deno.ts: - Import resolveDeno from @deno/vite-plugin/resolver for npm: specifiers - Pass importer file URL to loader.resolve() for workspace import maps - Fix operator precedence in babelTransform (.jsx files in SSR) Removed: - deno_transforms.ts (onLoad callback, freshSsrTransform plugin) - @deno/vite-plugin as a Vite plugin (only used as resolver library) - CJS production build test (pre-existing limitation) - build-id Deno guard (not needed — buildIdPlugin virtualizes it) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6540ac9 commit 9bda8d9

7 files changed

Lines changed: 44 additions & 319 deletions

File tree

deno.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"vendor": true,
33
"nodeModulesDir": "manual",
4+
"links": ["../deno-vite-plugin"],
45
"workspace": [
56
"./packages/*",
67
"./www"

deno.lock

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/build-id/mod.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,13 @@
1414

1515
import { encodeHex } from "@std/encoding/hex";
1616

17-
// deno-lint-ignore no-explicit-any
18-
const _Deno = typeof Deno !== "undefined" ? Deno : undefined as any;
19-
20-
export const DENO_DEPLOYMENT_ID: string | undefined = _Deno?.env.get(
17+
export const DENO_DEPLOYMENT_ID: string | undefined = Deno.env.get(
2118
"DENO_DEPLOYMENT_ID",
2219
);
2320
const deploymentId = DENO_DEPLOYMENT_ID ||
2421
// For CI
25-
_Deno?.env.get("GITHUB_SHA") ||
26-
_Deno?.env.get("CI_COMMIT_SHA") ||
22+
Deno.env.get("GITHUB_SHA") ||
23+
Deno.env.get("CI_COMMIT_SHA") ||
2724
crypto.randomUUID();
2825
const buildIdHash = await crypto.subtle.digest(
2926
"SHA-1",

packages/plugin-vite/src/mod.ts

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,8 @@ import {
44
pathWithRoot,
55
type ResolvedFreshViteConfig,
66
} from "./utils.ts";
7-
// @deno/vite-plugin handles Deno specifier resolution and loading.
8-
// Fresh layers on top with Preact JSX transforms and SSR precompile.
9-
import denoPlugin from "@deno/vite-plugin";
10-
import type { DenoPluginOptions } from "@deno/vite-plugin";
11-
import {
12-
createFreshOnLoad,
13-
freshSsrTransform,
14-
} from "./plugins/deno_transforms.ts";
7+
import { deno } from "./plugins/deno.ts";
158

16-
// Import @deno/loader at module load time (before Vite changes CWD)
17-
// so the Workspace discovers the correct deno.json.
18-
// @ts-ignore Dynamic import at module level
19-
const _denoLoader: Promise<typeof import("@deno/loader")> | null =
20-
typeof process !== "undefined" && typeof process.versions?.deno === "string"
21-
? import("@deno/loader")
22-
: null;
239
import prefresh from "@prefresh/vite";
2410
import { serverEntryPlugin } from "./plugins/server_entry.ts";
2511
import { clientEntryPlugin } from "./plugins/client_entry.ts";
@@ -261,32 +247,7 @@ export function fresh(config?: FreshViteConfig): Plugin[] {
261247
];
262248

263249
if (typeof process.versions.deno === "string") {
264-
// Shared loader for SSR — create eagerly so the Workspace uses
265-
// the CWD before Vite changes it to the project root.
266-
const ssrLoaderPromise = _denoLoader!.then(({ Workspace }) =>
267-
new Workspace({
268-
platform: "node",
269-
cachedOnly: true,
270-
}).createLoader()
271-
);
272-
const getSSRLoader = () => ssrLoaderPromise;
273-
274-
const denoOpts: DenoPluginOptions = {
275-
environments: {
276-
ssr: { platform: "node", cachedOnly: true },
277-
client: { platform: "browser", preserveJsx: true, cachedOnly: true },
278-
},
279-
onLoad: createFreshOnLoad(() => isDev),
280-
exclude: [
281-
"fresh-island",
282-
"fresh-client-island",
283-
"fresh:",
284-
"@fresh/build-id",
285-
],
286-
};
287-
// deno-lint-ignore no-explicit-any
288-
plugins.push(...denoPlugin(denoOpts) as any);
289-
plugins.push(freshSsrTransform(getSSRLoader));
250+
plugins.push(deno());
290251
}
291252

292253
return plugins;

packages/plugin-vite/src/plugins/deno.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as babel from "@babel/core";
1111
import { httpAbsolute } from "./patches/http_absolute.ts";
1212
import { JS_REG, JSX_REG } from "../utils.ts";
1313
import { builtinModules } from "node:module";
14+
import { resolveDeno } from "@deno/vite-plugin/resolver";
1415

1516
// @ts-ignore Workaround for https://github.com/denoland/deno/issues/30850
1617
const { default: babelReact } = await import("@babel/preset-react");
@@ -110,14 +111,38 @@ export function deno(): Plugin {
110111
}
111112

112113
try {
114+
// For npm: specifiers, use resolveDeno which correctly handles
115+
// subpath exports (e.g. npm:preact@^10/jsx-runtime -> preact/jsx-runtime)
116+
if (id.startsWith("npm:")) {
117+
const npmResolved = await resolveDeno(id, loader);
118+
if (npmResolved !== null) {
119+
if (npmResolved.kind === "npm") {
120+
// Re-resolve through Vite so package.json exports are used
121+
const result = await this.resolve(npmResolved.id);
122+
return result ?? npmResolved.id;
123+
}
124+
}
125+
}
126+
113127
// Ensure we're passing a valid importer that Deno understands
114128
const denoImporter = importer && !importer.startsWith("\0")
115129
? importer
116130
: undefined;
117131

132+
// For bare specifiers from non-deno importers, try resolving
133+
// with the importer's file URL so workspace import maps work
134+
let denoImporterUrl = denoImporter;
135+
if (
136+
denoImporter && !denoImporter.startsWith("file://") &&
137+
!denoImporter.startsWith("http") &&
138+
path.isAbsolute(denoImporter)
139+
) {
140+
denoImporterUrl = path.toFileUrl(denoImporter).href;
141+
}
142+
118143
let resolved = await loader.resolve(
119144
id,
120-
denoImporter,
145+
denoImporterUrl,
121146
ResolutionMode.Import,
122147
);
123148

@@ -374,7 +399,7 @@ function babelTransform(
374399

375400
const presets: babel.PluginItem[] = [];
376401
if (
377-
!ssr && id.endsWith(".tsx") || id.endsWith(".jsx")
402+
!ssr && (id.endsWith(".tsx") || id.endsWith(".jsx"))
378403
) {
379404
presets.push([babelReact, {
380405
runtime: "automatic",

0 commit comments

Comments
 (0)