├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── 1_Bug_report.md ├── dependabot.yml └── workflows │ └── nodejs.yml ├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── test ├── expect │ ├── basic.html │ ├── custom_language_default.html │ ├── custom_language_load.html │ ├── inline_code.html │ ├── invalid_language.html │ ├── preserves_classes.html │ ├── prism_ignore_attr.html │ └── prism_ignore_class.html ├── fixtures │ ├── basic.html │ ├── custom_language_default.html │ ├── custom_language_load.html │ ├── inline_code.html │ ├── invalid_language.html │ ├── preserves_classes.html │ ├── prism_ignore_attr.html │ └── prism_ignore_class.html └── test.js └── xo.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 2 6 | indent_style = space 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://mailviews.com'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1_Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | about: 'Report a general issue.' 4 | 5 | --- 6 | 7 | ## Problem 8 | 9 | _Describe your problem._ 10 | 11 | ## Environment 12 | 13 | - `posthtml-prism` plugin version: #.#.# 14 | - PostHTML version: #.#.# 15 | - Node.js version: #.#.# 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | time: "22:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [14, 16, 18] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm install 27 | - run: npm test 28 | env: 29 | CI: true 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | *.log 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Cosmin Popovici 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Prism Syntax Highlighting

4 | 5 | Compile-time syntax highlighting for code blocks with [Prism](https://prismjs.com/) 6 | 7 | [![Version][npm-version-shield]][npm] 8 | [![Build][github-ci-shield]][github-ci] 9 | [![License][license-shield]][license] 10 | [![Downloads][npm-stats-shield]][npm-stats] 11 |
12 | 13 | ## Introduction 14 | 15 | Before: 16 | 17 | ```html 18 |

 19 |   const foo = 'bar'
 20 |   console.log(foo)
 21 | 
22 | ``` 23 | 24 | After: 25 | 26 | ```html 27 |

 28 |   const foo = 'bar'
 29 |   console.log(foo)
 30 | 
31 | ``` 32 | 33 | ## Install 34 | 35 | ``` 36 | $ npm i posthtml posthtml-prism 37 | ``` 38 | 39 | ## Usage 40 | 41 | ```js 42 | const fs = require('fs') 43 | const posthtml = require('posthtml') 44 | const highlight = require('posthtml-prism') 45 | 46 | const source = fs.readFileSync('./before.html') 47 | 48 | posthtml([ 49 | highlight({ inline: true }) 50 | ]) 51 | .process(source) 52 | .then(result => fs.writeFileSync('./after.html', result.html)) 53 | ``` 54 | 55 | ## Options 56 | 57 | ### inline 58 | 59 | Type: `boolean`\ 60 | Default: `false` 61 | 62 | By default, only `` tags wrapped in `
` tags are highlighted. 
 63 | 
 64 | Pass in `inline: true` to highlight all code tags.
 65 | 
 66 | ## Styling
 67 | 
 68 | You will also need to include a Prism theme stylesheet in your HTML. 
 69 | 
 70 | See [PrismJS/prism-themes](https://github.com/PrismJS/prism-themes) for all available themes.
 71 | 
 72 | ## Languages
 73 | 
 74 | By default, Prism loads the following languages: `markup`, `css`, `clike`, and `javascript`.
 75 | 
 76 | You can specify the language to be used for highlighting your code, by adding a `language-*` or `lang-*` class to the `` tag:
 77 | 
 78 | ```html
 79 | 
 80 |   
 81 |     $app->post('framework/{id}', function($framework) {        
 82 |       $this->dispatch(new Energy($framework));
 83 |     });
 84 |   
 85 | 
86 | ``` 87 | 88 | ### Skip highlighting on a node 89 | 90 | You can skip highlighting on a node in two ways: 91 | 92 | 1. add a `prism-ignore` attribute on the node: 93 | ```html 94 |
 95 |     ...
 96 |   
97 | ``` 98 | 99 | 2. or, add a `prism-ignore` class: 100 | ```html 101 |
102 |     ...
103 |   
104 | ``` 105 | 106 | In both cases, the `prism-ignore` attribute/class will be removed and highlighting will be skipped. 107 | 108 | [npm]: https://www.npmjs.com/package/posthtml-prism 109 | [npm-version-shield]: https://img.shields.io/npm/v/posthtml-prism.svg 110 | [npm-stats]: http://npm-stat.com/charts.html?package=posthtml-prism&author=&from=&to= 111 | [npm-stats-shield]: https://img.shields.io/npm/dt/posthtml-prism.svg?maxAge=2592000 112 | [github-ci]: https://github.com/posthtml/posthtml-prism/actions 113 | [github-ci-shield]: https://github.com/posthtml/posthtml-prism/actions/workflows/nodejs.yml/badge.svg 114 | [license]: ./license 115 | [license-shield]: https://img.shields.io/npm/l/posthtml-prism.svg 116 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Prism = require('prismjs'); 4 | const {render} = require('posthtml-render'); 5 | const loadLanguages = require('prismjs/components/'); 6 | 7 | function createPrismPlugin(options) { 8 | return function (tree) { 9 | const highlightCodeTags = node => tree.match.call(node, {tag: 'code'}, highlightNode); 10 | 11 | if (options.inline) { 12 | highlightCodeTags(tree); 13 | } else { 14 | tree.match({tag: 'pre'}, highlightCodeTags); 15 | } 16 | }; 17 | } 18 | 19 | function highlightNode(node) { 20 | const attrs = node.attrs || {}; 21 | const classList = `${attrs.class || ''}`.trimStart(); 22 | 23 | if ('prism-ignore' in attrs) { 24 | delete node.attrs['prism-ignore']; 25 | return node; 26 | } 27 | 28 | if (classList.includes('prism-ignore')) { 29 | node.attrs.class = node.attrs.class.replace('prism-ignore', '').trim(); 30 | return node; 31 | } 32 | 33 | const lang = getExplicitLanguage(classList); 34 | 35 | if (lang && !classList.includes(`language-${lang}`)) { 36 | attrs.class = `${classList || ''} language-${lang}`.trimStart(); 37 | } 38 | 39 | node.attrs = attrs; 40 | 41 | if (node.content) { 42 | const html = (node.content[0].tag && !node.content[0].content) ? `<${node.content[0].tag}>` : render(node.content); 43 | 44 | node.content = mapStringOrNode(html, lang); 45 | } 46 | 47 | return node; 48 | } 49 | 50 | function mapStringOrNode(stringOrNode, lang = null) { 51 | if (typeof stringOrNode === 'string') { 52 | if (lang) { 53 | if (!Object.keys(Prism.languages).includes(lang)) { 54 | loadLanguages.silent = true; 55 | loadLanguages([lang]); 56 | } 57 | 58 | return Prism.highlight(stringOrNode, Prism.languages[lang], lang); 59 | } 60 | 61 | return Prism.highlight(stringOrNode, Prism.languages.markup, 'markup'); 62 | } 63 | 64 | highlightNode(stringOrNode); 65 | return stringOrNode; 66 | } 67 | 68 | function getExplicitLanguage(classList) { 69 | const matches = classList.match(/(?:lang|language)-(\w*)/); 70 | return matches === null ? null : matches[1]; 71 | } 72 | 73 | module.exports = options => { 74 | options = options || {}; 75 | options.inline = options.inline || false; 76 | 77 | return function (tree) { 78 | return createPrismPlugin(options)(tree); 79 | }; 80 | }; 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "posthtml-prism", 3 | "description": "PostHTML code syntax highlighting with Prism", 4 | "version": "2.0.1", 5 | "license": "MIT", 6 | "author": "Cosmin Popovici (https://github.com/cossssmin)", 7 | "bugs": "https://github.com/posthtml/posthtml-prism/issues", 8 | "homepage": "https://github.com/posthtml/posthtml-prism", 9 | "repository": "posthtml/posthtml-prism", 10 | "main": "index.js", 11 | "files": [ 12 | "index.js" 13 | ], 14 | "engines": { 15 | "node": ">=14.0.0" 16 | }, 17 | "scripts": { 18 | "test": "c8 ava", 19 | "pretest": "xo", 20 | "release": "np" 21 | }, 22 | "keywords": [ 23 | "html", 24 | "posthtml", 25 | "posthtml-plugin", 26 | "prism", 27 | "syntax", 28 | "highlight", 29 | "code", 30 | "pre" 31 | ], 32 | "dependencies": { 33 | "prismjs": "^1.19.0" 34 | }, 35 | "devDependencies": { 36 | "ava": "^5.2.0", 37 | "np": "^10.0.0", 38 | "c8": "^9.1.0", 39 | "posthtml": "^0.16.4", 40 | "posthtml-render": "^3.0.0", 41 | "xo": "^0.54.2" 42 | }, 43 | "peerDependencies": { 44 | "posthtml-render": "^3.0.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/expect/basic.html: -------------------------------------------------------------------------------- 1 |
2 |     
3 |         <div>
4 |             <p>Lorem ipsum</p>
5 |         </div>
6 |     
7 | 
-------------------------------------------------------------------------------- /test/expect/custom_language_default.html: -------------------------------------------------------------------------------- 1 |

2 |   const foo = 'foo'
3 |   console.log(foo)
4 | 
-------------------------------------------------------------------------------- /test/expect/custom_language_load.html: -------------------------------------------------------------------------------- 1 |

2 |     $app->post('framework/{id}', function($framework) {
3 |         $this->dispatch(new Energy($framework));
4 |     });
5 | 
6 | -------------------------------------------------------------------------------- /test/expect/inline_code.html: -------------------------------------------------------------------------------- 1 | The <div> is the generic container for flow content. -------------------------------------------------------------------------------- /test/expect/invalid_language.html: -------------------------------------------------------------------------------- 1 |

2 |   const foo = 'foo'
3 |   console.log(foo)
4 | 
-------------------------------------------------------------------------------- /test/expect/preserves_classes.html: -------------------------------------------------------------------------------- 1 |
2 |     
3 |         <div>
4 |             <p>Lorem ipsum</p>
5 |         </div>
6 |     
7 | 
-------------------------------------------------------------------------------- /test/expect/prism_ignore_attr.html: -------------------------------------------------------------------------------- 1 |

2 |   const foo = 'foo'
3 |   console.log(foo)
4 | 
-------------------------------------------------------------------------------- /test/expect/prism_ignore_class.html: -------------------------------------------------------------------------------- 1 |

2 |   const foo = 'foo'
3 |   console.log(foo)
4 | 
-------------------------------------------------------------------------------- /test/fixtures/basic.html: -------------------------------------------------------------------------------- 1 |
2 |     
3 |         
4 |

Lorem ipsum

5 |
6 |
7 |
-------------------------------------------------------------------------------- /test/fixtures/custom_language_default.html: -------------------------------------------------------------------------------- 1 |

2 |   const foo = 'foo'
3 |   console.log(foo)
4 | 
-------------------------------------------------------------------------------- /test/fixtures/custom_language_load.html: -------------------------------------------------------------------------------- 1 |

2 |     $app->post('framework/{id}', function($framework) {        
3 |         $this->dispatch(new Energy($framework));
4 |     });
5 | 
-------------------------------------------------------------------------------- /test/fixtures/inline_code.html: -------------------------------------------------------------------------------- 1 | The
is the generic container for flow content. -------------------------------------------------------------------------------- /test/fixtures/invalid_language.html: -------------------------------------------------------------------------------- 1 |

2 |   const foo = 'foo'
3 |   console.log(foo)
4 | 
-------------------------------------------------------------------------------- /test/fixtures/preserves_classes.html: -------------------------------------------------------------------------------- 1 |
2 |     
3 |         
4 |

Lorem ipsum

5 |
6 |
7 |
-------------------------------------------------------------------------------- /test/fixtures/prism_ignore_attr.html: -------------------------------------------------------------------------------- 1 |

2 |   const foo = 'foo'
3 |   console.log(foo)
4 | 
-------------------------------------------------------------------------------- /test/fixtures/prism_ignore_class.html: -------------------------------------------------------------------------------- 1 |

2 |   const foo = 'foo'
3 |   console.log(foo)
4 | 
-------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const {readFileSync} = require('fs'); 3 | const test = require('ava'); 4 | const posthtml = require('posthtml'); 5 | const highlight = require('..'); 6 | 7 | const fixture = file => readFileSync(path.join(__dirname, 'fixtures', `${file}.html`), 'utf8'); 8 | const expect = file => readFileSync(path.join(__dirname, 'expect', `${file}.html`), 'utf8'); 9 | 10 | const clean = html => html.replace(/[^\S\r\n]+$/gm, '').trim(); 11 | 12 | const process = (t, name, options, log = false) => { 13 | return posthtml([highlight(options)]) 14 | .process(fixture(name)) 15 | .then(result => log ? console.log(result.html) : clean(result.html)) 16 | .then(html => t.is(html, expect(name).trim())); 17 | }; 18 | 19 | test('Highlights tags inside
 tags', t => {
20 |   return process(t, 'basic');
21 | });
22 | 
23 | test('Highlights inline  tags', t => {
24 |   return process(t, 'inline_code', {inline: true});
25 | });
26 | 
27 | test('Ignores  blocks with prism-ignore attribute', t => {
28 |   return process(t, 'prism_ignore_attr');
29 | });
30 | 
31 | test('Ignores  blocks with prism-ignore class', t => {
32 |   return process(t, 'prism_ignore_class');
33 | });
34 | 
35 | test('Highlights block with one of the default languages specified', t => {
36 |   return process(t, 'custom_language_default');
37 | });
38 | 
39 | test('Loads custom language and highlights block', t => {
40 |   return process(t, 'custom_language_load');
41 | });
42 | 
43 | test('Preserves existing classes', t => {
44 |   return process(t, 'preserves_classes');
45 | });
46 | 
47 | test('Throws error when using an invalid language in class name', async t => {
48 |   await t.throwsAsync(async () => {
49 |     await process(t, 'invalid_language');
50 |   }, {instanceOf: Error});
51 | });
52 | 


--------------------------------------------------------------------------------
/xo.config.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |   space: true,
 3 |   rules: {
 4 |     quotes: ['error', 'single', {allowTemplateLiterals: true}],
 5 |     'promise/prefer-await-to-then': 0,
 6 |     'unicorn/prefer-node-protocol': 0,
 7 |     'unicorn/string-content': 0,
 8 |     'unicorn/prefer-module': 0,
 9 |     'arrow-body-style': 0,
10 |   },
11 | };
12 | 


--------------------------------------------------------------------------------