dumi 2.4.48 → 2.4.49-alpha.1

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.
@@ -0,0 +1 @@
1
+ export declare function areDumiDemoPropsEqual<T>(prev: T, next: T): boolean;
@@ -0,0 +1,4 @@
1
+ export function areDumiDemoPropsEqual(prev, next) {
2
+ // Ignore loader function identity while still detecting serializable demo updates.
3
+ return JSON.stringify(prev) === JSON.stringify(next);
4
+ }
@@ -5,6 +5,7 @@ import React, { createElement } from 'react';
5
5
  import Previewer from 'dumi/theme/builtins/Previewer';
6
6
  import { useRenderer } from "../useRenderer";
7
7
  import DemoErrorBoundary from "./DemoErrorBoundary";
8
+ import { areDumiDemoPropsEqual } from "./areDumiDemoPropsEqual";
8
9
  var InternalDumiDemo = function InternalDumiDemo(props) {
9
10
  var _useSiteData = useSiteData(),
10
11
  historyType = _useSiteData.historyType;
@@ -43,10 +44,7 @@ var InternalDumiDemo = function InternalDumiDemo(props) {
43
44
  "".concat(isHashRoute ? "#" : '').concat(basename).concat(SP_ROUTE_PREFIX, "demos/").concat(props.demo.id).concat(routeQuery)
44
45
  }, props.previewerProps), props.previewerProps.iframe ? null : demoNode);
45
46
  };
46
- export var DumiDemo = /*#__PURE__*/React.memo(InternalDumiDemo, function (prev, next) {
47
- // compare length for performance
48
- return JSON.stringify(prev).length === JSON.stringify(next).length;
49
- });
47
+ export var DumiDemo = /*#__PURE__*/React.memo(InternalDumiDemo, areDumiDemoPropsEqual);
50
48
  if (process.env.NODE_ENV !== 'production') {
51
49
  InternalDumiDemo.displayName = 'DumiDemo';
52
50
  }
@@ -0,0 +1,5 @@
1
+ type RouteMetaHMRListener = () => void;
2
+ export declare function getRouteMetaHMRRevision(routeId: string): number;
3
+ export declare function subscribeRouteMetaHMR(routeId: string, listener: RouteMetaHMRListener): () => void;
4
+ export declare function notifyRouteMetaHMR(routeId: string): void;
5
+ export {};
@@ -0,0 +1,23 @@
1
+ var revisions = new Map();
2
+ var listeners = new Map();
3
+ export function getRouteMetaHMRRevision(routeId) {
4
+ var _revisions$get;
5
+ return (_revisions$get = revisions.get(routeId)) !== null && _revisions$get !== void 0 ? _revisions$get : 0;
6
+ }
7
+ export function subscribeRouteMetaHMR(routeId, listener) {
8
+ var _listeners$get;
9
+ var routeListeners = (_listeners$get = listeners.get(routeId)) !== null && _listeners$get !== void 0 ? _listeners$get : new Set();
10
+ routeListeners.add(listener);
11
+ listeners.set(routeId, routeListeners);
12
+ return function () {
13
+ routeListeners.delete(listener);
14
+ if (!routeListeners.size) listeners.delete(routeId);
15
+ };
16
+ }
17
+ export function notifyRouteMetaHMR(routeId) {
18
+ var _listeners$get2;
19
+ revisions.set(routeId, getRouteMetaHMRRevision(routeId) + 1);
20
+ (_listeners$get2 = listeners.get(routeId)) === null || _listeners$get2 === void 0 || _listeners$get2.forEach(function (listener) {
21
+ return listener();
22
+ });
23
+ }
@@ -1,4 +1,5 @@
1
- import type { IRouteMeta } from './types';
1
+ import type { IRouteMeta, IRoutesById } from './types';
2
+ export declare function getCachedRouteMeta(route: IRoutesById[string], revision: number): IRouteMeta;
2
3
  /**
3
4
  * hook for get matched route
4
5
  * @internal internal use. Do not use in your production code.
@@ -7,27 +7,35 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
7
  import deepmerge from 'deepmerge';
8
8
  import { getRouteMetaById, matchRoutes, useAppData, useLocation, useRouteData } from 'dumi';
9
9
  import { useCallback, useState } from 'react';
10
+ import { getRouteMetaHMRRevision, subscribeRouteMetaHMR } from "./routeMetaHMR";
10
11
  import { useIsomorphicLayoutEffect } from "./utils";
11
12
  var cache = new Map();
12
13
  var asyncCache = new Map();
14
+ var cacheRevisions = new Map();
13
15
  var EMPTY_META = {
14
16
  frontmatter: {},
15
17
  toc: [],
16
18
  texts: []
17
19
  };
18
20
  var ASYNC_META_PROPS = ['texts'];
19
- function getCachedRouteMeta(route) {
21
+ export function getCachedRouteMeta(route, revision) {
20
22
  var cacheKey = route.id;
23
+ if (cacheRevisions.get(cacheKey) !== revision) {
24
+ cache.delete(cacheKey);
25
+ asyncCache.delete(cacheKey);
26
+ cacheRevisions.set(cacheKey, revision);
27
+ }
21
28
  if (!cache.get(cacheKey)) {
22
29
  var _meta$tabs;
23
30
  var merge = function merge() {
24
31
  var meta = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : EMPTY_META;
25
32
  if (route.meta) {
33
+ var _ref2, _ref3;
26
34
  Object.keys(route.meta).forEach(function (key) {
27
35
  var _ref, _ref$key;
28
36
  (_ref$key = (_ref = meta)[key]) !== null && _ref$key !== void 0 ? _ref$key : _ref[key] = route.meta[key];
29
37
  });
30
- meta.frontmatter = deepmerge(meta.frontmatter, route.meta.frontmatter, {
38
+ meta.frontmatter = deepmerge((_ref2 = revision ? route.meta.frontmatter : meta.frontmatter) !== null && _ref2 !== void 0 ? _ref2 : {}, (_ref3 = revision ? meta.frontmatter : route.meta.frontmatter) !== null && _ref3 !== void 0 ? _ref3 : {}, {
31
39
  arrayMerge: function arrayMerge(_destinationArray, sourceArray) {
32
40
  return sourceArray;
33
41
  }
@@ -45,6 +53,10 @@ function getCachedRouteMeta(route) {
45
53
  // load async meta then replace cache
46
54
  if (routeMetaPromise) {
47
55
  asyncCache.set(cacheKey, routeMetaPromise.then(function (full) {
56
+ if (cacheRevisions.get(cacheKey) !== revision) {
57
+ var _cacheRevisions$get;
58
+ return getCachedRouteMeta(route, (_cacheRevisions$get = cacheRevisions.get(cacheKey)) !== null && _cacheRevisions$get !== void 0 ? _cacheRevisions$get : 0);
59
+ }
48
60
  return cache.set(cacheKey, merge(full)).get(cacheKey);
49
61
  }));
50
62
  }
@@ -111,5 +123,25 @@ export var useMatchedRoute = function useMatchedRoute() {
111
123
  */
112
124
  export var useRouteMeta = function useRouteMeta() {
113
125
  var route = useMatchedRoute();
114
- return getCachedRouteMeta(route);
126
+ var _useState3 = useState(0),
127
+ _useState4 = _slicedToArray(_useState3, 2),
128
+ forceUpdate = _useState4[1];
129
+ var revision = process.env.NODE_ENV === 'production' ? 0 : getRouteMetaHMRRevision(route.id);
130
+ useIsomorphicLayoutEffect(function () {
131
+ if (process.env.NODE_ENV === 'production') return;
132
+ var unsubscribe = subscribeRouteMetaHMR(route.id, function () {
133
+ forceUpdate(function (count) {
134
+ return count + 1;
135
+ });
136
+ });
137
+
138
+ // Do not miss an update dispatched between render and subscription.
139
+ if (getRouteMetaHMRRevision(route.id) !== revision) {
140
+ forceUpdate(function (count) {
141
+ return count + 1;
142
+ });
143
+ }
144
+ return unsubscribe;
145
+ }, [route.id, revision]);
146
+ return getCachedRouteMeta(route, revision);
115
147
  };
@@ -1,5 +1,6 @@
1
1
  import type { IApi, IDumiTechStack } from '../../types';
2
2
  export declare const techStacks: IDumiTechStack[];
3
+ export declare function replaceTechStacks(nextTechStacks: IDumiTechStack[]): void;
3
4
  export declare function getUtoopackDemoAssetsFile(api: {
4
5
  env: string;
5
6
  paths: {
@@ -31,6 +31,7 @@ var compile_exports = {};
31
31
  __export(compile_exports, {
32
32
  default: () => compile_default,
33
33
  getUtoopackDemoAssetsFile: () => getUtoopackDemoAssetsFile,
34
+ replaceTechStacks: () => replaceTechStacks,
34
35
  techStacks: () => techStacks
35
36
  });
36
37
  module.exports = __toCommonJS(compile_exports);
@@ -44,6 +45,9 @@ var import_makoHooks = require("./makoHooks");
44
45
  var import_utils2 = require("./utils");
45
46
  var import_utoopackLoaders = require("./utoopackLoaders");
46
47
  var techStacks = [];
48
+ function replaceTechStacks(nextTechStacks) {
49
+ techStacks.splice(0, techStacks.length, ...nextTechStacks);
50
+ }
47
51
  function getUtoopackDemoAssetsFile(api) {
48
52
  if (api.env !== "production" || !api.isPluginEnable("exportStatic")) {
49
53
  return void 0;
@@ -113,8 +117,8 @@ var compile_default = (api) => {
113
117
  // and `before` only insert before the last one
114
118
  stage: -Infinity,
115
119
  async fn() {
116
- techStacks.push(
117
- ...await api.applyPlugins({
120
+ replaceTechStacks(
121
+ await api.applyPlugins({
118
122
  key: "registerTechStack",
119
123
  type: api.ApplyPluginsType.add
120
124
  })
@@ -256,5 +260,6 @@ var compile_default = (api) => {
256
260
  // Annotate the CommonJS export names for ESM import in node:
257
261
  0 && (module.exports = {
258
262
  getUtoopackDemoAssetsFile,
263
+ replaceTechStacks,
259
264
  techStacks
260
265
  });
@@ -328,6 +328,18 @@ var getUtoopackRules = (api, config = api.config) => {
328
328
  ],
329
329
  as: "*.js"
330
330
  },
331
+ // Keep page rendering on a separate module identity from lazy route
332
+ // metadata so React Refresh can accept text changes locally.
333
+ {
334
+ condition: { query: /^\?type=text-render$/ },
335
+ loaders: [
336
+ {
337
+ loader: mdLoaderPath,
338
+ options: { ...serializableBaseOpts, mode: "text-render" }
339
+ }
340
+ ],
341
+ as: "*.js"
342
+ },
331
343
  // extract demo index metadata from markdown
332
344
  {
333
345
  condition: { query: /^\?type=demo-index$/ },
@@ -84,7 +84,9 @@ var meta_default = (api) => {
84
84
  path: "dumi/meta/index.ts",
85
85
  tplPath: require.resolve("../templates/meta/index.ts.tpl"),
86
86
  context: {
87
+ enableUtoopackHMR: useUtoopackDemoHMR,
87
88
  metaFiles: parsedMetaFiles,
89
+ metaStructureHash: (0, import_utils.getContentHash)(JSON.stringify(parsedMetaFiles)),
88
90
  chunkName: function chunkName() {
89
91
  var _a;
90
92
  if (!("file" in this)) {
@@ -39,6 +39,7 @@ var import_path = __toESM(require("path"));
39
39
  var import_pluralize = require("pluralize");
40
40
  var import_plugin_utils = require("umi/plugin-utils");
41
41
  var CTX_LAYOUT_ID = "dumi-context-layout";
42
+ var ATOM_ROUTE_MARKDOWN_GLOB = "{*,*/index,*/index.*,*/README,*/README.*}.md";
42
43
  function normalizeDocDir(docDir) {
43
44
  return typeof docDir === "object" ? docDir : { dir: docDir };
44
45
  }
@@ -79,12 +80,23 @@ function flatRoute(route, docLayoutId) {
79
80
  }
80
81
  var routes_default = (api) => {
81
82
  api.describe({ key: "dumi:routes" });
82
- api.addTmpGenerateWatcherPaths(
83
- () => [
84
- ...api.config.resolve.atomDirs,
85
- ...api.config.resolve.docDirs.map(normalizeDocDir)
86
- ].map(({ dir }) => import_path.default.join(api.cwd, dir, "**/*.md"))
87
- );
83
+ api.addTmpGenerateWatcherPaths(() => {
84
+ const watcherPaths = [
85
+ ...api.config.resolve.atomDirs.map(
86
+ ({ dir }) => import_path.default.join(api.cwd, dir, ATOM_ROUTE_MARKDOWN_GLOB)
87
+ ),
88
+ ...api.config.resolve.docDirs.map(normalizeDocDir).map(({ dir }) => import_path.default.join(api.cwd, dir, "**/*.md"))
89
+ ].map(import_plugin_utils.winPath);
90
+ if (api.env === "development" && api.config.utoopack) {
91
+ return watcherPaths.map((watcherPath) => ({
92
+ path: watcherPath,
93
+ // Existing Markdown files are already watched by the bundler. Only
94
+ // route topology changes need to regenerate Umi's temporary files.
95
+ events: ["add", "unlink"]
96
+ }));
97
+ }
98
+ return watcherPaths;
99
+ });
88
100
  api.modifyDefaultConfig((memo) => {
89
101
  if (api.userConfig.resolve) {
90
102
  const keys = ["docDirs", "atomDirs"];
@@ -23,11 +23,18 @@ interface IMdLoaderFrontmatterModeOptions extends Omit<IMdLoaderDefaultModeOptio
23
23
  interface IMdLoaderTextModeOptions extends Omit<IMdLoaderDefaultModeOptions, 'builtins' | 'mode'> {
24
24
  mode: 'text';
25
25
  }
26
- export type IMdLoaderOptions = IMdLoaderDefaultModeOptions | IMdLoaderDemosModeOptions | IMdLoaderDemoModeOptions | IMdLoaderFrontmatterModeOptions | IMdLoaderTextModeOptions | IMdLoaderDemoIndexModeOptions;
26
+ interface IMdLoaderRenderTextModeOptions extends Omit<IMdLoaderDefaultModeOptions, 'builtins' | 'mode'> {
27
+ mode: 'text-render';
28
+ }
29
+ export type IMdLoaderOptions = IMdLoaderDefaultModeOptions | IMdLoaderDemosModeOptions | IMdLoaderDemoModeOptions | IMdLoaderFrontmatterModeOptions | IMdLoaderTextModeOptions | IMdLoaderRenderTextModeOptions | IMdLoaderDemoIndexModeOptions;
27
30
  type MdLoaderCache = {
28
31
  getSync: (key: string, defaultValue: any) => any;
29
32
  };
30
33
  export declare function getMdLoaderCacheSync<T>(cache: MdLoaderCache, key: string, defaultValue: T): T;
31
34
  export declare function getDemoSourceFiles(demos?: IMdTransformerResult['meta']['demos']): string[];
35
+ export declare function emitDefault(this: any, opts: IMdLoaderDefaultModeOptions, ret: IMdTransformerResult): string;
36
+ export declare function emitFrontmatter(this: any, opts: IMdLoaderFrontmatterModeOptions, ret: IMdTransformerResult): string;
37
+ export declare function emitText(this: any, opts: IMdLoaderTextModeOptions, ret: IMdTransformerResult): string;
38
+ export declare function emitRenderText(this: any, _opts: IMdLoaderRenderTextModeOptions, ret: IMdTransformerResult): string;
32
39
  export default function mdLoader(this: any, content: string): void;
33
40
  export {};
@@ -30,6 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  var markdown_exports = {};
31
31
  __export(markdown_exports, {
32
32
  default: () => mdLoader,
33
+ emitDefault: () => emitDefault,
34
+ emitFrontmatter: () => emitFrontmatter,
35
+ emitRenderText: () => emitRenderText,
36
+ emitText: () => emitText,
33
37
  getDemoSourceFiles: () => getDemoSourceFiles,
34
38
  getMdLoaderCacheSync: () => getMdLoaderCacheSync
35
39
  });
@@ -91,8 +95,10 @@ function getRouteId(opts, fileAbsPath) {
91
95
  }
92
96
  function emitDefault(opts, ret) {
93
97
  const { frontmatter, demos } = ret.meta;
98
+ const sharedDemoProps = ret.meta.demoSharedProps ?? [];
94
99
  const isTabContent = (0, import_tabs.isTabRouteFile)(this.resourcePath);
95
100
  const wrapper = isTabContent ? "" : "DumiPage";
101
+ const textMode = import_utoopackLoaders.UTOOPACK_LOADER_CTX_KEY in opts && process.env.NODE_ENV !== "production" ? "text-render" : "text";
96
102
  if (demos && opts.onResolveDemos) {
97
103
  opts.onResolveDemos(demos);
98
104
  }
@@ -125,7 +131,8 @@ import React, { Suspense } from 'react';
125
131
  import { DumiPage } from 'dumi';
126
132
  import { texts as ${import_rehypeText.CONTENT_TEXTS_OBJ_NAME} } from '${(0, import_plugin_utils.winPath)(
127
133
  this.resourcePath
128
- )}?type=text';
134
+ )}?type=${textMode}';
135
+ ${sharedDemoProps.length ? `const __dumi_demo_shared_props__ = ${JSON.stringify(sharedDemoProps)};` : ""}
129
136
 
130
137
  // export named function for fastRefresh
131
138
  // ref: https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#edits-always-lead-to-full-reload
@@ -295,24 +302,81 @@ function emitDemoIndex(opts, ret) {
295
302
  ${renderDemoIndex(this.resourcePath, opts, demos)}`;
296
303
  }
297
304
  function emitFrontmatter(opts, ret) {
298
- const { frontmatter, toc, demos } = ret.meta;
305
+ const { demos } = ret.meta;
306
+ const frontmatter = ret.meta.frontmatter;
307
+ const toc = ret.meta.toc ?? [];
299
308
  const resourcePath = (0, import_plugin_utils.winPath)(this.resourcePath);
300
309
  const isUtoopack = import_utoopackLoaders.UTOOPACK_LOADER_CTX_KEY in opts;
310
+ const enableUtoopackHMR = isUtoopack && process.env.NODE_ENV !== "production";
311
+ const routeMetaHMRPath = enableUtoopackHMR ? (0, import_plugin_utils.winPath)(require.resolve("../../client/theme-api/routeMetaHMR")) : void 0;
312
+ const routeId = getRouteId(opts, this.resourcePath);
301
313
  const demoIndex = renderDemoIndex(this.resourcePath, opts, demos);
302
314
  if (isUtoopack) {
315
+ const routeStructureHash = (0, import_utils.getContentHash)(
316
+ JSON.stringify({
317
+ atomId: frontmatter.atomId,
318
+ demoIds: demos == null ? void 0 : demos.map(({ id }) => id),
319
+ filename: frontmatter.filename,
320
+ group: frontmatter.group,
321
+ nav: frontmatter.nav,
322
+ order: frontmatter.order,
323
+ title: frontmatter.title
324
+ })
325
+ );
303
326
  const rendered = import_plugin_utils.Mustache.render(
304
- `import '${resourcePath}?watch=parent';
327
+ `{{#enableUtoopackHMR}}
328
+ import { notifyRouteMetaHMR } from {{{routeMetaHMRSpecifier}}};
329
+ {{/enableUtoopackHMR}}
330
+ import '${resourcePath}?watch=parent';
305
331
  globalThis.__DUMI_FM__ = globalThis.__DUMI_FM__ || {};
306
332
  globalThis.__DUMI_TOC__ = globalThis.__DUMI_TOC__ || {};
333
+ {{#enableUtoopackHMR}}
334
+ const hadPreviousMeta = Object.prototype.hasOwnProperty.call(globalThis.__DUMI_FM__, '${resourcePath}');
335
+ globalThis.__DUMI_META_STRUCTURE_HASH__ = globalThis.__DUMI_META_STRUCTURE_HASH__ || {};
336
+ const previousRouteStructureHash = globalThis.__DUMI_META_STRUCTURE_HASH__['${resourcePath}'];
337
+ const didRouteStructureChange = previousRouteStructureHash !== undefined && previousRouteStructureHash !== '{{{routeStructureHash}}}';
338
+ globalThis.__DUMI_META_STRUCTURE_HASH__['${resourcePath}'] = '{{{routeStructureHash}}}';
339
+ const nextFrontmatter = {{{frontmatter}}};
340
+ const nextToc = {{{toc}}};
341
+ const frontmatter = globalThis.__DUMI_FM__['${resourcePath}'] || {};
342
+ const toc = globalThis.__DUMI_TOC__['${resourcePath}'] || [];
343
+ Object.keys(frontmatter).forEach((key) => delete frontmatter[key]);
344
+ Object.assign(frontmatter, nextFrontmatter);
345
+ toc.splice(0, toc.length, ...nextToc);
346
+ globalThis.__DUMI_FM__['${resourcePath}'] = frontmatter;
347
+ globalThis.__DUMI_TOC__['${resourcePath}'] = toc;
348
+ {{/enableUtoopackHMR}}
349
+ {{^enableUtoopackHMR}}
307
350
  globalThis.__DUMI_FM__['${resourcePath}'] = {{{frontmatter}}};
308
351
  globalThis.__DUMI_TOC__['${resourcePath}'] = {{{toc}}};
309
- export const frontmatter = globalThis.__DUMI_FM__['${resourcePath}'];
310
- export const toc = globalThis.__DUMI_TOC__['${resourcePath}'];
311
- {{{demoIndex}}}`,
352
+ const frontmatter = globalThis.__DUMI_FM__['${resourcePath}'];
353
+ const toc = globalThis.__DUMI_TOC__['${resourcePath}'];
354
+ {{/enableUtoopackHMR}}
355
+ export { frontmatter, toc };
356
+ {{#enableUtoopackHMR}}
357
+ export const routeStructureHash = '{{{routeStructureHash}}}';
358
+ {{/enableUtoopackHMR}}
359
+ {{{demoIndex}}}
360
+ {{#enableUtoopackHMR}}
361
+ if (module.hot) {
362
+ module.hot.accept();
363
+ if (didRouteStructureChange) {
364
+ module.hot.invalidate();
365
+ }{{#routeId}} else if (hadPreviousMeta) {
366
+ notifyRouteMetaHMR({{{routeId}}});
367
+ }{{/routeId}}{{^routeId}} else if (hadPreviousMeta) {
368
+ module.hot.invalidate();
369
+ }{{/routeId}}
370
+ }
371
+ {{/enableUtoopackHMR}}`,
312
372
  {
313
373
  toc: JSON.stringify(toc),
314
374
  frontmatter: JSON.stringify(frontmatter),
315
- demoIndex
375
+ demoIndex,
376
+ enableUtoopackHMR,
377
+ routeId: routeId ? JSON.stringify(routeId) : void 0,
378
+ routeMetaHMRSpecifier: routeMetaHMRPath ? JSON.stringify(routeMetaHMRPath) : void 0,
379
+ routeStructureHash
316
380
  }
317
381
  );
318
382
  return rendered;
@@ -330,10 +394,45 @@ export const frontmatter = new Proxy({{{frontmatter}}}, {});
330
394
  );
331
395
  }
332
396
  function emitText(opts, ret) {
333
- const { texts } = ret.meta;
397
+ const texts = ret.meta.texts ?? [];
398
+ const resourcePath = (0, import_plugin_utils.winPath)(this.resourcePath);
399
+ const enableUtoopackHMR = import_utoopackLoaders.UTOOPACK_LOADER_CTX_KEY in opts && process.env.NODE_ENV !== "production";
400
+ if (enableUtoopackHMR) {
401
+ const routeId = getRouteId(opts, this.resourcePath);
402
+ const routeMetaHMRSpecifier = JSON.stringify(
403
+ (0, import_plugin_utils.winPath)(require.resolve("../../client/theme-api/routeMetaHMR"))
404
+ );
405
+ return import_plugin_utils.Mustache.render(
406
+ `import { notifyRouteMetaHMR } from {{{routeMetaHMRSpecifier}}};
407
+ import {{{watchSpecifier}}};
408
+ globalThis.__DUMI_TEXTS__ = globalThis.__DUMI_TEXTS__ || {};
409
+ const hadPreviousTexts = Object.prototype.hasOwnProperty.call(globalThis.__DUMI_TEXTS__, {{{resourceSpecifier}}});
410
+ const nextTexts = {{{texts}}};
411
+ const texts = globalThis.__DUMI_TEXTS__[{{{resourceSpecifier}}}] || [];
412
+ texts.splice(0, texts.length, ...nextTexts);
413
+ globalThis.__DUMI_TEXTS__[{{{resourceSpecifier}}}] = texts;
414
+ export { texts };
415
+
416
+ if (module.hot) {
417
+ module.hot.accept();
418
+ {{#routeId}}if (hadPreviousTexts) {
419
+ notifyRouteMetaHMR({{{routeId}}});
420
+ }{{/routeId}}{{^routeId}}if (hadPreviousTexts) {
421
+ module.hot.invalidate();
422
+ }{{/routeId}}
423
+ }`,
424
+ {
425
+ resourceSpecifier: JSON.stringify(resourcePath),
426
+ routeId: routeId ? JSON.stringify(routeId) : void 0,
427
+ routeMetaHMRSpecifier,
428
+ texts: JSON.stringify(texts),
429
+ watchSpecifier: JSON.stringify(`${resourcePath}?watch=parent`)
430
+ }
431
+ );
432
+ }
334
433
  return import_plugin_utils.Mustache.render(
335
434
  `
336
- import '${(0, import_plugin_utils.winPath)(this.resourcePath)}?watch=parent';
435
+ import '${resourcePath}?watch=parent';
337
436
  export const texts = {{{texts}}};
338
437
  `,
339
438
  {
@@ -341,6 +440,20 @@ function emitText(opts, ret) {
341
440
  }
342
441
  );
343
442
  }
443
+ function emitRenderText(_opts, ret) {
444
+ return import_plugin_utils.Mustache.render(
445
+ `
446
+ import {{{watchSpecifier}}};
447
+ export const texts = {{{texts}}};
448
+ `,
449
+ {
450
+ texts: JSON.stringify(ret.meta.texts ?? []),
451
+ watchSpecifier: JSON.stringify(
452
+ `${(0, import_plugin_utils.winPath)(this.resourcePath)}?watch=parent`
453
+ )
454
+ }
455
+ );
456
+ }
344
457
  function emit(opts, ret) {
345
458
  const { demos, embeds } = ret.meta;
346
459
  embeds.forEach((file) => this.addDependency(file));
@@ -358,6 +471,8 @@ function emit(opts, ret) {
358
471
  return emitFrontmatter.call(this, opts, ret);
359
472
  case "text":
360
473
  return emitText.call(this, opts, ret);
474
+ case "text-render":
475
+ return emitRenderText.call(this, opts, ret);
361
476
  default:
362
477
  return emitDefault.call(this, opts, ret);
363
478
  }
@@ -444,6 +559,10 @@ function mdLoader(content) {
444
559
  }
445
560
  // Annotate the CommonJS export names for ESM import in node:
446
561
  0 && (module.exports = {
562
+ emitDefault,
563
+ emitFrontmatter,
564
+ emitRenderText,
565
+ emitText,
447
566
  getDemoSourceFiles,
448
567
  getMdLoaderCacheSync
449
568
  });
@@ -43,6 +43,7 @@ declare module 'vfile' {
43
43
  frontmatter: IRouteMeta['frontmatter'];
44
44
  toc: IRouteMeta['toc'];
45
45
  embeds: string[];
46
+ demoSharedProps?: unknown[];
46
47
  }
47
48
  }
48
49
  export interface IMdTransformerOptions {
@@ -49,6 +49,8 @@ var DEMO_NODE_CONTAINER = "$demo-container";
49
49
  var DEMO_PROP_VALUE_KEY = "$demo-prop-value-key";
50
50
  var DEMO_LOADER_PLACEHOLDER = "__DUMI_DEMO_LOADER__";
51
51
  var DEMO_LOADER_PLACEHOLDER_VALUE = DEMO_LOADER_PLACEHOLDER;
52
+ var DEMO_SHARED_PROP_PLACEHOLDER_PREFIX = "__DUMI_DEMO_SHARED_PROP_";
53
+ var MIN_SHARED_PREVIEWER_PROP_SIZE = 1024;
52
54
  var DUMI_DEMO_TAG = "DumiDemo";
53
55
  var DUMI_DEMO_GRID_TAG = "DumiDemoGrid";
54
56
  var SKIP_DEMO_PARSE = "pure";
@@ -66,6 +68,55 @@ var alwaysDemoRE = new RegExp(
66
68
  ({ toString } = await import("hast-util-to-string"));
67
69
  ({ isElement } = await import("hast-util-is-element"));
68
70
  })();
71
+ function getDemoPropsFromNode(node) {
72
+ var _a;
73
+ return [].concat(
74
+ (_a = node.data) == null ? void 0 : _a[DEMO_PROP_VALUE_KEY]
75
+ );
76
+ }
77
+ function hoistSharedPreviewerProps(nodes) {
78
+ const candidates = /* @__PURE__ */ new Map();
79
+ nodes.forEach((node) => {
80
+ getDemoPropsFromNode(node).forEach(({ previewerProps }) => {
81
+ Object.values(previewerProps).forEach((value) => {
82
+ if (!value || typeof value !== "object")
83
+ return;
84
+ const serialized = JSON.stringify(value);
85
+ if (serialized.length < MIN_SHARED_PREVIEWER_PROP_SIZE)
86
+ return;
87
+ const candidate = candidates.get(serialized);
88
+ if (candidate) {
89
+ candidate.count += 1;
90
+ } else {
91
+ candidates.set(serialized, { count: 1, value });
92
+ }
93
+ });
94
+ });
95
+ });
96
+ const sharedValues = [];
97
+ const sharedIndexes = /* @__PURE__ */ new Map();
98
+ candidates.forEach(({ count, value }, serialized) => {
99
+ if (count > 1) {
100
+ sharedIndexes.set(serialized, sharedValues.length);
101
+ sharedValues.push(value);
102
+ }
103
+ });
104
+ if (!sharedValues.length)
105
+ return sharedValues;
106
+ nodes.forEach((node) => {
107
+ getDemoPropsFromNode(node).forEach(({ previewerProps }) => {
108
+ Object.entries(previewerProps).forEach(([key, value]) => {
109
+ if (!value || typeof value !== "object")
110
+ return;
111
+ const index = sharedIndexes.get(JSON.stringify(value));
112
+ if (index !== void 0) {
113
+ previewerProps[key] = `${DEMO_SHARED_PROP_PLACEHOLDER_PREFIX}${index}__`;
114
+ }
115
+ });
116
+ });
117
+ });
118
+ return sharedValues;
119
+ }
69
120
  function getCodeLang(node, opts) {
70
121
  var _a, _b, _c, _d;
71
122
  let lang = "";
@@ -380,6 +431,10 @@ function rehypeDemo(opts) {
380
431
  });
381
432
  await Promise.all(deferrers).then((demos) => {
382
433
  vFile.data.demos = demos;
434
+ const sharedPreviewerProps = opts.useUtoopackDemoHMR ? hoistSharedPreviewerProps(replaceNodes) : [];
435
+ if (sharedPreviewerProps.length) {
436
+ vFile.data.demoSharedProps = sharedPreviewerProps;
437
+ }
383
438
  replaceNodes.forEach((node) => {
384
439
  const demoLoader = `() => import('${(0, import_plugin_utils.winPath)(
385
440
  opts.fileAbsPath
@@ -390,6 +445,9 @@ function rehypeDemo(opts) {
390
445
  new RegExp(`"${DEMO_LOADER_PLACEHOLDER}"`, "g"),
391
446
  demoLoader
392
447
  );
448
+ sharedPreviewerProps.forEach((_, index2) => {
449
+ value = value.split(`"${DEMO_SHARED_PROP_PLACEHOLDER_PREFIX}${index2}__"`).join(`__dumi_demo_shared_props__[${index2}]`);
450
+ });
393
451
  }
394
452
  if (node.JSXAttributes[0].type === "JSXAttribute") {
395
453
  node.JSXAttributes[0].value = value;
@@ -3,11 +3,16 @@
3
3
  import { frontmatter as fm{{{index}}}, toc as t{{{index}}}, demoIndex as dmi{{{index}}} } from '{{{file}}}?type=frontmatter';
4
4
  {{/loadDemoIndex}}
5
5
  {{^loadDemoIndex}}
6
- import { frontmatter as fm{{{index}}}, toc as t{{{index}}} } from '{{{file}}}?type=frontmatter';
6
+ import { frontmatter as fm{{{index}}}, toc as t{{{index}}}{{#enableUtoopackHMR}}{{#isMarkdown}}, routeStructureHash as rsh{{{index}}}{{/isMarkdown}}{{/enableUtoopackHMR}} } from '{{{file}}}?type=frontmatter';
7
7
  {{/loadDemoIndex}}
8
8
  {{/metaFiles}}
9
9
 
10
+ {{#enableUtoopackHMR}}
11
+ const nextFilesMeta = {
12
+ {{/enableUtoopackHMR}}
13
+ {{^enableUtoopackHMR}}
10
14
  export const filesMeta = {
15
+ {{/enableUtoopackHMR}}
11
16
  {{#metaFiles}}
12
17
  '{{{id}}}': {
13
18
  frontmatter: fm{{{index}}},
@@ -25,4 +30,34 @@ export const filesMeta = {
25
30
  {{/metaFiles}}
26
31
  }
27
32
 
33
+ {{#enableUtoopackHMR}}
34
+ const dumiGlobal = globalThis as any;
35
+ dumiGlobal.__DUMI_FILES_META__ = dumiGlobal.__DUMI_FILES_META__ || {};
36
+ const filesMeta = dumiGlobal.__DUMI_FILES_META__;
37
+ Object.keys(filesMeta).forEach((id) => delete filesMeta[id]);
38
+ Object.assign(filesMeta, nextFilesMeta);
39
+ export { filesMeta };
40
+
41
+ const nextMetaStructureHash = JSON.stringify([
42
+ '{{{metaStructureHash}}}',
43
+ {{#metaFiles}}
44
+ {{#isMarkdown}}rsh{{{index}}},{{/isMarkdown}}
45
+ {{^isMarkdown}}JSON.stringify([fm{{{index}}}, t{{{index}}}]),{{/isMarkdown}}
46
+ {{/metaFiles}}
47
+ ]);
48
+ const previousMetaStructureHash = dumiGlobal.__DUMI_META_INDEX_STRUCTURE_HASH__;
49
+ const didMetaStructureChange = previousMetaStructureHash !== undefined && previousMetaStructureHash !== nextMetaStructureHash;
50
+ dumiGlobal.__DUMI_META_INDEX_STRUCTURE_HASH__ = nextMetaStructureHash;
51
+
52
+ if (module.hot) {
53
+ // Dynamic imports have an async-loader wrapper module. Accept at this
54
+ // stable metadata boundary as well as inside the loaded text module so an
55
+ // already-loaded search chunk cannot invalidate the global runtime graph.
56
+ module.hot.accept();
57
+ if (didMetaStructureChange) {
58
+ module.hot.invalidate();
59
+ }
60
+ }
61
+ {{/enableUtoopackHMR}}
62
+
28
63
  export { tabs as tabsMeta } from './tabs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dumi",
3
- "version": "2.4.48",
3
+ "version": "2.4.49-alpha.1",
4
4
  "description": "📖 Documentation Generator of React Component",
5
5
  "keywords": [
6
6
  "generator",
@@ -69,9 +69,9 @@
69
69
  "@swc/core": "1.9.2",
70
70
  "@types/hast": "^2.3.5",
71
71
  "@types/mdast": "^3.0.12",
72
- "@umijs/bundler-utils": "^4.6.82",
73
- "@umijs/core": "^4.6.82",
74
- "@umijs/utils": "^4.6.82",
72
+ "@umijs/bundler-utils": "^4.7.3",
73
+ "@umijs/core": "^4.7.3",
74
+ "@umijs/utils": "^4.7.3",
75
75
  "animated-scroll-to": "^2.3.0",
76
76
  "classnames": "2.3.2",
77
77
  "codesandbox-import-utils": "^2.2.3",
@@ -123,7 +123,7 @@
123
123
  "sass": "^1.64.1",
124
124
  "sitemap": "^7.1.1",
125
125
  "sucrase": "^3.34.0",
126
- "umi": "^4.6.82",
126
+ "umi": "^4.7.3",
127
127
  "unified": "^10.1.2",
128
128
  "unist-util-visit": "^4.1.2",
129
129
  "unist-util-visit-parents": "^5.1.3",
@@ -147,8 +147,8 @@
147
147
  "@types/react-copy-to-clipboard": "^5.0.4",
148
148
  "@types/react-dom": "^18.2.7",
149
149
  "@typescript/native-preview": "7.0.0-dev.20260623.1",
150
- "@umijs/lint": "^4.6.82",
151
- "@umijs/plugins": "4.6.82",
150
+ "@umijs/lint": "^4.7.3",
151
+ "@umijs/plugins": "4.7.3",
152
152
  "eslint": "^8.46.0",
153
153
  "esno": "^4.7.0",
154
154
  "fast-glob": "^3.3.1",