Skip to content

Commit

Permalink
feat(module-doc): support external demo (#4079)
Browse files Browse the repository at this point in the history
  • Loading branch information
sanyuan0704 committed Jun 28, 2023
1 parent dc81210 commit b40280c
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 62 deletions.
7 changes: 7 additions & 0 deletions .changeset/itchy-news-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modern-js/doc-plugin-preview': patch
---

feat(module-doc): support external demo

feat(module-doc): 支持外部 demo
180 changes: 132 additions & 48 deletions packages/cli/doc-plugin-preview/src/codeToDemo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,146 @@ 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
*/
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 <code src="xxx" /> 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) {
Expand All @@ -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
Expand All @@ -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);
Expand Down
61 changes: 47 additions & 14 deletions packages/cli/doc-plugin-preview/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type ContainerProps = {

const Container: React.FC<ContainerProps> = props => {
const { children, isMobile, url } = props;

const [showCode, setShowCode] = useState(false);
const lang = useLang() || Object.keys(locales)[0];
const dark = useDark();
Expand All @@ -41,6 +42,7 @@ const Container: React.FC<ContainerProps> = props => {
// Do nothing in ssr
return '';
};

const toggleCode = (e: any) => {
if (!showCode) {
e.target.blur();
Expand Down

0 comments on commit b40280c

Please sign in to comment.