Skip to content

Commit

Permalink
Support environments in Worker configs (#127)
Browse files Browse the repository at this point in the history
* Added cloudflare-env playground

* Updated wrangler and miniflare dependencies

* Updated Workflow playgrounds and tests

* Added support for CLOUDFLARE_ENV

* Added tests for CLOUDFLARE_ENV

* Update playgrounds and compatibility dates

* Made compatibility date required

* Made required fields non-nullable in types

* Use loadEnv to load .env files

* Added tests for CLOUDFLARE_ENV in .env.[mode] file

* Adding missingFieldErrorMessage function and included Worker environment in error message
  • Loading branch information
jamesopstad authored Jan 15, 2025
1 parent b852a42 commit 917395c
Show file tree
Hide file tree
Showing 48 changed files with 1,519 additions and 131 deletions.
2 changes: 1 addition & 1 deletion packages/vite-plugin-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
},
"dependencies": {
"@hattip/adapter-node": "^0.0.49",
"miniflare": "3.20241205.0",
"miniflare": "3.20241230.1",
"unenv": "catalog:default",
"ws": "^8.18.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
name = "my-worker"
main = "./index.ts"
compatibility_date = "2024-09-09"
compatibility_date = "2024-12-30"
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "my-worker"
main = "./index.ts"
compatibility_date = "2024-09-09"
compatibility_date = "2024-12-30"

base_dir = "./src"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ describe('getWorkerConfig', () => {
test('should return a simple raw config', () => {
const { raw } = getWorkerConfig(
fileURLToPath(new URL('fixtures/simple-wrangler.toml', import.meta.url)),
undefined,
);
expect(typeof raw).toEqual('object');

Expand Down Expand Up @@ -35,6 +36,7 @@ describe('getWorkerConfig', () => {
test('should return a simple config without non-applicable fields', () => {
const { config } = getWorkerConfig(
fileURLToPath(new URL('fixtures/simple-wrangler.toml', import.meta.url)),
undefined,
);
expect(typeof config).toEqual('object');

Expand All @@ -44,6 +46,7 @@ describe('getWorkerConfig', () => {
test("should not return any non-applicable config when there isn't any", () => {
const { nonApplicable } = getWorkerConfig(
fileURLToPath(new URL('fixtures/simple-wrangler.toml', import.meta.url)),
undefined,
);
expect(nonApplicable).toEqual({
replacedByVite: new Set(),
Expand All @@ -55,6 +58,7 @@ describe('getWorkerConfig', () => {
test('should read a simple wrangler.toml file', () => {
const { config, raw, nonApplicable } = getWorkerConfig(
fileURLToPath(new URL('fixtures/simple-wrangler.toml', import.meta.url)),
undefined,
);
expect(typeof config).toEqual('object');

Expand Down Expand Up @@ -91,6 +95,7 @@ describe('getWorkerConfig', () => {
import.meta.url,
),
),
undefined,
);

expect(typeof config).toEqual('object');
Expand Down
2 changes: 1 addition & 1 deletion packages/vite-plugin-cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin {
return { appType: 'custom' };
}

resolvedPluginConfig = resolvePluginConfig(pluginConfig, userConfig);
resolvedPluginConfig = resolvePluginConfig(pluginConfig, userConfig, env);

if (!workersConfigsWarningShown) {
workersConfigsWarningShown = true;
Expand Down
49 changes: 34 additions & 15 deletions packages/vite-plugin-cloudflare/src/plugin-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,38 @@ import type {

export type PersistState = boolean | { path: string };

interface PluginWorkerConfig {
configPath: string;
interface BaseWorkerConfig {
viteEnvironment?: { name?: string };
}

export interface PluginConfig extends Partial<PluginWorkerConfig> {
auxiliaryWorkers?: PluginWorkerConfig[];
interface EntryWorkerConfig extends BaseWorkerConfig {
configPath?: string;
}

interface AuxiliaryWorkerConfig extends BaseWorkerConfig {
configPath: string;
}

export interface PluginConfig extends EntryWorkerConfig {
auxiliaryWorkers?: AuxiliaryWorkerConfig[];
persistState?: PersistState;
}

type Defined<T> = Exclude<T, undefined>;

export type AssetsOnlyConfig = SanitizedWorkerConfig & {
interface BaseConfig extends SanitizedWorkerConfig {
topLevelName: Defined<SanitizedWorkerConfig['topLevelName']>;
name: Defined<SanitizedWorkerConfig['name']>;
compatibility_date: Defined<SanitizedWorkerConfig['compatibility_date']>;
}

export interface AssetsOnlyConfig extends BaseConfig {
assets: Defined<SanitizedWorkerConfig['assets']>;
};
}

export type WorkerConfig = SanitizedWorkerConfig & {
name: Defined<SanitizedWorkerConfig['name']>;
export interface WorkerConfig extends BaseConfig {
main: Defined<SanitizedWorkerConfig['main']>;
};
}

interface BasePluginConfig {
configPaths: Set<string>;
Expand Down Expand Up @@ -65,10 +77,12 @@ function workerNameToEnvironmentName(workerName: string) {
export function resolvePluginConfig(
pluginConfig: PluginConfig,
userConfig: vite.UserConfig,
viteEnv: vite.ConfigEnv,
): ResolvedPluginConfig {
const configPaths = new Set<string>();
const persistState = pluginConfig.persistState ?? true;
const root = userConfig.root ? path.resolve(userConfig.root) : process.cwd();
const { CLOUDFLARE_ENV } = vite.loadEnv(viteEnv.mode, root, '');

const configPath = pluginConfig.configPath
? path.resolve(root, pluginConfig.configPath)
Expand All @@ -79,10 +93,14 @@ export function resolvePluginConfig(
`Config not found. Have you created a wrangler.json(c) or wrangler.toml file?`,
);

const entryWorkerResolvedConfig = getWorkerConfig(configPath, {
visitedConfigPaths: configPaths,
isEntryWorker: true,
});
const entryWorkerResolvedConfig = getWorkerConfig(
configPath,
CLOUDFLARE_ENV,
{
visitedConfigPaths: configPaths,
isEntryWorker: true,
},
);

if (entryWorkerResolvedConfig.type === 'assets-only') {
return {
Expand All @@ -100,7 +118,7 @@ export function resolvePluginConfig(

const entryWorkerEnvironmentName =
pluginConfig.viteEnvironment?.name ??
workerNameToEnvironmentName(entryWorkerConfig.name);
workerNameToEnvironmentName(entryWorkerConfig.topLevelName);

const workers = {
[entryWorkerEnvironmentName]: entryWorkerConfig,
Expand All @@ -111,6 +129,7 @@ export function resolvePluginConfig(
for (const auxiliaryWorker of pluginConfig.auxiliaryWorkers ?? []) {
const workerResolvedConfig = getWorkerConfig(
path.resolve(root, auxiliaryWorker.configPath),
CLOUDFLARE_ENV,
{
visitedConfigPaths: configPaths,
},
Expand All @@ -127,7 +146,7 @@ export function resolvePluginConfig(

const workerEnvironmentName =
auxiliaryWorker.viteEnvironment?.name ??
workerNameToEnvironmentName(workerConfig.name);
workerNameToEnvironmentName(workerConfig.topLevelName);

if (workers[workerEnvironmentName]) {
throw new Error(
Expand Down
50 changes: 39 additions & 11 deletions packages/vite-plugin-cloudflare/src/workers-configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import assert from 'node:assert';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { unstable_readConfig } from 'wrangler';
import { name } from '../package.json';
import { name as packageName } from '../package.json';
import type { AssetsOnlyConfig, WorkerConfig } from './plugin-config';
import type { Optional } from './utils';
import type { Unstable_Config as RawWorkerConfig } from 'wrangler';
Expand Down Expand Up @@ -128,7 +128,10 @@ const nullableNonApplicable = [
'upload_source_maps',
] as const;

function readWorkerConfig(configPath: string): {
function readWorkerConfig(
configPath: string,
env: string | undefined,
): {
raw: RawWorkerConfig;
config: SanitizedWorkerConfig;
nonApplicable: NonApplicableConfigMap;
Expand All @@ -139,7 +142,7 @@ function readWorkerConfig(configPath: string): {
overridden: new Set(),
};
const config: Optional<RawWorkerConfig, 'build' | 'define'> =
unstable_readConfig({ config: configPath }, {});
unstable_readConfig({ config: configPath, env }, {});
const raw = structuredClone(config) as RawWorkerConfig;

nullableNonApplicable.forEach((prop) => {
Expand Down Expand Up @@ -273,7 +276,7 @@ function getWorkerNonApplicableWarnLines(

if (overridden.size > 0)
lines.push(
`${linePrefix}${[...overridden].map((config) => `\`${config}\``).join(', ')} which ${overridden.size > 1 ? 'are' : 'is'} overridden by \`${name}\``,
`${linePrefix}${[...overridden].map((config) => `\`${config}\``).join(', ')} which ${overridden.size > 1 ? 'are' : 'is'} overridden by \`${packageName}\``,
);

return lines;
Expand All @@ -297,8 +300,17 @@ function isOverridden(
return nonApplicableWorkerConfigs.overridden.includes(configName as any);
}

function missingFieldErrorMessage(
field: string,
configPath: string,
env: string | undefined,
) {
return `No ${field} field provided in '${configPath}'${env ? ` for '${env}' environment` : ''}`;
}

export function getWorkerConfig(
configPath: string,
env: string | undefined,
opts?: {
visitedConfigPaths?: Set<string>;
isEntryWorker?: boolean;
Expand All @@ -308,34 +320,50 @@ export function getWorkerConfig(
throw new Error(`Duplicate Wrangler config path found: ${configPath}`);
}

const { raw, config, nonApplicable } = readWorkerConfig(configPath);
const { raw, config, nonApplicable } = readWorkerConfig(configPath, env);

opts?.visitedConfigPaths?.add(configPath);

assert(
config.topLevelName,
missingFieldErrorMessage(`top-level 'name'`, configPath, env),
);
assert(config.name, missingFieldErrorMessage(`'name'`, configPath, env));
assert(
config.compatibility_date,
missingFieldErrorMessage(`'compatibility_date'`, configPath, env),
);

if (opts?.isEntryWorker && !config.main) {
assert(
config.assets,
`No main or assets field provided in ${config.configPath}`,
missingFieldErrorMessage(`'main' or 'assets'`, configPath, env),
);

return {
raw,
type: 'assets-only',
config: { ...config, assets: config.assets },
raw,
config: {
...config,
topLevelName: config.topLevelName,
name: config.name,
compatibility_date: config.compatibility_date,
assets: config.assets,
},
nonApplicable,
};
}

assert(config.main, `No main field provided in ${config.configPath}`);

assert(config.name, `No name field provided in ${config.configPath}`);
assert(config.main, missingFieldErrorMessage(`'main'`, configPath, env));

return {
type: 'worker',
raw,
config: {
...config,
topLevelName: config.topLevelName,
name: config.name,
compatibility_date: config.compatibility_date,
main: config.main,
},
nonApplicable,
Expand Down
1 change: 1 addition & 0 deletions playground/cloudflare-env/.env.custom-mode
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CLOUDFLARE_ENV=custom-env
6 changes: 6 additions & 0 deletions playground/cloudflare-env/__tests__/cloudflare-env.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { expect, test } from 'vitest';
import { getTextResponse } from '../../__test-utils__';

test('returns the correct top-level var when CLOUDFLARE_ENV is undefined', async () => {
expect(await getTextResponse()).toEqual('Top level var');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { expect, test } from 'vitest';
import { getTextResponse } from '../../../__test-utils__';

test('returns the correct var when CLOUDFLARE_ENV is provided in a .env.[mode] file', async () => {
expect(await getTextResponse()).toEqual('Custom env var');
});
22 changes: 22 additions & 0 deletions playground/cloudflare-env/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@playground/cloudflare-env",
"private": true,
"type": "module",
"scripts": {
"build": "vite build --app",
"build:custom-mode": "vite build --app -c vite.config.custom-mode.ts",
"check:types": "tsc --build",
"dev": "vite dev",
"dev:custom-mode": "vite dev -c vite.config.custom-mode.ts",
"preview": "vite preview",
"preview:custom-mode": "vite preview -c vite.config.custom-mode.ts"
},
"devDependencies": {
"@cloudflare/workers-types": "catalog:default",
"@flarelabs-net/vite-plugin-cloudflare": "workspace:*",
"@vite-plugin-cloudflare/typescript-config": "workspace:*",
"typescript": "catalog:default",
"vite": "catalog:default",
"wrangler": "catalog:default"
}
}
9 changes: 9 additions & 0 deletions playground/cloudflare-env/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
interface Env {
MY_VAR: string;
}

export default {
async fetch(request, env) {
return new Response(env.MY_VAR);
},
} satisfies ExportedHandler<Env>;
7 changes: 7 additions & 0 deletions playground/cloudflare-env/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.node.json" },
{ "path": "./tsconfig.worker.json" }
]
}
4 changes: 4 additions & 0 deletions playground/cloudflare-env/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": ["@vite-plugin-cloudflare/typescript-config/base.json"],
"include": ["vite.config.ts", "vite.config.custom-mode.ts", "__tests__"]
}
4 changes: 4 additions & 0 deletions playground/cloudflare-env/tsconfig.worker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": ["@vite-plugin-cloudflare/typescript-config/worker.json"],
"include": ["src"]
}
7 changes: 7 additions & 0 deletions playground/cloudflare-env/vite.config.custom-mode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { cloudflare } from '@flarelabs-net/vite-plugin-cloudflare';
import { defineConfig } from 'vite';

export default defineConfig({
mode: 'custom-mode',
plugins: [cloudflare({ persistState: false })],
});
6 changes: 6 additions & 0 deletions playground/cloudflare-env/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { cloudflare } from '@flarelabs-net/vite-plugin-cloudflare';
import { defineConfig } from 'vite';

export default defineConfig({
plugins: [cloudflare({ persistState: false })],
});
8 changes: 8 additions & 0 deletions playground/cloudflare-env/wrangler.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name = "worker"
main = "./src/index.ts"
compatibility_date = "2024-12-30"

vars = { MY_VAR = "Top level var" }

[env.custom-env]
vars = { MY_VAR = "Custom env var" }
2 changes: 1 addition & 1 deletion playground/durable-objects/wrangler.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "worker"
main = "./src/index.ts"
compatibility_date = "2024-09-09"
compatibility_date = "2024-12-30"

[durable_objects]
bindings = [{ name = "COUNTERS", class_name = "Counter" }]
Expand Down
2 changes: 1 addition & 1 deletion playground/external-durable-objects/worker-a/wrangler.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "worker-a"
main = "./index.ts"
compatibility_date = "2024-09-09"
compatibility_date = "2024-12-30"

[durable_objects]
bindings = [
Expand Down
Loading

0 comments on commit 917395c

Please sign in to comment.