Skip to content

Commit ad4d5ba

Browse files
authored
feat(plugin-preview): should support dark mode (#2816)
1 parent e91e0fd commit ad4d5ba

File tree

18 files changed

+215
-98
lines changed

18 files changed

+215
-98
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<template>
2+
<div class="example">Hello World VUE</div>
3+
</template>
4+
5+
<style scoped>
6+
.example {
7+
color: green;
8+
}
9+
</style>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Guide
2+
3+
```tsx preview="iframe-follow"
4+
export default function HelloWorld() {
5+
return <div>Hello World JSX</div>;
6+
}
7+
```
8+
9+
```jsx preview="iframe-follow"
10+
export default function HelloWorld() {
11+
return <div>Hello World TSX</div>;
12+
}
13+
```
14+
15+
```tsx file="./_component.jsx" preview="iframe-follow"
16+
17+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Vue
2+
3+
## iframe-follow
4+
5+
```vue preview="iframe-follow" file="./_component.vue"
6+
7+
```
Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1 @@
1-
# Guide
2-
3-
```tsx preview="iframe-follow"
4-
export default function HelloWorld() {
5-
return <div>Hello World JSX</div>;
6-
}
7-
```
8-
9-
```jsx preview="iframe-follow"
10-
export default function HelloWorld() {
11-
return <div>Hello World TSX</div>;
12-
}
13-
```
14-
15-
```tsx file="./component.jsx" preview="iframe-follow"
16-
17-
```
18-
19-
```vue preview="iframe-follow"
20-
<template>
21-
<div class="example">Hello World VUE</div>
22-
</template>
23-
24-
<style scoped>
25-
.example {
26-
color: green;
27-
}
28-
</style>
29-
```
1+
# plugin preview

e2e/fixtures/plugin-preview-custom-entry/index.test.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ test.describe('plugin test', async () => {
1717
});
1818

1919
test('Should render the element', async ({ page }) => {
20-
await page.goto(`http://localhost:${appPort}/`, {
20+
await page.goto(`http://localhost:${appPort}/guide/`, {
2121
waitUntil: 'networkidle',
2222
});
2323
const codeBlockElements = page.locator('.rspress-doc > .rp-preview');
24-
await expect(codeBlockElements).toHaveCount(4);
24+
await expect(codeBlockElements).toHaveCount(3);
2525

2626
const internalIframeJsxDemoCodePreview = await page
2727
.frameLocator('iframe')
@@ -38,15 +38,21 @@ test.describe('plugin test', async () => {
3838
.nth(2)
3939
.getByText('EXTERNAL')
4040
.innerText();
41+
42+
expect(internalIframeJsxDemoCodePreview).toBe('Hello World JSX');
43+
expect(internalIframeTsxDemoCodePreview).toBe('Hello World TSX');
44+
expect(externalIframeJsxDemoCodePreview).toBe('Hello World External');
45+
});
46+
47+
test('should render vue', async ({ page }) => {
48+
await page.goto(`http://localhost:${appPort}/guide/vue`, {
49+
waitUntil: 'networkidle',
50+
});
4151
const transformedCodePreview = await page
4252
.frameLocator('iframe')
43-
.nth(3)
4453
.getByText('VUE')
4554
.innerText();
4655

47-
expect(internalIframeJsxDemoCodePreview).toBe('Hello World JSX');
48-
expect(internalIframeTsxDemoCodePreview).toBe('Hello World TSX');
49-
expect(externalIframeJsxDemoCodePreview).toBe('Hello World External');
5056
expect(transformedCodePreview).toBe('Hello World VUE');
5157
});
5258
});

e2e/fixtures/plugin-preview-custom-entry/rspress.config.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,16 @@ export default defineConfig({
88
plugins: [
99
pluginPreview({
1010
iframeOptions: {
11-
customEntry: ({ entryCssPath, demoPath }) => {
11+
customEntry: ({ demoPath }) => {
1212
if (demoPath.endsWith('.vue')) {
1313
return `
1414
import { createApp } from 'vue';
1515
import App from ${JSON.stringify(demoPath)};
16-
import ${JSON.stringify(entryCssPath)};
1716
createApp(App).mount('#root');
1817
`;
1918
}
2019
return `
2120
import { createRoot } from 'react-dom/client';
22-
import ${JSON.stringify(entryCssPath)};
2321
import Demo from ${JSON.stringify(demoPath)};
2422
const container = document.getElementById('root');
2523
createRoot(container).render(<Demo />);

e2e/fixtures/plugin-preview/index.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,40 @@ test.describe('plugin test', async () => {
108108
await expect(fixedIframeBody).toContainText('This is a component');
109109
await expect(fixedIframeBody).toContainText('Hello World External');
110110
});
111+
112+
test('iframe dark mode', async ({ page }) => {
113+
await page.goto(`http://localhost:${appPort}/mixed`, {
114+
waitUntil: 'networkidle',
115+
});
116+
117+
// Toggle to dark mode
118+
await page.click('.rp-switch-appearance');
119+
120+
// Wait a bit for the theme change message to be posted to iframes
121+
await page.waitForTimeout(200);
122+
123+
// Test iframe-follow dark mode
124+
const iframeFollowBlocks = page.locator('.rp-preview--iframe-follow');
125+
const iframeFollow = iframeFollowBlocks
126+
.first()
127+
.locator('.rp-preview--iframe-follow__device iframe');
128+
const iframeFollowHtml = iframeFollow.contentFrame().locator('html');
129+
await expect(iframeFollowHtml).toHaveClass(/dark/);
130+
131+
// Test iframe-fixed dark mode
132+
const fixedDevice = page.locator('.rp-fixed-device');
133+
const fixedIframe = fixedDevice.locator('.rp-fixed-device__iframe');
134+
const fixedIframeHtml = fixedIframe.contentFrame().locator('html');
135+
await expect(fixedIframeHtml).toHaveClass(/dark/);
136+
137+
// Toggle back to light mode
138+
await page.click('.rp-switch-appearance');
139+
await page.waitForTimeout(200);
140+
141+
// Verify dark class is removed
142+
await expect(iframeFollowHtml).not.toHaveClass(/dark/);
143+
await expect(fixedIframeHtml).not.toHaveClass(/dark/);
144+
});
111145
});
112146

113147
test.describe('plugin preview build', async () => {

packages/plugin-preview/src/generateEntry.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
import { mkdir, writeFile } from 'node:fs/promises';
22
import { join } from 'node:path';
3-
import { STATIC_DIR, VIRTUAL_DEMO_DIR } from './constants';
3+
import { VIRTUAL_DEMO_DIR } from './constants';
44
import type { CustomEntry, DemoInfo } from './types';
55
import { toValidVarName } from './utils';
66

7+
function reactEntry({ demoPath }: CustomEntry) {
8+
return `
9+
import { createRoot } from 'react-dom/client';
10+
import Demo from ${JSON.stringify(demoPath)};
11+
const container = document.getElementById('root');
12+
createRoot(container).render(<Demo />);
13+
`;
14+
}
15+
716
export async function generateEntry(
817
globalDemos: DemoInfo,
918
framework: 'react' | 'solid',
1019
customEntry?: (meta: CustomEntry) => string,
1120
) {
1221
const sourceEntry: Record<string, string> = {};
13-
const entryCssPath = join(STATIC_DIR, 'global-styles', 'entry.css');
22+
23+
const generateEntry = (meta: CustomEntry) => {
24+
return customEntry
25+
? customEntry(meta)
26+
: framework === 'react'
27+
? reactEntry(meta)
28+
: '';
29+
};
1430

1531
await mkdir(VIRTUAL_DEMO_DIR, { recursive: true });
1632

@@ -25,21 +41,7 @@ export async function generateEntry(
2541
const followPromiseList = followDemos.map(async demo => {
2642
const { id, path: demoPath } = demo;
2743
const entry = join(VIRTUAL_DEMO_DIR, `${id}.entry.tsx`);
28-
const reactEntry = `
29-
import { createRoot } from 'react-dom/client';
30-
import ${JSON.stringify(entryCssPath)};
31-
import Demo from ${JSON.stringify(demoPath)};
32-
const container = document.getElementById('root');
33-
createRoot(container).render(<Demo />);
34-
`;
35-
const entryContent = customEntry
36-
? customEntry({
37-
entryCssPath,
38-
demoPath,
39-
})
40-
: framework === 'react'
41-
? reactEntry
42-
: '';
44+
const entryContent = generateEntry({ demoPath });
4345
await writeFile(entry, entryContent);
4446
sourceEntry[id] = entry;
4547
});
@@ -53,9 +55,8 @@ export async function generateEntry(
5355
if (fixedDemos.length === 0) {
5456
return;
5557
}
56-
const reactContent = `
57-
import { createRoot } from 'react-dom/client';
58-
import ${JSON.stringify(entryCssPath)};
58+
59+
const appContent = `
5960
${fixedDemos
6061
.map((demo, index) => {
6162
return `import Demo_${index} from ${JSON.stringify(demo.path)}`;
@@ -73,14 +74,18 @@ export async function generateEntry(
7374
</div>
7475
)
7576
}
76-
const container = document.getElementById('root');
77-
createRoot(container).render(<App />);
77+
export default App;
7878
`;
7979

80-
const renderContent = reactContent;
8180
const id = `_${toValidVarName(pageName)}`;
81+
const demoPath = join(VIRTUAL_DEMO_DIR, `${id}.app.tsx`);
82+
const entryContent = generateEntry({ demoPath });
8283
const entry = join(VIRTUAL_DEMO_DIR, `${id}.entry.tsx`);
83-
await writeFile(entry, renderContent);
84+
85+
await Promise.all([
86+
writeFile(demoPath, appContent),
87+
writeFile(entry, entryContent),
88+
]);
8489
sourceEntry[id] = entry;
8590
})();
8691

packages/plugin-preview/src/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
removeTrailingSlash,
1515
type UserConfig,
1616
} from '@rspress/core';
17+
import entryContent from '../static/iframe/entry?raw';
1718
import { STATIC_DIR } from './constants';
1819
import { generateEntry } from './generateEntry';
1920
import { globalDemos, isDirtyRef, remarkWriteCodeFile } from './remarkPlugin';
@@ -82,6 +83,20 @@ export function pluginPreview(options?: Options): RspressPlugin {
8283
source: {
8384
...otherSourceOptions,
8485
entry: await generateEntry(globalDemos, framework, customEntry),
86+
preEntry: [
87+
join(STATIC_DIR, 'iframe', 'entry.css'),
88+
...(builderConfig.source?.preEntry ?? []),
89+
],
90+
},
91+
html: {
92+
tags: [
93+
// why not preEntry to load js?
94+
// avoid theme flash
95+
{
96+
tag: 'script',
97+
children: entryContent,
98+
},
99+
],
85100
},
86101
resolve,
87102
output: {

0 commit comments

Comments
 (0)