Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add browser support #9

Merged
merged 5 commits into from
Oct 2, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .c8rc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"100": true,
"include": ["index.ts"],
"reporter": ["html", "lcov", "text"]
}
5 changes: 5 additions & 0 deletions .eslintrc.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
extends:
- remcohaszing
rules:
no-param-reassign: off
'@typescript-eslint/consistent-type-imports':
- error
- prefer: type-imports
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
- run: npm --global install npm@8
if: ${{ matrix.node-version == 14 }}
- run: npm ci
- run: npx playwright install

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to cache the browser binaries installed?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes total sense! I’ll look into it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

microsoft/playwright#7249 may offer some ideas

- run: npm test
- uses: codecov/codecov-action@v3
if: ${{ matrix.node-version == 18 }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
coverage/
dist/
node_modules/
test-results/
*.d.ts
*.js
*.log
Expand Down
3 changes: 1 addition & 2 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
__fixtures__/*/output.md
__snapshots__/
**/*-snapshots/*
coverage/
dist/
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,22 @@ console.log(value);
These options are passed to
[`puppeteer.launch()`](https://pptr.dev/#?product=Puppeteer&show=api-puppeteerlaunchoptions).

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

#### `svgo`

These options are passed to the [SVGO](https://github.com/svg/svgo) constructor. Set to `null` to
disable minifying using SVGO completely.

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

#### `mermaidOptions`

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.

## License

[MIT](LICENSE.md) © [Remco Haszing](https://github.com/remcohaszing)
39 changes: 39 additions & 0 deletions browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { fromDom } from 'hast-util-from-dom';
import { type Code, type Parent, type Root } from 'mdast';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this equivalent to

Suggested change
import { type Code, type Parent, type Root } from 'mdast';
import type { Code, Parent, Root } from 'mdast';

?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, those are equivalent. Because there are no ESLint rules to force a consistent way to write type imports, I have decided to disallow type imports in my personal ESLint config. However, Playwright doesn’t support unannotated type imports, so I decided to enforce type imports in this project for consistency and use a type annotation per import specifier. Using such annotations per specifier allows to combine both type imports and value imports in a single statement.

import mermaid from 'mermaid';
import { type Plugin } from 'unified';
import { visit } from 'unist-util-visit';

// eslint-disable-next-line jsdoc/require-jsdoc
function transformer(ast: Root): void {
const instances: [string, number, Parent][] = [];

visit(ast, { type: 'code', lang: 'mermaid' }, (node: Code, index, parent: Parent) => {
instances.push([node.value, index, parent]);
});

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

const results = instances.map(([code], index) =>
// @ts-expect-error The mermaid types are wrong.
mermaid.render(`remark-mermaid-${index}`, code),
);

const wrapper = document.createElement('div');
for (const [i, [, index, parent]] of instances.entries()) {
const value = results[i];
wrapper.innerHTML = value;
parent.children.splice(index, 1, {
type: 'paragraph',
children: [{ type: 'html', value }],
data: { hChildren: [fromDom(wrapper.firstChild!)] },
});
}
}

const remarkMermaid: Plugin<[], Root> = () => transformer;

export default remarkMermaid;
42 changes: 27 additions & 15 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { createRequire } from 'module';

import { fromParse5 } from 'hast-util-from-parse5';
import { Code, Parent, Root } from 'mdast';
import { Mermaid } from 'mermaid';
import { type Code, type Parent, type Root } from 'mdast';
import { type Mermaid } from 'mermaid';
import { parseFragment } from 'parse5';
import puppeteer, { Browser, Page, PuppeteerLaunchOptions } from 'puppeteer-core';
import { optimize, OptimizedSvg, OptimizeOptions } from 'svgo';
import { Plugin } from 'unified';
import puppeteer, { type Browser, type Page, type PuppeteerLaunchOptions } from 'puppeteer-core';
import { optimize, type OptimizedSvg, type OptimizeOptions } from 'svgo';
import { type Plugin } from 'unified';
import { visit } from 'unist-util-visit';

const mermaidScript = {
Expand Down Expand Up @@ -76,7 +76,9 @@ export const defaultSVGOOptions: OptimizeOptions = {

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

Expand All @@ -85,24 +87,31 @@ export interface RemarkMermaidOptions {
*
* Set to `null` explicitly to disable this.
*
* **Note**: This options is only supported in Node.js. In the browser this option is unused.
*
* @default defaultSVGOOptions
*/
svgo?: OptimizeOptions | null;

/**
* 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?: Parameters<typeof mermaid['initialize']>[0];
}

/**
* @param options Options that may be used to tweak the output.
*/
const remarkMermaid: Plugin<[RemarkMermaidOptions], Root> = ({
launchOptions,
mermaidOptions = {},
svgo = defaultSVGOOptions,
}) => {
const remarkMermaid: Plugin<[RemarkMermaidOptions?], Root> = (options) => {
if (!options?.launchOptions?.executablePath) {
throw new Error('The option `launchOptions.executablePath` is required when using Node.js');
}
remcohaszing marked this conversation as resolved.
Show resolved Hide resolved

const { launchOptions, mermaidOptions, svgo = defaultSVGOOptions } = options;

let browserPromise: Promise<Browser> | undefined;
let count = 0;

Expand Down Expand Up @@ -135,14 +144,17 @@ const remarkMermaid: Plugin<[RemarkMermaidOptions], Root> = ({
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) =>
codes.map((code) => {
const id = 'a';
(codes, initOptions) => {
if (initOptions) {
mermaid.initialize(initOptions);
}
return codes.map((code) => {
const id = 'a';
const div = document.createElement('div');
div.innerHTML = mermaid.render(id, code);
return div.innerHTML;
}),
});
},
/* C8 ignore stop */
instances.map((instance) => instance[0]),
mermaidOptions,
Expand Down
Loading