Skip to content

Commit

Permalink
feat: split markdown meta loader (#1875)
Browse files Browse the repository at this point in the history
* feat: split mdLoader type

* feat: split mdLoader

* chore: code optimize

* fix: use winPath

* chore: update getter
  • Loading branch information
MadCcc committed Sep 8, 2023
1 parent 92db2c4 commit 3528057
Show file tree
Hide file tree
Showing 3 changed files with 262 additions and 67 deletions.
10 changes: 10 additions & 0 deletions examples/normal/.dumi/pages/loader-test.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
import * as demo from '../../docs/index.md?type=demo';
import * as demoIndex from '../../docs/index.md?type=demo-index';
import * as frontmatter from '../../docs/index.md?type=frontmatter';
import * as text from '../../docs/index.md?type=text';

console.log('frontmatter', frontmatter);
console.log('demo', demo);
console.log('demoIndex', demoIndex);
console.log('text', text);

// Customize Page for dumi test
export default () => 'Customize Dumi Test Page';
49 changes: 49 additions & 0 deletions src/features/compile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default (api: IApi) => {
.type('javascript/auto')
.test(/\.md$/)
// get meta for each markdown file
// TODO: should be removed
.oneOf('md-meta')
.resourceQuery(/meta$/)
.use('babel-loader')
Expand All @@ -78,6 +79,54 @@ export default (api: IApi) => {
} as IMdLoaderOptions)
.end()
.end()
// get page demo for each markdown file
.oneOf('md-demo')
.resourceQuery(/demo$/)
.use('babel-loader')
.loader(babelInUmi.loader)
.options(babelInUmi.options)
.end()
.use('md-demo-loader')
.loader(loaderPath)
.options({
...loaderBaseOpts,
mode: 'demo',
})
.end()
.end()
// get page demo-index for each markdown file
.oneOf('md-demo-index')
.resourceQuery(/demo-index$/)
.use('md-demo-index-loader')
.loader(loaderPath)
.options({
...loaderBaseOpts,
mode: 'demo-index',
})
.end()
.end()
// get page frontmatter for each markdown file
.oneOf('md-frontmatter')
.resourceQuery(/frontmatter$/)
.use('md-frontmatter-loader')
.loader(loaderPath)
.options({
...loaderBaseOpts,
mode: 'frontmatter',
})
.end()
.end()
// get page text for each markdown file
.oneOf('md-text')
.resourceQuery(/text$/)
.use('md-text-loader')
.loader(loaderPath)
.options({
...loaderBaseOpts,
mode: 'text',
})
.end()
.end()
// get page component for each markdown file
.oneOf('md')
.use('babel-loader')
Expand Down
270 changes: 203 additions & 67 deletions src/loaders/markdown/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ interface IMdLoaderDefaultModeOptions
extends Omit<IMdTransformerOptions, 'fileAbsPath'> {
mode?: 'markdown';
builtins: IThemeLoadResult['builtins'];
}

interface IMdLoaderDemosModeOptions
extends Omit<IMdLoaderDefaultModeOptions, 'builtins' | 'mode'> {
mode: 'meta';
onResolveDemos?: (
demos: NonNullable<IMdTransformerResult['meta']['demos']>,
) => void;
Expand All @@ -27,9 +22,38 @@ interface IMdLoaderDemosModeOptions
) => void;
}

interface IMdLoaderDemosModeOptions
extends Omit<IMdLoaderDefaultModeOptions, 'builtins' | 'mode'> {
mode: 'meta';
}

interface IMdLoaderDemoModeOptions
extends Omit<IMdLoaderDefaultModeOptions, 'builtins' | 'mode'> {
mode: 'demo';
}

interface IMdLoaderDemoIndexModeOptions
extends Omit<IMdLoaderDefaultModeOptions, 'builtins' | 'mode'> {
mode: 'demo-index';
}

interface IMdLoaderFrontmatterModeOptions
extends Omit<IMdLoaderDefaultModeOptions, 'builtins' | 'mode'> {
mode: 'frontmatter';
}

interface IMdLoaderTextModeOptions
extends Omit<IMdLoaderDefaultModeOptions, 'builtins' | 'mode'> {
mode: 'text';
}

export type IMdLoaderOptions =
| IMdLoaderDefaultModeOptions
| IMdLoaderDemosModeOptions;
| IMdLoaderDemosModeOptions
| IMdLoaderDemoModeOptions
| IMdLoaderFrontmatterModeOptions
| IMdLoaderTextModeOptions
| IMdLoaderDemoIndexModeOptions;

function getDemoSourceFiles(demos: IMdTransformerResult['meta']['demos'] = []) {
return demos.reduce<string[]>((ret, demo) => {
Expand All @@ -41,30 +65,15 @@ function getDemoSourceFiles(demos: IMdTransformerResult['meta']['demos'] = []) {
}, []);
}

function emit(this: any, opts: IMdLoaderOptions, ret: IMdTransformerResult) {
const { demos, embeds } = ret.meta;

// declare embedded files as loader dependency, for re-compiling when file changed
embeds!.forEach((file) => this.addDependency(file));

// declare demo source files as loader dependency, for re-compiling when file changed
getDemoSourceFiles(demos).forEach((file) => this.addDependency(file));

if (opts.mode === 'meta') {
const { frontmatter, toc, texts } = ret.meta;

// apply demos resolve hook
if (demos && opts.onResolveDemos) {
opts.onResolveDemos(demos);
}
function emitMeta(
this: any,
opts: IMdLoaderDemosModeOptions,
ret: IMdTransformerResult,
) {
const { frontmatter, toc, texts, demos } = ret.meta;

// apply atom meta resolve hook
if (frontmatter!.atomId && opts.onResolveAtomMeta) {
opts.onResolveAtomMeta(frontmatter!.atomId, frontmatter);
}

return Mustache.render(
`import React from 'react';
return Mustache.render(
`import React from 'react';
export const demos = {
{{#demos}}
Expand All @@ -78,43 +87,60 @@ export const frontmatter = {{{frontmatter}}};
export const toc = {{{toc}}};
export const texts = {{{texts}}};
`,
{
demos,
frontmatter: JSON.stringify(frontmatter),
toc: JSON.stringify(toc),
texts: JSON.stringify(texts),
renderAsset: function renderAsset(this: NonNullable<typeof demos>[0]) {
// do not render asset for inline demo
if (!('asset' in this)) return 'null';

// render asset for normal demo
let { asset } = this;
const { sources } = this;

// use raw-loader to load all source files
Object.keys(this.sources).forEach((file: string) => {
// handle un-existed source file, e.g. custom tech-stack return custom dependencies
if (!asset.dependencies[file]) return;

// to avoid modify original asset object
asset = lodash.cloneDeep(asset);
asset.dependencies[
file
].value = `{{{require('-!${sources[file]}?dumi-raw').default}}}`;
});

return JSON.stringify(asset, null, 2).replace(/"{{{|}}}"/g, '');
},
{
demos,
frontmatter: JSON.stringify(frontmatter),
toc: JSON.stringify(toc),
texts: JSON.stringify(texts),
renderAsset: function renderAsset(this: NonNullable<typeof demos>[0]) {
// do not render asset for inline demo
if (!('asset' in this)) return 'null';

// render asset for normal demo
let { asset } = this;
const { sources } = this;

// use raw-loader to load all source files
Object.keys(this.sources).forEach((file: string) => {
// handle un-existed source file, e.g. custom tech-stack return custom dependencies
if (!asset.dependencies[file]) return;

// to avoid modify original asset object
asset = lodash.cloneDeep(asset);
asset.dependencies[
file
].value = `{{{require('-!${sources[file]}?dumi-raw').default}}}`;
});

return JSON.stringify(asset, null, 2).replace(/"{{{|}}}"/g, '');
},
);
} else {
// do not wrap DumiPage for tab content
const isTabContent = isTabRouteFile(this.resourcePath);

// import all builtin components, may be used by markdown content
return `${Object.values(opts.builtins)
.map((item) => `import ${item.specifier} from '${item.source}';`)
.join('\n')}
},
);
}

function emitDefault(
this: any,
opts: IMdLoaderDefaultModeOptions,
ret: IMdTransformerResult,
) {
const { frontmatter, demos } = ret.meta;
// do not wrap DumiPage for tab content
const isTabContent = isTabRouteFile(this.resourcePath);

// apply demos resolve hook
if (demos && opts.onResolveDemos) {
opts.onResolveDemos(demos);
}

// apply atom meta resolve hook
if (frontmatter!.atomId && opts.onResolveAtomMeta) {
opts.onResolveAtomMeta(frontmatter!.atomId, frontmatter);
}

// import all builtin components, may be used by markdown content
return `${Object.values(opts.builtins)
.map((item) => `import ${item.specifier} from '${item.source}';`)
.join('\n')}
import React from 'react';
${
isTabContent
Expand All @@ -126,12 +152,122 @@ ${
// ref: https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#edits-always-lead-to-full-reload
function DumiMarkdownContent() {
const { texts: ${CONTENT_TEXTS_OBJ_NAME} } = use${
isTabContent ? 'TabMeta' : 'RouteMeta'
}();
isTabContent ? 'TabMeta' : 'RouteMeta'
}();
return ${isTabContent ? ret.content : `<DumiPage>${ret.content}</DumiPage>`};
}
export default DumiMarkdownContent;`;
}

function emitDemo(opts: IMdLoaderDemoModeOptions, ret: IMdTransformerResult) {
const { demos } = ret.meta;

return Mustache.render(
`import React from 'react';
export const demos = {
{{#demos}}
'{{{id}}}': {
component: {{{component}}},
asset: {{{renderAsset}}}
},
{{/demos}}
};`,
{
demos,
renderAsset: function renderAsset(this: NonNullable<typeof demos>[0]) {
// do not render asset for inline demo
if (!('asset' in this)) return 'null';

// render asset for normal demo
let { asset } = this;
const { sources } = this;

// use raw-loader to load all source files
Object.keys(this.sources).forEach((file: string) => {
// handle un-existed source file, e.g. custom tech-stack return custom dependencies
if (!asset.dependencies[file]) return;

// to avoid modify original asset object
asset = lodash.cloneDeep(asset);
asset.dependencies[
file
].value = `{{{require('-!${sources[file]}?dumi-raw').default}}}`;
});

return JSON.stringify(asset, null, 2).replace(/"{{{|}}}"/g, '');
},
},
);
}

function emitDemoIndex(
this: any,
opts: IMdLoaderDemoIndexModeOptions,
ret: IMdTransformerResult,
) {
const { demos } = ret.meta;

return Mustache.render(
`export const demoIndex = {
ids: {{{ids}}},
getter: {{{getter}}}
};`,
{
ids: JSON.stringify(demos?.map((demo) => demo.id)),
getter: `() => import('${winPath(this.resourcePath)}?type=demo')`,
},
);
}

function emitFrontmatter(
opts: IMdLoaderFrontmatterModeOptions,
ret: IMdTransformerResult,
) {
const { frontmatter } = ret.meta;

return Mustache.render(`export const frontmatter = {{{frontmatter}}};`, {
frontmatter: JSON.stringify(frontmatter),
});
}

function emitText(opts: IMdLoaderTextModeOptions, ret: IMdTransformerResult) {
const { texts, toc } = ret.meta;

return Mustache.render(
`export const toc = {{{toc}}};
export const texts = {{{texts}}};`,
{
toc: JSON.stringify(toc),
texts: JSON.stringify(texts),
},
);
}

function emit(this: any, opts: IMdLoaderOptions, ret: IMdTransformerResult) {
const { demos, embeds } = ret.meta;

// declare embedded files as loader dependency, for re-compiling when file changed
embeds!.forEach((file) => this.addDependency(file));

// declare demo source files as loader dependency, for re-compiling when file changed
getDemoSourceFiles(demos).forEach((file) => this.addDependency(file));

switch (opts.mode) {
// TODO: Should be removed
case 'meta':
return emitMeta.call(this, opts, ret);
case 'demo':
return emitDemo.call(this, opts, ret);
case 'demo-index':
return emitDemoIndex.call(this, opts, ret);
case 'frontmatter':
return emitFrontmatter.call(this, opts, ret);
case 'text':
return emitText.call(this, opts, ret);
default:
return emitDefault.call(this, opts, ret);
}
}

Expand Down

0 comments on commit 3528057

Please sign in to comment.