Skip to content

Commit

Permalink
feat(preset): support to register compiletime component (#933)
Browse files Browse the repository at this point in the history
* feat: support to register markdown component

* test: update markdown component tests

* fix: improve module path in windows

* feat: support to replace the older component

* feat: compiler function supports return values
  • Loading branch information
mortalYoung authored Nov 19, 2021
1 parent cdf1d02 commit ccc5b67
Show file tree
Hide file tree
Showing 17 changed files with 256 additions and 6 deletions.
5 changes: 5 additions & 0 deletions packages/preset-dumi/src/fixtures/md-components/.umirc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
history: { type: 'memory' },
mountElementId: '',
plugins: ['./plugin.js'],
}
1 change: 1 addition & 0 deletions packages/preset-dumi/src/fixtures/md-components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<Test />
5 changes: 5 additions & 0 deletions packages/preset-dumi/src/fixtures/md-components/Test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export default () => (
<div>for testing markdown component</div>
)
19 changes: 19 additions & 0 deletions packages/preset-dumi/src/fixtures/md-components/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import path from "path";

export default (api) => {
api.chainWebpack((memo) => {
// babel compile src folder
memo.module.rule("js").include.add(path.join(__dirname, "../../.."));

return memo;
});

api.register({
key: "dumi.registerMdComponent",
fn: () => ({
name: "Test",
component: path.join(__dirname, "Test.js"),
compiler(node, i, parent, vFile) {},
}),
});
};
25 changes: 24 additions & 1 deletion packages/preset-dumi/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ describe('preset-dumi', () => {
'sitemap',
'dynamic-import',
'compiletime',
'custom-components'
'custom-components',
'md-components'
].forEach(dir => {
rimraf.sync(path.join(fixtures, dir, 'node_modules'));
});
Expand Down Expand Up @@ -443,4 +444,26 @@ describe('preset-dumi', () => {
expect(distContent).toContain('custom-single-component');
expect(distContent).toContain('custom-directory-component');
});

it('custom md components in compile time', async () => {
const cwd = path.join(fixtures, 'md-components');
const service = new Service({
cwd,
env: 'production',
presets: [require.resolve('@umijs/preset-built-in'), require.resolve('./index.ts')],
});

// add UMI_DIR to avoid alias error
process.env.UMI_DIR = path.dirname(require.resolve('umi/package'));

await service.run({
name: 'build',
});

const distContent = fs
.readFileSync(path.join(service.paths.absOutputPath, 'umi.js'))
.toString();

expect(distContent).toContain('for testing markdown component');
});
});
3 changes: 3 additions & 0 deletions packages/preset-dumi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export default () => {
// for disable dumi
require.resolve('./plugins/features/disable'),

// register markdown components
require.resolve('./plugins/features/mdComponent'),

// compiletime
require.resolve('./plugins/features/compiletime'),
],
Expand Down
1 change: 1 addition & 0 deletions packages/preset-dumi/src/loader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default async function loader(raw: string) {
import { Link, AnchorLink } from 'dumi/theme';
${theme.builtins
.concat(theme.fallbacks)
.concat(theme.customs)
.map(component => `import ${component.identifier} from '${component.source}';`)
.join('\n')}
import DUMI_ALL_DEMOS from '@@/dumi/demos';
Expand Down
17 changes: 17 additions & 0 deletions packages/preset-dumi/src/plugins/features/mdComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IApi } from '@umijs/types';
import type { IMarkdwonComponent } from '../../transformer/remark/mdComponent';
import { registerMdComponent } from '../../transformer/remark/mdComponent';

export default (api: IApi) => {
api.onPluginReady(async () => {
const mdComponents: IMarkdwonComponent[] = await api.applyPlugins({
type: api.ApplyPluginsType.add,
key: 'dumi.registerMdComponent',
initialValue: [],
});

mdComponents.forEach(comp => {
registerMdComponent(comp);
});
});
};
14 changes: 9 additions & 5 deletions packages/preset-dumi/src/routes/test/examples.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ describe('routes: examples', () => {
cwd,
paths: { cwd, absNodeModulesPath: cwd },
ApplyPluginsType: {},
applyPlugins: (() => ({
layoutPaths: { _: '' },
builtins: [{ identifier: 'Example', modulePath: '' }],
fallbacks: [],
})) as any,
applyPlugins: (({ key }) =>
key === 'dumi.registerMdComponent'
? []
: {
layoutPaths: { _: '' },
builtins: [{ identifier: 'Example', modulePath: '' }],
fallbacks: [],
customs: [],
}) as any,
} as IApi,
{} as IDumiOpts,
);
Expand Down
19 changes: 19 additions & 0 deletions packages/preset-dumi/src/theme/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path';
import { winPath, createDebug } from '@umijs/utils';
import { getModuleResolvePath } from '../utils/moduleResolver';
import ctx from '../context';
import type { IMarkdwonComponent } from '../transformer/remark/mdComponent';

const debug = createDebug('dumi:theme');

Expand Down Expand Up @@ -51,6 +52,10 @@ export interface IThemeLoadResult {
* fallback components
*/
fallbacks: ThemeComponent[];
/**
* customize markdown components
*/
customs: ThemeComponent[];
}

const THEME_PREFIX = 'dumi-theme-';
Expand Down Expand Up @@ -237,6 +242,19 @@ export default async () => {
},
);

// get markdown components
const mdComponents: IMarkdwonComponent[] = await ctx.umi.applyPlugins({
type: ctx.umi.ApplyPluginsType.add,
key: 'dumi.registerMdComponent',
initialValue: [],
});

const customs = mdComponents.map(component => ({
identifier: component.name,
source: pathJoin(component.component),
cModulePath: pathJoin(component.component),
}));

cache = await ctx.umi.applyPlugins({
key: 'dumi.modifyThemeResolved',
type: ctx.umi.ApplyPluginsType.modify,
Expand All @@ -246,6 +264,7 @@ export default async () => {
builtins: components,
fallbacks,
layoutPaths,
customs,
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import React from 'react';

export default () => <div>test</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<Test />
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<Test />
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<Test />
2 changes: 2 additions & 0 deletions packages/preset-dumi/src/transformer/remark/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import codeBlock from './codeBlock';
import code from './code';
import embed from './embed';
import api from './api';
import mdComponent from './mdComponent';
import link from './link';
import img from './img';
import previewer from './previewer';
Expand Down Expand Up @@ -139,6 +140,7 @@ export default (source: string, fileAbsPath: string, type: 'jsx' | 'html', maste
.use(debug('code'))
.use(api)
.use(debug('api'))
.use(mdComponent)
.use(slug)
.use(debug('slug'))
.use(embed)
Expand Down
58 changes: 58 additions & 0 deletions packages/preset-dumi/src/transformer/remark/mdComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import is from 'hast-util-is-element';
import visit from 'unist-util-visit';
import type { IDumiUnifiedTransformer, IDumiElmNode } from '.';
import type { Transformer } from 'unified';
import type unified from 'unified';

type ICompiler = (
this: Pick<unified.Processor<unified.Settings>, 'data'>,
node: Parameters<visit.Visitor<IDumiElmNode>>[0],
index: Parameters<visit.Visitor<IDumiElmNode>>[1],
parent: Parameters<visit.Visitor<IDumiElmNode>>[2],
vFile: Parameters<Transformer>[1],
) => ReturnType<visit.Visitor<IDumiElmNode>>;

export interface IMarkdwonComponent {
/**
* The markdown component name which should always start with a capital letter
*/
name: string;
/**
* The component path
*/
component: string;
/**
* The compiler function about how to parse the HTML Abstract Syntax Tree parsed by rehype
* @see https://github.com/syntax-tree/hast for more details about the HTML Abstract Syntax Tree
* @see https://github.com/syntax-tree/unist-util-visit-parents#returns for more details about the return value for this function
*/
compiler: ICompiler;
}

const markdownComponents: IMarkdwonComponent[] = [];
export function registerMdComponent(comp: IMarkdwonComponent) {
const target = markdownComponents.find(item => item.name === comp.name);
if (target) {
// replace the existed one
Object.assign(target, comp);
} else {
markdownComponents.push(comp);
}
}

/**
* remark plugin for parsing the customize markdwon components
*/
export default function mdComponent(): IDumiUnifiedTransformer {
return (ast, vFile) => {
visit<IDumiElmNode>(ast, 'element', (node, i, parent) => {
const componentNames = markdownComponents.map(a => a.name);
if (is(node, componentNames) && !node._dumi_parsed) {
const target = markdownComponents.find(item => item.name === node.tagName);
// mark this node as a parsed node by dumi
node._dumi_parsed = true;
return target.compiler.call(this, node, i, parent, vFile);
}
});
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import path from 'path';
import fs from 'fs';
import transformer from '..';
import { registerMdComponent } from '../remark/mdComponent';

describe('markdown component examples', () => {
const fixtures = path.join(__dirname, '../fixtures/remark-components');

it('Should NOT transform unrecognized markdown component', () => {
const filePath = path.join(fixtures, 'index.md');
const result = transformer.markdown(
fs.readFileSync(filePath, 'utf8').toString(),
filePath,
).content;

// compare transform content
expect(result).toEqual(`<div className="markdown"><Test /></div>`);
});

it('Should transform recognized markdown component', () => {
// register mdComponents
registerMdComponent({
name: 'Test',
component: path.join(fixtures, 'remark-components', 'Test.js'),
compiler(node, index, parent) {
// insert a h3 title before Test component
parent.children.splice(
index,
0,
...[
{
type: 'element',
tagName: 'h3',
properties: { id: `markdown-components` },
children: [],
},
],
);
},
});

const filePath = path.join(fixtures, 'test.md');
const result = transformer.markdown(
fs.readFileSync(filePath, 'utf8').toString(),
filePath,
).content;

// compare transform content
expect(result).toEqual(
`<div className=\"markdown\"><h3 id=\"markdown-components\"><AnchorLink to=\"#markdown-components\" aria-hidden=\"true\" tabIndex={-1}><span className=\"icon icon-link\" /></AnchorLink></h3><Test /></div>`,
);
});

it('Should support to replace the markdown component', () => {
// register another Test component
registerMdComponent({
name: 'Test',
component: path.join(fixtures, 'remark-components', 'Test.js'),
compiler(node, index, parent) {
// insert a h3 title before Test component
parent.children.splice(
index,
0,
...[
{
type: 'element',
tagName: 'h3',
properties: { id: `another-markdown-components` },
children: [],
},
],
);
},
});

const filePath = path.join(fixtures, 'another.md');
const result = transformer.markdown(
fs.readFileSync(filePath, 'utf8').toString(),
filePath,
).content;

// compare transform content
expect(result).toEqual(
`<div className=\"markdown\"><h3 id=\"another-markdown-components\"><AnchorLink to=\"#another-markdown-components\" aria-hidden=\"true\" tabIndex={-1}><span className=\"icon icon-link\" /></AnchorLink></h3><Test /></div>`,
);
});
});

0 comments on commit ccc5b67

Please sign in to comment.