Skip to content

Commit

Permalink
refactor: optimize live demo feature in v2.3 (#1992)
Browse files Browse the repository at this point in the history
* refactor: clean up compile-time logic

* feat: emit demo context in loader

* refactor: clean up runtime api and slots

* feat: add SourceCodeEditor slot

* feat: add useLiveDemo api

* refactor: rewrite live demo feature with useLiveDemo

* feat: live demo support iframe mode

* refactor: constants for browser runtime
  • Loading branch information
PeachScript committed Jan 4, 2024
1 parent e14fa51 commit 206b04e
Show file tree
Hide file tree
Showing 39 changed files with 591 additions and 593 deletions.
1 change: 0 additions & 1 deletion examples/normal/.dumirc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ export default {
themeConfig: { name: '示例' },
mfsu: false,
apiParser: {},
live: true,
resolve: { entryFile: './src/index.ts' },
};
38 changes: 27 additions & 11 deletions src/assetParsers/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import { pkgUp, winPath } from 'umi/plugin-utils';

export interface IParsedBlockAsset {
asset: ExampleBlockAsset;
sources: Record<string, string>;
/**
* resolveMap: {
* '@/constants': '/path/to/constants.ts',
* 'antd/es/button': '/path/to/antd/es/button/index.jsx',
* }
*/
resolveMap: Record<string, string>;
frontmatter: ReturnType<typeof parseCodeFrontmatter>['frontmatter'];
}

Expand All @@ -28,7 +34,7 @@ async function parseBlockAsset(opts: {
};
const result: IParsedBlockAsset = {
asset,
sources: {},
resolveMap: {},
frontmatter: null,
};

Expand Down Expand Up @@ -65,6 +71,8 @@ async function parseBlockAsset(opts: {
type: 'NPM',
value: pkg.version,
};

result.resolveMap[args.path] = resolved;
}

// make all deps external
Expand All @@ -76,7 +84,11 @@ async function parseBlockAsset(opts: {
args.kind !== 'entry-point'
? (opts.resolver(args.resolveDir, args.path) as string)
: path.join(args.resolveDir, args.path),
pluginData: { kind: args.kind, resolveDir: args.resolveDir },
pluginData: {
kind: args.kind,
resolveDir: args.resolveDir,
source: args.path,
},
};
});

Expand All @@ -92,13 +104,17 @@ async function parseBlockAsset(opts: {
'.json',
].includes(ext);
const isEntryPoint = args.pluginData.kind === 'entry-point';
const filename = isEntryPoint
? `index${ext}`
: winPath(
path.relative(path.dirname(opts.fileAbsPath), args.path),
)
// discard leading ./ or ../
.replace(/^(\.?\.\/)+/g, '');
// always add extname for highlight in runtime
const filename = `${
isEntryPoint
? 'index'
: winPath(
path.format({
...path.parse(args.pluginData.source),
ext: '',
}),
)
}${ext}`;

if (isModule || isPlainText) {
asset.dependencies[filename] = {
Expand Down Expand Up @@ -131,7 +147,7 @@ async function parseBlockAsset(opts: {
// save file absolute path for load file via raw-loader
// to avoid bundle same file to save bundle size
if (!isEntryPoint || !opts.entryPointCode) {
result.sources[filename] = args.path;
result.resolveMap[filename] = args.path;
}

return {
Expand Down
25 changes: 22 additions & 3 deletions src/client/pages/Demo/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { useDemo, useParams } from 'dumi';
import { createElement, type FC } from 'react';
import { useDemo, useLiveDemo, useParams } from 'dumi';
import { createElement, useEffect, type FC } from 'react';
import './index.less';

const DemoRenderPage: FC = () => {
const { id } = useParams();
const { component } = useDemo(id!) || {};
const { node: liveDemoNode, setSources } = useLiveDemo(id!);
const finalNode = liveDemoNode || (component && createElement(component));

return component && createElement(component);
useEffect(() => {
const handler = (
ev: MessageEvent<{
type: string;
value: Parameters<typeof setSources>[0];
}>,
) => {
if (ev.data.type === 'dumi.liveDemo.setSources') {
setSources(ev.data.value);
}
};

window.addEventListener('message', handler);

return () => window.removeEventListener('message', handler);
}, [setSources]);

return finalNode;
};

export default DemoRenderPage;
12 changes: 0 additions & 12 deletions src/client/theme-api/evalCode.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/client/theme-api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ export { DumiDemo } from './DumiDemo';
export { DumiDemoGrid } from './DumiDemoGrid';
export { DumiPage } from './DumiPage';
export { useSiteData } from './context';
export { evalCode } from './evalCode';
export { LiveContext, LiveProvider, isLiveEnabled } from './live';
export { openCodeSandbox } from './openCodeSandbox';
export { openStackBlitz } from './openStackBlitz';
export type { IPreviewerProps } from './types';
export { useAtomAssets } from './useAtomAssets';
export { useLiveDemo } from './useLiveDemo';
export { useLocale } from './useLocale';
export { useNavData } from './useNavData';
export { usePrefersColor } from './usePrefersColor';
Expand Down
85 changes: 0 additions & 85 deletions src/client/theme-api/live/LiveProvider.tsx

This file was deleted.

4 changes: 0 additions & 4 deletions src/client/theme-api/live/index.ts

This file was deleted.

18 changes: 0 additions & 18 deletions src/client/theme-api/live/useDemoScopes.ts

This file was deleted.

5 changes: 1 addition & 4 deletions src/client/theme-api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ export interface IPreviewerProps {
* background color for demo content
*/
background?: string;
/**
* enable live demo
*/
live?: boolean;
/**
* asset metadata of current demo
*/
Expand Down Expand Up @@ -245,4 +241,5 @@ export type IDemoData = {
component: ComponentType;
asset: IPreviewerProps['asset'];
routeId: string;
context?: Record<string, unknown>;
};
65 changes: 65 additions & 0 deletions src/client/theme-api/useLiveDemo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useDemo } from 'dumi';
import {
createElement,
useCallback,
useState,
type ComponentType,
type ReactNode,
} from 'react';
import DemoErrorBoundary from './DumiDemo/DemoErrorBoundary';

export const useLiveDemo = (id: string) => {
const { context, asset } = useDemo(id)!;
const [demoNode, setDemoNode] = useState<ReactNode>();
const [error, setError] = useState<Error | null>(null);
const setSources = useCallback(
(sources: Record<string, string>) => {
const entryFileName = Object.keys(asset.dependencies).find(
(k) => asset.dependencies[k].type === 'FILE',
)!;
const entryFileCode = sources[entryFileName];
const require = (v: string) => {
if (v in context) return context[v];
throw new Error(`Cannot find module: ${v}`);
};
const exports: { default?: ComponentType } = {};
const module = { exports };

// lazy load react-dom/server
import('react-dom/server').then(({ renderToStaticMarkup }) => {
try {
// initial component with fake runtime
new Function('module', 'exports', 'require', entryFileCode)(
module,
exports,
require,
);
const newDemoNode = createElement(
DemoErrorBoundary,
null,
createElement(exports.default!),
);
const oError = console.error;

// hijack console.error to avoid useLayoutEffect error
console.error = (...args) =>
!args[0].includes('useLayoutEffect does nothing on the server') &&
oError.apply(console, args);

// check component is renderable, to avoid show react overlay error
renderToStaticMarkup(newDemoNode);
console.error = oError;

// set new demo node with passing sources
setDemoNode(newDemoNode);
setError(null);
} catch (err: any) {
setError(err);
}
});
},
[context],
);

return { node: demoNode, error, setSources };
};
2 changes: 1 addition & 1 deletion src/client/theme-api/useSiteSearch/useSearchData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function useSearchData(): [
});

// omit demo component for postmessage
Object.entries(demos).forEach(([id, { component, ...demo }]) => {
Object.entries(demos).forEach(([id, { component, context, ...demo }]) => {
demos[id] = demo;
});

Expand Down
34 changes: 34 additions & 0 deletions src/client/theme-default/builtins/Previewer/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,40 @@
}
}

&-demo-error {
@height: 30px;
@color: darken(desaturate(@c-error, 20%), 1%);

margin-top: -@height;
height: @height;
padding: 0 24px;
line-height: @height;
color: @color;
font-size: 13px;
white-space: nowrap;
text-overflow: ellipsis;
background: lighten(@c-error, 51%);
box-sizing: border-box;

@{dark-selector} & {
@color: lighten(desaturate(@c-error, 20%), 5%);

color: @color;
background: darken(@c-error-dark, 22%);

> svg {
fill: @color;
}
}

> svg {
width: 14px;
padding-right: 4px;
fill: @c-error;
vertical-align: -0.14em;
}
}

&-meta {
border-top: 1px solid @c-border-light;

Expand Down
Loading

0 comments on commit 206b04e

Please sign in to comment.