├── .eslintignore ├── .prettierignore ├── .gitignore ├── codecov.yml ├── babel.config.js ├── src ├── index.ts ├── worker │ ├── tsconfig.json │ └── index.ts ├── __tests__ │ ├── __snapshots__ │ │ └── BareHighlight.spec.js.snap │ └── BareHighlight.spec.js ├── Highlight.tsx └── BareHighlight.tsx ├── renovate.json ├── .eslintrc.js ├── .editorconfig ├── tsconfig.json ├── test └── setup-jest.js ├── LICENSE ├── .github └── workflows │ ├── ossar-analysis.yml │ ├── codeql-analysis.yml │ └── build.yml ├── package.json ├── README.md └── CHANGELOG.md /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | typings 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | esm 2 | flow-typed 3 | lib 4 | node_modules 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | lib 4 | node_modules 5 | typings 6 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | parsers: 3 | javascript: 4 | enable_partials: yes 5 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | presets: ['@researchgate/babel-preset', '@babel/preset-typescript'], 5 | }; 6 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Highlight from './Highlight'; 2 | import BareHighlight from './BareHighlight'; 3 | 4 | export { Highlight, BareHighlight }; 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["@researchgate:lib"], 4 | "labels": ["dependencies"] 5 | } 6 | -------------------------------------------------------------------------------- /src/worker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["webworker"] 5 | }, 6 | "include":["./index.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/BareHighlight.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`className is passed through 1`] = ` 4 |
 5 |   
 8 |     test
 9 |   
10 | 
11 | `; 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This file was created by spire-plugin-eslint for editor support 4 | const config = require('@researchgate/spire-config/eslint/react-typescript'); 5 | 6 | config.globals = config.globals || {}; 7 | config.globals.ReturnType = false; 8 | 9 | module.exports = config; 10 | -------------------------------------------------------------------------------- /src/worker/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-env worker */ 2 | import hjs from 'highlight.js'; 3 | 4 | onmessage = (event) => { 5 | const { code, languages } = event.data; 6 | let result; 7 | if (languages && languages.length === 1) { 8 | result = hjs.highlight(languages[0], code, true); 9 | } else { 10 | result = hjs.highlightAuto(code, languages); 11 | } 12 | 13 | postMessage(result); 14 | }; 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://EditorConfig.org 2 | # top-most EditorConfig file 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | insert_final_newline = false 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.{js,scss}] 14 | charset = utf-8 15 | indent_style = space 16 | insert_final_newline = true 17 | 18 | [*.js] 19 | indent_size = 4 20 | 21 | [*.scss] 22 | indent_size = 2 23 | 24 | [{*.json,.travis.yml,.*rc}] 25 | indent_style = space 26 | indent_size = 2 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "declaration": true, 5 | "outDir": "./typings", 6 | "emitDeclarationOnly": true, 7 | "strict": true, 8 | "strictNullChecks": true, 9 | "noImplicitThis": true, 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noUnusedParameters": true, 13 | "noUnusedLocals": true, 14 | "moduleResolution": "node", 15 | "esModuleInterop": true, 16 | "forceConsistentCasingInFileNames": true 17 | }, 18 | "include": ["src/*"] 19 | } 20 | -------------------------------------------------------------------------------- /test/setup-jest.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jest */ 2 | import Enzyme from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | Enzyme.configure({ adapter: new Adapter() }); 6 | 7 | // eslint-disable-next-line no-console 8 | const consoleError = console.error; 9 | 10 | function logToError(error) { 11 | throw new Error(error); 12 | } 13 | 14 | beforeEach(() => { 15 | // eslint-disable-next-line no-console 16 | console.error = logToError; 17 | }); 18 | 19 | afterEach(() => { 20 | // eslint-disable-next-line no-console 21 | console.error = consoleError; 22 | }); 23 | -------------------------------------------------------------------------------- /src/Highlight.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import hljs from 'highlight.js'; 4 | import BareHighlight from './BareHighlight'; 5 | 6 | interface Props { 7 | children: string; 8 | className?: string; 9 | languages?: Array; 10 | worker?: Worker; 11 | } 12 | 13 | const Highlight = (props: Props) => { 14 | const { children, ...rest } = props; 15 | 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | }; 22 | 23 | Highlight.defaultProps = { 24 | className: '', 25 | languages: [], 26 | worker: null, 27 | }; 28 | 29 | Highlight.propTypes = { 30 | children: PropTypes.string.isRequired, 31 | className: PropTypes.string, 32 | languages: PropTypes.arrayOf(PropTypes.string), 33 | worker: PropTypes.object, // eslint-disable-line react/forbid-prop-types 34 | }; 35 | 36 | export default Highlight; 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) ResearchGate GmbH and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/workflows/ossar-analysis.yml: -------------------------------------------------------------------------------- 1 | # This workflow integrates a collection of open source static analysis tools 2 | # with GitHub code scanning. For documentation, or to provide feedback, visit 3 | # https://github.com/github/ossar-action 4 | name: OSSAR 5 | 6 | on: 7 | push: 8 | pull_request: 9 | 10 | jobs: 11 | OSSAR-Scan: 12 | # OSSAR runs on windows-latest. 13 | # ubuntu-latest and macos-latest support coming soon 14 | runs-on: windows-latest 15 | 16 | steps: 17 | # Checkout your code repository to scan 18 | - name: Checkout repository 19 | uses: actions/checkout@v3 20 | with: 21 | # We must fetch at least the immediate parents so that if this is 22 | # a pull request then we can checkout the head. 23 | fetch-depth: 2 24 | 25 | # If this run was triggered by a pull request event, then checkout 26 | # the head of the pull request instead of the merge commit. 27 | - run: git checkout HEAD^2 28 | if: ${{ github.event_name == 'pull_request' }} 29 | 30 | # Ensure a compatible version of dotnet is installed. 31 | # The [Microsoft Security Code Analysis CLI](https://aka.ms/mscadocs) is built with dotnet v3.1.201. 32 | # A version greater than or equal to v3.1.201 of dotnet must be installed on the agent in order to run this action. 33 | # GitHub hosted runners already have a compatible version of dotnet installed and this step may be skipped. 34 | # For self-hosted runners, ensure dotnet version 3.1.201 or later is installed by including this action: 35 | # - name: Install .NET 36 | # uses: actions/setup-dotnet@v1 37 | # with: 38 | # dotnet-version: '3.1.x' 39 | 40 | # Run open source static analysis tools 41 | - name: Run OSSAR 42 | uses: github/ossar-action@v1 43 | id: ossar 44 | 45 | # Upload results to the Security tab 46 | - name: Upload OSSAR results 47 | uses: github/codeql-action/upload-sarif@v1 48 | with: 49 | sarif_file: ${{ steps.ossar.outputs.sarifFile }} 50 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '34 9 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'javascript' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v3 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | NODE_VERSION: 14 11 | 12 | jobs: 13 | tests: 14 | name: Unit tests 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ env.NODE_VERSION }} 25 | 26 | - name: Cache node_modules 27 | uses: actions/cache@v3 28 | id: cache-nodemodules 29 | with: 30 | path: node_modules 31 | key: ${{ runner.os }}-${{ env.NODE_VERSION }}-nodemodules-${{ hashFiles('**/yarn.lock') }} 32 | 33 | - name: Install dependencies 34 | if: steps.cache-nodemodules.outputs.cache-hit != 'true' 35 | run: yarn install --frozen-lockfile --non-interactive 36 | 37 | - name: Unit tests 38 | run: yarn test --coverage 39 | 40 | - name: Upload coverage to Codecov 41 | uses: codecov/codecov-action@v2 42 | with: 43 | files: ./coverage/coverage-final.json 44 | fail_ci_if_error: true 45 | 46 | lint: 47 | name: Lint 48 | runs-on: ubuntu-latest 49 | 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v3 53 | 54 | - name: Setup Node.js 55 | uses: actions/setup-node@v3 56 | with: 57 | node-version: ${{ env.NODE_VERSION }} 58 | 59 | - name: Cache node_modules 60 | uses: actions/cache@v3 61 | id: cache-nodemodules 62 | with: 63 | path: node_modules 64 | key: ${{ runner.os }}-${{ env.NODE_VERSION }}-nodemodules-${{ hashFiles('**/yarn.lock') }} 65 | 66 | - name: Install dependencies 67 | if: steps.cache-nodemodules.outputs.cache-hit != 'true' 68 | run: yarn install --frozen-lockfile --non-interactive 69 | 70 | - name: Lint 71 | run: yarn lint 72 | 73 | release: 74 | needs: [tests, lint] 75 | if: github.ref == 'refs/heads/main' 76 | name: Release 77 | runs-on: ubuntu-latest 78 | 79 | steps: 80 | - name: Checkout 81 | uses: actions/checkout@v3 82 | with: 83 | fetch-depth: 0 84 | 85 | - name: Setup Node.js 86 | uses: actions/setup-node@v3 87 | with: 88 | node-version: ${{ env.NODE_VERSION }} 89 | 90 | - name: Cache node_modules 91 | uses: actions/cache@v3 92 | id: cache-nodemodules 93 | with: 94 | path: node_modules 95 | key: ${{ runner.os }}-${{ env.NODE_VERSION }}-nodemodules-${{ hashFiles('**/yarn.lock') }} 96 | 97 | - name: Install dependencies 98 | if: steps.cache-nodemodules.outputs.cache-hit != 'true' 99 | run: yarn install --frozen-lockfile --non-interactive 100 | 101 | - name: Release 102 | run: yarn release 103 | env: 104 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 105 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-fast-highlight", 3 | "version": "4.1.0", 4 | "description": "A fast react component wrapper for highlight.js", 5 | "main": "lib/js/index.js", 6 | "module": "lib/esm/index.js", 7 | "types": "typings/index.d.ts", 8 | "scripts": { 9 | "clean": "rimraf lib", 10 | "build": "run-p build:*", 11 | "build:commonjs": "cross-env BABEL_ENV=production BABEL_OUTPUT=cjs babel src --out-dir lib/js --extensions .ts,.tsx --ignore '**/__tests__'", 12 | "build:esm": "cross-env BABEL_ENV=production BABEL_OUTPUT=esm babel src --out-dir lib/esm --extensions .ts,.tsx --ignore '**/__tests__'", 13 | "build:types": "tsc", 14 | "format": "spire format", 15 | "lint": "spire lint", 16 | "prepare": "run-s clean build", 17 | "release": "spire release --branches main", 18 | "test": "spire test" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/researchgate/react-fast-highlight.git" 23 | }, 24 | "files": [ 25 | "lib", 26 | "typings" 27 | ], 28 | "keywords": [ 29 | "react", 30 | "component", 31 | "highlight", 32 | "syntax", 33 | "highlightjs" 34 | ], 35 | "author": "Daniel Tschinder ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/researchgate/react-fast-highlight/issues" 39 | }, 40 | "homepage": "https://github.com/researchgate/react-fast-highlight#readme", 41 | "peerDependencies": { 42 | "highlight.js": "^10.0.0 || ^11.0.0", 43 | "react": "^16.0.0" 44 | }, 45 | "devDependencies": { 46 | "@babel/cli": "7.28.3", 47 | "@babel/core": "7.28.5", 48 | "@babel/preset-typescript": "7.28.5", 49 | "@researchgate/babel-preset": "2.0.14", 50 | "@researchgate/spire-config": "5.0.7", 51 | "@types/classnames": "2.3.4", 52 | "@types/prop-types": "15.7.15", 53 | "@types/react": "16.14.68", 54 | "cross-env": "7.0.3", 55 | "enzyme": "3.11.0", 56 | "enzyme-adapter-react-16": "1.15.8", 57 | "enzyme-to-json": "3.6.2", 58 | "highlight.js": "11.11.1", 59 | "jest-snapshot": "29.7.0", 60 | "npm-run-all": "4.1.5", 61 | "react": "16.14.0", 62 | "react-dom": "16.14.0", 63 | "rimraf": "3.0.2", 64 | "spire": "3.2.3", 65 | "spire-plugin-semantic-release": "3.2.3", 66 | "typescript": "4.9.5" 67 | }, 68 | "dependencies": { 69 | "@types/highlight.js": "^9.12.3", 70 | "clsx": "^1.1.1", 71 | "prop-types": "^15.5.6" 72 | }, 73 | "jest": { 74 | "collectCoverageFrom": [ 75 | "src/**/*.ts", 76 | "src/**/*.tsx", 77 | "!src/**/__tests__/**/*" 78 | ], 79 | "setupFilesAfterEnv": [ 80 | "/test/setup-jest.js" 81 | ], 82 | "snapshotSerializers": [ 83 | "enzyme-to-json/serializer" 84 | ] 85 | }, 86 | "spire": { 87 | "extends": [ 88 | [ 89 | "@researchgate/spire-config", 90 | { 91 | "eslint": "react-typescript" 92 | } 93 | ] 94 | ], 95 | "plugins": [ 96 | "spire-plugin-semantic-release" 97 | ] 98 | }, 99 | "prettier": "@researchgate/prettier-config" 100 | } 101 | -------------------------------------------------------------------------------- /src/__tests__/BareHighlight.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { mount, shallow } from 'enzyme'; 3 | import highlightjs from 'highlight.js'; 4 | import BareHighlight from '../BareHighlight'; 5 | 6 | test('no language - calls correct highlightCall', (done) => { 7 | const hljs = { 8 | highlightAuto: jest.fn(() => ({ value: 'othertest', language: 'xml' })), 9 | }; 10 | 11 | const wrapper = mount( 12 | test 13 | ); 14 | 15 | setTimeout(() => { 16 | expect(wrapper.state('language')).toBe('xml'); 17 | expect(wrapper.state('highlightedCode')).toBe('othertest'); 18 | expect(hljs.highlightAuto).toHaveBeenCalledWith('test', []); 19 | expect(hljs.highlightAuto).toHaveBeenCalledTimes(1); 20 | 21 | done(); 22 | }, 1); 23 | }); 24 | 25 | test('can correctly rerender code', (done) => { 26 | let value = 'initalresult'; 27 | const hljs = { 28 | highlightAuto: jest.fn(() => ({ value, language: 'xml' })), 29 | }; 30 | 31 | const wrapper = mount( 32 | test 33 | ); 34 | 35 | setTimeout(() => { 36 | value = 'changed'; 37 | wrapper.setProps({ children: 'newtest' }); 38 | setTimeout(() => { 39 | expect(wrapper.state('language')).toBe('xml'); 40 | expect(wrapper.state('highlightedCode')).toBe('changed'); 41 | expect(hljs.highlightAuto).toHaveBeenCalledWith('newtest', []); 42 | expect(hljs.highlightAuto).toHaveBeenCalledTimes(2); 43 | 44 | done(); 45 | }, 1); 46 | }, 1); 47 | }); 48 | 49 | test('one language - calls correct highlightCall', (done) => { 50 | const hljs = { 51 | highlight: jest.fn(() => ({ value: 'othertest', language: 'js' })), 52 | }; 53 | 54 | const wrapper = mount( 55 | 56 | test 57 | 58 | ); 59 | 60 | setTimeout(() => { 61 | expect(wrapper.state('language')).toBe('js'); 62 | expect(wrapper.state('highlightedCode')).toBe('othertest'); 63 | expect(hljs.highlight).toHaveBeenCalledWith("test", { "language": "js" }); 64 | expect(hljs.highlight).toHaveBeenCalledTimes(1); 65 | 66 | done(); 67 | }, 1); 68 | }); 69 | 70 | test('multiple languages - calls correct highlightCall', (done) => { 71 | const hljs = { 72 | highlightAuto: jest.fn(() => ({ value: 'othertest', language: 'js' })), 73 | }; 74 | 75 | const wrapper = mount( 76 | 77 | test 78 | 79 | ); 80 | 81 | setTimeout(() => { 82 | expect(wrapper.state('language')).toBe('js'); 83 | expect(wrapper.state('highlightedCode')).toBe('othertest'); 84 | expect(hljs.highlightAuto).toHaveBeenCalledWith('test', ['js', 'html']); 85 | expect(hljs.highlightAuto).toHaveBeenCalledTimes(1); 86 | 87 | done(); 88 | }, 1); 89 | }); 90 | 91 | test('className is passed through', () => { 92 | const wrapper = shallow( 93 | 94 | test 95 | 96 | ); 97 | expect(wrapper).toMatchSnapshot(); 98 | }); 99 | -------------------------------------------------------------------------------- /src/BareHighlight.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'clsx'; 4 | import type hljs from 'highlight.js'; 5 | 6 | interface Props { 7 | children: string; 8 | className?: string; 9 | highlightjs: typeof hljs; 10 | languages?: Array; 11 | worker?: Worker; 12 | } 13 | 14 | interface State { 15 | highlightedCode?: string; 16 | language?: string; 17 | } 18 | 19 | export default class BareHighlight extends React.PureComponent { 20 | static defaultProps = { 21 | className: '', 22 | languages: [], 23 | worker: null, 24 | }; 25 | 26 | static propTypes = { 27 | children: PropTypes.string.isRequired, 28 | className: PropTypes.string, 29 | highlightjs: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types 30 | languages: PropTypes.arrayOf(PropTypes.string), 31 | worker: PropTypes.object, // eslint-disable-line react/forbid-prop-types 32 | }; 33 | 34 | state: State = {}; 35 | 36 | componentDidMount() { 37 | this.highlightCode(); 38 | } 39 | 40 | componentDidUpdate(prevProps: Props) { 41 | // If the text changed make sure to reset the state 42 | // This way we ensure that the new text is immediately displayed. 43 | if (prevProps.children !== this.props.children) { 44 | this.setState({ highlightedCode: undefined, language: undefined }); 45 | return; 46 | } 47 | 48 | // Do not call highlight.js if we already have highlighted code 49 | // If the children changed highlightedCode will be null 50 | if (this.state.highlightedCode) return; 51 | 52 | this.highlightCode(); 53 | } 54 | 55 | getInitialCode() { 56 | const type = typeof this.props.children; 57 | if (type !== 'string') { 58 | throw new Error( 59 | `Children of must be a string. '${type}' supplied` 60 | ); 61 | } 62 | 63 | return this.props.children; 64 | } 65 | 66 | getHighlightPromise(): Promise> { 67 | const { highlightjs, languages } = this.props; 68 | return new Promise((resolve) => { 69 | if (languages && languages.length === 1) { 70 | resolve(highlightjs.highlight(this.getInitialCode(), { 71 | language: languages[0], 72 | })); 73 | } else { 74 | resolve(highlightjs.highlightAuto(this.getInitialCode(), languages)); 75 | } 76 | }); 77 | } 78 | 79 | highlightCode() { 80 | const { languages, worker } = this.props; 81 | 82 | if (worker) { 83 | worker.onmessage = (event) => 84 | this.setState({ 85 | highlightedCode: event.data.value, 86 | language: event.data.language, 87 | }); 88 | worker.postMessage({ code: this.getInitialCode(), languages }); 89 | } else { 90 | this.getHighlightPromise().then((result) => 91 | this.setState({ 92 | highlightedCode: result.value, 93 | language: result.language, 94 | }) 95 | ); 96 | } 97 | } 98 | 99 | render() { 100 | const code = this.state.highlightedCode; 101 | const classes = cx(this.props.className, 'hljs', this.state.language); 102 | 103 | if (code) { 104 | return ( 105 |
106 |           
110 |         
111 | ); 112 | } 113 | 114 | return ( 115 |
116 |         {this.getInitialCode()}
117 |       
118 | ); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-fast-highlight 2 | 3 | ###### A fast react component wrapper for highlight.js 4 | 5 | [![codecov](https://codecov.io/gh/researchgate/react-fast-highlight/branch/master/graph/badge.svg)](https://codecov.io/gh/researchgate/react-fast-highlight) 6 | 7 | ## Requirements 8 | 9 | - Requires `react` version 16 10 | - Requires `highlight.js` version 9 or 10 11 | 12 | ## Install 13 | 14 | `npm install --save react-fast-highlight react highlight.js` 15 | 16 | or 17 | 18 | `yarn add react-fast-highlight react highlight.js` 19 | 20 | ## Usage 21 | 22 | ```js 23 | import React, { Component } from 'react'; 24 | import { Highlight } from 'react-fast-highlight'; 25 | 26 | class App extends Component { 27 | 28 | render() { 29 | const code = 'let t = 0.5 * 5 * 4;'; 30 | 31 | return ( 32 | {/* 33 | `languages` is an optional property to set the languages that highlight.js should pick from. 34 | By default it is empty, which means highlight.js will pick from all available languages. 35 | If only one language is provided, this language will be used without doing checks beforehand. 36 | (Be aware that automatic language detection is not as fast as when specifing a language.) 37 | 38 | `worker` is used to take advantage of the possibility to do the highlighting work in a 39 | web-worker. As different module-bundlers use different ways to load web-workers, it is up 40 | to you to load the webworker and supply it to the highlight component. (see example) 41 | Currently every instance of the highlight component needs its own web-worker. 42 | 43 | `className` sets additional class names on the generated html markup. 44 | */} 45 | 49 | {code} 50 | 51 | ); 52 | } 53 | ``` 54 | 55 | ### Advanced Usage 56 | 57 | #### Custom highlight.js distribution 58 | 59 | In cases where you bundle this component with a module bundler such as webpack, 60 | rollup or browserify and you know upfront which languages you want to support 61 | you can supply a custom distribution of `highlight.js`. This ensures you are not 62 | bundling all available languages of `highlight.js` which reduces the size of 63 | your bundle. 64 | 65 | A custom distribution might look like this 66 | 67 | ```js 68 | import hljs from 'highlight.js/lib/highlight'; 69 | 70 | // Lets only register javascript, scss, html/xml 71 | hljs.registerLanguage('scss', require('highlight.js/lib/languages/scss')); 72 | hljs.registerLanguage( 73 | 'javascript', 74 | require('highlight.js/lib/languages/javascript') 75 | ); 76 | hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml')); 77 | 78 | export default hljs; 79 | ``` 80 | 81 | To actually use a custom distribution you need to use the `BareHighlight` 82 | component. With it you can build your wrapper for highlighting code. 83 | 84 | ```js 85 | import React, { Component } from 'react'; 86 | import BareHighlight from 'react-fast-highlight/lib/js/BareHighlight'; 87 | import hljs from './customhljs'; 88 | 89 | class CustomHighlight extends Component { 90 | render() { 91 | const { children, ...props } = this.props; 92 | return ( 93 | 94 | {children} 95 | 96 | ); 97 | } 98 | ``` 99 | 100 | Now you can use this wrapper the same way as the default `Highlight` component 101 | with only support for certain languages included. 102 | 103 | > In case you also want to use a webworker you should not use the supplied 104 | > worker, as it includes all languages. Instead you will need to copy the worker 105 | > from this repo and adjust the `highlight.js` import. 106 | 107 | #### Webworker 108 | 109 | It wasn't tested with browserify and rollup but it should work. If you managed 110 | to get it working please open a PR with the necessary changes and the 111 | documentation. 112 | 113 | ##### Webpack 114 | 115 | To make web-workers working with webpack you additionally need to install 116 | `worker-loader`. 117 | 118 | `npm install --save worker-loader react-fast-highlight` 119 | 120 | ```js 121 | import React from 'react'; 122 | import { Highlight } from 'react-fast-highlight'; 123 | import Worker from 'worker!react-fast-highlight/lib/js/worker'; 124 | 125 | class App extends React.Component { 126 | 127 | render() { 128 | const code = 'let t = 0.5 * 5 * 4;'; 129 | 130 | return ( 131 | 135 | {code} 136 | 137 | ); 138 | } 139 | ``` 140 | 141 | ## License 142 | 143 | react-fast-highlight is licensed under the MIT license. 144 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [4.1.0](https://github.com/researchgate/react-fast-highlight/compare/v4.0.0...v4.1.0) (2021-03-07) 2 | 3 | 4 | ### Features 5 | 6 | * Replace classnames dependency with clsx ([7f6222f](https://github.com/researchgate/react-fast-highlight/commit/7f6222fef56cd1c4c5cf13f70196ae3e07a7fc8f)) 7 | 8 | # [4.0.0](https://github.com/researchgate/react-fast-highlight/compare/v3.0.0...v4.0.0) (2020-04-30) 9 | 10 | ### Bug Fixes 11 | 12 | - **deps:** support dependency highlight.js v10 in addition to v9 13 | ([5fbbf75](https://github.com/researchgate/react-fast-highlight/commit/5fbbf7516bd95a53acd7cba994cec6f0e8c0326f)) 14 | 15 | ### BREAKING CHANGES 16 | 17 | - **deps:** highlight.js is now a peer dependency (v9 or v10). Please install it 18 | in your project. 19 | 20 | # [3.0.0](https://github.com/researchgate/react-fast-highlight/compare/v2.2.2...v3.0.0) (2019-11-30) 21 | 22 | ### Features 23 | 24 | - Migrate to typescript and spire 25 | ([e2ae43f](https://github.com/researchgate/react-fast-highlight/commit/e2ae43f6a07319c40adb95554d73c686deb4ec36)) 26 | 27 | ### BREAKING CHANGES 28 | 29 | - No default export anymore. Use named exports. 30 | 31 | # Change Log 32 | 33 | All notable changes to this project will be documented in this file. See 34 | [standard-version](https://github.com/conventional-changelog/standard-version) 35 | for commit guidelines. 36 | 37 | 38 | 39 | ## [2.2.2](https://github.com/researchgate/react-fast-highlight/compare/v2.2.1...v2.2.2) (2018-06-09) 40 | 41 | ### Bug Fixes 42 | 43 | - **babel:** Do not include tests in published package 44 | ([03d0fc6](https://github.com/researchgate/react-fast-highlight/commit/03d0fc6)) 45 | 46 | 47 | 48 | ## [2.2.1](https://github.com/researchgate/react-fast-highlight/compare/v2.2.0...v2.2.1) (2018-06-09) 49 | 50 | ### Bug Fixes 51 | 52 | - **react:** Remove deprecation warning with react 16.4 53 | ([3a9b707](https://github.com/researchgate/react-fast-highlight/commit/3a9b707)) 54 | 55 | 56 | 57 | # [2.2.0](https://github.com/researchgate/react-fast-highlight/compare/v2.1.2...v2.2.0) (2017-10-11) 58 | 59 | ### Features 60 | 61 | - **api:** Export Highlight and BareHighlight also as named exports 62 | ([613f98c](https://github.com/researchgate/react-fast-highlight/commit/613f98c)) 63 | 64 | 65 | 66 | ## [2.1.2](https://github.com/researchgate/react-fast-highlight/compare/v2.1.1...v2.1.2) (2017-10-11) 67 | 68 | ### Bug Fixes 69 | 70 | - **dependencies:** Allow react 16 71 | ([29e4467](https://github.com/researchgate/react-fast-highlight/commit/29e4467)) 72 | 73 | 74 | 75 | ## [2.1.1](https://github.com/researchgate/react-fast-highlight/compare/v2.1.0...v2.1.1) (2017-04-11) 76 | 77 | ### Bug Fixes 78 | 79 | - Fix deprecation warnings with react 15.5 80 | 81 | 82 | 83 | # [2.1.0](https://github.com/researchgate/react-fast-highlight/compare/v2.0.0...v2.1.0) (2016-12-06) 84 | 85 | ### Features 86 | 87 | - Support custom highlight.js distributions 88 | 89 | 90 | 91 | # [2.0.0](https://github.com/researchgate/react-fast-highlight/compare/v1.1.2...v2.0.0) (2016-08-03) 92 | 93 | ### Features 94 | 95 | - Use PureComponent of react 15.3 96 | 97 | ### BREAKING CHANGES 98 | 99 | - Requires now react version 15.3 100 | 101 | 102 | 103 | ## [1.1.2](https://github.com/researchgate/react-fast-highlight/compare/v1.1.1...v1.1.2) (2016-08-03) 104 | 105 | ### Bug Fixes 106 | 107 | - Correctly set upper limit for react version (< 15.3) 108 | 109 | 110 | 111 | ## [1.1.1](https://github.com/researchgate/react-fast-highlight/compare/v1.1.0...v1.1.1) (2016-05-23) 112 | 113 | ### Bug Fixes 114 | 115 | - Correctly export the component 116 | 117 | 118 | 119 | # [1.1.1](https://github.com/researchgate/react-fast-highlight/compare/v1.0.2...v1.1.0) (2016-05-20) 120 | 121 | ### Features 122 | 123 | - Use upstream highlight.js 124 | - Fixes errors with removed/renamed languages in newer versions. 125 | - Stays always updated for new languages 126 | - Fixes webpack bug with autoit language. 127 | 128 | 129 | 130 | ## [1.0.2](https://github.com/researchgate/react-fast-highlight/compare/v1.0.1...v1.0.2) (2016-04-28) 131 | 132 | ### Bug Fixes 133 | 134 | - Allow react 15 135 | 136 | 137 | 138 | ## [1.0.1](https://github.com/researchgate/react-fast-highlight/compare/v1.0.0...v1.0.1) (2016-03-14) 139 | 140 | ### Bug Fixes 141 | 142 | - Lower dependency version for react-addons-shallow-compare 143 | 144 | 145 | 146 | # [1.0.0](https://github.com/researchgate/react-fast-highlight/compare/v0.3.0...v1.0.0) (2016-02-21) 147 | 148 | ### Bug Fixes 149 | 150 | - Use react-addons-shallow-compare instead of the file in react itself 151 | 152 | 153 | 154 | # 0.3.0 (2016-01-20) 155 | 156 | ### Bug Fixes 157 | 158 | - Initial version 159 | --------------------------------------------------------------------------------