Skip to content

Commit

Permalink
feat: Create very basic Asset Worker and plumb it into wrangler dev (#…
Browse files Browse the repository at this point in the history
…6370)

This commit does the ground work needed in order to
add Assets support for Workers in `wrangler dev`.
Amongst others, it implements the following:

- it creates a new package called `workers-shared`
that hosts the `Asset Server Worker`, and the
`Router Worker`in the future
- it scaffolds the `Asset Server Worker` in some very
basic form, with basic configuration. Further behaviour
implementation will follow in a subsequent PR
- it does the ground work of plumbing ASW into Miniflare
  • Loading branch information
CarmenPopoviciu authored Aug 2, 2024
1 parent c3e19b7 commit 8a3c6c0
Show file tree
Hide file tree
Showing 30 changed files with 582 additions and 110 deletions.
12 changes: 12 additions & 0 deletions .changeset/orange-icons-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@cloudflare/workers-shared": minor
"wrangler": minor
---

feat: Create very basic Asset Server Worker and plumb it into `wrangler dev`

These changes do the ground work needed in order to add Assets support for Workers in `wrangler dev`. They implement the following:

- it creates a new package called `workers-shared` that hosts the `Asset Server Worker`, and the `Router Worker`in the future
- it scaffolds the `Asset Server Worker` in some very basic form, with basic configuration. Further behaviour implementation will follow in a subsequent PR
- it does the ground work of plumbing ASW into Miniflare
23 changes: 23 additions & 0 deletions fixtures/workers-with-assets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# workers-with-assets

`workers-with-assets` is a test fixture that showcases Workers with Assets. This particular fixture sets up an assets-only Workers project.

## dev

To start a dev session you can run

```
wrangler dev
```

or

```
wrangler dev --experimental-assets=./public
```

## Run tests

```
npm run test
```
20 changes: 20 additions & 0 deletions fixtures/workers-with-assets/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "assets-worker",
"private": true,
"scripts": {
"check:type": "tsc",
"dev": "npx wrangler dev",
"test:ci": "vitest run",
"test:watch": "vitest",
"type:tests": "tsc -p ./tests/tsconfig.json"
},
"devDependencies": {
"@cloudflare/workers-tsconfig": "workspace:*",
"@cloudflare/workers-types": "^4.20240725.0",
"undici": "^5.28.4",
"wrangler": "workspace:*"
},
"volta": {
"extends": "../../package.json"
}
}
1 change: 1 addition & 0 deletions fixtures/workers-with-assets/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Hello Workers + Assets World!</h1>
26 changes: 26 additions & 0 deletions fixtures/workers-with-assets/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { resolve } from "node:path";
import { fetch } from "undici";
import { afterAll, beforeAll, describe, it } from "vitest";
import { runWranglerDev } from "../../shared/src/run-wrangler-long-lived";

describe("[Workers + Assets] `wrangler dev`", () => {
let ip: string, port: number, stop: (() => Promise<unknown>) | undefined;

beforeAll(async () => {
({ ip, port, stop } = await runWranglerDev(resolve(__dirname, ".."), [
"--port=0",
"--inspector-port=0",
]));
});

afterAll(async () => {
await stop?.();
});

it("renders ", async ({ expect }) => {
const response = await fetch(`http://${ip}:${port}/`);
const text = await response.text();
expect(response.status).toBe(200);
expect(text).toContain(`Hello from Asset Server Worker 🚀`);
});
});
7 changes: 7 additions & 0 deletions fixtures/workers-with-assets/tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@cloudflare/workers-tsconfig/tsconfig.json",
"compilerOptions": {
"types": ["node"]
},
"include": ["**/*.ts", "../../../node-types.d.ts"]
}
13 changes: 13 additions & 0 deletions fixtures/workers-with-assets/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2020",
"esModuleInterop": true,
"module": "CommonJS",
"lib": ["ES2020"],
"types": ["node"],
"moduleResolution": "node",
"noEmit": true,
"skipLibCheck": true
},
"include": ["tests", "../../node-types.d.ts"]
}
5 changes: 5 additions & 0 deletions fixtures/workers-with-assets/wrangler.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name = "assets-worker"
compatibility_date = "2024-01-01"

[experimental_assets]
directory = "./public"
13 changes: 13 additions & 0 deletions packages/workers-shared/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# `@cloudflare/workers-shared`

This is a package that is used at Cloudflare to power some internal features of [Cloudflare Workers](https://developers.cloudflare.com/workers/), as well as their open-source equivalents here in workers-sdk and Wrangler.

## `asset-server`

The Asset Server Worker.

For more details please refer to the dedicated README file.

> [!NOTE]
> Since code in this package is used by the Workers infrastructure, it is important that PRs are given careful review with regards to how they could cause a failure in production.
> Ideally, there should be comprehensive tests for changes being made to give extra confidence about the behavior.
3 changes: 3 additions & 0 deletions packages/workers-shared/asset-server-worker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `asset-server`

The Asset Server is a [Cloudflare Worker](https://developers.cloudflare.com/workers/) that is responsible of serving assets for Workers deployed on the edge, that contain static assets as well.
5 changes: 5 additions & 0 deletions packages/workers-shared/asset-server-worker/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
async fetch(request, env) {
return new Response("Hello from Asset Server Worker 🚀");
},
};
12 changes: 12 additions & 0 deletions packages/workers-shared/asset-server-worker/wrangler.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
##
# Configuration file for the Asset Server Worker
#
# Please note that wrangler has a dependency on this file, and will
# attempt to read it as part of setting up a new Miniflare instance
# in developemnt mode. We should ensure that any configuration changes
# to this file are persisted in wrangler as well, when necessary.
# (see packages/wrangler/src/dev/miniflare.ts -> buildMiniflareOptions())
##
name = "asset-server"
main = "src/index.ts"
compatibility_date = "2024-07-31"
17 changes: 17 additions & 0 deletions packages/workers-shared/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@cloudflare/workers-shared",
"version": "0.0.1",
"description": "Package that is used at Cloudflare to power some internal features of Cloudflare Workers.",
"scripts": {
"build": "pnpm run clean && pnpm run bundle:asset-server",
"bundle:asset-server": "esbuild asset-server-worker/src/index.ts --format=esm --bundle --outfile=dist/asset-server-worker.mjs --sourcemap=external",
"clean": "rimraf dist",
"dev": "pnpm run clean && concurrently -n bundle:asset-server -c blue \"pnpm run bundle:asset-server --watch\""
},
"devDependencies": {
"concurrently": "^8.2.2",
"esbuild": "0.17.19",
"rimraf": "^6.0.1",
"typescript": "^5.5.4"
}
}
9 changes: 9 additions & 0 deletions packages/workers-shared/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "http://turbo.build/schema.json",
"extends": ["//"],
"pipeline": {
"build": {
"outputs": ["dist/**"]
}
}
}
1 change: 1 addition & 0 deletions packages/wrangler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"@cloudflare/eslint-config-worker": "workspace:*",
"@cloudflare/pages-shared": "workspace:^",
"@cloudflare/types": "^6.18.4",
"@cloudflare/workers-shared": "workspace:*",
"@cloudflare/workers-tsconfig": "workspace:*",
"@cloudflare/workers-types": "^4.20240725.0",
"@cspotcode/source-map-support": "0.8.1",
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/scripts/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const EXTERNAL_DEPENDENCIES = [
// and read when we are bundling the worker application
"unenv",
"workerd/worker.mjs",
"@cloudflare/workers-shared",
];

const pathToPackageJson = path.resolve(__dirname, "..", "package.json");
Expand Down
2 changes: 1 addition & 1 deletion packages/wrangler/src/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4363,7 +4363,7 @@ addEventListener('fetch', event => {});`
});
writeWorkerSource();
mockUploadWorkerRequest({
expectedMainModule: "no-op-worker.js",
expectedMainModule: "no-op-assets-worker.js",
});
mockSubDomainRequest();
await runWrangler("deploy");
Expand Down
83 changes: 75 additions & 8 deletions packages/wrangler/src/__tests__/dev.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1303,7 +1303,7 @@ describe("wrangler dev", () => {
await expect(
runWrangler("dev --legacy-assets abc --site xyz")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use Assets and Workers Sites in the same Worker.]`
`[Error: Cannot use Legacy Assets and Workers Sites in the same Worker.]`
);
});

Expand All @@ -1318,7 +1318,7 @@ describe("wrangler dev", () => {
await expect(
runWrangler("dev --legacy-assets abc")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use Assets and Workers Sites in the same Worker.]`
`[Error: Cannot use Legacy Assets and Workers Sites in the same Worker.]`
);
});

Expand All @@ -1331,7 +1331,7 @@ describe("wrangler dev", () => {
await expect(
runWrangler("dev --site xyz")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use Assets and Workers Sites in the same Worker.]`
`[Error: Cannot use Legacy Assets and Workers Sites in the same Worker.]`
);
});

Expand All @@ -1347,7 +1347,7 @@ describe("wrangler dev", () => {
await expect(
runWrangler("dev --legacy-assets abc")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use Assets and Workers Sites in the same Worker.]`
`[Error: Cannot use Legacy Assets and Workers Sites in the same Worker.]`
);
});

Expand Down Expand Up @@ -1441,11 +1441,11 @@ describe("wrangler dev", () => {
await expect(
runWrangler("dev")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use Assets and Workers Sites in the same Worker.]`
`[Error: Cannot use Experimental Assets and Workers Sites in the same Worker.]`
);
});

it("should error if --experimental-assets and config.site are used together", async () => {
it("should error if config.site and --experimental-assets are used together", async () => {
writeWranglerToml({
main: "./index.js",
site: {
Expand All @@ -1457,7 +1457,74 @@ describe("wrangler dev", () => {
await expect(
runWrangler("dev --experimental-assets assets")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use Assets and Workers Sites in the same Worker.]`
`[Error: Cannot use Experimental Assets and Workers Sites in the same Worker.]`
);
});

it("should error if config.experimental_assets and config.legacy_assets are used together", async () => {
writeWranglerToml({
main: "./index.js",
experimental_assets: { directory: "assets" },
legacy_assets: {
bucket: "xyz",
include: [],
exclude: [],
browser_TTL: undefined,
serve_single_page_app: true,
},
});
fs.writeFileSync("index.js", `export default {};`);
fs.openSync("assets", "w");
await expect(
runWrangler("dev")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use Legacy Assets and Experimental Assets in the same Worker.]`
);
});

it("should error if --experimental-assets and --legacy-assets are used together", async () => {
fs.writeFileSync("index.js", `export default {};`);
fs.openSync("assets", "w");
await expect(
runWrangler("dev --experimental-assets assets --legacy-assets assets")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use Legacy Assets and Experimental Assets in the same Worker.]`
);
});

it("should error if --experimental-assets and config.legacy_assets are used together", async () => {
writeWranglerToml({
main: "./index.js",
legacy_assets: {
bucket: "xyz",
include: [],
exclude: [],
browser_TTL: undefined,
serve_single_page_app: true,
},
});
fs.writeFileSync("index.js", `export default {};`);
fs.openSync("assets", "w");
await expect(
runWrangler("dev --experimental-assets assets")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use Legacy Assets and Experimental Assets in the same Worker.]`
);
});

it("should error if config.experimental_assets and --legacy-assets are used together", async () => {
writeWranglerToml({
main: "./index.js",
experimental_assets: {
directory: "xyz",
},
});
fs.writeFileSync("index.js", `export default {};`);
fs.openSync("xyz", "w");
await expect(
runWrangler("dev --legacy-assets xyz")
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: Cannot use Legacy Assets and Experimental Assets in the same Worker.]`
);
});

Expand All @@ -1475,7 +1542,7 @@ describe("wrangler dev", () => {
);
});

it("should error if directory specified by 'experimental_assets' configuration key does not exist", async () => {
it("should error if directory specified by '[experimental_assets]' configuration key does not exist", async () => {
writeWranglerToml({
main: "./index.js",
experimental_assets: {
Expand Down
2 changes: 1 addition & 1 deletion packages/wrangler/src/api/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export async function unstable_dev(
localProtocol: options?.localProtocol,
httpsKeyPath: options?.httpsKeyPath,
httpsCertPath: options?.httpsCertPath,
experimentalAssets: options?.experimentalAssets,
experimentalAssets: undefined,
legacyAssets: options?.legacyAssets,
site: options?.site, // Root folder of static assets for Workers Sites
siteInclude: options?.siteInclude, // Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.
Expand Down
7 changes: 7 additions & 0 deletions packages/wrangler/src/api/startDevWorker/ConfigController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ async function resolveConfig(
legacyAssets: Boolean(legacyAssets),
script: input.entrypoint,
moduleRoot: input.build?.moduleRoot,
// getEntry only needs to know if experimental_assets was specified.
// The actualy value is not relevant here, which is why not passing
// the entire ExperimentalAssets object is fine.
experimentalAssets: input?.experimental?.assets?.directory,
},
config,
"dev"
Expand Down Expand Up @@ -257,6 +261,9 @@ async function resolveConfig(
capnp: input.unsafe?.capnp ?? unsafe?.capnp,
metadata: input.unsafe?.metadata ?? unsafe?.metadata,
},
experimental: {
assets: input?.experimental?.assets,
},
} satisfies StartDevWorkerOptions;

if (resolved.legacy.legacyAssets && resolved.legacy.site) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ async function convertToConfigBundle(
includePatterns: event.config.legacy?.site?.include ?? [],
}
: undefined,
experimentalAssets: event.config.experimental?.assets,
initialPort: undefined,
initialIp: "127.0.0.1",
rules: [],
Expand Down
Loading

0 comments on commit 8a3c6c0

Please sign in to comment.