├── .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 | [![Build Status](https://travis-ci.com/mapbox/rehype-prism.svg?branch=main)](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 | --------------------------------------------------------------------------------