Skip to content

Commit 8aa41bf

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 8aa41bf

14 files changed

+336
-82
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

+53-8
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ describe("wrangler dev", () => {
799799
});
800800
});
801801

802-
describe("site", () => {
802+
describe("serve static assets", () => {
803803
it("should error if --site is used with no value", async () => {
804804
await expect(
805805
runWrangler("dev --site")
@@ -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,11 +878,56 @@ 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."`
884884
);
885885
});
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."`
913+
);
914+
});
915+
916+
it("should indicate whether Sites is being used", async () => {
917+
writeWranglerToml({
918+
main: "index.js",
919+
});
920+
fs.writeFileSync("index.js", `export default {};`);
921+
922+
await runWrangler("dev");
923+
expect((Dev as jest.Mock).mock.calls[0][0].isWorkersSite).toEqual(false);
924+
925+
await runWrangler("dev --site abc");
926+
expect((Dev as jest.Mock).mock.calls[1][0].isWorkersSite).toEqual(true);
927+
928+
await runWrangler("dev --assets abc");
929+
expect((Dev as jest.Mock).mock.calls[2][0].isWorkersSite).toEqual(false);
930+
});
886931
});
887932

888933
describe("--inspect", () => {

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

+49-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,31 @@ 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 when trying to use --assets with a service-worker Worker", async () => {
1444+
writeWranglerToml({
1445+
main: "./index.js",
1446+
});
1447+
writeWorkerSource({ type: "sw" });
1448+
await expect(
1449+
runWrangler("publish --assets abc")
1450+
).rejects.toThrowErrorMatchingInlineSnapshot(
1451+
`"You cannot use the service-worker format with an \`assets\` directory yet. For information on how to migrate to the module-worker format, see https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/"`
1452+
);
1453+
});
1454+
1455+
it("should error if --assets and --site are used together", async () => {
14441456
writeWranglerToml({
14451457
main: "./index.js",
14461458
});
14471459
writeWorkerSource();
14481460
await expect(
1449-
runWrangler("publish --experimental-public abc --site xyz")
1461+
runWrangler("publish --assets abc --site xyz")
14501462
).rejects.toThrowErrorMatchingInlineSnapshot(
1451-
`"Cannot use --experimental-public and a Site configuration together."`
1463+
`"Cannot use Assets and Workers Sites in the same Worker."`
14521464
);
14531465
});
14541466

1455-
it("should error if --experimental-public and config.site are used together", async () => {
1467+
it("should error if --assets and config.site are used together", async () => {
14561468
writeWranglerToml({
14571469
main: "./index.js",
14581470
site: {
@@ -1461,9 +1473,38 @@ addEventListener('fetch', event => {});`
14611473
});
14621474
writeWorkerSource();
14631475
await expect(
1464-
runWrangler("publish --experimental-public abc")
1476+
runWrangler("publish --assets abc")
1477+
).rejects.toThrowErrorMatchingInlineSnapshot(
1478+
`"Cannot use Assets and Workers Sites in the same Worker."`
1479+
);
1480+
});
1481+
1482+
it("should error if config.assets and --site are used together", async () => {
1483+
writeWranglerToml({
1484+
main: "./index.js",
1485+
assets: "abc",
1486+
});
1487+
writeWorkerSource();
1488+
await expect(
1489+
runWrangler("publish --site xyz")
1490+
).rejects.toThrowErrorMatchingInlineSnapshot(
1491+
`"Cannot use Assets and Workers Sites in the same Worker."`
1492+
);
1493+
});
1494+
1495+
it("should error if config.assets and config.site are used together", async () => {
1496+
writeWranglerToml({
1497+
main: "./index.js",
1498+
assets: "abc",
1499+
site: {
1500+
bucket: "xyz",
1501+
},
1502+
});
1503+
writeWorkerSource();
1504+
await expect(
1505+
runWrangler("publish")
14651506
).rejects.toThrowErrorMatchingInlineSnapshot(
1466-
`"Cannot use --experimental-public and a Site configuration together."`
1507+
`"Cannot use Assets and Workers Sites in the same Worker."`
14671508
);
14681509
});
14691510

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

+12-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 yet. For information on how to migrate to the module-worker format, see https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/"
5963
);
6064
}
6165

@@ -131,12 +135,12 @@ 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(
142+
props.assetPaths && !props.isWorkersSite && !props.local
143+
),
140144
tsconfig: props.tsconfig,
141145
minify: props.minify,
142146
nodeCompat: props.nodeCompat,
@@ -152,7 +156,7 @@ function DevSession(props: DevSessionProps) {
152156
compatibilityFlags={props.compatibilityFlags}
153157
bindings={props.bindings}
154158
assetPaths={props.assetPaths}
155-
public={props.public}
159+
isWorkersSite={props.isWorkersSite}
156160
port={props.port}
157161
ip={props.ip}
158162
rules={props.rules}
@@ -168,7 +172,7 @@ function DevSession(props: DevSessionProps) {
168172
accountId={props.accountId}
169173
bindings={props.bindings}
170174
assetPaths={props.assetPaths}
171-
public={props.public}
175+
isWorkersSite={props.isWorkersSite}
172176
port={props.port}
173177
ip={props.ip}
174178
localProtocol={props.localProtocol}

0 commit comments

Comments
 (0)