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

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.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,74 +25,137 @@ 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
  }
90
+ const getASTNodeImport = (name, from)=>({
91
+ type: 'mdxjsEsm',
92
+ value: `import ${name} from ${JSON.stringify(from)}`,
93
+ data: {
94
+ estree: {
95
+ type: 'Program',
96
+ sourceType: 'module',
97
+ body: [
98
+ {
99
+ type: 'ImportDeclaration',
100
+ specifiers: [
101
+ {
102
+ type: 'ImportDefaultSpecifier',
103
+ local: {
104
+ type: 'Identifier',
105
+ name
106
+ }
107
+ }
108
+ ],
109
+ source: {
110
+ type: 'Literal',
111
+ value: from,
112
+ raw: `${JSON.stringify(from)}`
113
+ }
114
+ }
115
+ ]
116
+ }
117
+ }
118
+ });
119
+ function parsePreviewInfoFromMeta(options) {
120
+ const { meta, defaultPreviewMode, defaultRenderMode } = options;
121
+ const result = {
122
+ isPure: false,
123
+ isPreview: false,
124
+ previewMode: null
125
+ };
126
+ if (!meta) {
127
+ if ('preview' === defaultRenderMode) {
128
+ result.isPreview = true;
129
+ result.previewMode = defaultPreviewMode;
130
+ } else result.isPure = true;
131
+ return result;
132
+ }
133
+ if (meta.includes('pure')) {
134
+ result.isPure = true;
135
+ return result;
136
+ }
137
+ const previewMatch = meta.match(/preview(?:="([^"]+)")?/);
138
+ if (previewMatch) {
139
+ result.isPreview = true;
140
+ const explicitMode = previewMatch[1];
141
+ if (explicitMode && [
142
+ 'internal',
143
+ 'iframe-fixed',
144
+ 'iframe-follow'
145
+ ].includes(explicitMode)) result.previewMode = explicitMode;
146
+ else result.previewMode = defaultPreviewMode;
147
+ return result;
148
+ }
149
+ if (meta.includes('iframe')) logger.warn('The "iframe" meta is deprecated, please use \`\`\`tsx preview="iframe-fixed" or \`\`\`tsx preview="iframe-follow" instead.');
150
+ if ('preview' === defaultRenderMode) {
151
+ result.isPreview = true;
152
+ result.previewMode = defaultPreviewMode;
153
+ } else result.isPure = true;
154
+ return result;
155
+ }
156
+ function color(d) {
157
+ return '\u001B[33m' + d + '\u001B[39m';
158
+ }
102
159
  const convert = function(test) {
103
160
  if (null == test) return ok;
104
161
  if ('function' == typeof test) return castFactory(test);
@@ -145,9 +202,6 @@ function ok() {
145
202
  function looksLikeANode(value) {
146
203
  return null !== value && 'object' == typeof value && 'type' in value;
147
204
  }
148
- function color(d) {
149
- return '\u001B[33m' + d + '\u001B[39m';
150
- }
151
205
  const empty = [];
152
206
  const CONTINUE = true;
153
207
  const EXIT = false;
@@ -226,138 +280,66 @@ function lib_visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) {
226
280
  return visitor(node, index, parent);
227
281
  }
228
282
  }
229
- const getASTNodeImport = (name, from)=>({
230
- type: 'mdxjsEsm',
231
- value: `import ${name} from ${JSON.stringify(from)}`,
232
- data: {
233
- estree: {
234
- type: 'Program',
235
- sourceType: 'module',
236
- body: [
237
- {
238
- type: 'ImportDeclaration',
239
- specifiers: [
240
- {
241
- type: 'ImportDefaultSpecifier',
242
- local: {
243
- type: 'Identifier',
244
- name
245
- }
246
- }
247
- ],
248
- source: {
249
- type: 'Literal',
250
- value: from,
251
- raw: `${JSON.stringify(from)}`
252
- }
253
- }
254
- ]
255
- }
256
- }
257
- });
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 }) {
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,114 @@ 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
+ watchOptions: {
443
+ ignored: /\.git/
444
+ }
445
+ }
446
+ }
447
+ }, builderConfig);
448
+ const rsbuildInstance = await createRsbuild({
449
+ callerName: 'rspress',
450
+ rsbuildConfig
451
+ });
452
+ if ('react' === framework) rsbuildInstance.addPlugins([
453
+ pluginReact()
454
+ ]);
455
+ if (isProd) rsbuildInstance.build();
456
+ else devServer = await rsbuildInstance.startDevServer();
457
+ isDirtyRef.current = false;
458
+ }
412
459
  return {
413
460
  name: '@rspress/plugin-preview',
414
461
  config (config) {
@@ -437,57 +484,7 @@ function pluginPreview(options) {
437
484
  }
438
485
  },
439
486
  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();
487
+ await rsbuildStartOrBuild(config, isProd);
491
488
  },
492
489
  builderConfig: {
493
490
  source: {
@@ -506,6 +503,9 @@ function pluginPreview(options) {
506
503
  }
507
504
  }
508
505
  },
506
+ performance: {
507
+ buildCache: false
508
+ },
509
509
  plugins: [
510
510
  {
511
511
  name: 'close-demo-server',
@@ -527,11 +527,10 @@ function pluginPreview(options) {
527
527
  markdown: {
528
528
  remarkPlugins: [
529
529
  [
530
- remarkCodeToDemo,
530
+ remarkWriteCodeFile,
531
531
  {
532
532
  getRouteMeta,
533
- position,
534
- previewMode,
533
+ defaultPreviewMode,
535
534
  defaultRenderMode,
536
535
  previewLanguages,
537
536
  previewCodeTransform
@@ -539,11 +538,12 @@ function pluginPreview(options) {
539
538
  ]
540
539
  ],
541
540
  globalComponents: [
542
- join(staticPath, 'global-components', 'Container.tsx')
541
+ join(STATIC_DIR, 'global-components', 'Preview.tsx')
543
542
  ]
544
543
  },
545
- globalUIComponents,
546
- globalStyles: join(staticPath, 'global-styles', `${previewMode}.css`)
544
+ globalUIComponents: [
545
+ join(STATIC_DIR, 'global-components', 'FixedDevice.tsx')
546
+ ]
547
547
  };
548
548
  }
549
549
  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