@rspress/plugin-preview 2.0.0-rc.1 → 2.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -2,7 +2,6 @@ import type { RsbuildConfig } from '@rsbuild/core';
2
2
  import { RspressPlugin } from '@rspress/core';
3
3
 
4
4
  declare interface CustomEntry {
5
- entryCssPath: string;
6
5
  demoPath: string;
7
6
  }
8
7
 
@@ -10,13 +9,9 @@ declare type IframeOptions = {
10
9
  /**
11
10
  * framework in the iframe
12
11
  * @default 'react'
12
+ * @deprecated
13
13
  */
14
- framework?: 'react' | 'solid';
15
- /**
16
- * position of the iframe
17
- * @default 'follow'
18
- */
19
- position?: 'fixed' | 'follow';
14
+ framework?: 'react';
20
15
  /**
21
16
  * dev server port for the iframe
22
17
  * @default 7890
@@ -31,31 +26,19 @@ declare type IframeOptions = {
31
26
 
32
27
  export declare type Options = {
33
28
  /**
34
- * @deprecated Use previewMode instead.
35
- * true = 'iframe'
36
- * false = 'internal'
37
- */
38
- isMobile?: boolean;
39
- /**
40
- * @deprecated Use iframeOptions.position instead.
29
+ * determine how to handle a internal code block without meta like \`\`\`tsx
30
+ * @default 'preview'
41
31
  */
42
- iframePosition?: 'fixed' | 'follow';
32
+ defaultRenderMode?: 'pure' | 'preview';
43
33
  /**
44
- * internal mode: component will be rendered inside the documentation, only support react.
45
- *
46
- * iframe mode: component will be rendered in iframe, note that aside will hide.
34
+ * determine how to preview \`\`\`tsx preview code block
47
35
  * @default 'internal'
48
36
  */
49
- previewMode?: 'internal' | 'iframe';
37
+ defaultPreviewMode?: 'internal' | 'iframe-fixed' | 'iframe-follow';
50
38
  /**
51
39
  * enable when preview mode is iframe.
52
40
  */
53
41
  iframeOptions?: IframeOptions;
54
- /**
55
- * determine how to handle a internal code block without meta
56
- * @default 'preview'
57
- */
58
- defaultRenderMode?: 'pure' | 'preview';
59
42
  /**
60
43
  * Supported languages to be previewed
61
44
  */
package/dist/index.js CHANGED
@@ -1,25 +1,19 @@
1
1
  import node_net from "node:net";
2
- import node_path, { dirname, join, resolve as external_node_path_resolve } from "node:path";
3
- import { createRsbuild, mergeRsbuildConfig } from "@rsbuild/core";
4
- import { pluginBabel } from "@rsbuild/plugin-babel";
2
+ import node_path, { join } from "node:path";
3
+ import { createRsbuild, logger, mergeRsbuildConfig } from "@rsbuild/core";
5
4
  import { pluginReact } from "@rsbuild/plugin-react";
6
- import { pluginSolid } from "@rsbuild/plugin-solid";
7
5
  import { RSPRESS_TEMP_DIR, normalizePosixPath, removeTrailingSlash } from "@rspress/core";
8
- import { cloneDeep, isEqual } from "lodash";
9
6
  import { mkdir, writeFile } from "node:fs/promises";
10
7
  import node_fs from "node:fs";
11
- const staticPath = node_path.join(__dirname, '..', 'static');
12
- const demoBlockComponentPath = node_path.join(staticPath, 'global-components', 'DemoBlock.tsx');
13
- const virtualDir = node_path.join(process.cwd(), 'node_modules', RSPRESS_TEMP_DIR, 'virtual-demo');
8
+ import { isDeepStrictEqual } from "node:util";
9
+ const entryraw_namespaceObject = "const storageKey = 'rspress-plugin-preview-theme-appearance';\n\nfunction setDocumentTheme(isDark) {\n if (isDark) {\n document.documentElement.classList.add('dark');\n document.documentElement.style.colorScheme = 'dark';\n } else {\n document.documentElement.classList.remove('dark');\n document.documentElement.style.colorScheme = 'light';\n }\n localStorage.setItem(\n 'rspress-plugin-preview-theme-appearance',\n isDark ? 'dark' : 'light',\n );\n}\n\nconst saved = localStorage.getItem(storageKey) || 'light';\nsetDocumentTheme(saved === 'dark');\n\nwindow.addEventListener('message', event => {\n if (event.data.type === 'theme-change') {\n const isDark = event.data.dark;\n setDocumentTheme(isDark);\n }\n});\n";
10
+ const STATIC_DIR = node_path.join(__dirname, '..', 'static');
11
+ const VIRTUAL_DEMO_DIR = node_path.join(process.cwd(), 'node_modules', RSPRESS_TEMP_DIR, 'virtual-demo');
14
12
  const toValidVarName = (str)=>{
15
13
  if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(str)) return str;
16
14
  return str.replace(/[^0-9a-zA-Z_$]/g, '_').replace(/^([0-9])/, '_$1');
17
15
  };
18
16
  const generateId = (pageName, index)=>`_${toValidVarName(pageName)}_${index}`;
19
- const injectDemoBlockImport = (str, path)=>`
20
- import DemoBlock from ${JSON.stringify(path)};
21
- ${str}
22
- `;
23
17
  const getLangFileExt = (lang)=>{
24
18
  switch(lang){
25
19
  case 'jsx':
@@ -31,72 +25,66 @@ const getLangFileExt = (lang)=>{
31
25
  return lang;
32
26
  }
33
27
  };
34
- async function generateEntry(demos, framework, position, customEntry) {
28
+ function reactEntry({ demoPath }) {
29
+ return `
30
+ import { createRoot } from 'react-dom/client';
31
+ import Demo from ${JSON.stringify(demoPath)};
32
+ const container = document.getElementById('root');
33
+ createRoot(container).render(<Demo />);
34
+ `;
35
+ }
36
+ async function generateEntry_generateEntry(globalDemos, framework, customEntry) {
35
37
  const sourceEntry = {};
36
- const entryCssPath = join(staticPath, 'global-styles', 'entry.css');
37
- await mkdir(virtualDir, {
38
+ const generateEntry = (meta)=>customEntry ? customEntry(meta) : 'react' === framework ? reactEntry(meta) : '';
39
+ await mkdir(VIRTUAL_DEMO_DIR, {
38
40
  recursive: true
39
41
  });
40
- if ('follow' === position) await Promise.all(Object.values(demos).map((routes)=>routes.map(async (route)=>{
41
- const { id, path: demoPath } = route;
42
- const entry = join(virtualDir, `${id}.entry.tsx`);
43
- const solidEntry = `
44
- import { render } from 'solid-js/web';
45
- import ${JSON.stringify(entryCssPath)};
46
- import Demo from ${JSON.stringify(demoPath)};
47
- render(() => <Demo />, document.getElementById('root'));
48
- `;
49
- const reactEntry = `
50
- import { createRoot } from 'react-dom/client';
51
- import ${JSON.stringify(entryCssPath)};
52
- import Demo from ${JSON.stringify(demoPath)};
53
- const container = document.getElementById('root');
54
- createRoot(container).render(<Demo />);
55
- `;
56
- const entryContent = customEntry ? customEntry({
57
- entryCssPath,
42
+ await Promise.all(Object.entries(globalDemos).map(([pageName, demos])=>{
43
+ const followDemos = demos.filter((demo)=>'iframe-follow' === demo.previewMode);
44
+ const followPromiseList = followDemos.map(async (demo)=>{
45
+ const { id, path: demoPath } = demo;
46
+ const entry = join(VIRTUAL_DEMO_DIR, `${id}.entry.tsx`);
47
+ const entryContent = generateEntry({
58
48
  demoPath
59
- }) : 'react' === framework ? reactEntry : solidEntry;
49
+ });
60
50
  await writeFile(entry, entryContent);
61
51
  sourceEntry[id] = entry;
62
- })).flat());
63
- else await Promise.all(Object.entries(demos).map(async ([key, routes])=>{
64
- if (0 === routes.length) return;
65
- const reactContent = `
66
- import { createRoot } from 'react-dom/client';
67
- import ${JSON.stringify(entryCssPath)};
68
- ${routes.map((demo, index)=>`import Demo_${index} from ${JSON.stringify(demo.path)}`).join('\n')}
69
- function App() {
70
- return (
71
- <div className="preview-container">
72
- <div className="preview-nav">{"${routes[0].title}"}</div>
73
- ${routes.map((_demo, index)=>`<Demo_${index} />`).join('\n')}
74
- </div>
75
- )
76
- }
77
- const container = document.getElementById('root');
78
- createRoot(container).render(<App />);
79
- `;
80
- const solidContent = `
81
- import { render } from 'solid-js/web';
82
- import ${JSON.stringify(entryCssPath)};
83
- ${routes.map((demo, index)=>`import Demo_${index} from ${JSON.stringify(demo.path)}`).join('\n')}
52
+ });
53
+ const fixedDemos = demos.filter((demo)=>'iframe-fixed' === demo.previewMode);
54
+ const fixedPromise = (async ()=>{
55
+ if (0 === fixedDemos.length) return;
56
+ const appContent = `
57
+ ${fixedDemos.map((demo, index)=>`import Demo_${index} from ${JSON.stringify(demo.path)}`).join('\n')}
84
58
  function App() {
85
59
  return (
86
- <div class="preview-container">
87
- <div class="preview-nav">{"${routes[0].title}"}</div>
88
- ${routes.map((_, index)=>`<Demo_${index} />`).join('\n')}
60
+ <div className="rp-preview-container">
61
+ <div className="rp-preview-nav">{"${fixedDemos[0].title}"}</div>
62
+ ${fixedDemos.map((_demo, index)=>`<Demo_${index} />`).join('\n')}
89
63
  </div>
90
64
  )
91
65
  }
92
- render(() => <App /> , document.getElementById('root'));
66
+ export default App;
93
67
  `;
94
- const renderContent = 'solid' === framework ? solidContent : reactContent;
95
- const id = `_${toValidVarName(key)}`;
96
- const entry = join(virtualDir, `${id}.entry.tsx`);
97
- await writeFile(entry, renderContent);
98
- sourceEntry[id] = entry;
99
- }));
68
+ const id = `_${toValidVarName(pageName)}`;
69
+ const demoPath = join(VIRTUAL_DEMO_DIR, `${id}.app.tsx`);
70
+ const entryContent = generateEntry({
71
+ demoPath
72
+ });
73
+ const entry = join(VIRTUAL_DEMO_DIR, `${id}.entry.tsx`);
74
+ await Promise.all([
75
+ writeFile(demoPath, appContent),
76
+ writeFile(entry, entryContent)
77
+ ]);
78
+ sourceEntry[id] = entry;
79
+ })();
80
+ return [
81
+ ...followPromiseList,
82
+ fixedPromise
83
+ ];
84
+ }).flat());
85
+ if (0 === Object.keys(sourceEntry).length) return {
86
+ _index: 'data:text/javascript,console.log("no demo found");'
87
+ };
100
88
  return sourceEntry;
101
89
  }
102
90
  const convert = function(test) {
@@ -255,109 +243,103 @@ const getASTNodeImport = (name, from)=>({
255
243
  }
256
244
  }
257
245
  });
258
- const getExternalDemoContent = (tempVar)=>({
259
- type: 'mdxJsxFlowElement',
260
- name: '',
261
- attributes: [],
262
- children: [
263
- {
264
- type: 'mdxJsxFlowElement',
265
- name: 'pre',
266
- attributes: [],
267
- children: [
268
- {
269
- type: 'mdxJsxFlowElement',
270
- name: 'code',
271
- attributes: [
272
- {
273
- type: 'mdxJsxAttribute',
274
- name: 'className',
275
- value: 'language-tsx'
276
- },
277
- {
278
- type: 'mdxJsxAttribute',
279
- name: 'children',
280
- value: {
281
- type: 'mdxJsxExpressionAttribute',
282
- value: tempVar,
283
- data: {
284
- estree: {
285
- type: 'Program',
286
- body: [
287
- {
288
- type: 'ExpressionStatement',
289
- expression: {
290
- type: 'Identifier',
291
- name: tempVar
292
- }
293
- }
294
- ]
295
- }
296
- }
297
- }
298
- }
299
- ]
300
- }
301
- ]
302
- }
303
- ]
304
- });
305
- const remarkPlugin_demos = {};
306
- const remarkCodeToDemo = function({ getRouteMeta, previewMode, defaultRenderMode, position, previewLanguages, previewCodeTransform }) {
246
+ function parsePreviewInfoFromMeta(options) {
247
+ const { meta, defaultPreviewMode, defaultRenderMode } = options;
248
+ const result = {
249
+ isPure: false,
250
+ isPreview: false,
251
+ previewMode: null
252
+ };
253
+ if (!meta) {
254
+ if ('preview' === defaultRenderMode) {
255
+ result.isPreview = true;
256
+ result.previewMode = defaultPreviewMode;
257
+ } else result.isPure = true;
258
+ return result;
259
+ }
260
+ if (meta.includes('pure')) {
261
+ result.isPure = true;
262
+ return result;
263
+ }
264
+ const previewMatch = meta.match(/preview(?:="([^"]+)")?/);
265
+ if (previewMatch) {
266
+ result.isPreview = true;
267
+ const explicitMode = previewMatch[1];
268
+ if (explicitMode && [
269
+ 'internal',
270
+ 'iframe-fixed',
271
+ 'iframe-follow'
272
+ ].includes(explicitMode)) result.previewMode = explicitMode;
273
+ else result.previewMode = defaultPreviewMode;
274
+ return result;
275
+ }
276
+ if (meta.includes('iframe')) logger.warn('The "iframe" meta is deprecated, please use \`\`\`tsx preview="iframe-fixed" or \`\`\`tsx preview="iframe-follow" instead.');
277
+ if ('preview' === defaultRenderMode) {
278
+ result.isPreview = true;
279
+ result.previewMode = defaultPreviewMode;
280
+ } else result.isPure = true;
281
+ return result;
282
+ }
283
+ const remarkPlugin_globalDemos = {};
284
+ const isDirtyRef = {
285
+ current: false
286
+ };
287
+ const remarkWriteCodeFile = function({ getRouteMeta, defaultPreviewMode, defaultRenderMode, previewLanguages, previewCodeTransform }) {
307
288
  const routeMeta = getRouteMeta();
308
- node_fs.mkdirSync(virtualDir, {
289
+ node_fs.mkdirSync(VIRTUAL_DEMO_DIR, {
309
290
  recursive: true
310
291
  });
311
292
  const data = this.data();
312
293
  return (tree, vfile)=>{
313
- const demoMdx = [];
314
294
  const route = routeMeta.find((meta)=>normalizePosixPath(meta.absolutePath) === normalizePosixPath(vfile.path || vfile.history[0]));
315
295
  if (!route) return;
316
296
  const { pageName } = route;
317
- remarkPlugin_demos[pageName] = [];
318
297
  let title = pageName;
319
298
  let index = 1;
320
- function constructDemoNode(demoId, demoPath, currentNode, isMobileMode, externalDemoIndex) {
321
- if (isMobileMode) {
322
- const relativePathReg = new RegExp(/^\.\.?\/.*$/);
323
- remarkPlugin_demos[pageName].push({
324
- title,
325
- id: demoId,
326
- path: relativePathReg.test(demoPath) ? external_node_path_resolve(vfile.dirname || dirname(vfile.path), demoPath) : demoPath
327
- });
328
- } else demoMdx.push(getASTNodeImport(`Demo${demoId}`, demoPath));
329
- const tempVar = `externalDemoContent${externalDemoIndex}`;
330
- if (void 0 !== externalDemoIndex) demoMdx.push(getASTNodeImport(tempVar, `!!${demoPath}?raw`));
331
- if (isMobileMode && 'fixed' === position) void 0 !== externalDemoIndex && Object.assign(currentNode, getExternalDemoContent(tempVar));
332
- else Object.assign(currentNode, {
333
- type: 'mdxJsxFlowElement',
334
- name: 'Container',
335
- attributes: [
336
- {
337
- type: 'mdxJsxAttribute',
338
- name: 'isMobile',
339
- value: isMobileMode
340
- },
341
- {
342
- type: 'mdxJsxAttribute',
343
- name: 'demoId',
344
- value: demoId
345
- }
346
- ],
347
- children: [
348
- void 0 === externalDemoIndex ? {
349
- ...currentNode,
350
- hasVisited: true
351
- } : getExternalDemoContent(tempVar),
352
- isMobileMode ? {
353
- type: 'mdxJsxFlowElement',
354
- name: null
355
- } : {
356
- type: 'mdxJsxFlowElement',
357
- name: `Demo${demoId}`
358
- }
359
- ]
299
+ const demoMdxImports = [];
300
+ const demosInCurrPage = {
301
+ [pageName]: []
302
+ };
303
+ function handleCodeBlockByPreviewMode(demoId, demoPath, currentNode, previewMode) {
304
+ if ('iframe-fixed' === previewMode || 'iframe-follow' === previewMode) demosInCurrPage[pageName].push({
305
+ title,
306
+ id: demoId,
307
+ path: demoPath,
308
+ previewMode
360
309
  });
310
+ else demoMdxImports.push(getASTNodeImport(`Demo${demoId}`, demoPath));
311
+ if ('internal' === previewMode || 'iframe-follow' === previewMode) {
312
+ const originalCodeAst = {
313
+ ...currentNode,
314
+ hasVisited: true
315
+ };
316
+ Object.assign(currentNode, {
317
+ type: 'mdxJsxFlowElement',
318
+ name: 'Preview',
319
+ attributes: [
320
+ {
321
+ type: 'mdxJsxAttribute',
322
+ name: 'previewMode',
323
+ value: previewMode
324
+ },
325
+ {
326
+ type: 'mdxJsxAttribute',
327
+ name: 'demoId',
328
+ value: demoId
329
+ }
330
+ ],
331
+ children: [
332
+ originalCodeAst,
333
+ 'iframe-follow' === previewMode ? {
334
+ type: 'mdxJsxFlowElement',
335
+ name: null
336
+ } : {
337
+ type: 'mdxJsxFlowElement',
338
+ name: `Demo${demoId}`
339
+ }
340
+ ]
341
+ });
342
+ }
361
343
  }
362
344
  lib_visit(tree, 'heading', (node)=>{
363
345
  if (1 === node.depth) {
@@ -366,49 +348,115 @@ const remarkCodeToDemo = function({ getRouteMeta, previewMode, defaultRenderMode
366
348
  }
367
349
  });
368
350
  lib_visit(tree, 'code', (node)=>{
369
- if ('hasVisited' in node) return;
351
+ if ('hasVisited' in node && true === node.hasVisited) return;
370
352
  if (node.lang && previewLanguages.includes(node.lang)) {
371
- if (node.meta?.includes('pure') || !node.meta?.includes('preview') && 'pure' === defaultRenderMode) return;
372
- const isJsx = 'jsx' === node.lang || 'tsx' === node.lang;
373
- const value = isJsx ? injectDemoBlockImport(previewCodeTransform({
374
- language: node.lang,
375
- code: node.value
376
- }), demoBlockComponentPath) : previewCodeTransform({
353
+ const { isPure, previewMode } = parsePreviewInfoFromMeta({
354
+ meta: node.meta,
355
+ defaultPreviewMode,
356
+ defaultRenderMode
357
+ });
358
+ if (isPure || !previewMode) return;
359
+ const virtualFileContent = previewCodeTransform({
377
360
  language: node.lang,
378
361
  code: node.value
379
362
  });
380
- const isMobileMode = node.meta?.includes('mobile') || node.meta?.includes('iframe') || !node.meta?.includes('web') && !node.meta?.includes('internal') && 'iframe' === previewMode;
381
363
  const id = generateId(pageName, index++);
382
- const virtualModulePath = join(virtualDir, `${id}.${getLangFileExt(node.lang)}`);
383
- constructDemoNode(id, virtualModulePath, node, isMobileMode);
364
+ const virtualModulePath = join(VIRTUAL_DEMO_DIR, `${id}.${getLangFileExt(node.lang)}`);
384
365
  if (node_fs.existsSync(virtualModulePath)) {
385
366
  const content = node_fs.readFileSync(virtualModulePath, 'utf-8');
386
- if (content === value) return;
387
- }
388
- node_fs.writeFileSync(virtualModulePath, value);
367
+ if (content !== virtualFileContent) node_fs.writeFileSync(virtualModulePath, virtualFileContent);
368
+ } else node_fs.writeFileSync(virtualModulePath, virtualFileContent);
369
+ handleCodeBlockByPreviewMode(id, virtualModulePath, node, previewMode);
389
370
  }
390
371
  });
391
- tree.children.unshift(...demoMdx);
392
- if (remarkPlugin_demos[pageName].length > 0) data.pageMeta.haveDemos = true;
372
+ tree.children.unshift(...demoMdxImports);
373
+ if (demosInCurrPage[pageName].some((i)=>'iframe-fixed' === i.previewMode)) data.pageMeta.haveIframeFixedDemos = true;
374
+ if (!isDeepStrictEqual(remarkPlugin_globalDemos[pageName] ?? [], demosInCurrPage[pageName])) isDirtyRef.current = true;
375
+ if (0 !== demosInCurrPage[pageName].length) remarkPlugin_globalDemos[pageName] = demosInCurrPage[pageName];
393
376
  };
394
377
  };
395
378
  let src_routeMeta;
396
- const DEFAULT_PREVIEW_LANGUAGES = [
397
- 'jsx',
398
- 'tsx'
399
- ];
400
379
  function pluginPreview(options) {
401
- const { isMobile = false, iframeOptions = {}, iframePosition = 'follow', defaultRenderMode = 'preview', previewLanguages = DEFAULT_PREVIEW_LANGUAGES, previewCodeTransform = ({ code })=>code } = options ?? {};
402
- const previewMode = options?.previewMode ?? (isMobile ? 'iframe' : 'internal');
403
- const { devPort = 7890, framework = 'react', position = iframePosition, builderConfig = {}, customEntry } = iframeOptions;
404
- const globalUIComponents = 'fixed' === position ? [
405
- join(staticPath, 'global-components', 'Device.tsx')
406
- ] : [];
380
+ const { iframeOptions = {}, defaultPreviewMode = 'internal', defaultRenderMode = 'pure', previewLanguages = [
381
+ 'jsx',
382
+ 'tsx'
383
+ ], previewCodeTransform = ({ code })=>code } = options ?? {};
384
+ const { devPort = 7890, framework = 'react', builderConfig = {}, customEntry } = iframeOptions;
407
385
  const getRouteMeta = ()=>src_routeMeta;
408
- let lastDemos;
409
386
  let devServer;
410
387
  let clientConfig;
411
388
  const port = devPort;
389
+ async function rsbuildStartOrBuild(config, isProd) {
390
+ if (devServer && !isProd && !isDirtyRef.current) return;
391
+ if (devServer && !isProd) {
392
+ await devServer.server.close();
393
+ devServer = void 0;
394
+ logger.info('[@rspress/plugin-preview] Restarting preview server due to demo changes...');
395
+ }
396
+ const outDir = join(config.outDir ?? 'doc_build', '~demo');
397
+ const { source, output, performance, resolve } = clientConfig ?? {};
398
+ const { preEntry: _, ...otherSourceOptions } = source ?? {};
399
+ const rsbuildConfig = mergeRsbuildConfig({
400
+ server: {
401
+ port: devPort,
402
+ printUrls: ()=>void 0,
403
+ strictPort: true
404
+ },
405
+ dev: {
406
+ lazyCompilation: false,
407
+ writeToDisk: true
408
+ },
409
+ performance: {
410
+ ...performance,
411
+ printFileSize: false,
412
+ buildCache: false
413
+ },
414
+ source: {
415
+ ...otherSourceOptions,
416
+ entry: await generateEntry_generateEntry(remarkPlugin_globalDemos, framework, customEntry),
417
+ preEntry: [
418
+ join(STATIC_DIR, 'iframe', 'entry.css'),
419
+ ...builderConfig.source?.preEntry ?? []
420
+ ]
421
+ },
422
+ html: {
423
+ tags: [
424
+ {
425
+ tag: "script",
426
+ children: entryraw_namespaceObject
427
+ }
428
+ ]
429
+ },
430
+ resolve,
431
+ output: {
432
+ ...output,
433
+ target: 'web',
434
+ assetPrefix: output?.assetPrefix ? `${removeTrailingSlash(output.assetPrefix)}/~demo` : '/~demo',
435
+ distPath: {
436
+ root: outDir
437
+ },
438
+ copy: void 0
439
+ },
440
+ tools: {
441
+ rspack: {
442
+ lazyCompilation: false,
443
+ watchOptions: {
444
+ ignored: /\.git/
445
+ }
446
+ }
447
+ }
448
+ }, builderConfig);
449
+ const rsbuildInstance = await createRsbuild({
450
+ callerName: 'rspress',
451
+ rsbuildConfig
452
+ });
453
+ if ('react' === framework) rsbuildInstance.addPlugins([
454
+ pluginReact()
455
+ ]);
456
+ if (isProd) rsbuildInstance.build();
457
+ else devServer = await rsbuildInstance.startDevServer();
458
+ isDirtyRef.current = false;
459
+ }
412
460
  return {
413
461
  name: '@rspress/plugin-preview',
414
462
  config (config) {
@@ -437,57 +485,7 @@ function pluginPreview(options) {
437
485
  }
438
486
  },
439
487
  async afterBuild (config, isProd) {
440
- if (isEqual(remarkPlugin_demos, lastDemos)) return;
441
- lastDemos = cloneDeep(remarkPlugin_demos);
442
- await devServer?.server?.close();
443
- devServer = void 0;
444
- const sourceEntry = await generateEntry(remarkPlugin_demos, framework, position, customEntry);
445
- const outDir = join(config.outDir ?? 'doc_build', '~demo');
446
- if (0 === Object.keys(sourceEntry).length) return;
447
- const { source, output, performance } = clientConfig ?? {};
448
- const { preEntry: _, ...otherSourceOptions } = source ?? {};
449
- const rsbuildConfig = mergeRsbuildConfig({
450
- server: {
451
- port: devPort,
452
- printUrls: ()=>void 0,
453
- strictPort: true
454
- },
455
- dev: {
456
- lazyCompilation: false
457
- },
458
- performance: {
459
- ...performance,
460
- printFileSize: false,
461
- buildCache: false
462
- },
463
- source: {
464
- ...otherSourceOptions,
465
- entry: sourceEntry
466
- },
467
- output: {
468
- ...output,
469
- assetPrefix: output?.assetPrefix ? `${removeTrailingSlash(output.assetPrefix)}/~demo` : '/~demo',
470
- distPath: {
471
- root: outDir
472
- },
473
- copy: void 0
474
- }
475
- }, builderConfig);
476
- const rsbuildInstance = await createRsbuild({
477
- callerName: 'rspress',
478
- rsbuildConfig
479
- });
480
- if ('solid' === framework) rsbuildInstance.addPlugins([
481
- pluginBabel({
482
- include: /\.(?:jsx|tsx)$/
483
- }),
484
- pluginSolid()
485
- ]);
486
- if ('react' === framework) rsbuildInstance.addPlugins([
487
- pluginReact()
488
- ]);
489
- if (isProd) rsbuildInstance.build();
490
- else devServer = await rsbuildInstance.startDevServer();
488
+ await rsbuildStartOrBuild(config, isProd);
491
489
  },
492
490
  builderConfig: {
493
491
  source: {
@@ -506,6 +504,9 @@ function pluginPreview(options) {
506
504
  }
507
505
  }
508
506
  },
507
+ performance: {
508
+ buildCache: false
509
+ },
509
510
  plugins: [
510
511
  {
511
512
  name: 'close-demo-server',
@@ -527,11 +528,10 @@ function pluginPreview(options) {
527
528
  markdown: {
528
529
  remarkPlugins: [
529
530
  [
530
- remarkCodeToDemo,
531
+ remarkWriteCodeFile,
531
532
  {
532
533
  getRouteMeta,
533
- position,
534
- previewMode,
534
+ defaultPreviewMode,
535
535
  defaultRenderMode,
536
536
  previewLanguages,
537
537
  previewCodeTransform
@@ -539,11 +539,12 @@ function pluginPreview(options) {
539
539
  ]
540
540
  ],
541
541
  globalComponents: [
542
- join(staticPath, 'global-components', 'Container.tsx')
542
+ join(STATIC_DIR, 'global-components', 'Preview.tsx')
543
543
  ]
544
544
  },
545
- globalUIComponents,
546
- globalStyles: join(staticPath, 'global-styles', `${previewMode}.css`)
545
+ globalUIComponents: [
546
+ join(STATIC_DIR, 'global-components', 'FixedDevice.tsx')
547
+ ]
547
548
  };
548
549
  }
549
550
  export { pluginPreview };
package/dist/utils.d.ts CHANGED
@@ -2,8 +2,6 @@ export declare const generateId: (pageName: string, index: number) => string;
2
2
 
3
3
  export declare const getLangFileExt: (lang: string) => string;
4
4
 
5
- export declare const injectDemoBlockImport: (str: string, path: string) => string;
6
-
7
5
  /**
8
6
  * remove .html extension and validate
9
7
  * @param routePath id from pathname