Skip to content

Commit

Permalink
feat: generate & render demo asset data
Browse files Browse the repository at this point in the history
  • Loading branch information
PeachScript committed Sep 9, 2022
1 parent b3ab232 commit 6d5822b
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 30 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@
"estree-util-visit": "^1.2.0",
"hast-util-raw": "^7.2.2",
"hast-util-to-estree": "^2.1.0",
"js-yaml": "^4.1.0",
"mdast-util-to-string": "^3.1.0",
"pluralize": "^8.0.0",
"raw-loader": "^4.0.2",
"remark-breaks": "^3.0.2",
"remark-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1",
Expand All @@ -74,6 +76,7 @@
"@commitlint/config-conventional": "^17.0.3",
"@jest/types": "^27.0.0",
"@types/jest": "^27.0.0",
"@types/js-yaml": "^4.0.5",
"@types/node": "^18.6.3",
"@types/pluralize": "^0.0.29",
"@types/react": "^18.0.16",
Expand Down
20 changes: 20 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

156 changes: 156 additions & 0 deletions src/assetParsers/block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { build } from '@umijs/bundler-utils/compiled/esbuild';
import type { ExampleBlockAsset } from 'dumi-assets-types';
import fs from 'fs';
import yaml from 'js-yaml';
import path from 'path';
import { pkgUp, winPath } from 'umi/plugin-utils';

export interface IParsedBlockAsset {
asset: ExampleBlockAsset;
sources: Record<string, string>;
frontmatter: ReturnType<typeof parseCodeFrontmatter>['frontmatter'];
}

function parseCodeFrontmatter(raw: string) {
const [, comment = '', code = ''] = raw
// clear head break lines
.replace(/^\n\s*/, '')
// split head comments & remaining code
.match(/^(\/\*\*[^]*?\n\s*\*\/)?(?:\s|\n)*([^]+)?$/)!;

const yamlComment = comment
// clear / from head & foot for comment
.replace(/^\/|\/$/g, '')
// remove * from comments
.replace(/(^|\n)\s*\*+/g, '$1');
let frontmatter: Record<string, any> | null = null;

try {
frontmatter = yaml.load(yamlComment) as any;
} catch {}

return { code: frontmatter ? code : raw, frontmatter };
}

async function parseBlockAsset(opts: {
fileAbsPath: string;
id: string;
refAtomIds: string[];
entryPointCode?: string;
}) {
const asset: IParsedBlockAsset['asset'] = {
type: 'BLOCK',
id: opts.id,
refAtomIds: opts.refAtomIds,
dependencies: {},
};
const result: IParsedBlockAsset = {
asset,
sources: {},
frontmatter: {},
};

await build({
// do not emit file
write: false,
// enable bundle for trigger onResolve hook, but all deps will be externalized
bundle: true,
logLevel: 'silent',
format: 'esm',
target: 'esnext',
// esbuild need relative entry path
entryPoints: [path.basename(opts.fileAbsPath)],
absWorkingDir: path.dirname(opts.fileAbsPath),
plugins: [
{
name: 'plugin-dumi-collect-deps',
setup: (builder) => {
builder.onResolve({ filter: /.*/ }, (args) => {
if (args.kind !== 'entry-point' && !args.path.startsWith('.')) {
const pkgJsonPath = pkgUp.pkgUpSync({
cwd: require.resolve(args.path, { paths: [args.resolveDir] }),
});

if (pkgJsonPath) {
asset.dependencies[args.path] = {
type: 'NPM',
value: require(pkgJsonPath).version,
};
}

// make all deps external
return { path: args.path, external: true };
}

return {
path: path.join(args.resolveDir, args.path),
pluginData: { kind: args.kind, resolveDir: args.resolveDir },
};
});

builder.onLoad({ filter: /.*/ }, (args) => {
const ext = path.extname(args.path);
const isModule = ['.js', '.jsx', '.ts', '.tsx'].includes(ext);
const isPlainText = [
'.css',
'.less',
'.sass',
'.scss',
'.styl',
'.json',
].includes(ext);
const isEntryPoint = args.pluginData.kind === 'entry-point';
const filename = isEntryPoint
? `index${ext}`
: winPath(
path.relative(path.dirname(opts.fileAbsPath), args.path),
);

if (isModule || isPlainText) {
asset.dependencies[filename] = {
type: 'FILE',
value:
opts.entryPointCode ?? fs.readFileSync(args.path, 'utf-8'),
};

// extract entry point frontmatter as asset metadata
if (isEntryPoint) {
const { code, frontmatter } = parseCodeFrontmatter(
asset.dependencies[filename].value,
);

if (frontmatter) {
asset.dependencies[filename].value = code;
result.frontmatter = frontmatter;

// TODO: locale for title & description
['description', 'title', 'snapshot', 'keywords'].forEach(
(key) => {
asset.dependencies[key] = frontmatter?.[key];
},
);
}
}

// save file absolute path for load file via raw-loader
// to avoid bundle same file to save bundle size
if (!isEntryPoint || !opts.entryPointCode) {
result.sources[filename] = args.path;
}

return {
// only continue to load for module files
contents: isModule ? asset.dependencies[filename].value : '',
loader: isModule ? (ext.slice(1) as any) : 'text',
};
}
});
},
},
],
});

return result;
}

export default parseBlockAsset;
2 changes: 1 addition & 1 deletion src/client/theme/DumiDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Context } from './context';

export const DumiDemo: FC<{ id: string }> = (props) => {
const { demos } = useContext(Context);
const component = demos[props.id];
const { component } = demos[props.id];

return createElement(component);
};
2 changes: 1 addition & 1 deletion src/client/theme/context.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createContext, type ComponentType } from 'react';

export interface IThemeContext {
demos: Record<string, ComponentType>;
demos: Record<string, { component: ComponentType }>;
}

export const Context = createContext<IThemeContext>({ demos: {} });
7 changes: 7 additions & 0 deletions src/features/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ export default (api: IApi) => {
.loader(require.resolve('../loaders/demo'))
.options({ techStacks, cwd: api.cwd } as IDemoLoaderOptions);

// get raw content for demo source file
memo.module
.rule('dumi-raw')
.resourceQuery(/raw/)
.use('raw-loader')
.loader(require.resolve('raw-loader'));

// enable fast-refresh for md component in development mode
if (api.env === 'development') {
memo.plugin('fastRefresh').tap(([params]) => [
Expand Down
22 changes: 20 additions & 2 deletions src/loaders/markdown/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,28 @@ export default function mdLoader(this: any, raw: string) {
export default {
{{#demos}}
'{{{id}}}': {{{component}}},
'{{{id}}}': {
component: {{{component}}},
asset: {{{renderAsset}}}
},
{{/demos}}
}`,
{ demos: ret.meta.demos },
{
demos: ret.meta.demos,
renderAsset: function renderAsset() {
// use raw-loader to load all source files
Object.keys(this.sources).forEach((file: string) => {
this.asset.dependencies[
file
].value = `{{{require('!!raw-loader!${this.sources[file]}?raw').default}}}`;
});

return JSON.stringify(this.asset, null, 2).replace(
/"{{{|}}}"/g,
'',
);
},
},
),
);
} else {
Expand Down
1 change: 1 addition & 0 deletions src/loaders/markdown/transformer/fixtures/demo/demo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => 'demo';
8 changes: 7 additions & 1 deletion src/loaders/markdown/transformer/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { IParsedBlockAsset } from '@/assetParsers/block';
import type { IDumiTechStack } from '@/types';
import type { DataMap } from 'vfile';
import rehypeDemo from './rehypeDemo';
Expand All @@ -19,7 +20,12 @@ declare module 'hast' {

declare module 'vfile' {
interface DataMap {
demos: { id: string; component: string }[];
demos: {
id: string;
component: string;
asset: IParsedBlockAsset['asset'];
sources: IParsedBlockAsset['sources'];
}[];
}
}

Expand Down
Loading

0 comments on commit 6d5822b

Please sign in to comment.