From b40280cdaf7b09199698a07448382521d36ab7a8 Mon Sep 17 00:00:00 2001
From: yangxingyuan <39261479+sanyuan0704@users.noreply.github.com>
Date: Wed, 28 Jun 2023 18:11:45 +0800
Subject: [PATCH] feat(module-doc): support external demo (#4079)
---
.changeset/itchy-news-drive.md | 7 +
.../cli/doc-plugin-preview/src/codeToDemo.ts | 180 +++++++++++++-----
packages/cli/doc-plugin-preview/src/index.ts | 61 ++++--
.../static/global-components/Container.tsx | 2 +
4 files changed, 188 insertions(+), 62 deletions(-)
create mode 100644 .changeset/itchy-news-drive.md
diff --git a/.changeset/itchy-news-drive.md b/.changeset/itchy-news-drive.md
new file mode 100644
index 000000000000..a7f1a686132e
--- /dev/null
+++ b/.changeset/itchy-news-drive.md
@@ -0,0 +1,7 @@
+---
+'@modern-js/doc-plugin-preview': patch
+---
+
+feat(module-doc): support external demo
+
+feat(module-doc): 支持外部 demo
diff --git a/packages/cli/doc-plugin-preview/src/codeToDemo.ts b/packages/cli/doc-plugin-preview/src/codeToDemo.ts
index 5461dbcfb9e4..a6189e382731 100644
--- a/packages/cli/doc-plugin-preview/src/codeToDemo.ts
+++ b/packages/cli/doc-plugin-preview/src/codeToDemo.ts
@@ -9,6 +9,46 @@ import { injectDemoBlockImport, toValidVarName } from './utils';
import { demoBlockComponentPath } from './constant';
import { demoRoutes } from '.';
+const getExternalDemoContent = (tempVar: string) => ({
+ type: 'mdxJsxFlowElement',
+ name: 'pre',
+ children: [
+ {
+ type: 'mdxJsxFlowElement',
+ name: 'code',
+ attributes: [
+ {
+ type: 'mdxJsxAttribute',
+ name: 'className',
+ value: 'language-tsx',
+ },
+ {
+ type: 'mdxJsxAttribute',
+ name: 'children',
+ value: {
+ type: 'mdxJsxExpressionAttribute',
+ value: tempVar,
+ data: {
+ estree: {
+ type: 'Program',
+ body: [
+ {
+ type: 'ExpressionStatement',
+ expression: {
+ type: 'Identifier',
+ name: tempVar,
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ],
+ },
+ ],
+});
+
/**
* remark plugin to transform code to demo
*/
@@ -16,9 +56,99 @@ export const remarkCodeToDemo: Plugin<
[{ isMobile: boolean; getRouteMeta: () => RouteMeta[] }],
Root
> = ({ isMobile, getRouteMeta }) => {
+ const routeMeta = getRouteMeta();
+
return (tree, vfile) => {
const demos: MdxjsEsm[] = [];
let index = 1;
+ const route = routeMeta.find(meta => meta.absolutePath === vfile.path);
+ if (!route) {
+ return;
+ }
+ const { pageName } = route;
+
+ function constructDemoNode(
+ demoId: string,
+ demoPath: string,
+ currentNode: any,
+ isMobileMode: boolean,
+ // Only for external demo
+ externalDemoIndex?: number,
+ ) {
+ const demoRoute = `/~demo/${demoId}`;
+ if (isMobileMode) {
+ // only add demoRoutes in mobile mode
+ demoRoutes.push({
+ path: demoRoute,
+ });
+ } else {
+ demos.push(getASTNodeImport(`Demo${demoId}`, demoPath));
+ }
+ const tempVar = `externalDemoContent${externalDemoIndex}`;
+
+ if (externalDemoIndex !== undefined) {
+ demos.push(getASTNodeImport(tempVar, `${demoPath}?raw`));
+ }
+ Object.assign(currentNode, {
+ type: 'mdxJsxFlowElement',
+ name: 'Container',
+ attributes: [
+ {
+ type: 'mdxJsxAttribute',
+ name: 'isMobile',
+ value: isMobileMode,
+ },
+ {
+ type: 'mdxJsxAttribute',
+ name: 'url',
+ value: demoRoute,
+ },
+ {
+ type: 'mdxJsxAttribute',
+ name: 'isExternal',
+ value: externalDemoIndex !== undefined,
+ },
+ ],
+ children: [
+ externalDemoIndex === undefined
+ ? {
+ ...currentNode,
+ hasVisited: true,
+ }
+ : getExternalDemoContent(tempVar),
+ isMobileMode
+ ? {
+ type: 'mdxJsxFlowElement',
+ name: null,
+ }
+ : {
+ type: 'mdxJsxFlowElement',
+ name: `Demo${demoId}`,
+ },
+ ],
+ });
+ }
+ let externalDemoIndex = 0;
+ // 1. External demo , use
to declare demo
+ tree.children.forEach((node: any) => {
+ if (node.type === 'mdxJsxFlowElement' && node.name === 'code') {
+ const src = node.attributes.find(
+ (attr: { name: string; value: string }) => attr.name === 'src',
+ )?.value;
+ const isMobileMode =
+ node.attributes.find(
+ (attr: { name: string; value: boolean }) =>
+ attr.name === 'isMobile',
+ )?.value ?? isMobile;
+ if (!src) {
+ return;
+ }
+ const id = `${toValidVarName(pageName)}_${index++}`;
+ constructDemoNode(id, src, node, isMobileMode, externalDemoIndex++);
+ }
+ });
+
+ // 2. Internal demo, use ```j/tsx to declare demo
visit(tree, 'code', node => {
// hasVisited is a custom property
if ('hasVisited' in node) {
@@ -40,17 +170,13 @@ export const remarkCodeToDemo: Plugin<
node?.meta?.includes('mobile') ||
(!node?.meta?.includes('web') && isMobile);
- const routeMeta = getRouteMeta();
- const { pageName } = routeMeta.find(
- meta => meta.absolutePath === vfile.path,
- )!;
- const id = `${toValidVarName(pageName)}_${index++}`;
const demoDir = join(
process.cwd(),
'node_modules',
'.modern-doc',
`virtual-demo`,
);
+ const id = `${toValidVarName(pageName)}_${index++}`;
const virtualModulePath = join(demoDir, `${id}.tsx`);
fs.ensureDirSync(join(demoDir));
// Only when the content of the file changes, the file will be written
@@ -61,49 +187,7 @@ export const remarkCodeToDemo: Plugin<
fs.writeFileSync(virtualModulePath, value);
}
}
- demos.push(getASTNodeImport(`Demo${id}`, virtualModulePath));
- const demoRoute = `/~demo/${id}`;
-
- if (isMobileMode) {
- // only add demoRoutes in mobile mode
- demoRoutes.push({
- path: demoRoute,
- });
- } else {
- demos.push(getASTNodeImport(`Demo${id}`, virtualModulePath));
- }
- Object.assign(node, {
- type: 'mdxJsxFlowElement',
- name: 'Container',
- attributes: [
- {
- type: 'mdxJsxAttribute',
- name: 'isMobile',
- value: isMobileMode,
- },
- {
- type: 'mdxJsxAttribute',
- name: 'url',
- value: demoRoute,
- },
- ],
- children: [
- {
- // if lang not change, this node will be visited again and again
- ...node,
- hasVisited: true,
- },
- isMobileMode
- ? {
- type: 'mdxJsxFlowElement',
- name: null,
- }
- : {
- type: 'mdxJsxFlowElement',
- name: `Demo${id}`,
- },
- ],
- });
+ constructDemoNode(id, virtualModulePath, node, isMobileMode);
}
});
tree.children.unshift(...demos);
diff --git a/packages/cli/doc-plugin-preview/src/index.ts b/packages/cli/doc-plugin-preview/src/index.ts
index 1539cfb4ebd4..6b3baf2dfcb4 100644
--- a/packages/cli/doc-plugin-preview/src/index.ts
+++ b/packages/cli/doc-plugin-preview/src/index.ts
@@ -48,6 +48,49 @@ export function pluginPreview(options?: Options): DocPlugin {
const source = await fs.readFile(filepath, 'utf-8');
const ast = processor.parse(source);
let index = 1;
+ const { pageName } = routeMeta.find(
+ meta => meta.absolutePath === filepath,
+ )!;
+
+ const registerDemo = (
+ demoId: string,
+ demoPath: string,
+ isMobileMode: boolean,
+ ) => {
+ if (isMobileMode) {
+ // only add demoMeta in mobile mode
+ demoMeta[filepath] = demoMeta[filepath] ?? [];
+ const isExist = demoMeta[filepath].find(
+ item => item.id === demoId,
+ );
+ if (!isExist) {
+ demoMeta[filepath].push({
+ id: demoId,
+ virtualModulePath: demoPath,
+ });
+ }
+ }
+ };
+
+ visit(ast, 'mdxJsxFlowElement', (node: any) => {
+ if (node.name === 'code') {
+ const src = node.attributes.find(
+ (attr: { name: string; value: string }) =>
+ attr.name === 'src',
+ )?.value;
+ const isMobileMode =
+ node.attributes.find(
+ (attr: { name: string; value: boolean }) =>
+ attr.name === 'isMobile',
+ )?.value ?? isMobile;
+ if (!src) {
+ return;
+ }
+ const id = `${toValidVarName(pageName)}_${index++}`;
+ registerDemo(id, src, isMobileMode);
+ }
+ });
+
visit(ast, 'code', (node: any) => {
if (node.lang === 'jsx' || node.lang === 'tsx') {
const { value } = node;
@@ -76,20 +119,7 @@ export function pluginPreview(options?: Options): DocPlugin {
);
const virtualModulePath = join(demoDir, `${id}.tsx`);
-
- if (isMobileMode) {
- // only add demoMeta in mobile mode
- demoMeta[filepath] = demoMeta[filepath] ?? [];
- const isExist = demoMeta[filepath].find(
- item => item.id === id,
- );
- if (!isExist) {
- demoMeta[filepath].push({
- id,
- virtualModulePath,
- });
- }
- }
+ registerDemo(id, virtualModulePath, isMobileMode);
fs.ensureDirSync(join(demoDir));
fs.writeFileSync(
@@ -147,6 +177,9 @@ import Demo from '${demoComponentPath}'
},
bundlerChain(chain) {
chain.module
+ .rule('Raw')
+ .resourceQuery(/raw/)
+ .type('asset/source')
.rule('MDX')
.oneOf('MDXMeta')
.before('MDXCompile')
diff --git a/packages/cli/doc-plugin-preview/static/global-components/Container.tsx b/packages/cli/doc-plugin-preview/static/global-components/Container.tsx
index 9e6d7bc15be6..d3d5e8e6c3e0 100644
--- a/packages/cli/doc-plugin-preview/static/global-components/Container.tsx
+++ b/packages/cli/doc-plugin-preview/static/global-components/Container.tsx
@@ -30,6 +30,7 @@ type ContainerProps = {
const Container: React.FC = props => {
const { children, isMobile, url } = props;
+
const [showCode, setShowCode] = useState(false);
const lang = useLang() || Object.keys(locales)[0];
const dark = useDark();
@@ -41,6 +42,7 @@ const Container: React.FC = props => {
// Do nothing in ssr
return '';
};
+
const toggleCode = (e: any) => {
if (!showCode) {
e.target.blur();