Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import assert from 'node:assert';
import { builtinModules } from 'node:module';
import * as vite from 'vite';
import { getNodeCompatExternals } from './node-js-compat';
import { INIT_PATH, UNKNOWN_HOST } from './shared';
import { toMiniflareRequest } from './utils';
import type { ResolvedPluginConfig, WorkerConfig } from './plugin-config';
Expand Down Expand Up @@ -133,6 +131,7 @@ export function createCloudflareEnvironmentOptions(
noExternal: true,
// We want to use `workerd` package exports if available (e.g. for postgres).
conditions: ['workerd', 'module', 'browser', 'development|production'],
builtins: [...cloudflareBuiltInModules],
},
dev: {
createEnvironment(name, config) {
Expand All @@ -152,17 +151,11 @@ export function createCloudflareEnvironmentOptions(
// dev pre-bundling crawling (were we not to set this input field we'd have to appropriately set
// optimizeDeps.entries in the dev config)
input: workerConfig.main,
external: [...cloudflareBuiltInModules, ...getNodeCompatExternals()],
},
},
optimizeDeps: {
// Note: ssr pre-bundling is opt-in and we need to enable it by setting `noDiscovery` to false
noDiscovery: false,
exclude: [
...cloudflareBuiltInModules,
// we have to exclude all node modules to work in dev-mode not just the unenv externals...
...builtinModules.concat(builtinModules.map((m) => `node:${m}`)),
],
esbuildOptions: {
platform: 'neutral',
resolveExtensions: [
Expand Down
128 changes: 128 additions & 0 deletions packages/vite-plugin-cloudflare/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import assert from 'node:assert';
import * as fs from 'node:fs';
import { builtinModules } from 'node:module';
import path from 'node:path';
import { createMiddleware } from '@hattip/adapter-node';
import { Miniflare } from 'miniflare';
import * as vite from 'vite';
import { unstable_getMiniflareWorkerOptions } from 'wrangler';
import { getRouterWorker } from './assets';
import {
createCloudflareEnvironmentOptions,
Expand All @@ -15,7 +17,9 @@ import {
} from './miniflare-options';
import {
getNodeCompatAliases,
getNodeCompatExternals,
injectGlobalCode,
isNodeCompat,
resolveNodeAliases,
} from './node-js-compat';
import { resolvePluginConfig } from './plugin-config';
Expand Down Expand Up @@ -92,6 +96,13 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin {
},
};
},
configResolved(config) {
addNodeBuiltinsToWorkerEnvironmentsIfNeeded(config, resolvedPluginConfig);
addBuildNodeExternalsToWorkerEnvironmentsIfNeeded(
config,
resolvedPluginConfig,
);
},
configEnvironment(name, options) {
if (resolvedPluginConfig.type === 'workers') {
options.build = {
Expand Down Expand Up @@ -257,3 +268,120 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin {
},
};
}

/**
* In the resolved configuration adds to all the worker environments the necessary node builtin config options
* if the worker is under nodejs compat
*
* @param resolvedConfig the vite resolved config (that will be side-effectfully updated)
* @param resolvedPluginConfig the resolved plugin config
*/
function addNodeBuiltinsToWorkerEnvironmentsIfNeeded(
resolvedConfig: vite.ResolvedConfig,
resolvedPluginConfig: ResolvedPluginConfig,
) {
if (resolvedPluginConfig.type === 'workers') {
Object.entries(resolvedPluginConfig.workers).map(
([environmentName, workerConfig]) => {
invariant(
resolvedConfig.environments[environmentName],
`environment with name "${environmentName}" not found`,
);

const nodeCompat = isNodeCompat(workerConfig);

const nodeCompatModules = nodeCompat
? [...builtinModules.concat(builtinModules.map((m) => `node:${m}`))]
: [];

resolvedConfig.environments[environmentName].resolve.builtins = [
...resolvedConfig.environments[environmentName].resolve.builtins,
...nodeCompatModules,
];
resolvedConfig.environments[environmentName].optimizeDeps.exclude = [
...(resolvedConfig.environments[environmentName].optimizeDeps
.exclude ?? []),
...nodeCompatModules,
];
},
);
}
}

/**
* Adds to the worker resolved configuration the necessary node external modules config options if the worker is under nodejs compat
* (this config is under `build.rollupOptions.externals`)
*
* Note: this includes polyfill methods as well as nodejs builtins (given the `resolve.builtins` option maybe externals here should
* not include such modules...?)
*
* @param resolvedConfig the vite resolved config (that will be side-effectfully updated)
* @param name the name of the worker
* @param resolvedPluginConfig the normalized/resolved plugin config
*/
function addBuildNodeExternalsToWorkerEnvironmentsIfNeeded(
resolvedConfig: vite.ResolvedConfig,
resolvedPluginConfig: ResolvedPluginConfig,
) {
if (resolvedPluginConfig.type === 'workers') {
Object.entries(resolvedPluginConfig.workers).map(
([environmentName, workerConfig]) => {
invariant(
resolvedConfig.environments[environmentName],
`environment with name "${environmentName}" not found`,
);

const nodeCompat = isNodeCompat(workerConfig);

if (!nodeCompat) {
return;
}

const nodeCompatExternals = [...getNodeCompatExternals()];

const existingExternalOption =
resolvedConfig.environments[environmentName].build.rollupOptions
.external;
if (!existingExternalOption) {
resolvedConfig.environments[
environmentName
].build.rollupOptions.external = [...nodeCompatExternals];
return;
}

if (typeof existingExternalOption !== 'function') {
const existingExternals = Array.isArray(existingExternalOption)
? existingExternalOption
: [existingExternalOption];
resolvedConfig.environments[
environmentName
].build.rollupOptions.external = [
...nodeCompatExternals,
...existingExternals,
];
return;
}

const nodeCompatExternalsSet = new Set(nodeCompatExternals);

resolvedConfig.environments[
environmentName
].build.rollupOptions.external = (
source: string,
importer: string | undefined,
isResolved: boolean,
) => {
const existingOptionResult = existingExternalOption(
source,
importer,
isResolved,
);
if (existingOptionResult) {
return existingOptionResult;
}
return nodeCompatExternalsSet.has(source);
};
},
);
}
}
11 changes: 0 additions & 11 deletions packages/vite-plugin-cloudflare/src/miniflare-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,17 +290,6 @@ export function getDevMiniflareOptions(

const [moduleId] = invokePayloadData.data;

// For some reason we need this here for cloudflare built-ins (e.g. `cloudflare:workers`) but not for node built-ins (e.g. `node:path`)
// See https://github.com/flarelabs-net/vite-plugin-cloudflare/issues/46
if (moduleId.startsWith('cloudflare:')) {
const result = {
externalize: moduleId,
type: 'builtin',
} satisfies vite.FetchResult;

return new MiniflareResponse(JSON.stringify({ result }));
}

// Sometimes Vite fails to resolve built-ins and converts them to "url-friendly" ids
// that start with `/@id/...`.
if (moduleId.startsWith('/@id/')) {
Expand Down
Loading
Loading