Skip to content

Commit ca94b13

Browse files
committed
feat: --assets / config.assets to serve a folder of static assets
This adds support for defining `assets` in `wrangler.toml`. You can configure it with a string path, or a `{bucket, include, exclude}` object (much like `[site]`). This also renames the `--experimental-public` arg as `--assets`. Via #1162
1 parent d8ee04f commit ca94b13

14 files changed

+300
-81
lines changed

.changeset/wise-steaks-end.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
feat: `--assets` / `config.assets` to serve a folder of static assets
6+
7+
This adds support for defining `assets` in `wrangler.toml`. You can configure it with a string path, or a `{bucket, include, exclude}` object (much like `[site]`). This also renames the `--experimental-public` arg as `--assets`.
8+
9+
Via https://github.com/cloudflare/wrangler2/issues/1162

packages/wrangler/src/__tests__/configuration.test.ts

+53
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,59 @@ describe("normalizeAndValidateConfig()", () => {
412412
});
413413
});
414414

415+
describe("assets", () => {
416+
it("should error if `assets` config is missing `bucket`", () => {
417+
const expectedConfig: RawConfig = {
418+
// @ts-expect-error we're intentionally passing an invalid configuration here
419+
assets: {
420+
include: ["INCLUDE_1", "INCLUDE_2"],
421+
exclude: ["EXCLUDE_1", "EXCLUDE_2"],
422+
},
423+
};
424+
425+
const { config, diagnostics } = normalizeAndValidateConfig(
426+
expectedConfig,
427+
undefined,
428+
{ env: undefined }
429+
);
430+
431+
expect(config).toEqual(expect.objectContaining(expectedConfig));
432+
expect(diagnostics.hasWarnings()).toBe(false);
433+
expect(diagnostics.hasErrors()).toBe(true);
434+
435+
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
436+
"Processing wrangler configuration:
437+
- \\"assets.bucket\\" is a required field."
438+
`);
439+
});
440+
441+
it("should error on invalid `assets` values", () => {
442+
const expectedConfig = {
443+
assets: {
444+
bucket: "BUCKET",
445+
include: [222, 333],
446+
exclude: [444, 555],
447+
},
448+
};
449+
450+
const { config, diagnostics } = normalizeAndValidateConfig(
451+
expectedConfig as unknown as RawConfig,
452+
undefined,
453+
{ env: undefined }
454+
);
455+
456+
expect(config).toEqual(expect.objectContaining(expectedConfig));
457+
expect(diagnostics.hasWarnings()).toBe(false);
458+
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
459+
"Processing wrangler configuration:
460+
- Expected \\"assets.include.[0]\\" to be of type string but got 222.
461+
- Expected \\"assets.include.[1]\\" to be of type string but got 333.
462+
- Expected \\"assets.exclude.[0]\\" to be of type string but got 444.
463+
- Expected \\"assets.exclude.[1]\\" to be of type string but got 555."
464+
`);
465+
});
466+
});
467+
415468
it("should map `wasm_module` paths from relative to the config path to relative to the cwd", () => {
416469
const expectedConfig: RawConfig = {
417470
wasm_modules: {

packages/wrangler/src/__tests__/dev.test.tsx

+36-7
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,7 @@ describe("wrangler dev", () => {
839839
--routes, --route Routes to upload [array]
840840
--host Host to forward requests to, defaults to the zone of project [string]
841841
--local-protocol Protocol to listen to requests on, defaults to http. [choices: \\"http\\", \\"https\\"]
842-
--experimental-public Static assets to be served [string]
842+
--assets Static assets to be served [string]
843843
--site Root folder of static assets for Workers Sites [string]
844844
--site-include Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded. [array]
845845
--site-exclude Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded. [array]
@@ -857,19 +857,19 @@ describe("wrangler dev", () => {
857857
`);
858858
});
859859

860-
it("should error if --experimental-public and --site are used together", async () => {
860+
it("should error if --assets and --site are used together", async () => {
861861
writeWranglerToml({
862862
main: "./index.js",
863863
});
864864
fs.writeFileSync("index.js", `export default {};`);
865865
await expect(
866-
runWrangler("dev --experimental-public abc --site xyz")
866+
runWrangler("dev --assets abc --site xyz")
867867
).rejects.toThrowErrorMatchingInlineSnapshot(
868-
`"Cannot use --experimental-public and a Site configuration together."`
868+
`"Cannot use Assets and Workers Sites in the same Worker."`
869869
);
870870
});
871871

872-
it("should error if --experimental-public and config.site are used together", async () => {
872+
it("should error if --assets and config.site are used together", async () => {
873873
writeWranglerToml({
874874
main: "./index.js",
875875
site: {
@@ -878,9 +878,38 @@ describe("wrangler dev", () => {
878878
});
879879
fs.writeFileSync("index.js", `export default {};`);
880880
await expect(
881-
runWrangler("dev --experimental-public abc")
881+
runWrangler("dev --assets abc")
882882
).rejects.toThrowErrorMatchingInlineSnapshot(
883-
`"Cannot use --experimental-public and a Site configuration together."`
883+
`"Cannot use Assets and Workers Sites in the same Worker."`
884+
);
885+
});
886+
887+
it("should error if config.assets and --site are used together", async () => {
888+
writeWranglerToml({
889+
main: "./index.js",
890+
assets: "abc",
891+
});
892+
fs.writeFileSync("index.js", `export default {};`);
893+
await expect(
894+
runWrangler("dev --site xyz")
895+
).rejects.toThrowErrorMatchingInlineSnapshot(
896+
`"Cannot use Assets and Workers Sites in the same Worker."`
897+
);
898+
});
899+
900+
it("should error if config.assets and config.site are used together", async () => {
901+
writeWranglerToml({
902+
main: "./index.js",
903+
assets: "abc",
904+
site: {
905+
bucket: "xyz",
906+
},
907+
});
908+
fs.writeFileSync("index.js", `export default {};`);
909+
await expect(
910+
runWrangler("dev --assets abc")
911+
).rejects.toThrowErrorMatchingInlineSnapshot(
912+
`"Cannot use Assets and Workers Sites in the same Worker."`
884913
);
885914
});
886915
});

packages/wrangler/src/__tests__/publish.test.ts

+37-8
Original file line numberDiff line numberDiff line change
@@ -1404,7 +1404,7 @@ addEventListener('fetch', event => {});`
14041404
expect(std.err).toMatchInlineSnapshot(`""`);
14051405
});
14061406

1407-
it("should upload all the files in the directory specified by `--experimental-public`", async () => {
1407+
it("should upload all the files in the directory specified by `--assets`", async () => {
14081408
const assets = [
14091409
{ filePath: "file-1.txt", content: "Content of file-1" },
14101410
{ filePath: "file-2.txt", content: "Content of file-2" },
@@ -1425,7 +1425,7 @@ addEventListener('fetch', event => {});`
14251425
mockListKVNamespacesRequest(kvNamespace);
14261426
mockKeyListRequest(kvNamespace.id, []);
14271427
mockUploadAssetsToKVRequest(kvNamespace.id, assets);
1428-
await runWrangler("publish --experimental-public assets");
1428+
await runWrangler("publish --assets assets");
14291429

14301430
expect(std.out).toMatchInlineSnapshot(`
14311431
"Reading file-1.txt...
@@ -1440,19 +1440,19 @@ addEventListener('fetch', event => {});`
14401440
expect(std.err).toMatchInlineSnapshot(`""`);
14411441
});
14421442

1443-
it("should error if --experimental-public and --site are used together", async () => {
1443+
it("should error if --assets and --site are used together", async () => {
14441444
writeWranglerToml({
14451445
main: "./index.js",
14461446
});
14471447
writeWorkerSource();
14481448
await expect(
1449-
runWrangler("publish --experimental-public abc --site xyz")
1449+
runWrangler("publish --assets abc --site xyz")
14501450
).rejects.toThrowErrorMatchingInlineSnapshot(
1451-
`"Cannot use --experimental-public and a Site configuration together."`
1451+
`"Cannot use Assets and Workers Sites in the same Worker."`
14521452
);
14531453
});
14541454

1455-
it("should error if --experimental-public and config.site are used together", async () => {
1455+
it("should error if --assets and config.site are used together", async () => {
14561456
writeWranglerToml({
14571457
main: "./index.js",
14581458
site: {
@@ -1461,9 +1461,38 @@ addEventListener('fetch', event => {});`
14611461
});
14621462
writeWorkerSource();
14631463
await expect(
1464-
runWrangler("publish --experimental-public abc")
1464+
runWrangler("publish --assets abc")
14651465
).rejects.toThrowErrorMatchingInlineSnapshot(
1466-
`"Cannot use --experimental-public and a Site configuration together."`
1466+
`"Cannot use Assets and Workers Sites in the same Worker."`
1467+
);
1468+
});
1469+
1470+
it("should error if config.assets and --site are used together", async () => {
1471+
writeWranglerToml({
1472+
main: "./index.js",
1473+
assets: "abc",
1474+
});
1475+
writeWorkerSource();
1476+
await expect(
1477+
runWrangler("publish --site xyz")
1478+
).rejects.toThrowErrorMatchingInlineSnapshot(
1479+
`"Cannot use Assets and Workers Sites in the same Worker."`
1480+
);
1481+
});
1482+
1483+
it("should error if config.assets and config.site are used together", async () => {
1484+
writeWranglerToml({
1485+
main: "./index.js",
1486+
assets: "abc",
1487+
site: {
1488+
bucket: "xyz",
1489+
},
1490+
});
1491+
writeWorkerSource();
1492+
await expect(
1493+
runWrangler("publish")
1494+
).rejects.toThrowErrorMatchingInlineSnapshot(
1495+
`"Cannot use Assets and Workers Sites in the same Worker."`
14671496
);
14681497
});
14691498

packages/wrangler/src/config/config.ts

+9
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ export interface ConfigFields<Dev extends RawDevConfig> {
114114
}
115115
| undefined;
116116

117+
/**
118+
* Serve a folder of static assets with your Worker, without any additional code.
119+
* This can either be a string, or an object with additional config fields.
120+
*/
121+
assets:
122+
| string
123+
| { bucket: string; include: string[]; exclude: string[] }
124+
| undefined;
125+
117126
/**
118127
* A list of wasm modules that your worker should be bound to. This is
119128
* the "legacy" way of binding to a wasm module. ES module workers should

packages/wrangler/src/config/validation.ts

+30
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ export function normalizeAndValidateConfig(
186186
rawConfig,
187187
activeEnv.main
188188
),
189+
assets: normalizeAndValidateAssets(diagnostics, configPath, rawConfig),
189190
wasm_modules: normalizeAndValidateModulePaths(
190191
diagnostics,
191192
configPath,
@@ -571,6 +572,35 @@ function normalizeAndValidateSite(
571572
return undefined;
572573
}
573574

575+
/**
576+
* Validate the `assets` configuration and return normalized values.
577+
*/
578+
function normalizeAndValidateAssets(
579+
diagnostics: Diagnostics,
580+
configPath: string | undefined,
581+
rawConfig: RawConfig
582+
) {
583+
if (
584+
typeof rawConfig?.assets === "string" ||
585+
rawConfig?.assets === undefined
586+
) {
587+
return rawConfig?.assets;
588+
}
589+
590+
const { bucket, include = [], exclude = [], ...rest } = rawConfig.assets;
591+
592+
validateAdditionalProperties(diagnostics, "assets", Object.keys(rest), []);
593+
validateRequiredProperty(diagnostics, "assets", "bucket", bucket, "string");
594+
validateTypedArray(diagnostics, "assets.include", include, "string");
595+
validateTypedArray(diagnostics, "assets.exclude", exclude, "string");
596+
597+
return {
598+
bucket,
599+
include,
600+
exclude,
601+
};
602+
}
603+
574604
/**
575605
* Map the paths of the `wasm_modules`, `text_blobs` or `data_blobs` configuration to be relative to the current working directory.
576606
*/

packages/wrangler/src/dev/dev.tsx

+10-8
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export type DevProps = {
3838
enableLocalPersistence: boolean;
3939
bindings: CfWorkerInit["bindings"];
4040
crons: Config["triggers"]["crons"];
41-
public: string | undefined;
41+
isWorkersSite: boolean;
4242
assetPaths: AssetPaths | undefined;
4343
compatibilityDate: string;
4444
compatibilityFlags: string[] | undefined;
@@ -53,9 +53,13 @@ export type DevProps = {
5353
};
5454

5555
export function DevImplementation(props: DevProps): JSX.Element {
56-
if (props.public && props.entry.format === "service-worker") {
56+
if (
57+
!props.isWorkersSite &&
58+
props.assetPaths &&
59+
props.entry.format === "service-worker"
60+
) {
5761
throw new Error(
58-
"You cannot use the service-worker format with a `public` directory."
62+
"You cannot use the service-worker format with an `assets` directory."
5963
);
6064
}
6165

@@ -131,12 +135,10 @@ function DevSession(props: DevSessionProps) {
131135
const bundle = useEsbuild({
132136
entry: props.entry,
133137
destination: directory,
134-
staticRoot: props.public,
135138
jsxFactory: props.jsxFactory,
136139
rules: props.rules,
137140
jsxFragment: props.jsxFragment,
138-
// In dev for remote mode, we serve --experimental-assets from the local proxy before we send the request to the worker.
139-
serveAssetsFromWorker: !!props.public && !!props.local,
141+
serveAssetsFromWorker: Boolean(props.isWorkersSite || props.local),
140142
tsconfig: props.tsconfig,
141143
minify: props.minify,
142144
nodeCompat: props.nodeCompat,
@@ -152,7 +154,7 @@ function DevSession(props: DevSessionProps) {
152154
compatibilityFlags={props.compatibilityFlags}
153155
bindings={props.bindings}
154156
assetPaths={props.assetPaths}
155-
public={props.public}
157+
isWorkersSite={props.isWorkersSite}
156158
port={props.port}
157159
ip={props.ip}
158160
rules={props.rules}
@@ -168,7 +170,7 @@ function DevSession(props: DevSessionProps) {
168170
accountId={props.accountId}
169171
bindings={props.bindings}
170172
assetPaths={props.assetPaths}
171-
public={props.public}
173+
isWorkersSite={props.isWorkersSite}
172174
port={props.port}
173175
ip={props.ip}
174176
localProtocol={props.localProtocol}

packages/wrangler/src/dev/local.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ interface LocalProps {
2323
bindings: CfWorkerInit["bindings"];
2424
localProtocol: "http" | "https";
2525
assetPaths: AssetPaths | undefined;
26-
public: string | undefined;
26+
isWorkersSite: boolean;
2727
port: number;
2828
ip: string;
2929
rules: Config["rules"];
@@ -50,7 +50,7 @@ function useLocalWorker({
5050
compatibilityFlags,
5151
bindings,
5252
assetPaths,
53-
public: publicDirectory,
53+
isWorkersSite,
5454
port,
5555
rules,
5656
enableLocalPersistence,
@@ -310,7 +310,7 @@ function useLocalWorker({
310310
compatibilityFlags,
311311
localPersistencePath,
312312
assetPaths,
313-
publicDirectory,
313+
isWorkersSite,
314314
rules,
315315
bindings.wasm_modules,
316316
bindings.text_blobs,

0 commit comments

Comments
 (0)