dumi 2.4.36 → 2.4.38-beta.0

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.
@@ -1,11 +1,14 @@
1
- import { useDemo, useLiveDemo, useParams } from 'dumi';
1
+ import { useDemo, useLiveDemo, useLocation, useParams } from 'dumi';
2
2
  import { createElement, useEffect } from 'react';
3
3
  import { useRenderer } from "../../theme-api/useRenderer";
4
4
  import "./index.less";
5
5
  var DemoRenderPage = function DemoRenderPage() {
6
6
  var params = useParams();
7
+ var _useLocation = useLocation(),
8
+ search = _useLocation.search;
7
9
  var id = params.id;
8
- var demo = useDemo(id);
10
+ var routeId = new URLSearchParams(search).get('routeId') || undefined;
11
+ var demo = useDemo(id, undefined, undefined, routeId);
9
12
  var _useRenderer = useRenderer({
10
13
  id: id,
11
14
  component: demo.component,
@@ -1,11 +1,52 @@
1
1
  function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
3
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
4
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
5
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
6
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
7
+ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
2
8
  import { SP_ROUTE_PREFIX } from "../../../constants";
3
9
  import { useAppData, useDemo, useSiteData } from 'dumi';
4
- import React, { createElement } from 'react';
10
+ import React, { createElement, useEffect, useRef, useState } from 'react';
5
11
  import Previewer from 'dumi/theme/builtins/Previewer';
6
12
  import { useRenderer } from "../useRenderer";
7
13
  import DemoErrorBoundary from "./DemoErrorBoundary";
8
- var InternalDumiDemo = function InternalDumiDemo(props) {
14
+ var LazyDemoPlaceholder = function LazyDemoPlaceholder(_ref) {
15
+ var id = _ref.id,
16
+ onVisible = _ref.onVisible;
17
+ var ref = useRef(null);
18
+ useEffect(function () {
19
+ if (typeof IntersectionObserver === 'undefined') {
20
+ onVisible();
21
+ return;
22
+ }
23
+ var observer = new IntersectionObserver(function (_ref2) {
24
+ var _ref3 = _slicedToArray(_ref2, 1),
25
+ entry = _ref3[0];
26
+ if (entry.isIntersecting) {
27
+ observer.disconnect();
28
+ onVisible();
29
+ }
30
+ }, {
31
+ rootMargin: '1000px 0px'
32
+ });
33
+ if (ref.current) {
34
+ observer.observe(ref.current);
35
+ }
36
+ return function () {
37
+ return observer.disconnect();
38
+ };
39
+ }, [onVisible]);
40
+ return /*#__PURE__*/React.createElement("div", {
41
+ id: id,
42
+ ref: ref,
43
+ "data-dumi-demo-lazy": true,
44
+ style: {
45
+ minHeight: 160
46
+ }
47
+ });
48
+ };
49
+ var LoadedDumiDemo = function LoadedDumiDemo(props) {
9
50
  var _useSiteData = useSiteData(),
10
51
  historyType = _useSiteData.historyType;
11
52
  var _useAppData = useAppData(),
@@ -34,17 +75,34 @@ var InternalDumiDemo = function InternalDumiDemo(props) {
34
75
  return demoNode;
35
76
  }
36
77
  var isHashRoute = historyType === 'hash';
78
+ var routeQuery = demo.routeId ? "?routeId=".concat(encodeURIComponent(demo.routeId)) : '';
37
79
  return /*#__PURE__*/React.createElement(Previewer, _extends({
38
80
  asset: asset,
39
81
  demoUrl:
40
82
  // allow user override demoUrl by frontmatter
41
83
  props.previewerProps.demoUrl || // when use hash route, browser can automatically handle relative paths starting with #
42
- "".concat(isHashRoute ? "#" : '').concat(basename).concat(SP_ROUTE_PREFIX, "demos/").concat(props.demo.id)
84
+ "".concat(isHashRoute ? "#" : '').concat(basename).concat(SP_ROUTE_PREFIX, "demos/").concat(props.demo.id).concat(routeQuery)
43
85
  }, props.previewerProps), props.previewerProps.iframe ? null : demoNode);
44
86
  };
87
+ var InternalDumiDemo = function InternalDumiDemo(props) {
88
+ var shouldLazyLoad = process.env.NODE_ENV !== 'production' && Boolean(props.demo.loader) && !props.demo.inline;
89
+ var _useState = useState(!shouldLazyLoad),
90
+ _useState2 = _slicedToArray(_useState, 2),
91
+ visible = _useState2[0],
92
+ setVisible = _useState2[1];
93
+ if (!visible) {
94
+ return /*#__PURE__*/React.createElement(LazyDemoPlaceholder, {
95
+ id: props.demo.id,
96
+ onVisible: function onVisible() {
97
+ return setVisible(true);
98
+ }
99
+ });
100
+ }
101
+ return /*#__PURE__*/React.createElement(LoadedDumiDemo, props);
102
+ };
45
103
  export var DumiDemo = /*#__PURE__*/React.memo(InternalDumiDemo, function (prev, next) {
46
104
  // compare length for performance
47
- return JSON.stringify(prev).length === JSON.stringify(next).length;
105
+ return prev.demo.id === next.demo.id && prev.demo.inline === next.demo.inline && prev.demo.version === next.demo.version && JSON.stringify(prev.previewerProps).length === JSON.stringify(next.previewerProps).length;
48
106
  });
49
107
  if (process.env.NODE_ENV !== 'production') {
50
108
  InternalDumiDemo.displayName = 'DumiDemo';
@@ -40,6 +40,8 @@ var import_utils = require("@umijs/utils");
40
40
  var import_path = __toESM(require("path"));
41
41
  var import_utils2 = require("./utils");
42
42
  var mdLoaderPath = require.resolve("../../loaders/markdown");
43
+ var utilsRegisterPath = require.resolve("@umijs/utils");
44
+ var esbuildImplementorPath = require.resolve("@umijs/bundler-utils/compiled/esbuild");
43
45
  var UTOOPACK_LOADER_CTX_KEY = "__dumiLoaderContextPath";
44
46
  var LOADER_CTX_FILENAME = "dumi-loader-ctx.cjs";
45
47
  function toSerializable(value) {
@@ -115,6 +117,129 @@ function toRequireRef(found) {
115
117
  const modRef = `require(${JSON.stringify(found.modulePath)})`;
116
118
  return found.exportName === "module.exports" ? modRef : `(${modRef})[${JSON.stringify(found.exportName)}]`;
117
119
  }
120
+ function createTechStackMockApi(onRegister) {
121
+ const api = {
122
+ cwd: process.cwd(),
123
+ config: {},
124
+ userConfig: {},
125
+ pkg: {},
126
+ paths: {},
127
+ service: {},
128
+ registerTechStack: onRegister,
129
+ register(opts) {
130
+ if ((opts == null ? void 0 : opts.key) === "registerTechStack" && typeof opts.fn === "function") {
131
+ onRegister(opts.fn);
132
+ }
133
+ }
134
+ };
135
+ return new Proxy(api, {
136
+ get(target, key) {
137
+ if (key in target) {
138
+ return target[key];
139
+ }
140
+ return () => {
141
+ };
142
+ }
143
+ });
144
+ }
145
+ function isSameTechStack(a, b) {
146
+ return a === b || a.name === b.name && a.constructor.name === b.constructor.name;
147
+ }
148
+ function collectTechStacksFromPlugin(plugin) {
149
+ if (typeof plugin !== "function")
150
+ return [];
151
+ const ret = [];
152
+ const api = createTechStackMockApi((fn) => {
153
+ try {
154
+ const techStack = fn();
155
+ if (techStack)
156
+ ret.push(techStack);
157
+ } catch {
158
+ }
159
+ });
160
+ try {
161
+ plugin(api);
162
+ } catch {
163
+ }
164
+ return ret;
165
+ }
166
+ function findTechStackPluginInModule(target, mod) {
167
+ const exp = mod.exports;
168
+ if (!exp)
169
+ return null;
170
+ const candidates = [
171
+ ["module.exports", exp],
172
+ ["default", exp == null ? void 0 : exp.default],
173
+ ...Object.entries(exp)
174
+ ];
175
+ for (const [exportName, plugin] of candidates) {
176
+ const techStacks = collectTechStacksFromPlugin(plugin);
177
+ if (techStacks.some((techStack) => isSameTechStack(techStack, target))) {
178
+ return { modulePath: mod.filename, exportName };
179
+ }
180
+ }
181
+ return null;
182
+ }
183
+ function findTechStackPluginInSourceFiles(target, sourceFiles) {
184
+ import_utils.register.register({ implementor: import_esbuild.default });
185
+ import_utils.register.clearFiles();
186
+ try {
187
+ for (const file of sourceFiles) {
188
+ try {
189
+ require(file);
190
+ const mod = require.cache[file];
191
+ if (!(mod == null ? void 0 : mod.exports))
192
+ continue;
193
+ const found = findTechStackPluginInModule(target, mod);
194
+ if (found)
195
+ return found;
196
+ } catch {
197
+ }
198
+ }
199
+ return null;
200
+ } finally {
201
+ for (const file of import_utils.register.getFiles()) {
202
+ delete require.cache[file];
203
+ }
204
+ for (const file of sourceFiles) {
205
+ delete require.cache[file];
206
+ }
207
+ import_utils.register.restore();
208
+ }
209
+ }
210
+ function toTechStackRefs(techStacks, sourceFiles = []) {
211
+ const refs = [];
212
+ for (const ts of techStacks) {
213
+ const ctor = ts.constructor;
214
+ let ref;
215
+ if (ctor !== Object) {
216
+ const found = findInRequireCache(ctor);
217
+ if (found) {
218
+ ref = `new (${toRequireRef(found)})()`;
219
+ }
220
+ } else {
221
+ const found = findInRequireCache(ts);
222
+ if (found) {
223
+ ref = toRequireRef(found);
224
+ }
225
+ }
226
+ if (!ref) {
227
+ const found = findTechStackPluginInSourceFiles(ts, sourceFiles);
228
+ if (found) {
229
+ ref = `...loadTechStacksFromPlugin(${toRequireRef(found)})`;
230
+ }
231
+ }
232
+ if (ref) {
233
+ refs.push(ref);
234
+ } else {
235
+ const name = ts.constructor.name ? ` (${ts.constructor.name})` : "";
236
+ console.warn(
237
+ `[dumi] Utoopack markdown loader cannot serialize tech stack "${ts.name}"${name}. Please export the tech stack class or register it from an exported dumi plugin.`
238
+ );
239
+ }
240
+ }
241
+ return refs;
242
+ }
118
243
  function toPluginTargetRef(target, sourceFiles) {
119
244
  if (typeof target === "string")
120
245
  return JSON.stringify(target);
@@ -141,25 +266,53 @@ function toPluginRefs(plugins = [], sourceFiles = []) {
141
266
  }).join(", ")}]`;
142
267
  }
143
268
  function buildLoaderContextContent(techStacks, builtins = {}, routes = {}, extraRemarkPlugins = [], extraRehypePlugins = [], sourceFiles = []) {
144
- const refs = [];
145
- for (const ts of techStacks) {
146
- const ctor = ts.constructor;
147
- if (ctor !== Object) {
148
- const found = findInRequireCache(ctor);
149
- if (found) {
150
- refs.push(`new (${toRequireRef(found)})()`);
151
- }
152
- } else {
153
- const found = findInRequireCache(ts);
154
- if (found) {
155
- refs.push(toRequireRef(found));
156
- }
157
- }
158
- }
269
+ const refs = toTechStackRefs(techStacks, sourceFiles);
159
270
  return `'use strict';
160
271
  try {
161
- require('@umijs/utils').register.register({ implementor: require('@umijs/bundler-utils/compiled/esbuild') });
162
- } catch (_) {}
272
+ require(${JSON.stringify(
273
+ utilsRegisterPath
274
+ )}).register.register({ implementor: require(${JSON.stringify(
275
+ esbuildImplementorPath
276
+ )}) });
277
+ } catch (e) {
278
+ console.warn('[dumi] failed to register TS require hook for utoopack loader context:', e);
279
+ }
280
+ function loadTechStacksFromPlugin(plugin) {
281
+ if (typeof plugin !== 'function') return [];
282
+ const techStacks = [];
283
+ const registerTechStack = (fn) => {
284
+ try {
285
+ const techStack = fn();
286
+ if (techStack) techStacks.push(techStack);
287
+ } catch (e) {
288
+ console.warn('[dumi] failed to register tech stack from utoopack loader context:', e);
289
+ }
290
+ };
291
+ const api = new Proxy({
292
+ cwd: process.cwd(),
293
+ config: {},
294
+ userConfig: {},
295
+ pkg: {},
296
+ paths: {},
297
+ service: {},
298
+ registerTechStack,
299
+ register(opts) {
300
+ if (opts && opts.key === 'registerTechStack' && typeof opts.fn === 'function') {
301
+ registerTechStack(opts.fn);
302
+ }
303
+ },
304
+ }, {
305
+ get(target, key) {
306
+ return key in target ? target[key] : () => {};
307
+ },
308
+ });
309
+ try {
310
+ plugin(api);
311
+ } catch (e) {
312
+ console.warn('[dumi] failed to load tech stack plugin in utoopack loader context:', e);
313
+ }
314
+ return techStacks;
315
+ }
163
316
  exports.techStacks = [${refs.join(", ")}];
164
317
  exports.builtins = ${JSON.stringify(builtins)};
165
318
  exports.routes = ${JSON.stringify(routes)};
@@ -311,7 +464,7 @@ var getUtoopackRules = (api, config = api.config) => {
311
464
  },
312
465
  // compile inline demo code blocks
313
466
  {
314
- condition: { query: /^\?type=demo$/ },
467
+ condition: { query: /^\?type=demo(?:&.*)?$/ },
315
468
  loaders: [
316
469
  {
317
470
  loader: mdLoaderPath,
@@ -1,5 +1,6 @@
1
1
  import type { IApi } from '../types';
2
2
  export declare const TABS_META_PATH = "dumi/meta/tabs.ts";
3
3
  export declare const ATOMS_META_PATH = "dumi/meta/atoms.ts";
4
+ export declare const DEMO_INDEX_META_PATH = "dumi/meta/demoIndex.ts";
4
5
  export default _default;
5
6
  declare function _default(api: IApi): void;
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  var meta_exports = {};
31
31
  __export(meta_exports, {
32
32
  ATOMS_META_PATH: () => ATOMS_META_PATH,
33
+ DEMO_INDEX_META_PATH: () => DEMO_INDEX_META_PATH,
33
34
  TABS_META_PATH: () => TABS_META_PATH,
34
35
  default: () => meta_default
35
36
  });
@@ -40,6 +41,7 @@ var import_plugin_utils = require("umi/plugin-utils");
40
41
  var import_tabs = require("./tabs");
41
42
  var TABS_META_PATH = "dumi/meta/tabs.ts";
42
43
  var ATOMS_META_PATH = "dumi/meta/atoms.ts";
44
+ var DEMO_INDEX_META_PATH = "dumi/meta/demoIndex.ts";
43
45
  var meta_default = (api) => {
44
46
  const metaFiles = [];
45
47
  api.register({
@@ -71,9 +73,11 @@ var meta_default = (api) => {
71
73
  key: "dumi.modifyMetaFiles",
72
74
  initialValue: JSON.parse(JSON.stringify(metaFiles))
73
75
  });
76
+ const useUtoopackDemoHMR = api.env === "development" && Boolean(api.config.utoopack);
74
77
  parsedMetaFiles.forEach((metaFile) => {
75
78
  metaFile.isMarkdown = metaFile.file.endsWith(".md");
76
- metaFile.loadDemoIndex = metaFile.isMarkdown && !(api.env === "development" && Boolean(api.config.utoopack));
79
+ metaFile.loadDemoIndex = metaFile.isMarkdown && !useUtoopackDemoHMR;
80
+ metaFile.loadDemoIndexMap = metaFile.isMarkdown && useUtoopackDemoHMR;
77
81
  });
78
82
  api.writeTmpFile({
79
83
  noPluginDir: true,
@@ -94,6 +98,14 @@ var meta_default = (api) => {
94
98
  }
95
99
  }
96
100
  });
101
+ api.writeTmpFile({
102
+ noPluginDir: true,
103
+ path: DEMO_INDEX_META_PATH,
104
+ tplPath: require.resolve("../templates/meta/demoIndex.ts.tpl"),
105
+ context: {
106
+ metaFiles: parsedMetaFiles
107
+ }
108
+ });
97
109
  api.writeTmpFile({
98
110
  noPluginDir: true,
99
111
  path: "dumi/meta/runtime.ts",
@@ -119,5 +131,6 @@ var meta_default = (api) => {
119
131
  // Annotate the CommonJS export names for ESM import in node:
120
132
  0 && (module.exports = {
121
133
  ATOMS_META_PATH,
134
+ DEMO_INDEX_META_PATH,
122
135
  TABS_META_PATH
123
136
  });
@@ -50,9 +50,27 @@ function getDemoSourceFiles(demos = []) {
50
50
  return ret;
51
51
  }, []);
52
52
  }
53
+ function getDemoIdFromQuery(resourceQuery = "") {
54
+ return new URLSearchParams(resourceQuery.replace(/^\?/, "")).get("demoId");
55
+ }
56
+ function filterDemosByQuery(demos, resourceQuery) {
57
+ const demoId = getDemoIdFromQuery(resourceQuery);
58
+ return demoId ? demos == null ? void 0 : demos.filter((demo) => demo.id === demoId) : demos;
59
+ }
53
60
  function isRelativePath(path2) {
54
61
  return /^\.{1,2}(?!\w)/.test(path2);
55
62
  }
63
+ function normalizeRouteFile(file) {
64
+ return (0, import_plugin_utils.winPath)(file).replace(/\.(mdx?)\.js$/, ".$1");
65
+ }
66
+ function getRouteId(opts, fileAbsPath) {
67
+ const normalizedFile = normalizeRouteFile(fileAbsPath);
68
+ const route = Object.values(opts.routes ?? {}).find((item) => {
69
+ const routeFile = item.file || item.__absFile;
70
+ return routeFile && normalizeRouteFile(routeFile) === normalizedFile;
71
+ });
72
+ return route == null ? void 0 : route.id;
73
+ }
56
74
  function emitDefault(opts, ret) {
57
75
  const { frontmatter, demos } = ret.meta;
58
76
  const isTabContent = (0, import_tabs.isTabRouteFile)(this.resourcePath);
@@ -94,7 +112,7 @@ function DumiMarkdownContent() {
94
112
  export default DumiMarkdownContent;`;
95
113
  }
96
114
  function emitDemo(opts, ret) {
97
- const { demos } = ret.meta;
115
+ const demos = filterDemosByQuery(ret.meta.demos, this.resourceQuery);
98
116
  const shareDepsMap = {};
99
117
  const demoDepsMap = {};
100
118
  const relativeDepsMap = {};
@@ -133,6 +151,7 @@ function emitDemo(opts, ret) {
133
151
  }).filter((item) => item !== void 0)
134
152
  );
135
153
  }, []);
154
+ const routeId = getRouteId(opts, this.resourcePath);
136
155
  return import_plugin_utils.Mustache.render(
137
156
  `import React from 'react';
138
157
  import '${(0, import_plugin_utils.winPath)(this.resourcePath)}?watch=parent';
@@ -146,6 +165,9 @@ export const demos = {
146
165
  component: {{{component}}},
147
166
  {{/component}}
148
167
  asset: {{{renderAsset}}},
168
+ {{#routeId}}
169
+ routeId: '{{{routeId}}}',
170
+ {{/routeId}}
149
171
  context: {{{renderContext}}},
150
172
  renderOpts: {{{renderRenderOpts}}},
151
173
  },
@@ -154,6 +176,7 @@ export const demos = {
154
176
  {
155
177
  demos,
156
178
  dedupedDemosDeps,
179
+ routeId,
157
180
  renderAsset: function renderAsset() {
158
181
  if (!("asset" in this))
159
182
  return "null";
@@ -290,8 +313,13 @@ function emitText(opts, ret) {
290
313
  }
291
314
  function emit(opts, ret) {
292
315
  const { demos, embeds } = ret.meta;
316
+ const dependencyDemos = opts.mode === "demo" ? filterDemosByQuery(demos, this.resourceQuery) : demos;
293
317
  embeds.forEach((file) => this.addDependency(file));
294
- getDemoSourceFiles(demos).forEach((file) => this.addDependency(file));
318
+ if (opts.mode !== "demo-index") {
319
+ getDemoSourceFiles(dependencyDemos).forEach(
320
+ (file) => this.addDependency(file)
321
+ );
322
+ }
295
323
  if (this.resourceQuery.includes("watch=parent"))
296
324
  return null;
297
325
  switch (opts.mode) {
@@ -320,7 +348,7 @@ function mdLoader(content) {
320
348
  var _a, _b, _c;
321
349
  let opts = this.getOptions();
322
350
  const loaderContextPath = opts[import_utoopackLoaders.UTOOPACK_LOADER_CTX_KEY];
323
- const useUtoopackDemoHMR = process.env.NODE_ENV === "development" && Boolean(loaderContextPath);
351
+ const useUtoopackDemoHMR = process.env.NODE_ENV !== "production" && Boolean(loaderContextPath);
324
352
  if (loaderContextPath) {
325
353
  const ctx = require(loaderContextPath);
326
354
  if (!((_a = opts.techStacks) == null ? void 0 : _a.length)) {
@@ -48,7 +48,13 @@ var isElement;
48
48
  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
- var DEMO_LOADER_PLACEHOLDER_VALUE = DEMO_LOADER_PLACEHOLDER;
51
+ var DEMO_LOADER_PLACEHOLDER_RE = new RegExp(
52
+ `"${DEMO_LOADER_PLACEHOLDER}:([^"]+)"`,
53
+ "g"
54
+ );
55
+ var getDemoLoaderPlaceholder = (id) => `${DEMO_LOADER_PLACEHOLDER}:${encodeURIComponent(
56
+ id
57
+ )}`;
52
58
  var DUMI_DEMO_TAG = "DumiDemo";
53
59
  var DUMI_DEMO_GRID_TAG = "DumiDemoGrid";
54
60
  var SKIP_DEMO_PARSE = "pure";
@@ -252,106 +258,97 @@ function rehypeDemo(opts) {
252
258
  }
253
259
  const propDemo = { id: parseOpts.id };
254
260
  if (opts.useUtoopackDemoHMR) {
255
- propDemo.loader = DEMO_LOADER_PLACEHOLDER_VALUE;
261
+ propDemo.loader = getDemoLoaderPlaceholder(parseOpts.id);
256
262
  }
257
263
  demoIds.push(parseOpts.id);
258
264
  deferrers.push(
259
265
  (0, import_block.default)({
260
266
  ...parseOpts,
261
267
  cacheable: opts.useUtoopackDemoHMR
262
- }).then(
263
- async ({ asset, resolveMap, frontmatter }) => {
264
- var _a2, _b2, _c2;
265
- if (demoIds.indexOf(parseOpts.id) !== demoIds.lastIndexOf(parseOpts.id)) {
266
- const startLine = (_a2 = node.position) == null ? void 0 : _a2.start.line;
267
- const suffix = startLine ? `:${startLine}` : "";
268
- import_plugin_utils.logger.warn(
269
- `Duplicate demo id found due to filename conflicts, please consider adding a unique id to code tag to resolve this.
268
+ }).then(async ({ asset, resolveMap, frontmatter }) => {
269
+ var _a2, _b2, _c2;
270
+ if (demoIds.indexOf(parseOpts.id) !== demoIds.lastIndexOf(parseOpts.id)) {
271
+ const startLine = (_a2 = node.position) == null ? void 0 : _a2.start.line;
272
+ const suffix = startLine ? `:${startLine}` : "";
273
+ import_plugin_utils.logger.warn(
274
+ `Duplicate demo id found due to filename conflicts, please consider adding a unique id to code tag to resolve this.
270
275
  at ${opts.fileAbsPath}${suffix}`
271
- );
272
- }
273
- const { src, className, ...restAttrs } = codeNode.properties || {};
274
- const validAssetAttrs = [
275
- "title",
276
- "snapshot",
277
- "keywords"
278
- ];
279
- const techStackOpts = {
280
- type: codeType,
281
- mdAbsPath: opts.fileAbsPath,
282
- fileAbsPath: codeType === "external" ? parseOpts.fileAbsPath : void 0,
283
- entryPointCode: parseOpts.entryPointCode
284
- };
285
- Object.keys(restAttrs).forEach((key) => {
286
- if (restAttrs[key] === "")
287
- restAttrs[key] = true;
288
- });
289
- const originalProps = Object.assign(
290
- {},
291
- frontmatter,
292
- restAttrs
293
276
  );
294
- validAssetAttrs.forEach((key) => {
295
- if (originalProps[key])
296
- asset[key] = originalProps[key];
297
- });
298
- if (opts.useUtoopackDemoHMR) {
299
- propDemo.version = (0, import_utils.getContentHash)(
300
- JSON.stringify(asset.dependencies)
301
- );
302
- }
303
- if (/ inline/.test(String((_b2 = codeNode.data) == null ? void 0 : _b2.meta)) || originalProps.inline) {
304
- propDemo.inline = true;
305
- return {
306
- // TODO: special id for inline demo
307
- id: asset.id,
308
- component,
309
- renderOpts: {
310
- rendererPath: runtimeOpts == null ? void 0 : runtimeOpts.rendererPath,
311
- preflightPath: runtimeOpts == null ? void 0 : runtimeOpts.preflightPath
312
- }
313
- };
314
- }
315
- Object.assign(
316
- previewerProps,
317
- await ((_c2 = techStack.generatePreviewerProps) == null ? void 0 : _c2.call(
318
- techStack,
319
- originalProps,
320
- techStackOpts
321
- )) || originalProps
277
+ }
278
+ const { src, className, ...restAttrs } = codeNode.properties || {};
279
+ const validAssetAttrs = [
280
+ "title",
281
+ "snapshot",
282
+ "keywords"
283
+ ];
284
+ const techStackOpts = {
285
+ type: codeType,
286
+ mdAbsPath: opts.fileAbsPath,
287
+ fileAbsPath: codeType === "external" ? parseOpts.fileAbsPath : void 0,
288
+ entryPointCode: parseOpts.entryPointCode
289
+ };
290
+ Object.keys(restAttrs).forEach((key) => {
291
+ if (restAttrs[key] === "")
292
+ restAttrs[key] = true;
293
+ });
294
+ const originalProps = Object.assign({}, frontmatter, restAttrs);
295
+ validAssetAttrs.forEach((key) => {
296
+ if (originalProps[key])
297
+ asset[key] = originalProps[key];
298
+ });
299
+ if (opts.useUtoopackDemoHMR) {
300
+ propDemo.version = (0, import_utils.getContentHash)(
301
+ JSON.stringify(asset.dependencies)
322
302
  );
323
- if (previewerProps.description) {
324
- const { unified } = await import("unified");
325
- const { default: remarkParse } = await import("remark-parse");
326
- const { default: remarkGfm } = await import("remark-gfm");
327
- const { default: remarkRehype } = await import("remark-rehype");
328
- const { default: rehypeStringify } = await import("rehype-stringify");
329
- const { convert } = require("html-to-text");
330
- const result = await unified().use(remarkParse).use(remarkGfm).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeStringify, { allowDangerousHtml: true }).process(previewerProps.description);
331
- previewerProps.description = String(result.value);
332
- asset.description = convert(result.value, {
333
- wordwrap: false
334
- });
335
- }
303
+ }
304
+ if (/ inline/.test(String((_b2 = codeNode.data) == null ? void 0 : _b2.meta)) || originalProps.inline) {
305
+ propDemo.inline = true;
336
306
  return {
307
+ // TODO: special id for inline demo
337
308
  id: asset.id,
338
309
  component,
339
- asset: techStack.generateMetadata ? await techStack.generateMetadata(asset, techStackOpts) : asset,
340
- /**
341
- * keep `generateSources` rather than `generateResolveMap` for compatibility
342
- */
343
- resolveMap: techStack.generateSources ? await techStack.generateSources(
344
- resolveMap,
345
- techStackOpts
346
- ) : resolveMap,
347
310
  renderOpts: {
348
311
  rendererPath: runtimeOpts == null ? void 0 : runtimeOpts.rendererPath,
349
- compilePath: runtimeOpts == null ? void 0 : runtimeOpts.compilePath,
350
312
  preflightPath: runtimeOpts == null ? void 0 : runtimeOpts.preflightPath
351
313
  }
352
314
  };
353
315
  }
354
- )
316
+ Object.assign(
317
+ previewerProps,
318
+ await ((_c2 = techStack.generatePreviewerProps) == null ? void 0 : _c2.call(
319
+ techStack,
320
+ originalProps,
321
+ techStackOpts
322
+ )) || originalProps
323
+ );
324
+ if (previewerProps.description) {
325
+ const { unified } = await import("unified");
326
+ const { default: remarkParse } = await import("remark-parse");
327
+ const { default: remarkGfm } = await import("remark-gfm");
328
+ const { default: remarkRehype } = await import("remark-rehype");
329
+ const { default: rehypeStringify } = await import("rehype-stringify");
330
+ const { convert } = require("html-to-text");
331
+ const result = await unified().use(remarkParse).use(remarkGfm).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeStringify, { allowDangerousHtml: true }).process(previewerProps.description);
332
+ previewerProps.description = String(result.value);
333
+ asset.description = convert(result.value, {
334
+ wordwrap: false
335
+ });
336
+ }
337
+ return {
338
+ id: asset.id,
339
+ component,
340
+ asset: techStack.generateMetadata ? await techStack.generateMetadata(asset, techStackOpts) : asset,
341
+ /**
342
+ * keep `generateSources` rather than `generateResolveMap` for compatibility
343
+ */
344
+ resolveMap: techStack.generateSources ? await techStack.generateSources(resolveMap, techStackOpts) : resolveMap,
345
+ renderOpts: {
346
+ rendererPath: runtimeOpts == null ? void 0 : runtimeOpts.rendererPath,
347
+ compilePath: runtimeOpts == null ? void 0 : runtimeOpts.compilePath,
348
+ preflightPath: runtimeOpts == null ? void 0 : runtimeOpts.preflightPath
349
+ }
350
+ };
351
+ })
355
352
  );
356
353
  demosPropData.push({
357
354
  demo: propDemo,
@@ -381,15 +378,13 @@ function rehypeDemo(opts) {
381
378
  await Promise.all(deferrers).then((demos) => {
382
379
  vFile.data.demos = demos;
383
380
  replaceNodes.forEach((node) => {
384
- const demoLoader = `() => import('${(0, import_plugin_utils.winPath)(
385
- opts.fileAbsPath
386
- )}?type=demo')`;
387
381
  let value = JSON.stringify(node.data[DEMO_PROP_VALUE_KEY]);
388
382
  if (opts.useUtoopackDemoHMR) {
389
- value = value.replace(
390
- new RegExp(`"${DEMO_LOADER_PLACEHOLDER}"`, "g"),
391
- demoLoader
392
- );
383
+ value = value.replace(DEMO_LOADER_PLACEHOLDER_RE, (_, demoId) => {
384
+ return `() => import('${(0, import_plugin_utils.winPath)(
385
+ opts.fileAbsPath
386
+ )}?type=demo&demoId=${demoId}')`;
387
+ });
393
388
  }
394
389
  if (node.JSXAttributes[0].type === "JSXAttribute") {
395
390
  node.JSXAttributes[0].value = value;
@@ -0,0 +1,10 @@
1
+ export const demoIndexMap = {
2
+ {{#metaFiles}}
3
+ {{#loadDemoIndexMap}}
4
+ '{{{id}}}': () =>
5
+ import('{{{file}}}?type=demo-index').then(
6
+ ({ demoIndex }) => demoIndex,
7
+ ),
8
+ {{/loadDemoIndexMap}}
9
+ {{/metaFiles}}
10
+ };
@@ -34,21 +34,53 @@ export function use<T>(promise: ReactPromise<T>): T {
34
34
  }
35
35
  }
36
36
 
37
- const demoIdMap = Object.keys(filesMeta).reduce((total, current) => {
38
- if (filesMeta[current].demoIndex) {
39
- const { ids, getter } = filesMeta[current].demoIndex;
37
+ type DemoGetter = () => Promise<{ demos: Record<string, IDemoData> }>;
38
+ type DemoIndex = {
39
+ ids: string[];
40
+ getter: DemoGetter;
41
+ };
42
+ type DemoIndexGetter = () => Promise<DemoIndex>;
40
43
 
41
- ids.forEach((id) => {
42
- total[id] = getter;
43
- });
44
- }
44
+ const demoIndexes = Object.entries(filesMeta)
45
+ .map(([id, meta]) => ({
46
+ id,
47
+ demoIndex: meta.demoIndex,
48
+ }))
49
+ .filter(
50
+ (item): item is { id: string; demoIndex: DemoIndex } =>
51
+ Boolean(item.demoIndex),
52
+ );
45
53
 
46
- return total;
47
- }, {});
54
+ const demoIdMap = demoIndexes.reduce<Record<string, DemoGetter>>(
55
+ (total, { demoIndex }) => {
56
+ if (demoIndex) {
57
+ const { ids, getter } = demoIndex;
48
58
 
49
- type DemoGetter = () => Promise<{ demos: Record<string, IDemoData> }>;
59
+ ids.forEach((id) => {
60
+ total[id] = getter;
61
+ });
62
+ }
63
+
64
+ return total;
65
+ },
66
+ {},
67
+ );
50
68
 
51
69
  const demosCache = new Map<string, Promise<IDemoData | undefined>>();
70
+ let demoIndexMapPromise:
71
+ | Promise<Record<string, DemoIndexGetter | undefined>>
72
+ | undefined;
73
+
74
+ function loadDemoIndexMap() {
75
+ if (!demoIndexMapPromise) {
76
+ demoIndexMapPromise = import('./demoIndex').then(
77
+ ({ demoIndexMap }) =>
78
+ demoIndexMap as Record<string, DemoIndexGetter | undefined>,
79
+ );
80
+ }
81
+
82
+ return demoIndexMapPromise;
83
+ }
52
84
 
53
85
  /**
54
86
  * expand context for source omit extension
@@ -67,6 +99,65 @@ function expandDemoContext(context?: IDemoData['context']) {
67
99
  }
68
100
  }
69
101
 
102
+ function getDemoIdCandidates(id: string) {
103
+ const ids = [id];
104
+ const localeLessId = id.replace(/-[a-z]{2}(?:-[A-Z]{2})?$/, '');
105
+
106
+ if (localeLessId !== id) {
107
+ ids.push(localeLessId);
108
+ }
109
+
110
+ return ids;
111
+ }
112
+
113
+ async function getDemoFromDemoIndex(
114
+ id: string,
115
+ demoIndexGetter: DemoIndexGetter,
116
+ ) {
117
+ const demoIndex = await demoIndexGetter();
118
+ const demoId = getDemoIdCandidates(id).find((candidate) =>
119
+ demoIndex.ids.includes(candidate),
120
+ );
121
+ const getter = demoId ? demoIndex.getter : undefined;
122
+
123
+ if (!getter) return undefined;
124
+
125
+ demoIdMap[id] = getter;
126
+ const { demos } = await getter();
127
+ const demo = demos[id] ?? demos[demoId!];
128
+
129
+ if (!demo) return undefined;
130
+
131
+ expandDemoContext(demo.context);
132
+ return demo;
133
+ }
134
+
135
+ async function getDemoFromRouteIndex(id: string, routeId: string) {
136
+ const demoIndexMap = await loadDemoIndexMap();
137
+ const demoIndexGetter = demoIndexMap[routeId];
138
+
139
+ if (!demoIndexGetter) return getDemoFromIndexMap(id);
140
+
141
+ return (
142
+ (await getDemoFromDemoIndex(id, demoIndexGetter)) ??
143
+ getDemoFromIndexMap(id)
144
+ );
145
+ }
146
+
147
+ async function getDemoFromIndexMap(id: string) {
148
+ const demoIndexMap = await loadDemoIndexMap();
149
+ const demoIndexGetters = Object.values(demoIndexMap).filter(
150
+ (item): item is DemoIndexGetter => Boolean(item),
151
+ );
152
+
153
+ for (const demoIndexGetter of demoIndexGetters) {
154
+ const demo = await getDemoFromDemoIndex(id, demoIndexGetter).catch(
155
+ () => undefined,
156
+ );
157
+ if (demo) return demo;
158
+ }
159
+ }
160
+
70
161
  /**
71
162
  * use demo data by id
72
163
  */
@@ -74,20 +165,30 @@ export function useDemo(
74
165
  id: string,
75
166
  loader?: DemoGetter,
76
167
  version?: string,
168
+ routeId?: string,
77
169
  ): IDemoData | undefined {
78
- const cacheKey = version ? `${id}:${version}` : id;
170
+ const cacheKey = version
171
+ ? `${id}:${version}`
172
+ : routeId
173
+ ? `${id}:route=${routeId}`
174
+ : id;
79
175
  const getter = loader ?? demoIdMap[id];
80
176
 
81
177
  if (!demosCache.get(cacheKey)) {
82
- if (!getter) return undefined;
83
-
84
178
  demosCache.set(
85
179
  cacheKey,
86
- getter().then(({ demos }) => {
87
- // expand context for omit ext
88
- expandDemoContext(demos[id].context);
89
- return demos[id];
90
- }),
180
+ getter
181
+ ? getter().then(({ demos }) => {
182
+ const demo = demos[id];
183
+ if (!demo) return undefined;
184
+
185
+ // expand context for omit ext
186
+ expandDemoContext(demo.context);
187
+ return demo;
188
+ })
189
+ : routeId
190
+ ? getDemoFromRouteIndex(id, routeId)
191
+ : getDemoFromIndexMap(id),
91
192
  );
92
193
 
93
194
  // Reuse local demo data for consumers that still call useDemo(id), such as useLiveDemo.
@@ -100,15 +201,30 @@ export function useDemo(
100
201
  /**
101
202
  * get all demos
102
203
  */
103
- export async function getFullDemos() {
104
- const demoFilesMeta = Object.entries(filesMeta).filter(
105
- ([_id, meta]) => meta.demoIndex,
204
+ export async function getFullDemos(opts: { loadLazy?: boolean } = {}) {
205
+ const loadLazy = opts.loadLazy ?? process.env.NODE_ENV === 'production';
206
+ const lazyDemoIndexes = loadLazy
207
+ ? await loadDemoIndexMap().then((demoIndexMap) =>
208
+ Promise.all(
209
+ Object.entries(demoIndexMap).map(async ([id, demoIndexGetter]) => ({
210
+ id,
211
+ demoIndex: await demoIndexGetter?.().catch(() => undefined),
212
+ })),
213
+ ),
214
+ )
215
+ : [];
216
+ const allDemoIndexes = [
217
+ ...demoIndexes,
218
+ ...lazyDemoIndexes,
219
+ ].filter(
220
+ (item): item is { id: string; demoIndex: DemoIndex } =>
221
+ Boolean(item.demoIndex),
106
222
  );
107
223
 
108
224
  return Promise.all(
109
- demoFilesMeta.map(async ([id, meta]) => ({
225
+ allDemoIndexes.map(async ({ id, demoIndex }) => ({
110
226
  id,
111
- demos: (await meta.demoIndex.getter()).demos as Record<string, IDemoData>,
227
+ demos: (await demoIndex.getter()).demos as Record<string, IDemoData>,
112
228
  })),
113
229
  ).then((ret) =>
114
230
  ret.reduce<Record<string, IDemoData>>((total, { id, demos }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dumi",
3
- "version": "2.4.36",
3
+ "version": "2.4.38-beta.0",
4
4
  "description": "📖 Documentation Generator of React Component",
5
5
  "keywords": [
6
6
  "generator",
package/theme-api.d.ts CHANGED
@@ -309,7 +309,9 @@ export declare const useTabMeta: () =>
309
309
  export declare const openCodeSandbox: (data: IPreviewerProps) => void;
310
310
  export declare const openStackBlitz: (data: IPreviewerProps) => void;
311
311
  export declare function useDemo(id: string): IDemoData | undefined;
312
- export declare function getFullDemos(): Promise<Record<string, IDemoData>>;
312
+ export declare function getFullDemos(opts?: {
313
+ loadLazy?: boolean;
314
+ }): Promise<Record<string, IDemoData>>;
313
315
  export declare function getRouteMetaById<T extends { syncOnly?: boolean }>(
314
316
  id: string,
315
317
  opts?: T,