Skip to content

Commit

Permalink
refactor(umi-plugin): use a common way to parse yaml config from md (#20
Browse files Browse the repository at this point in the history
)
  • Loading branch information
PeachScript committed Dec 8, 2019
1 parent cfdf256 commit 3290147
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 60 deletions.
13 changes: 3 additions & 10 deletions packages/umi-plugin-father-doc/src/routes/getFrontMatter.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import fs from 'fs';
import yaml from 'js-yaml';

const FRONT_MATTER_EXP = /^\n*---\n([^]+?)\n---/;
import path from 'path';
import transformer from '../transformer';

/**
* extract Front Matter config from markdown file
*/
export default (filePath: string): { [key: string]: any } => {
const content = fs.readFileSync(filePath).toString();
const raw = (content.match(FRONT_MATTER_EXP) || [])[1];
const result = {};

if (raw) {
Object.assign(result, yaml.safeLoad(raw));
}

return result;
return transformer.markdown(content, path.parse(filePath).dir, true).config;
}
23 changes: 19 additions & 4 deletions packages/umi-plugin-father-doc/src/transformer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,27 @@ export interface TransformResult {
}

export default {
markdown(raw: string, dir: string): TransformResult {
const result = remark(raw, dir);
const contents = (result.contents as string).replace(/class="/g, 'className="');
/**
* transform markdown content to jsx & meta data
* @param raw content
* @param fileAbsDir absolute path of markdown file
* @param onlyConfig whether transform meta data only
*/
markdown(raw: string, fileAbsDir: string, onlyConfig?: boolean): TransformResult {
const result = remark(raw, { fileAbsDir, strategy: onlyConfig ? 'data' : 'default' });
let content = '';

if (!onlyConfig) {
// convert class to className for jsx
// Todo: process in a Unified way
content = (result.contents as string).replace(/class="/g, 'className="');

// wrap by page component
content = MD_WRAPPER.replace('$CONTENT', content)
}

return {
content: MD_WRAPPER.replace('$CONTENT', contents),
content,
config: {
...result.data as TransformResult['config'],
},
Expand Down
65 changes: 49 additions & 16 deletions packages/umi-plugin-father-doc/src/transformer/remark/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,64 @@ import slug from 'rehype-slug';
import headings from 'rehype-autolink-headings';
import comments from 'rehype-remove-comments';
import prism from '@mapbox/rehype-prism';
import parse from './parse';
import parse, { IParseProps } from './parse';
import rehype from './rehype';
import yaml from './yaml';
import externalDemo from './externalDemo';
import previewer from './previewer';
import jsx from './jsx';
import isolation from './isolation';

export default (raw: string, fileAbsDir: string) => {
export interface IRemarkOpts {
/**
* the directory for the file which will be transformed
*/
fileAbsDir?: string;
/**
* transform strategy
* @note both md & data will be transformed by default
* only return data (FrontMatter & other vFile data) if pass 'data'
*/
strategy: IParseProps['strategy'];
}

/**
* strategy mapping for different transform strategy
*/
const PLUGIN_STRATEGIES = {
default: [
[frontmatter],
[yaml],
[externalDemo],
[rehype],
[stringify, { allowDangerousHTML: true, closeSelfClosing: true }],
[slug],
[headings],
[comments, { removeConditional: true }],
[prism],
[previewer],
[jsx],
[isolation],
],
data: [
[frontmatter],
[yaml],
[rehype],
[stringify],
],
} as {
[key: string]: [any][]
};

export default (raw: string, opts: IRemarkOpts) => {
const processor = unified()
.use(parse)
.use(frontmatter)
.use(yaml)
.use(externalDemo)
.use(rehype)
.use(stringify, { allowDangerousHTML: true, closeSelfClosing: true })
.use(slug)
.use(headings)
.use(comments, { removeConditional: true })
.use(prism)
.use(previewer)
.use(jsx)
.use(isolation);
.use(parse, { strategy: opts.strategy || 'default' })
.data('fileAbsDir', opts.fileAbsDir);

processor.data('fileAbsDir', fileAbsDir);
// apply plugins through strategy
PLUGIN_STRATEGIES[opts.strategy].forEach((plugin) => {
processor.use(...plugin);
});

return processor.processSync(raw);
};
105 changes: 75 additions & 30 deletions packages/umi-plugin-father-doc/src/transformer/remark/parse.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
import parse from 'remark-parse';
import { filenameToPath } from '../../routes/getRouteConfigFromDir';
import parse, { RemarkParseOptions } from 'remark-parse';
import { Plugin } from 'unified';

const { blockTokenizers, inlineTokenizers } = parse.Parser.prototype as any;
export interface IParseProps extends RemarkParseOptions {
/**
* transform strategy
* @note turn on all markdown tokenizers by default
* only turn on tokenizers which use for parse yaml data if pass 'data'
*/
strategy: 'default' | 'data';
}

const { blockTokenizers, inlineTokenizers, setOptions } = parse.Parser.prototype as any;
const oFencedCode = blockTokenizers.fencedCode;
const oHtml = inlineTokenizers.html;
const DISABLEABLE_TOKENIZERS = [
'indentedCode',
'fencedCode',
'blockquote',
'thematicBreak',
'list',
'setextHeading',
'html',
'footnote',
'definition',
'table',
'escape',
'autoLink',
'url',
'html',
'link',
'reference',
'strong',
'emphasis',
'deletion',
'code',
'break',
];

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

// only process jsx & tsx code block
Expand All @@ -23,32 +54,46 @@ blockTokenizers.fencedCode = function (...args) {
return result;
}

// override original inline html tokenizer
inlineTokenizers.html = function (...args) {
const result = oHtml.apply(this, args);

// replace internal .md link
if (result?.type === 'html' && /^<a.* href=/.test(result.value)) {
result.value = result.value.replace(/^(.*href=['"]?)([^ '">]*\.md)(['"]?.*)$/, (origin, head, link, foot) => {
let str = origin;

if (link) {
str = `${
head
}${
// convert .md file path to route path
filenameToPath(link.replace(/(\/index)?\.md$/, ''))
}${
foot
}`
}

return str;
});
/**
* a decorator use for turn off tokenizer feature
* @param oTokenizer the original tokenizer
*/
function tokenizerDecorator(oTokenizer) {
const tokenizer = function (...args) {
// turn off disableable tokenizers if strategy is 'data'
if (
this.options.strategy === 'data'
&& DISABLEABLE_TOKENIZERS.indexOf(oTokenizer.name) > -1
) {
return true;
}

return oTokenizer.apply(this, args);
}

return result;
if (oTokenizer.locator) {
tokenizer.locator = oTokenizer.locator;
}

return tokenizer;
}

// decorate for all block tokenizers
Object.keys(blockTokenizers).forEach(method => {
blockTokenizers[method] = tokenizerDecorator(blockTokenizers[method]);
});

// decorate for all inline tokenizers
Object.keys(inlineTokenizers).forEach(method => {
inlineTokenizers[method] = tokenizerDecorator(inlineTokenizers[method]);
});

// proxy set options to avoid remove the custom strategy option
(parse.Parser.prototype as any).setOptions = function (opts) {
if (this.options.strategy) {
opts.strategy = this.options.strategy;
}
setOptions.call(this, opts);
}
inlineTokenizers.html.locator = oHtml.locator;

export default parse;
export default parse as Plugin<[Partial<IParseProps>?]>;

0 comments on commit 3290147

Please sign in to comment.