Skip to content

Commit e7b4acf

Browse files
committed
feat(runtime): add preview.pathname
1 parent 3b2c776 commit e7b4acf

File tree

7 files changed

+79
-25
lines changed

7 files changed

+79
-25
lines changed

docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,18 +91,32 @@ Configure whether or not the editor should be rendered. If an object is provided
9191
##### `previews`
9292
Configure which ports should be used for the previews allowing you to align the behavior with your demo application's dev server setup. If not specified, the lowest port will be used.
9393

94-
You can optionally provide these as an array of tuples where the first element is the port number and the second is the title of the preview, or as an object.
9594
<PropertyTable inherited type={'Preview[]'} />
9695

9796
The `Preview` type has the following shape:
9897

9998
```ts
100-
type Preview = string
99+
type Preview =
100+
| number
101+
| string
101102
| [port: number, title: string]
102-
| { port: number, title: string }
103+
| [port: number, title: string, pathname: string]
104+
| { port: number, title: string, pathname?: string }
103105

104106
```
105107

108+
Example value:
109+
110+
```yaml
111+
previews:
112+
- 3000 # Preview is on :3000/
113+
- "3001/docs" # Preview is on :3001/docs/
114+
- [3002, "Dev Server"] # Preview is on :3002/. Displayed title is "Dev Server".
115+
- [3003, "Dev Server", "/docs"] # Preview is on :3003/docs/. Displayed title is "Dev Server".
116+
- { port: 3004, title: "Dev Server" } # Preview is on :3004/. Displayed title is "Dev Server".
117+
- { port: 3005, title: "Dev Server", pathname: "/docs" } # Preview is on :3005/docs/. Displayed title is "Dev Server".
118+
```
119+
106120
##### `mainCommand`
107121
The main command to be executed. This command will run after the `prepareCommands`.
108122
<PropertyTable inherited type="Command" />

packages/runtime/src/store/previews.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { PreviewInfo } from '../webcontainer/preview-info.js';
44
import type { WebContainer } from '@webcontainer/api';
55

66
export class PreviewsStore {
7-
private _availablePreviews = new Map<number, PreviewInfo>();
7+
private _availablePreviews: PreviewInfo[] = [];
88
private _previewsLayout: PreviewInfo[] = [];
99

1010
/**
@@ -21,18 +21,21 @@ export class PreviewsStore {
2121
const webcontainer = await webcontainerPromise;
2222

2323
webcontainer.on('port', (port, type, url) => {
24-
let previewInfo = this._availablePreviews.get(port);
24+
const previewInfos = this._availablePreviews.filter((preview) => preview.port === port);
2525

26-
if (!previewInfo) {
27-
previewInfo = new PreviewInfo(port, type === 'open');
28-
this._availablePreviews.set(port, previewInfo);
26+
if (previewInfos.length === 0) {
27+
const info = new PreviewInfo(port, type === 'open');
28+
previewInfos.push(info);
29+
this._availablePreviews.push(info);
2930
}
3031

31-
previewInfo.ready = type === 'open';
32-
previewInfo.baseUrl = url;
32+
previewInfos.forEach((info) => {
33+
info.ready = type === 'open';
34+
info.baseUrl = url;
35+
});
3336

3437
if (this._previewsLayout.length === 0) {
35-
this.previews.set([previewInfo]);
38+
this.previews.set(previewInfos);
3639
} else {
3740
this._previewsLayout = [...this._previewsLayout];
3841
this.previews.set(this._previewsLayout);
@@ -58,14 +61,12 @@ export class PreviewsStore {
5861
const previewInfos = previews.map((preview) => {
5962
const info = new PreviewInfo(preview);
6063

61-
let previewInfo = this._availablePreviews.get(info.port);
64+
let previewInfo = this._availablePreviews.find((availablePreview) => PreviewInfo.equals(info, availablePreview));
6265

6366
if (!previewInfo) {
6467
previewInfo = info;
6568

66-
this._availablePreviews.set(previewInfo.port, previewInfo);
67-
} else {
68-
previewInfo.title = info.title;
69+
this._availablePreviews.push(previewInfo);
6970
}
7071

7172
return previewInfo;

packages/runtime/src/webcontainer/preview-info.spec.ts

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,52 @@ import { describe, it, expect } from 'vitest';
22
import { PreviewInfo } from './preview-info.js';
33

44
describe('PreviewInfo', () => {
5-
it('should accept a port', () => {
5+
it('should accept a number for port', () => {
66
const previewInfo = new PreviewInfo(3000);
77

88
expect(previewInfo.port).toBe(3000);
9+
expect(previewInfo.title).toBe(undefined);
10+
expect(previewInfo.pathname).toBe(undefined);
11+
});
12+
13+
it('should accept a string for port and pathname', () => {
14+
const previewInfo = new PreviewInfo('3000/some/nested/path');
15+
16+
expect(previewInfo.port).toBe(3000);
17+
expect(previewInfo.pathname).toBe('some/nested/path');
18+
expect(previewInfo.title).toBe(undefined);
919
});
1020

1121
it('should accept a tuple of [port, title]', () => {
1222
const previewInfo = new PreviewInfo([3000, 'Local server']);
1323

1424
expect(previewInfo.port).toBe(3000);
1525
expect(previewInfo.title).toBe('Local server');
26+
expect(previewInfo.pathname).toBe(undefined);
27+
});
28+
29+
it('should accept a tuple of [port, title, pathname]', () => {
30+
const previewInfo = new PreviewInfo([3000, 'Local server', '/docs']);
31+
32+
expect(previewInfo.port).toBe(3000);
33+
expect(previewInfo.title).toBe('Local server');
34+
expect(previewInfo.pathname).toBe('/docs');
1635
});
1736

1837
it('should accept an object with { port, title }', () => {
1938
const previewInfo = new PreviewInfo({ port: 3000, title: 'Local server' });
2039

2140
expect(previewInfo.port).toBe(3000);
2241
expect(previewInfo.title).toBe('Local server');
42+
expect(previewInfo.pathname).toBe(undefined);
43+
});
44+
45+
it('should accept an object with { port, title, pathname }', () => {
46+
const previewInfo = new PreviewInfo({ port: 3000, title: 'Local server', pathname: '/docs' });
47+
48+
expect(previewInfo.port).toBe(3000);
49+
expect(previewInfo.title).toBe('Local server');
50+
expect(previewInfo.pathname).toBe('/docs');
2351
});
2452

2553
it('should not be ready by default', () => {
@@ -41,9 +69,8 @@ describe('PreviewInfo', () => {
4169
});
4270

4371
it('should have a url with a custom pathname and baseUrl', () => {
44-
const previewInfo = new PreviewInfo(3000);
72+
const previewInfo = new PreviewInfo('3000/foo');
4573
previewInfo.baseUrl = 'https://example.com';
46-
previewInfo.pathname = '/foo';
4774

4875
expect(previewInfo.url).toBe('https://example.com/foo');
4976
});
@@ -71,10 +98,10 @@ describe('PreviewInfo', () => {
7198

7299
it('should not be equal to another preview info with a different pathname', () => {
73100
const a = new PreviewInfo(3000);
74-
const b = new PreviewInfo(3000);
75-
76-
a.pathname = '/foo';
101+
const b = new PreviewInfo('3000/b');
102+
const c = new PreviewInfo('3000/c');
77103

78104
expect(PreviewInfo.equals(a, b)).toBe(false);
105+
expect(PreviewInfo.equals(b, c)).toBe(false);
79106
});
80107
});

packages/runtime/src/webcontainer/preview-info.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@ export class PreviewInfo {
1818
constructor(preview: Exclude<PreviewSchema, boolean>[0], ready?: boolean) {
1919
if (typeof preview === 'number') {
2020
this.port = preview;
21+
} else if (typeof preview === 'string') {
22+
const [port, ...rest] = preview.split('/');
23+
this.port = parseInt(port);
24+
this.pathname = rest.join('/');
2125
} else if (Array.isArray(preview)) {
2226
this.port = preview[0];
2327
this.title = preview[1];
28+
this.pathname = preview[2];
2429
} else {
2530
this.port = preview.port;
2631
this.title = preview.title;
32+
this.pathname = preview.pathname;
2733
}
2834

2935
this.ready = !!ready;

packages/template/src/content/tutorial/1-basics/1-introduction/2-foo/content.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ title: Foo from part 1
44
slug: foo
55
focus: /src/index.html
66
previews:
7-
- [8080, 'Main Page']
7+
- { title: 'Main Page', port: 8080, pathname: '/src'}
88
- [1, 'Test Runner']
9-
- [2, 'Bar']
9+
- '2/some/custom/pathname'
10+
- '2/another/pathname'
1011
terminal:
1112
panels: 'terminal'
1213
editPageLink: 'https://tutorialkit.dev'

packages/template/src/templates/default/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ createServer((_req, res) => {
1515
`);
1616
}).listen(1);
1717

18-
createServer((_req, res) => res.end('Server 2')).listen(2);
18+
createServer((req, res) => res.end(`Server 2\n${req.method} ${req.url}`)).listen(2);
1919

2020
servor({
2121
root: 'src/',

packages/types/src/schemas/common.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,17 @@ export const previewSchema = z.union([
3737
// a single number, the port for the preview
3838
z.number(),
3939

40-
// a tuple, the port followed by a title
40+
// a string, the port and pathname
41+
z.string(),
42+
43+
// a tuple, the port followed by a title and optional pathname
4144
z.tuple([z.number(), z.string()]),
45+
z.tuple([z.number(), z.string(), z.string()]),
4246

4347
z.strictObject({
4448
port: z.number().describe('Port number of the preview.'),
4549
title: z.string().describe('Title of the preview.'),
50+
pathname: z.string().optional().describe('Pathname of the preview URL.'),
4651
}),
4752
])
4853
.array(),

0 commit comments

Comments
 (0)