├── .eslintignore ├── .eslintrc ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __snapshots__ └── test.js.snap ├── index.js ├── package-lock.json ├── package.json └── test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true, 5 | "es6": true, 6 | "jest": true 7 | }, 8 | "plugins": ["node"], 9 | "rules": { 10 | "node/no-unsupported-features": ["error", {"version": 4}], 11 | "node/no-missing-require": ["error"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | coverage 5 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: jammy 2 | 3 | language: node_js 4 | node_js: 5 | - 18 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.9.0 4 | 5 | - Update dependencies 6 | 7 | ## 0.8.0 8 | 9 | - Add `alias` option to support aliases in refractor. 10 | 11 | ## 0.7.0 12 | 13 | - Update dependencies: refractor@3.4.0, eslint, jest, and prettier. 14 | 15 | ## 0.6.0 16 | 17 | - Update dependencies. 18 | 19 | ## 0.5.0 20 | 21 | - Update dependencies. 22 | 23 | ## 0.4.0 24 | 25 | - Update snapshot, use Node 10, remove yarn in favor of package-lock.json, update Jest to fix security alerts. 26 | 27 | ## 0.3.1 28 | 29 | - Allow uppercase language names in the `language-*` class (e.g. `language-CSS`). 30 | 31 | ## 0.3.0 32 | 33 | - Add `language-*` class to the `
` tag of the output, because many Prism themes rely on this undocumented pattern.
34 |
35 | ## 0.2.0
36 |
37 | - **Breaking:** Add `options.ignoreMissing` which defaults to `false`.
38 | If you are relying on *silent* failures to highlight when the language is not defined, you'll need to use this option.
39 | - **Breaking:** Remove support for `nohighlight` and `no-highlight` classes.
40 | You can skip highlighting for any given `` by *not* putting a `language-*` class on it.
41 | - Under the hood, use [refractor](https://github.com/wooorm/refractor) instead of Parse5 and PrismJS directly.
42 |
43 | ## 0.1.0
44 |
45 | - Initial release.
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Mapbox
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Note: This repository is no longer maintained
2 | As of April 2024, this repo is no longer being used by the Mapbox docs team.
3 |
4 | # @mapbox/rehype-prism
5 |
6 | [](https://travis-ci.com/mapbox/rehype-prism)
7 |
8 | [rehype](https://github.com/wooorm/rehype) plugin to highlight code blocks in HTML with [Prism] (via [refractor]).
9 |
10 | (If you would like to highlight code blocks with [highlight.js](https://github.com/isagalaev/highlight.js), instead, check out [rehype-highlight](https://github.com/wooorm/rehype-highlight).)
11 |
12 | **Best suited for usage in Node.**
13 | If you would like to perform syntax highlighting *in the browser*, you should look into [less heavy ways to use refractor](https://github.com/wooorm/refractor#browser).
14 |
15 | ## Installation
16 |
17 | ```
18 | npm install @mapbox/rehype-prism
19 | ```
20 |
21 | ## API
22 |
23 | `rehype().use(rehypePrism, [options])`
24 |
25 | Syntax highlights `pre > code`.
26 | Under the hood, it uses [refractor], which is a virtual version of [Prism].
27 |
28 | The code language is configured by setting a `language-{name}` class on the `` element.
29 | You can use any [language supported by refractor].
30 |
31 | If no `language-{name}` class is found on a `` element, it will be skipped.
32 |
33 | ### options
34 |
35 | #### options.ignoreMissing
36 |
37 | Type: `boolean`.
38 | Default: `false`.
39 |
40 | By default, if `{name}` does not correspond to a [language supported by refractor] an error will be thrown.
41 |
42 | If you would like to silently skip `` elements with invalid languages, set this option to `true`.
43 |
44 | #### options.alias
45 |
46 | Type: `Record`.
47 | Default: `undefined`.
48 |
49 | Provide [aliases] to refractor to register as alternative names for a language.
50 |
51 | ## Usage
52 |
53 | Use this package [as a rehype plugin](https://github.com/rehypejs/rehype/blob/master/doc/plugins.md#using-plugins).
54 |
55 | Some examples of how you might do that:
56 |
57 | ```js
58 | const rehype = require('rehype');
59 | const rehypePrism = require('@mapbox/rehype-prism');
60 |
61 | rehype()
62 | .use(rehypePrism)
63 | .process(/* some html */);
64 | ```
65 |
66 | ```js
67 | const unified = require('unified');
68 | const rehypeParse = require('rehype-parse');
69 | const rehypePrism = require('@mapbox/rehype-prism');
70 |
71 | unified()
72 | .use(rehypeParse)
73 | .use(rehypePrism)
74 | .processSync(/* some html */);
75 | ```
76 |
77 | If you'd like to get syntax highlighting in Markdown, parse the Markdown (with remark-parse), convert it to rehype, then use this plugin.
78 |
79 | ```js
80 | const unified = require('unified');
81 | const remarkParse = require('remark-parse');
82 | const remarkRehype = require('remark-rehype');
83 | const rehypePrism = require('@mapbox/rehype-prism');
84 |
85 | unified()
86 | .use(remarkParse)
87 | .use(remarkRehype)
88 | .use(rehypePrism)
89 | .process(/* some markdown */);
90 | ```
91 |
92 | ## FAQ
93 |
94 |
95 | Why does rehype-prism copy the language-
class to the <pre>
tag?
96 |
97 | [Prism recommends](https://prismjs.com/#basic-usage) adding the `language-` class to the `` tag like this:
98 |
99 | ```html
100 | p { color: red }
101 | ```
102 |
103 | It bases this recommendation on the HTML5 spec. However, an undocumented behavior of their JavaScript is that, in the process of highlighting the code, they also copy the `language-` class to the `` tag:
104 |
105 | ```html
106 | p { color: red }
107 | ```
108 |
109 | This resulted in many [Prism themes](https://github.com/PrismJS/prism-themes) relying on this behavior by using CSS selectors like `pre[class*="language-"]`. So in order for people using rehype-prism to get the most out of these themes, we decided to do the same.
110 |
111 |
112 | [Prism]: http://prismjs.com/
113 |
114 | [refractor]: https://github.com/wooorm/refractor
115 |
116 | [language supported by refractor]: https://github.com/wooorm/refractor#syntaxes
117 |
118 | [aliases]: https://github.com/wooorm/refractor#refractoraliasname-alias
119 |
--------------------------------------------------------------------------------
/__snapshots__/test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`copies the language- class to pre tag 1`] = `"
"`;
4 |
5 | exports[`does nothing to code block without language- class 1`] = `"p { color: red }
"`;
6 |
7 | exports[`finds code and highlights 1`] = `
8 | "
9 | foo
10 | p { color: red }
11 | "
12 | `;
13 |
14 | exports[`handles uppercase languages correctly 1`] = `
15 | "
16 | foo
17 | p { color: red }
18 | "
19 | `;
20 |
21 | exports[`with options.alias it can highlight language aliases 1`] = `
22 | "
23 | <script setup>
24 | const id = 7
25 | </script>
26 |
27 |
"
28 | `;
29 |
30 | exports[`with options.ignoreMissing, does nothing to code block with fake language- class 1`] = `"p { color: red }
"`;
31 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const visit = require('unist-util-visit');
4 | const nodeToString = require('hast-util-to-string');
5 | const refractor = require('refractor');
6 |
7 | module.exports = (options) => {
8 | options = options || {};
9 |
10 | if (options.alias) {
11 | refractor.alias(options.alias);
12 | }
13 |
14 | return (tree) => {
15 | visit(tree, 'element', visitor);
16 | };
17 |
18 | function visitor(node, index, parent) {
19 | if (!parent || parent.tagName !== 'pre' || node.tagName !== 'code') {
20 | return;
21 | }
22 |
23 | const lang = getLanguage(node);
24 |
25 | if (lang === null) {
26 | return;
27 | }
28 |
29 | let result;
30 | try {
31 | parent.properties.className = (parent.properties.className || []).concat(
32 | 'language-' + lang
33 | );
34 | result = refractor.highlight(nodeToString(node), lang);
35 | } catch (err) {
36 | if (options.ignoreMissing && /Unknown language/.test(err.message)) {
37 | return;
38 | }
39 | throw err;
40 | }
41 |
42 | node.children = result;
43 | }
44 | };
45 |
46 | function getLanguage(node) {
47 | const className = node.properties.className || [];
48 |
49 | for (const classListItem of className) {
50 | if (classListItem.slice(0, 9) === 'language-') {
51 | return classListItem.slice(9).toLowerCase();
52 | }
53 | }
54 |
55 | return null;
56 | }
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mapbox/rehype-prism",
3 | "version": "0.9.0",
4 | "description": "rehype plugin to highlight code blocks in HTML with Prism",
5 | "main": "index.js",
6 | "scripts": {
7 | "lint": "eslint .",
8 | "format": "prettier --write '{,lib/,test/}*.js'",
9 | "test-jest": "jest",
10 | "pretest": "npm run lint",
11 | "test": "jest",
12 | "prepare": "husky install"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/mapbox/rehype-prism.git"
17 | },
18 | "keywords": [
19 | "rehype",
20 | "rehype-plugin",
21 | "syntax-highlighting",
22 | "prism",
23 | "html"
24 | ],
25 | "author": "Mapbox",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/mapbox/rehype-prism/issues"
29 | },
30 | "homepage": "https://github.com/mapbox/rehype-prism#readme",
31 | "dependencies": {
32 | "hast-util-to-string": "^1.0.4",
33 | "refractor": "^3.4.0",
34 | "unist-util-visit": "^2.0.3"
35 | },
36 | "devDependencies": {
37 | "@mapbox/prettier-config-docs": "^2",
38 | "dedent": "^0.7.0",
39 | "eslint": "^8",
40 | "eslint-plugin-node": "^11.1.0",
41 | "husky": "^8",
42 | "jest": "^29",
43 | "lint-staged": "^15",
44 | "prettier": "^3",
45 | "rehype": "^11.0.0"
46 | },
47 | "prettier": "@mapbox/prettier-config-docs",
48 | "lint-staged": {
49 | "{,lib/,test/}*.js": [
50 | "prettier --write"
51 | ],
52 | "*.js": [
53 | "eslint --fix",
54 | "prettier --write"
55 | ]
56 | },
57 | "jest": {
58 | "coverageReporters": [
59 | "json",
60 | "lcov",
61 | "text",
62 | "html"
63 | ],
64 | "resetMocks": true,
65 | "testRegex": "/test\\.js$"
66 | },
67 | "engines": {
68 | "node": ">=10"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const rehype = require('rehype');
4 | const dedent = require('dedent');
5 | const rehypePrism = require('./index');
6 |
7 | const processHtml = (html, options) => {
8 | return rehype()
9 | .data('settings', { fragment: true })
10 | .use(rehypePrism, options)
11 | .processSync(html)
12 | .toString();
13 | };
14 |
15 | test('copies the language- class to pre tag', () => {
16 | const result = processHtml(dedent`
17 |
18 | `);
19 | expect(result).toMatchSnapshot();
20 | });
21 |
22 | test('finds code and highlights', () => {
23 | const result = processHtml(dedent`
24 |
25 | foo
26 | p { color: red }
27 |
28 | `);
29 | expect(result).toMatchSnapshot();
30 | });
31 |
32 | test('handles uppercase languages correctly', () => {
33 | const result = processHtml(dedent`
34 |
35 | foo
36 | p { color: red }
37 |
38 | `);
39 | expect(result).toMatchSnapshot();
40 | });
41 |
42 | test('does nothing to code block without language- class', () => {
43 | const result = processHtml(dedent`
44 | p { color: red }
45 | `);
46 | expect(result).toMatchSnapshot();
47 | });
48 |
49 | test('throw error with fake language- class', () => {
50 | expect(() => {
51 | processHtml(dedent`
52 | p { color: red }
53 | `);
54 | }).toThrow(/Unknown language/);
55 | });
56 |
57 | test('with options.ignoreMissing, does nothing to code block with fake language- class', () => {
58 | const html = dedent`
59 | p { color: red }
60 | `;
61 | const result = processHtml(html, { ignoreMissing: true });
62 | expect(result).toMatchSnapshot();
63 | });
64 |
65 | test('with options.alias it can highlight language aliases', () => {
66 | const html = dedent`
67 |
68 |
69 | <script setup>
70 | const id = 7
71 | </script>
72 |
73 |
74 | `;
75 | const result = processHtml(html, { alias: { markup: ['vue', 'html'] } });
76 | expect(result).toMatchSnapshot();
77 | });
78 |
--------------------------------------------------------------------------------