Skip to content

Commit 36e696a

Browse files
committed
Support custom hljs distributions
1 parent 6b4a521 commit 36e696a

File tree

7 files changed

+201
-107
lines changed

7 files changed

+201
-107
lines changed

README.md

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,23 @@
77

88
Version 1.x works with React 0.14 and <=15.2
99

10-
Version 2.x works with React >=15.3
11-
12-
## Usage
10+
Version >=2.0 works with React >=15.3
11+
12+
## Install
1313

1414
`npm install --save react-fast-highlight`
1515

16+
or
17+
18+
`yarn add react-fast-highlight`
19+
20+
## Usage
21+
1622
```js
17-
import React from 'react';
18-
import Highlight from 'react-fast-highlight';
23+
import React, { Component } from 'react';
24+
import { Highlight } from 'react-fast-highlight';
1925

20-
class App extends React.Component {
26+
class App extends Component {
2127

2228
render() {
2329
const code = 'let t = 0.5 * 5 * 4;';
@@ -46,17 +52,64 @@ class App extends React.Component {
4652
}
4753
```
4854
49-
### Webworker
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, rollup or browserify and you know upfront
60+
which languages you want to support you can supply a custom distribution of `highlight.js`. This ensures
61+
you are not bundling all available languages of `highlight.js` which reduces the size of your bundle.
62+
63+
A custom distribution might look like this
64+
65+
```js
66+
import hljs from 'highlight.js/lib/highlight';
67+
68+
// Lets only register javascript, scss, html/xml
69+
hljs.registerLanguage('scss', require('highlight.js/languages/scss'));
70+
hljs.registerLanguage('javascript', require('highlight.js/languages/javascript'));
71+
hljs.registerLanguage('xml', require('highlight.js/languages/xml'));
72+
73+
export default hljs;
74+
```
75+
76+
To actually use a custom distribution you need to use the `BareHighlight` component. With it
77+
you can build your wrapper for highlighting code.
78+
79+
```js
80+
import React, { Component } from 'react';
81+
import { BareHighlight } from 'react-fast-highlight';
82+
import hljs from './customhljs';
83+
84+
class CustomHighlight extends Component {
85+
render() {
86+
const { children, ...props } = this.props;
87+
return (
88+
<BareHighlight {...props} highlightjs={hljs}>
89+
{children}
90+
</BareHighlight>
91+
);
92+
}
93+
```
94+
95+
Now you can use this wrapper the same way as the default `Highlight` component with only support for
96+
certain languages.
97+
98+
#### Webworker
99+
100+
It wasn't tested with browserify and rollup but it should work.
101+
If you managed to get it working please open a PR with the necessary
102+
changes and the documentation.
50103
51-
#### Webpack
104+
##### Webpack
52105
53106
To make web-workers working with webpack you additionally need to install `worker-loader`.
54107
55108
`npm install --save worker-loader react-fast-highlight`
56109
57110
```js
58111
import React from 'react';
59-
import Highlight from 'react-fast-highlight';
112+
import { Highlight } from 'react-fast-highlight';
60113
import Worker from 'worker!react-fast-highlight/lib/worker';
61114

62115
class App extends React.Component {

src/components/BareHighlight.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/* @flow */
2+
import React, { PureComponent, PropTypes } from 'react';
3+
import cx from 'classnames';
4+
5+
type Props = {
6+
children: string,
7+
className?: string,
8+
highlightjs: Object,
9+
languages?: Array<string>,
10+
worker?: Object,
11+
};
12+
13+
type State = {
14+
highlightedCode: ?string,
15+
language: ?string,
16+
};
17+
18+
export default class BareHighlight extends PureComponent {
19+
20+
state: State = {
21+
highlightedCode: null,
22+
language: null,
23+
};
24+
25+
props: Props;
26+
27+
componentDidMount() {
28+
this._highlightCode();
29+
}
30+
31+
componentWillReceiveProps(nextProps: Props) {
32+
// If the text changed make sure to reset the state
33+
// This way we ensure that the new text is immediately displayed.
34+
if (nextProps.children !== this.props.children) {
35+
this.setState({ highlightedCode: null, language: null });
36+
}
37+
}
38+
39+
componentDidUpdate() {
40+
this._highlightCode();
41+
}
42+
43+
getInitialCode() {
44+
const type = typeof this.props.children;
45+
if (type !== 'string') {
46+
throw new Error(`Children of <Highlight> must be a string. '${type}' supplied`);
47+
}
48+
49+
return this.props.children;
50+
}
51+
52+
getHighlightPromise() {
53+
const { highlightjs, languages } = this.props;
54+
55+
return new Promise((resolve: (x: *) => void) => {
56+
if (languages && languages.length === 1) {
57+
resolve(highlightjs.highlight(languages[0], this.getInitialCode()));
58+
} else {
59+
resolve(highlightjs.highlightAuto(this.getInitialCode(), languages));
60+
}
61+
});
62+
}
63+
64+
_highlightCode() {
65+
const { highlightjs, languages, worker } = this.props;
66+
67+
if (worker) {
68+
worker.onmessage = event => this.setState({
69+
highlightedCode: event.data.value,
70+
language: event.data.language,
71+
});
72+
worker.postMessage({ code: this.getInitialCode(), languages, highlightjs });
73+
} else {
74+
this.getHighlightPromise()
75+
.then(
76+
result => this.setState({ highlightedCode: result.value, language: result.language }),
77+
);
78+
}
79+
}
80+
81+
render() {
82+
const code = this.state.highlightedCode;
83+
const classes = cx(this.props.className, 'hljs', this.state.language);
84+
85+
if (code) {
86+
return (
87+
<pre>
88+
<code className={classes} dangerouslySetInnerHTML={{ __html: code }} />
89+
</pre>
90+
);
91+
}
92+
93+
return <pre><code className={classes}>{this.getInitialCode()}</code></pre>;
94+
}
95+
}
96+
97+
BareHighlight.propTypes = {
98+
children: PropTypes.string.isRequired,
99+
className: PropTypes.string,
100+
highlightjs: PropTypes.object, // eslint-disable-line react/forbid-prop-types
101+
languages: PropTypes.arrayOf(PropTypes.string),
102+
worker: PropTypes.object, // eslint-disable-line react/forbid-prop-types
103+
};

src/components/Highlight.js

Lines changed: 9 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* @flow */
2-
import React, { PureComponent, PropTypes } from 'react';
2+
import React, { PropTypes } from 'react';
33
import hljs from 'highlight.js';
4-
import cx from 'classnames';
4+
import BareHighlight from './BareHighlight';
55

66
type Props = {
77
children: string,
@@ -10,88 +10,18 @@ type Props = {
1010
worker?: Object,
1111
};
1212

13-
type State = {
14-
highlightedCode: ?string,
15-
language: ?string,
16-
};
17-
18-
export default class Highlight extends PureComponent {
19-
20-
state: State = {
21-
highlightedCode: null,
22-
language: null,
23-
};
24-
25-
props: Props;
26-
27-
componentDidMount() {
28-
this._highlightCode();
29-
}
30-
31-
componentDidUpdate() {
32-
this._highlightCode();
33-
}
34-
35-
getInitialCode() {
36-
const type = typeof this.props.children;
37-
if (type !== 'string') {
38-
throw new Error(`Children of <Highlight> must be a string. '${type}' supplied`);
39-
}
40-
41-
return this.props.children;
42-
}
43-
44-
getHighlightCallback() {
45-
let callback;
13+
const Highlight = (props: Props) => {
14+
const { children, ...rest } = props;
4615

47-
if (this.props.languages && this.props.languages.length === 1) {
48-
const language:string = this.props.languages[0];
49-
callback = (resolve: (x: *) => void) => // eslint-disable-line arrow-parens
50-
resolve(hljs.highlight(language, this.getInitialCode()));
51-
} else {
52-
callback = (resolve: (x: *) => void) => // eslint-disable-line arrow-parens
53-
resolve(hljs.highlightAuto(this.getInitialCode(), this.props.languages));
54-
}
55-
56-
return callback;
57-
}
58-
59-
_highlightCode() {
60-
const worker = this.props.worker;
61-
if (worker) {
62-
worker.onmessage = event => this.setState({
63-
highlightedCode: event.data.value,
64-
language: event.data.language,
65-
});
66-
worker.postMessage({ code: this.getInitialCode(), languages: this.props.languages });
67-
} else {
68-
const promise = new Promise(this.getHighlightCallback());
69-
70-
promise.then(
71-
result => this.setState({ highlightedCode: result.value, language: result.language }),
72-
);
73-
}
74-
}
75-
76-
render() {
77-
const code = this.state.highlightedCode;
78-
const classes = cx(this.props.className, 'hljs', this.state.language);
79-
80-
if (code) {
81-
return (
82-
<pre>
83-
<code className={classes} dangerouslySetInnerHTML={{ __html: code }} />
84-
</pre>
85-
);
86-
}
87-
88-
return <pre><code className={classes}>{this.getInitialCode()}</code></pre>;
89-
}
90-
}
16+
// $FlowIssue does not support children
17+
return <BareHighlight {...rest} highlightjs={hljs}>{children}</BareHighlight>;
18+
};
9119

9220
Highlight.propTypes = {
9321
children: PropTypes.string.isRequired,
9422
className: PropTypes.string,
9523
languages: PropTypes.arrayOf(PropTypes.string),
9624
worker: PropTypes.object, // eslint-disable-line react/forbid-prop-types
9725
};
26+
27+
export default Highlight;

src/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
/* @flow */
2-
export { default } from './components/Highlight';
2+
import BareHighlight from './components/BareHighlight';
3+
import Highlight from './components/Highlight';
4+
5+
export { BareHighlight, Highlight };

src/worker.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
/* global postMessage, onmessage:true */
1+
/* eslint-env worker */
22
/* @flow */
3-
import hjs from 'highlight.js';
43

54
declare function postMessage(result: Object): void;
65

76
onmessage = (event) => {
8-
const { code, languages } = event.data;
7+
const { code, languages, highlightjs } = event.data;
98
let result;
109
if (languages && languages.length === 1) {
11-
result = hjs.highlight(languages[0], code, true);
10+
result = highlightjs.highlight(languages[0], code, true);
1211
} else {
13-
result = hjs.highlightAuto(code, languages);
12+
result = highlightjs.highlightAuto(code, languages);
1413
}
1514

1615
postMessage(result);

test/components/Highlight.js renamed to test/components/BareHighlight.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ import React from 'react';
22
import td from 'testdouble';
33
import test from 'ava';
44
import { mount, shallow } from 'enzyme';
5+
import BareHighlight from '../../src/components/BareHighlight';
56

6-
const hjs = td.replace('highlight.js');
7-
const Highlight = require('../../src/components/Highlight').default;
8-
9-
test.cb('no language - calls correct highlightCall', t => {
7+
test.cb('no language - calls correct highlightCall', (t) => {
8+
const hjs = td.replace('highlight.js');
109
td
1110
.when(hjs.highlightAuto('test', undefined))
1211
.thenReturn({ value: 'othertest', language: 'xml' });
13-
const wrapper = mount(<Highlight>test</Highlight>);
12+
const wrapper = mount(<BareHighlight highlightjs={hjs}>test</BareHighlight>);
1413

1514
setTimeout(() => {
1615
t.is(wrapper.state('language'), 'xml');
@@ -19,11 +18,12 @@ test.cb('no language - calls correct highlightCall', t => {
1918
}, 1);
2019
});
2120

22-
test.cb('one language - calls correct highlightCall', t => {
21+
test.cb('one language - calls correct highlightCall', (t) => {
22+
const hjs = td.replace('highlight.js');
2323
td
2424
.when(hjs.highlight('js', 'test'))
2525
.thenReturn({ value: 'othertest', language: 'js' });
26-
const wrapper = mount(<Highlight languages={['js']}>test</Highlight>);
26+
const wrapper = mount(<BareHighlight highlightjs={hjs} languages={['js']}>test</BareHighlight>);
2727

2828
setTimeout(() => {
2929
t.is(wrapper.state('language'), 'js');
@@ -32,11 +32,12 @@ test.cb('one language - calls correct highlightCall', t => {
3232
}, 1);
3333
});
3434

35-
test.cb('multiple languages - calls correct highlightCall', t => {
35+
test.cb('multiple languages - calls correct highlightCall', (t) => {
36+
const hjs = td.replace('highlight.js');
3637
td
3738
.when(hjs.highlightAuto('test', ['js', 'html']))
3839
.thenReturn({ value: 'othertest', language: 'js' });
39-
const wrapper = mount(<Highlight languages={['js', 'html']}>test</Highlight>);
40+
const wrapper = mount(<BareHighlight highlightjs={hjs} languages={['js', 'html']}>test</BareHighlight>);
4041

4142
setTimeout(() => {
4243
t.is(wrapper.state('language'), 'js');
@@ -45,8 +46,9 @@ test.cb('multiple languages - calls correct highlightCall', t => {
4546
}, 1);
4647
});
4748

48-
test('className is passed through', t => {
49-
const wrapper = shallow(<Highlight className="foobar">test</Highlight>);
49+
test('className is passed through', (t) => {
50+
const hjs = require('highlight.js');
51+
const wrapper = shallow(<BareHighlight highlightjs={hjs} className="foobar">test</BareHighlight>);
5052

5153
t.true(wrapper.childAt(0).hasClass('foobar'));
5254
});

0 commit comments

Comments
 (0)