Skip to content

Commit

Permalink
feat(umi-plugin): support to transform code block to demo previewer (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
PeachScript committed Nov 7, 2019
1 parent 1227906 commit 7a399ba
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 12 deletions.
7 changes: 7 additions & 0 deletions packages/umi-plugin-father-doc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
"PeachScript <scdzwyxst@gmail.com> (https://github.com/PeachScript)"
],
"dependencies": {
"@babel/core": "^7.7.2",
"@babel/generator": "^7.7.2",
"@babel/plugin-transform-typescript": "^7.7.2",
"@babel/preset-env": "^7.7.1",
"@babel/preset-react": "^7.7.0",
"@babel/traverse": "^7.7.2",
"@babel/types": "^7.7.2",
"@mapbox/rehype-prism": "^0.3.1",
"rehype-stringify": "^6.0.0",
"remark-parse": "^7.0.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default (paths: IApi['paths'], routes: IRoute[]) => {
switch (cPathParsed.ext) {
case '.md':
// todo: handle YAML config
componentContent = transformer.markdown(componentContent).content;
componentContent = transformer.markdown(componentContent, cPathParsed.dir).content;
break;
default:
}
Expand Down
4 changes: 2 additions & 2 deletions packages/umi-plugin-father-doc/src/transformer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ export interface TransformResult {
}

export default {
markdown(raw: string): TransformResult {
markdown(raw: string, dir: string): TransformResult {
return {
content: `export default function () {
return (
<div>${remark(raw)}</div>
<div>${remark(raw, dir)}</div>
)
}`,
config: {},
Expand Down
72 changes: 72 additions & 0 deletions packages/umi-plugin-father-doc/src/transformer/previewer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import path from 'path';
import * as babel from '@babel/core';
import * as types from '@babel/types';
import traverse from '@babel/traverse';
import generator from '@babel/generator';

export const PREVIEWER_NAME = 'FatherDocPreviewer';

/**
* transform code block statments to preview
*/
export default (raw: string, dir: string, isTSX?: boolean) => {
const code = babel.transformSync(raw, {
presets: [
require.resolve('@babel/preset-react'),
require.resolve('@babel/preset-env'),
],
plugins: (isTSX
? [['@babel/plugin-transform-typescript', { isTSX: true }]]
: []
),
ast: true,
});
const body = code.ast.program.body as types.Statement[];
let returnStatement;

// traverse call expression
traverse(code.ast, {
CallExpression(callPath) {
const nodeCallee = callPath.node.callee;

// remove original render expression
if (
types.isMemberExpression(nodeCallee)
&& nodeCallee.object
&& (nodeCallee.object.loc as any).identifierName === 'ReactDOM'
&& nodeCallee.property
&& nodeCallee.property.name === 'render'
&& types.isExpression(callPath.node.arguments[0])
) {
// save render expression as return statement
returnStatement = types.returnStatement(callPath.node.arguments[0]);
callPath.remove();
}

// replace relative module path
if (
dir
&& types.isIdentifier(nodeCallee)
&& nodeCallee.name === 'require'
&& types.isStringLiteral(callPath.node.arguments[0])
&& callPath.node.arguments[0].value.startsWith('.')
) {
callPath.node.arguments[0].value = path.join(dir, callPath.node.arguments[0].value);
}
},
});

// push return statement to program body
if (returnStatement) {
body.push(returnStatement);
}

// create preview function
const previewFunction = types.functionDeclaration(
types.identifier(PREVIEWER_NAME),
[],
types.blockStatement(body),
);

return generator(types.program([previewFunction]), {}, raw).code;
}
20 changes: 11 additions & 9 deletions packages/umi-plugin-father-doc/src/transformer/remark/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import unified from 'unified';
import parse from 'remark-parse';
import rehype from 'remark-rehype';
import stringify from 'rehype-stringify';
import prism from '@mapbox/rehype-prism';
import parse from './parse';
import rehype from './rehype';
import jsx from './jsx';

const processor = unified()
.use(parse)
.use(rehype)
.use(stringify, { allowDangerousHTML: true })
.use(prism)
.use(jsx);
export default (raw: string, dir: string) => {
const processor = unified()
.use(parse)
.use(rehype, { dir })
.use(stringify, { allowDangerousHTML: true })
.use(prism)
.use(jsx);

export default (raw: string) => processor.processSync(raw).contents as string;
return processor.processSync(raw).contents as string;
};
24 changes: 24 additions & 0 deletions packages/umi-plugin-father-doc/src/transformer/remark/parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import parse from 'remark-parse';

const { blockTokenizers } = parse.Parser.prototype as any;
const oFencedCode = blockTokenizers.fencedCode;

// override original fencedCode tokenizer
blockTokenizers.fencedCode = function (...args) {
const result = oFencedCode.apply(this, args);

// only process jsx & tsx code block
if (result && /^[jt]sx$/.test(result.lang)) {
if ((result.meta || '').indexOf('pure') > -1) {
// clear useless meta if the lang with pure modifier
result.meta = result.meta.replace(/ ?\| ?pure/, '');
} else {
// customize type (use for rehype previewer handler)
result.type = 'previewer';
}
}

return result;
}

export default parse;
24 changes: 24 additions & 0 deletions packages/umi-plugin-father-doc/src/transformer/remark/rehype.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import rehype from 'remark-rehype';
import unist from 'unist-builder';
import transformer, { PREVIEWER_NAME } from '../previewer';

/**
* handle previewer type node from parse
*/
function previewerHandler(h, node) {
const code = `{(function () {
${transformer(node.value, this.dir, node.lang === 'tsx')}
return <${PREVIEWER_NAME} />;
})()}`
;

return h(node.position, 'div', [unist('raw', code)]);
}

export default (options: { [key: string]: any } = {}) => {
return rehype(Object.assign({
handlers: {
previewer: previewerHandler.bind({ dir: options.dir }),
},
}, options));
}

0 comments on commit 7a399ba

Please sign in to comment.