Skip to content

Commit

Permalink
Use mermaid-isomorphic
Browse files Browse the repository at this point in the history
This handles the logic of dealing with a browser.

This also means that under the hood Playwright is used instead of
Puppeteer.

This also updates to Mermaid 10.

Since there are no more different implementations for Node.js and the
browser in this package, browser tests have been removed.
  • Loading branch information
remcohaszing committed May 6, 2023
1 parent 40cf8ff commit 2312642
Show file tree
Hide file tree
Showing 54 changed files with 322 additions and 1,537 deletions.
47 changes: 33 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@
[![prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io)

A [remark](https://remark.js.org) plugin to render [mermaid](https://mermaid-js.github.io) diagrams
using [puppeteer](https://pptr.dev).
using [playwright](https://playwright.dev).

## Table of Contents

- [Installation](#installation)
- [Usage](#usage)
- [Options](#options)
- [`unified().use(remarkMermaid, options?)`](#unifieduseremarkmermaid-options)
- [`browser`](#browser)
- [`css`](#css)
- [`errorFallback`](#errorfallback)
- [`launchOptions`](#launchoptions)
- [`mermaidOptions`](#mermaidoptions)
- [`prefix`](#prefix)
- [License](#license)

## Installation
Expand All @@ -24,10 +28,15 @@ using [puppeteer](https://pptr.dev).
npm install remark-mermaidjs
```

Since this package uses Google Chrome, You have to make sure it’s available on your system. You may
also need to install some additional packages, such as fonts, depending on your system. For more
information, see the Puppeteer
[troubleshooting](https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md) guide.
In Node.js this package uses [playwright](https://playwright.dev) under the hood. To use it, you may
need to install additional dependencies. These can be installed with:

```sh
npx playwright install --with-deps chromium
```

See the Playwright [Browsers](https://playwright.dev/docs/browsers) documentation for more
information.

## Usage

Expand All @@ -41,13 +50,7 @@ import remarkMermaid from 'remark-mermaidjs';

const { value } = await remark()
.use(remarkMermaid, {
launchOptions: {
executablePath: 'path/to/chrome/executable',

/* More puppeteer launch options */
},

/* More options */
/* Options */
})
.process(await readFile('readme.md'));

Expand All @@ -56,6 +59,19 @@ console.log(value);

### Options

This package has a default export `remarkMermaid`.

### `unified().use(remarkMermaid, options?)`

#### `browser`

The Playwright browser to use. (`object`, default: chromium)

#### `css`

A URL that points to a custom CSS file to load. Use this to load custom fonts. This option is
ignored in the browser. You need to include the CSS in your build manually. (`string` | `URL`)

#### `errorFallback`

Create a fallback node if processing of a mermaid diagram fails. If nothing is returned, the code
Expand All @@ -67,8 +83,7 @@ block is removed. The function receives the following arguments:

#### `launchOptions`

These options are passed to
[`puppeteer.launch()`](https://pptr.dev/#?product=Puppeteer&show=api-puppeteerlaunchoptions).
The options used to launch the browser. (`object`)

- **Note**: This options is required in Node.js. In the browser this option is unused.

Expand All @@ -79,6 +94,10 @@ The [mermaid options](https://mermaid-js.github.io/mermaid/#/Setup) to use.
**Note**: This options is only supported in Node.js. In the browser this option is unused. If you
use this in a browser, call `mermaid.initialize()` manually.

#### `prefix`

A custom prefix to use for Mermaid IDs. (`string`, default: `mermaid`)

## License

[MIT](LICENSE.md) © [Remco Haszing](https://github.com/remcohaszing)
35 changes: 0 additions & 35 deletions browser.ts

This file was deleted.

1 change: 0 additions & 1 deletion index.html

This file was deleted.

134 changes: 46 additions & 88 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,19 @@
import { createRequire } from 'node:module';

import { type BlockContent, type Code, type Root } from 'mdast';
import { type MermaidConfig } from 'mermaid';
import puppeteer, { type Browser, type Page, type PuppeteerLaunchOptions } from 'puppeteer-core';
import { fromHtmlIsomorphic } from 'hast-util-from-html-isomorphic';
import { type BlockContent, type Code, type Parent, type Root } from 'mdast';
import {
createMermaidRenderer,
type CreateMermaidRendererOptions,
type RenderOptions,
} from 'mermaid-isomorphic';
import { type Plugin } from 'unified';
import { visit } from 'unist-util-visit';
import { type VFile } from 'vfile';

import { extractCodeBlocks, replaceCodeBlocks, type Result } from './shared.js';

const mermaidScript = {
path: createRequire(import.meta.url).resolve('mermaid/dist/mermaid.min.js'),
};

// eslint-disable-next-line @typescript-eslint/consistent-type-imports
declare const mermaid: typeof import('mermaid').default;

export interface RemarkMermaidOptions {
/**
* Launch options to pass to puppeteer.
*
* **Note**: This options is required in Node.js. In the browser this option is unused.
*/
launchOptions?: PuppeteerLaunchOptions;

/**
* The mermaid options to use.
*
* **Note**: This options is only supported in Node.js. In the browser this option is unused. If
* you use this in a browser, call `mermaid.initialize()` manually.
*/
mermaidOptions?: MermaidConfig;
type CodeInstance = [Code, Parent];

export interface RemarkMermaidOptions
extends CreateMermaidRendererOptions,
Omit<RenderOptions, 'screenshot'> {
/**
* Create a fallback node if processing of a mermaid diagram fails.
*
Expand All @@ -50,72 +33,47 @@ export type RemarkMermaid = Plugin<[RemarkMermaidOptions?], Root>;
* @param options Options that may be used to tweak the output.
*/
const remarkMermaid: RemarkMermaid = (options) => {
if (!options?.launchOptions?.executablePath) {
throw new Error('The option `launchOptions.executablePath` is required when using Node.js');
}

const { launchOptions, mermaidOptions } = options;

let browserPromise: Promise<Browser> | undefined;
let count = 0;
const render = createMermaidRenderer(options);

return async function transformer(ast, file) {
const instances = extractCodeBlocks(ast);
const instances: CodeInstance[] = [];

// Nothing to do. No need to start puppeteer in this case.
if (!instances.length) {
return;
}

count += 1;
browserPromise ??= puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
...launchOptions,
visit(ast, { type: 'code', lang: 'mermaid' }, (node: Code, index, parent: Parent) => {
instances.push([node, parent]);
});
const browser = await browserPromise;
let page: Page | undefined;
let results: Result[];
try {
page = await browser.newPage();
await page.goto(String(new URL('index.html', import.meta.url)));
await page.addScriptTag(mermaidScript);

results = await page.evaluate(
// We can’t calculate coverage on this function, as it’s run by Chrome, not Node.
/* c8 ignore start */
(codes, initOptions) => {
if (initOptions) {
mermaid.initialize(initOptions);
}
return codes.map((code, index) => {
try {
return {
success: true,
result: mermaid.render(`remark-mermaid-${index}`, code),
};
} catch (error) {
return {
success: false,
result: error instanceof Error ? error.message : String(error),
};
}
});
},

/* C8 ignore stop */
instances.map((instance) => instance[0].value),
mermaidOptions,
);
} finally {
count -= 1;
await page?.close();
// Nothing to do. No need to start a browser in this case.
if (!instances.length) {
return;
}

replaceCodeBlocks(instances, results, options, file);

if (!count) {
browserPromise = undefined;
await browser?.close();
const results = await render(
instances.map((instance) => instance[0].value),
options,
);

for (const [i, [node, parent]] of instances.entries()) {
const result = results[i];
const nodeIndex = parent.children.indexOf(node);

if (result.status === 'fulfilled') {
const { svg } = result.value;
const hChildren = fromHtmlIsomorphic(svg, { fragment: true }).children;
parent.children[nodeIndex] = {
type: 'paragraph',
children: [{ type: 'html', value: svg }],
data: { hChildren },
};
} else if (options?.errorFallback) {
const fallback = options.errorFallback(node, result.reason, file);
if (fallback) {
parent.children[nodeIndex] = fallback;
} else {
parent.children.splice(nodeIndex, 1);
}
} else {
file.fail(result.reason, node, 'remark-mermaidjs:remark-mermaidjs');
}
}
};
};
Expand Down
Loading

0 comments on commit 2312642

Please sign in to comment.