diff --git a/package.json b/package.json index 54773ddb7b..8d03ca7454 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,8 @@ "@types/hast": "^2.3.4", "@umijs/core": "^4.0.9", "estree-util-to-js": "^1.1.0", + "hast-util-has-property": "^2.0.0", + "hast-util-is-element": "^2.1.2", "hast-util-to-estree": "^2.1.0", "mdast-util-to-string": "^3.1.0", "rehype-raw": "^6.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e260c5d5ee..a132f16be6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,8 @@ specifiers: eslint: ^8.20.0 estree-util-to-js: ^1.1.0 father: ^4.0.0-rc.8 + hast-util-has-property: ^2.0.0 + hast-util-is-element: ^2.1.2 hast-util-to-estree: ^2.1.0 husky: ^8.0.1 jest: ^27.0.0 @@ -40,6 +42,8 @@ dependencies: '@types/hast': 2.3.4 '@umijs/core': 4.0.9 estree-util-to-js: 1.1.0 + hast-util-has-property: 2.0.0 + hast-util-is-element: 2.1.2 hast-util-to-estree: 2.1.0 mdast-util-to-string: 3.1.0 rehype-raw: 6.1.1 @@ -5073,6 +5077,17 @@ packages: web-namespaces: 2.0.1 dev: false + /hast-util-has-property/2.0.0: + resolution: {integrity: sha512-4Qf++8o5v14us4Muv3HRj+Er6wTNGA/N9uCaZMty4JWvyFKLdhULrv4KE1b65AthsSO9TXSZnjuxS8ecIyhb0w==} + dev: false + + /hast-util-is-element/2.1.2: + resolution: {integrity: sha512-thjnlGAnwP8ef/GSO1Q8BfVk2gundnc2peGQqEg2kUt/IqesiGg/5mSwN2fE7nLzy61pg88NG6xV+UrGOrx9EA==} + dependencies: + '@types/hast': 2.3.4 + '@types/unist': 2.0.6 + dev: false + /hast-util-parse-selector/3.1.0: resolution: {integrity: sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==} dependencies: diff --git a/src/loaders/markdown/transformer/index.ts b/src/loaders/markdown/transformer/index.ts index b4adfe51d2..cb487b1670 100644 --- a/src/loaders/markdown/transformer/index.ts +++ b/src/loaders/markdown/transformer/index.ts @@ -1,6 +1,7 @@ import type { IDumiTechStack } from '@/types'; import rehypeDemo from './rehypeDemo'; import rehypeJsxify from './rehypeJsxify'; +import rehypeImg from './rehypeImg'; export interface IMdTransformerOptions { cwd: string; @@ -32,6 +33,7 @@ export default async (raw: string, opts: IMdTransformerOptions) => { fileAbsPath: opts.fileAbsPath, }) .use(rehypeJsxify) + .use(rehypeImg) .process(raw); return { diff --git a/src/loaders/markdown/transformer/rehypeImg.ts b/src/loaders/markdown/transformer/rehypeImg.ts new file mode 100644 index 0000000000..57b939218a --- /dev/null +++ b/src/loaders/markdown/transformer/rehypeImg.ts @@ -0,0 +1,38 @@ +import path from 'path'; +import type { Element, Root } from 'hast'; +import type { Transformer } from 'unified'; + +let visit: typeof import('unist-util-visit').visit; +let hasProperty: typeof import('hast-util-has-property').hasProperty; +let isElement: typeof import('hast-util-is-element').isElement; + +// workaround to import pure esm module +(async () => { + ({ visit } = await import('unist-util-visit')); + ({ hasProperty } = await import('hast-util-has-property')); + ({ isElement } = await import('hast-util-is-element')); +})(); + +function isRelativeUrl(url: string) { + return typeof url === 'string' && !/^(?:(?:blob:)?\w+:)?\/\//.test(url) && !path.isAbsolute(url); +} + +/** + * rehype plugin to handle img source from local + */ + export default function rehypeImg(): Transformer { + return tree => { + visit(tree, 'element', (node: Element) => { + if (isElement(node, 'img') && hasProperty(node, 'src')) { + const src = node.properties!.src as string; + + if (isRelativeUrl(src)) { + // use wrapper element to workaround for skip props escape + // https://github.com/mapbox/jsxtreme-markdown/blob/main/packages/hast-util-to-jsx/index.js#L159 + // eslint-disable-next-line no-new-wrappers + node.properties!.src = `require('${decodeURI(src)}')`; + } + } + }); + }; +}