Skip to content

Commit 41ed3ac

Browse files
ascorbicflorian-lefebvredelucissarah11918ematipico
authored
feat: add automatic chrome workspace handling (#14122)
Co-authored-by: Sarah Rainsberger <[email protected]> Co-authored-by: florian-lefebvre <[email protected]> Co-authored-by: delucis <[email protected]> Co-authored-by: sarah11918 <[email protected]> Co-authored-by: ematipico <[email protected]> Co-authored-by: bjohansebas <[email protected]>
1 parent cad3cc6 commit 41ed3ac

File tree

5 files changed

+156
-0
lines changed

5 files changed

+156
-0
lines changed

.changeset/plain-dragons-bow.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'astro': minor
3+
---
4+
5+
Adds experimental support for automatic [Chrome DevTools workspace folders](https://developer.chrome.com/docs/devtools/workspaces)
6+
7+
This feature allows you to edit files directly in the browser and have those changes reflected in your local file system via a connected workspace folder. This allows you to apply edits such as CSS tweaks without leaving your browser tab!
8+
9+
With this feature enabled, the Astro dev server will automatically configure a Chrome DevTools workspace for your project. Your project will then appear as a workspace source, ready to connect. Then, changes that you make in the "Sources" panel are automatically saved to your project source code.
10+
11+
To enable this feature, add the experimental flag `chromeDevtoolsWorkspace` to your Astro config:
12+
13+
```js
14+
// astro.config.mjs
15+
import { defineConfig } from 'astro/config';
16+
17+
export default defineConfig({
18+
experimental: {
19+
chromeDevtoolsWorkspace: true,
20+
},
21+
});
22+
```
23+
24+
See the [experimental Chrome DevTools workspace feature documentation](https://docs.astro.build/en/reference/experimental-flags/chrome-devtools-workspace/) for more information.

packages/astro/src/core/config/schemas/base.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export const ASTRO_CONFIG_DEFAULTS = {
103103
liveContentCollections: false,
104104
csp: false,
105105
rawEnvValues: false,
106+
chromeDevtoolsWorkspace: false,
106107
},
107108
} satisfies AstroUserConfig & { server: { open: boolean } };
108109

@@ -502,6 +503,10 @@ export const AstroConfigSchema = z.object({
502503
.optional()
503504
.default(ASTRO_CONFIG_DEFAULTS.experimental.csp),
504505
rawEnvValues: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.rawEnvValues),
506+
chromeDevtoolsWorkspace: z
507+
.boolean()
508+
.optional()
509+
.default(ASTRO_CONFIG_DEFAULTS.experimental.chromeDevtoolsWorkspace),
505510
})
506511
.strict(
507512
`Invalid or outdated experimental feature.\nCheck for incorrect spelling or outdated Astro version.\nSee https://docs.astro.build/en/reference/experimental-flags/ for a list of all current experiments.`,

packages/astro/src/types/public/config.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2458,6 +2458,31 @@ export interface ViteUserConfig extends OriginalViteUserConfig {
24582458
* See the [experimental raw environment variables guide](https://docs.astro.build/en/reference/experimental-flags/raw-env-values/) for more information.
24592459
*/
24602460
rawEnvValues?: boolean;
2461+
/**
2462+
* @name experimental.chromeDevtoolsWorkspace
2463+
* @type {boolean}
2464+
* @default `false`
2465+
* @version 5.13
2466+
* @description
2467+
*
2468+
* Enables Chrome DevTools workspace integration for the Astro dev server.
2469+
*
2470+
* When enabled, the dev server will automatically configure a [Chrome DevTools workspace](https://developer.chrome.com/docs/devtools/workspaces) for your project,
2471+
* allowing you to edit files directly in the browser and have those changes reflected in your local file system.
2472+
*
2473+
* ```js
2474+
* import { defineConfig } from 'astro/config';
2475+
*
2476+
* export default defineConfig({
2477+
* experimental: {
2478+
* chromeDevtoolsWorkspace: true,
2479+
* },
2480+
* });
2481+
* ```
2482+
*
2483+
* See the [experimental Chrome DevTools workspace feature documentation](https://docs.astro.build/en/reference/experimental-flags/chrome-devtools-workspace/) for more information.
2484+
*/
2485+
chromeDevtoolsWorkspace?: boolean;
24612486
};
24622487
}
24632488

packages/astro/src/vite-plugin-astro-server/plugin.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { AsyncLocalStorage } from 'node:async_hooks';
2+
import { randomUUID } from 'node:crypto';
23
import type fs from 'node:fs';
4+
import { existsSync } from 'node:fs';
5+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
36
import { IncomingMessage } from 'node:http';
47
import { fileURLToPath } from 'node:url';
58
import type * as vite from 'vite';
@@ -135,6 +138,44 @@ export default function createVitePluginAstroServer({
135138
route: '',
136139
handle: trailingSlashMiddleware(settings),
137140
});
141+
142+
// Chrome DevTools workspace handler
143+
// See https://chromium.googlesource.com/devtools/devtools-frontend/+/main/docs/ecosystem/automatic_workspace_folders.md
144+
viteServer.middlewares.use(async function chromeDevToolsHandler(request, response, next) {
145+
if (request.url !== '/.well-known/appspecific/com.chrome.devtools.json') {
146+
return next();
147+
}
148+
if (!settings.config.experimental.chromeDevtoolsWorkspace) {
149+
// Return early to stop console spam
150+
response.writeHead(404);
151+
response.end();
152+
return;
153+
}
154+
155+
const cacheDir = settings.config.cacheDir;
156+
const configPath = new URL('./chrome-workspace.json', cacheDir);
157+
158+
if (!existsSync(cacheDir)) {
159+
await mkdir(cacheDir, { recursive: true });
160+
}
161+
162+
let config;
163+
try {
164+
config = JSON.parse(await readFile(configPath, 'utf-8'));
165+
} catch {
166+
config = {
167+
version: '1.0',
168+
projectId: randomUUID(),
169+
workspaceRoot: fileURLToPath(settings.config.root),
170+
};
171+
await writeFile(configPath, JSON.stringify(config));
172+
}
173+
174+
response.setHeader('Content-Type', 'application/json');
175+
response.end(JSON.stringify(config));
176+
return;
177+
});
178+
138179
// Note that this function has a name so other middleware can find it.
139180
viteServer.middlewares.use(async function astroDevHandler(request, response) {
140181
if (request.url === undefined || !request.method) {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import assert from 'node:assert/strict';
2+
import { after, before, describe, it } from 'node:test';
3+
import { loadFixture } from './test-utils.js';
4+
5+
describe('Chrome DevTools workspace', () => {
6+
describe('with experimental flag enabled', () => {
7+
let fixture;
8+
let devServer;
9+
10+
before(async () => {
11+
fixture = await loadFixture({
12+
root: './fixtures/astro-dev-headers/',
13+
experimental: {
14+
chromeDevtoolsWorkspace: true,
15+
},
16+
});
17+
devServer = await fixture.startDevServer();
18+
});
19+
20+
after(async () => {
21+
await devServer.stop();
22+
});
23+
24+
it('serves Chrome DevTools workspace endpoint', async () => {
25+
const result = await fixture.fetch('/.well-known/appspecific/com.chrome.devtools.json');
26+
assert.equal(result.status, 200);
27+
assert.equal(result.headers.get('content-type'), 'application/json');
28+
29+
const data = await result.json();
30+
assert.equal(data.version, '1.0');
31+
assert.equal(typeof data.projectId, 'string');
32+
assert.equal(data.projectId.length, 36); // UUID length
33+
assert.equal(typeof data.workspaceRoot, 'string');
34+
assert.ok(data.workspaceRoot.includes('astro-dev-headers'));
35+
});
36+
});
37+
38+
describe('with experimental flag disabled', () => {
39+
let fixture;
40+
let devServer;
41+
42+
before(async () => {
43+
fixture = await loadFixture({
44+
root: './fixtures/astro-dev-headers/',
45+
experimental: {
46+
chromeDevtoolsWorkspace: false,
47+
},
48+
});
49+
devServer = await fixture.startDevServer();
50+
});
51+
52+
after(async () => {
53+
await devServer.stop();
54+
});
55+
56+
it('returns 404 for Chrome DevTools workspace endpoint', async () => {
57+
const result = await fixture.fetch('/.well-known/appspecific/com.chrome.devtools.json');
58+
assert.equal(result.status, 404);
59+
});
60+
});
61+
});

0 commit comments

Comments
 (0)