Skip to content

Commit

Permalink
feature: alias modules in the worker (#6167)
Browse files Browse the repository at this point in the history
Sometimes, users want to replace modules with other modules. This commonly happens inside a third party dependency itself. As an example, a user might have imported `node-fetch`, which will probably never work in workerd. You can use the alias config to replace any of these imports with a module of your choice.

Let's say you make a `fetch-nolyfill.js`
```ts
export default fetch; // all this does is export the standard fetch function`
```

You can then configure `wrangler.toml` like so:
```toml
[alias]
"node-fetch": "./fetch-nolyfill"
```

So any calls to `import fetch from 'node-fetch';` will simply use our nolyfilled version.

You can also pass aliases in the cli (for both `dev` and `deploy`). Like:
```bash
npx wrangler dev --alias node-fetch:./fetch-nolyfill
```
  • Loading branch information
threepointone committed Jun 28, 2024
1 parent 4e0f32f commit e048958
Show file tree
Hide file tree
Showing 19 changed files with 193 additions and 0 deletions.
29 changes: 29 additions & 0 deletions .changeset/friendly-olives-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"wrangler": patch
---

feature: alias modules in the worker

Sometimes, users want to replace modules with other modules. This commonly happens inside a third party dependency itself. As an example, a user might have imported `node-fetch`, which will probably never work in workerd. You can use the alias config to replace any of these imports with a module of your choice.

Let's say you make a `fetch-nolyfill.js`

```ts
export default fetch; // all this does is export the standard fetch function`
```

You can then configure `wrangler.toml` like so:

```toml
# ...
[alias]
"node-fetch": "./fetch-nolyfill"
```

So any calls to `import fetch from 'node-fetch';` will simply use our nolyfilled version.

You can also pass aliases in the cli (for both `dev` and `deploy`). Like:

```bash
npx wrangler dev --alias node-fetch:./fetch-nolyfill
```
61 changes: 61 additions & 0 deletions packages/wrangler/src/__tests__/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,67 @@ describe("normalizeAndValidateConfig()", () => {
});
});

describe("[alias]", () => {
it("errors with a non-object", () => {
const { config: _config, diagnostics } = normalizeAndValidateConfig(
{
alias: "some silly string",
} as unknown as RawConfig,
undefined,
{ env: undefined }
);

expect(diagnostics.hasWarnings()).toBe(false);
expect(diagnostics.hasErrors()).toBe(true);

expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- Expected alias to be an object, but got string"
`);
});

it("errors with non string values", () => {
const { config: _config, diagnostics } = normalizeAndValidateConfig(
{
alias: {
"some-module": 123,
},
} as unknown as RawConfig,
undefined,
{ env: undefined }
);

expect(diagnostics.hasWarnings()).toBe(false);
expect(diagnostics.hasErrors()).toBe(true);

expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- Expected alias[\\"some-module\\"] to be a string, but got number"
`);
});

it("returns the alias config when valid", () => {
const { config, diagnostics } = normalizeAndValidateConfig(
{
alias: {
"some-module": "./path/to/some-module",
},
} as unknown as RawConfig,
undefined,
{ env: undefined }
);

expect(diagnostics.hasWarnings()).toBe(false);
expect(diagnostics.hasErrors()).toBe(false);

expect(config.alias).toMatchInlineSnapshot(`
Object {
"some-module": "./path/to/some-module",
}
`);
});
});

describe("[assets]", () => {
it("normalizes a string input to an object", () => {
const { config, diagnostics } = normalizeAndValidateConfig(
Expand Down
2 changes: 2 additions & 0 deletions packages/wrangler/src/__tests__/navigator-user-agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ describe("defineNavigatorUserAgent is respected", () => {
serveAssetsFromWorker: false,
doBindings: [],
define: {},
alias: {},
checkFetch: false,
targetConsumer: "deploy",
local: true,
Expand Down Expand Up @@ -174,6 +175,7 @@ describe("defineNavigatorUserAgent is respected", () => {
serveAssetsFromWorker: false,
doBindings: [],
define: {},
alias: {},
checkFetch: false,
targetConsumer: "deploy",
local: true,
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/src/api/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export async function unstable_dev(
upstreamProtocol: undefined,
var: undefined,
define: undefined,
alias: undefined,
jsxFactory: undefined,
jsxFragment: undefined,
tsconfig: undefined,
Expand Down
2 changes: 2 additions & 0 deletions packages/wrangler/src/api/startDevWorker/BundlerController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export class BundlerController extends Controller<BundlerControllerEventMap> {
nodejsCompatMode: config.build.nodejsCompatMode,
define: config.build.define,
checkFetch: true,
alias: config.build.alias,
assets: config.legacy?.assets,
// enable the cache when publishing
bypassAssetCache: false,
Expand Down Expand Up @@ -232,6 +233,7 @@ export class BundlerController extends Controller<BundlerControllerEventMap> {
minify: config.build?.minify,
nodejsCompatMode: config.build.nodejsCompatMode,
define: config.build.define,
alias: config.build.alias,
noBundle: !config.build?.bundle,
findAdditionalModules: config.build?.findAdditionalModules,
durableObjects: bindings?.durable_objects ?? { bindings: [] },
Expand Down
2 changes: 2 additions & 0 deletions packages/wrangler/src/api/startDevWorker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ export interface StartDevWorkerOptions {
moduleRules?: Rule[];
/** Replace global identifiers with constant expressions, e.g. { debug: 'true', version: '"1.0.0"' }. Only takes effect if bundle: true. */
define?: Record<string, string>;
/** Alias modules */
alias?: Record<string, string>;
/** Whether the bundled worker is minified. Only takes effect if bundle: true. */
minify?: boolean;
/** Options controlling a custom build step. */
Expand Down
7 changes: 7 additions & 0 deletions packages/wrangler/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ export interface ConfigFields<Dev extends RawDevConfig> {
}
| undefined;

/**
* A map of module aliases. Lets you swap out a module for any others.
* Corresponds with esbuild's `alias` config
*/
alias: { [key: string]: string } | undefined;

/**
* By default, wrangler.toml is the source of truth for your environment configuration, like a terraform file.
*
Expand Down Expand Up @@ -336,6 +342,7 @@ export const defaultWranglerConfig: Config = {
text_blobs: undefined,
data_blobs: undefined,
keep_vars: undefined,
alias: undefined,

/** INHERITABLE ENVIRONMENT FIELDS **/
account_id: undefined,
Expand Down
38 changes: 38 additions & 0 deletions packages/wrangler/src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ export function normalizeAndValidateConfig(
activeEnv.main
),
assets: normalizeAndValidateAssets(diagnostics, configPath, rawConfig),
alias: normalizeAndValidateAliases(diagnostics, configPath, rawConfig),
wasm_modules: normalizeAndValidateModulePaths(
diagnostics,
configPath,
Expand Down Expand Up @@ -608,6 +609,43 @@ function normalizeAndValidateSite(
return undefined;
}

/**
* Validate the `alias` configuration
*/
function normalizeAndValidateAliases(
diagnostics: Diagnostics,
configPath: string | undefined,
rawConfig: RawConfig
): Config["alias"] {
if (rawConfig?.alias === undefined) {
return undefined;
}
if (
["string", "boolean", "number"].includes(typeof rawConfig?.alias) ||
typeof rawConfig?.alias !== "object"
) {
diagnostics.errors.push(
`Expected alias to be an object, but got ${typeof rawConfig?.alias}`
);
return undefined;
}

let isValid = true;
for (const [key, value] of Object.entries(rawConfig?.alias)) {
if (typeof value !== "string") {
diagnostics.errors.push(
`Expected alias["${key}"] to be a string, but got ${typeof value}`
);
isValid = false;
}
}
if (isValid) {
return rawConfig.alias;
}

return;
}

/**
* Validate the `assets` configuration and return normalized values.
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/wrangler/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ type Props = {
assetPaths: AssetPaths | undefined;
vars: Record<string, string> | undefined;
defines: Record<string, string> | undefined;
alias: Record<string, string> | undefined;
triggers: string[] | undefined;
routes: string[] | undefined;
legacyEnv: boolean | undefined;
Expand Down Expand Up @@ -526,6 +527,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
nodejsCompatMode,
define: { ...config.define, ...props.defines },
checkFetch: false,
alias: config.alias,
assets: config.assets,
// enable the cache when publishing
bypassAssetCache: false,
Expand Down
8 changes: 8 additions & 0 deletions packages/wrangler/src/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ export function deployOptions(yargs: CommonYargsArgv) {
requiresArg: true,
array: true,
})
.option("alias", {
describe: "A module pair to be substituted in the script",
type: "string",
requiresArg: true,
array: true,
})
.option("triggers", {
describe: "cron schedules to attach",
alias: ["schedule", "schedules"],
Expand Down Expand Up @@ -263,6 +269,7 @@ export async function deployHandler(

const cliVars = collectKeyValues(args.var);
const cliDefines = collectKeyValues(args.define);
const cliAlias = collectKeyValues(args.alias);

const accountId = args.dryRun ? undefined : await requireAuth(config);

Expand Down Expand Up @@ -293,6 +300,7 @@ export async function deployHandler(
compatibilityFlags: args.compatibilityFlags,
vars: cliVars,
defines: cliDefines,
alias: cliAlias,
triggers: args.triggers,
jsxFactory: args.jsxFactory,
jsxFragment: args.jsxFragment,
Expand Down
3 changes: 3 additions & 0 deletions packages/wrangler/src/deployment-bundle/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export type BundleOptions = {
minify?: boolean;
nodejsCompatMode?: NodeJSCompatMode;
define: Config["define"];
alias: Config["alias"];
checkFetch: boolean;
targetConsumer: "dev" | "deploy";
testScheduled?: boolean;
Expand Down Expand Up @@ -108,6 +109,7 @@ export async function bundleWorker(
tsconfig,
minify,
nodejsCompatMode,
alias,
define,
checkFetch,
assets,
Expand Down Expand Up @@ -312,6 +314,7 @@ export async function bundleWorker(
...define,
},
}),
alias,
loader: {
...COMMON_ESBUILD_OPTIONS.loader,
...(loader || {}),
Expand Down
12 changes: 12 additions & 0 deletions packages/wrangler/src/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ export function devOptions(yargs: CommonYargsArgv) {
requiresArg: true,
array: true,
})
.option("alias", {
describe: "A module pair to be substituted in the script",
type: "string",
requiresArg: true,
array: true,
})
.option("jsx-factory", {
describe: "The function that is called for each JSX element",
type: "string",
Expand Down Expand Up @@ -501,6 +507,7 @@ export async function startDev(args: StartDevOptions) {
getInspectorPort,
getRuntimeInspectorPort,
cliDefines,
cliAlias,
localPersistencePath,
processEntrypoint,
additionalModules,
Expand Down Expand Up @@ -592,6 +599,7 @@ export async function startDev(args: StartDevOptions) {
nodejsCompatMode={nodejsCompatMode}
build={configParam.build || {}}
define={{ ...configParam.define, ...cliDefines }}
alias={{ ...configParam.alias, ...cliAlias }}
initialMode={args.remote ? "remote" : "local"}
jsxFactory={args.jsxFactory || configParam.jsx_factory}
jsxFragment={args.jsxFragment || configParam.jsx_fragment}
Expand Down Expand Up @@ -684,6 +692,7 @@ export async function startApiDev(args: StartDevOptions) {
getInspectorPort,
getRuntimeInspectorPort,
cliDefines,
cliAlias,
localPersistencePath,
processEntrypoint,
additionalModules,
Expand Down Expand Up @@ -754,6 +763,7 @@ export async function startApiDev(args: StartDevOptions) {
nodejsCompatMode: nodejsCompatMode,
build: configParam.build || {},
define: { ...config.define, ...cliDefines },
alias: { ...config.alias, ...cliAlias },
initialMode: args.remote ? "remote" : "local",
jsxFactory: args.jsxFactory ?? configParam.jsx_factory,
jsxFragment: args.jsxFragment ?? configParam.jsx_fragment,
Expand Down Expand Up @@ -989,6 +999,7 @@ export async function validateDevServerSettings(
);

const cliDefines = collectKeyValues(args.define);
const cliAlias = collectKeyValues(args.alias);

return {
entry,
Expand All @@ -999,6 +1010,7 @@ export async function validateDevServerSettings(
host,
routes,
cliDefines,
cliAlias,
localPersistencePath,
processEntrypoint: !!args.processEntrypoint,
additionalModules: args.additionalModules ?? [],
Expand Down
2 changes: 2 additions & 0 deletions packages/wrangler/src/dev/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ export type DevProps = {
liveReload: boolean;
bindings: CfWorkerInit["bindings"];
define: Config["define"];
alias: Config["alias"];
crons: Config["triggers"]["crons"];
queueConsumers: Config["queues"]["consumers"];
isWorkersSite: boolean;
Expand Down Expand Up @@ -767,6 +768,7 @@ function DevSession(props: DevSessionProps) {
minify: props.minify,
nodejsCompatMode: props.nodejsCompatMode,
define: props.define,
alias: props.alias,
noBundle: props.noBundle,
findAdditionalModules: props.findAdditionalModules,
assets: props.assetsConfig,
Expand Down
4 changes: 4 additions & 0 deletions packages/wrangler/src/dev/start-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export async function startDevServer(
define: props.define,
noBundle: props.noBundle,
findAdditionalModules: props.findAdditionalModules,
alias: props.alias,
assets: props.assetsConfig,
testScheduled: props.testScheduled,
local: props.local,
Expand Down Expand Up @@ -343,6 +344,7 @@ async function runEsbuild({
processEntrypoint,
additionalModules,
rules,
alias,
assets,
serveAssetsFromWorker,
tsconfig,
Expand All @@ -364,6 +366,7 @@ async function runEsbuild({
processEntrypoint: boolean;
additionalModules: CfModule[];
rules: Config["rules"];
alias: Config["alias"];
assets: Config["assets"];
define: Config["define"];
serveAssetsFromWorker: boolean;
Expand Down Expand Up @@ -412,6 +415,7 @@ async function runEsbuild({
nodejsCompatMode,
define,
checkFetch: true,
alias,
assets,
// disable the cache in dev
bypassAssetCache: true,
Expand Down
Loading

0 comments on commit e048958

Please sign in to comment.