Skip to content

Commit 1898b25

Browse files
authored
Provide smaller browser version of entities (#359)
Browser version leverages textarea to decode html entities. The trick is took here #29 The size of browser bundle is down from 75906 to 47170 parsed and from 25446 to 13775 gzipped. Significant for browser libs right? Browser bundle is distributed with package.json field. Modern bundlers are able to consume it. ``` "browser": { "./dist/cjs/index.js": "./dist/cjs/index.browser.js", "./dist/esm/index.js": "./dist/esm/index.browser.js" }, ``` Test is currently should be tested manually by test-browser script.
1 parent cda227f commit 1898b25

File tree

9 files changed

+446
-22
lines changed

9 files changed

+446
-22
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ temp
1919
tmp
2020
TODO.md
2121
dist
22+
/index.html

lib/common/entities.browser.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
var textarea;
2+
3+
export function decodeEntity(name) {
4+
textarea = textarea || document.createElement('textarea');
5+
textarea.innerHTML = '&' + name;
6+
return textarea.value;
7+
}

lib/common/entities.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//
55

66
/*eslint quotes:0*/
7-
export default {
7+
var entities = {
88
"Aacute":"\u00C1",
99
"aacute":"\u00E1",
1010
"Abreve":"\u0102",
@@ -2131,3 +2131,19 @@ export default {
21312131
"zwj":"\u200D",
21322132
"zwnj":"\u200C"
21332133
};
2134+
2135+
var hasOwn = Object.prototype.hasOwnProperty;
2136+
2137+
function has(object, key) {
2138+
return object
2139+
? hasOwn.call(object, key)
2140+
: false;
2141+
}
2142+
2143+
export function decodeEntity(name) {
2144+
if (has(entities, name)) {
2145+
return entities[name]
2146+
} else {
2147+
return name;
2148+
}
2149+
}

lib/common/utils.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import entities from './entities';
1+
import { decodeEntity } from './entities';
22

33
/**
44
* Utility functions
@@ -85,9 +85,10 @@ var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i;
8585

8686
function replaceEntityPattern(match, name) {
8787
var code = 0;
88+
var decoded = decodeEntity(name);
8889

89-
if (has(entities, name)) {
90-
return entities[name];
90+
if (name !== decoded) {
91+
return decoded;
9192
} else if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) {
9293
code = name[1].toLowerCase() === 'x' ?
9394
parseInt(name.slice(2), 16)

lib/rules_inline/entity.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Process html entity - {, ¯, ", ...
22

3-
import entities from '../common/entities';
4-
import { has, isValidEntityCode, fromCodePoint } from '../common/utils';
3+
import { decodeEntity } from '../common/entities';
4+
import { isValidEntityCode, fromCodePoint } from '../common/utils';
55

66

77
var DIGITAL_RE = /^&#((?:x[a-f0-9]{1,8}|[0-9]{1,8}));/i;
@@ -29,8 +29,9 @@ export default function entity(state, silent) {
2929
} else {
3030
match = state.src.slice(pos).match(NAMED_RE);
3131
if (match) {
32-
if (has(entities, match[1])) {
33-
if (!silent) { state.pending += entities[match[1]]; }
32+
var decoded = decodeEntity(match[1]);
33+
if (match[1] !== decoded) {
34+
if (!silent) { state.pending += decoded; }
3435
state.pos += match[0].length;
3536
return true;
3637
}

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,16 @@
5252
"bin": "./bin/remarkable.js",
5353
"main": "./dist/cjs/index.js",
5454
"module": "./dist/esm/index.js",
55+
"browser": {
56+
"./dist/cjs/index.js": "./dist/cjs/index.browser.js",
57+
"./dist/esm/index.js": "./dist/esm/index.browser.js"
58+
},
5559
"engines": {
5660
"node": ">= 0.10.0"
5761
},
5862
"scripts": {
5963
"build": "make rollup",
64+
"test-browser": "yarn build && node -r esm ./test/test-browser.js && serve .",
6065
"test": "make test",
6166
"test-ci": " nyc mocha -r esm -R spec --bail",
6267
"coverage": "yarn add coveralls@2 && nyc report --reporter=text-lcov | coveralls",
@@ -84,7 +89,8 @@
8489
"rollup": "^1.16.7",
8590
"rollup-plugin-json": "^4.0.0",
8691
"rollup-plugin-node-resolve": "^5.2.0",
87-
"rollup-plugin-terser": "^5.1.1"
92+
"rollup-plugin-terser": "^5.1.1",
93+
"serve": "^11.1.0"
8894
},
8995
"keywords": [
9096
"commonmark",

rollup.config.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,30 @@ export default [
1414
plugins: [json()]
1515
},
1616

17+
{
18+
input: './lib/index.js',
19+
output: { file: 'dist/cjs/index.browser.js', format: 'cjs' },
20+
external,
21+
plugins: [
22+
nodeResolve({ extensions: ['.browser.js', '.js'] })
23+
]
24+
},
25+
1726
{
1827
input: ['./lib/index.js', './lib/linkify.js'],
1928
output: { dir: 'dist/esm', format: 'esm' },
2029
external
2130
},
2231

32+
{
33+
input: './lib/index.js',
34+
output: { file: 'dist/esm/index.browser.js', format: 'esm' },
35+
external,
36+
plugins: [
37+
nodeResolve({ extensions: ['.browser.js', '.js'] })
38+
]
39+
},
40+
2341
{
2442
input: './lib/umd.js',
2543
output: { file: 'dist/remarkable.js', format: 'umd', name },

test/test-browser.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
4+
const readFixture = file =>
5+
JSON.stringify(fs.readFileSync(file, 'utf-8'));
6+
7+
const readFixtureDir = dir =>
8+
fs.readdirSync(dir).map(file => [JSON.stringify(file), readFixture(path.join(dir, file))]);
9+
10+
const content = `
11+
<script type="module">
12+
import RemarkableWithMap from './dist/esm/index.js';
13+
import RemarkableWithTextarea from './dist/esm/index.browser.js';
14+
15+
const markdownWithMap = new RemarkableWithMap('commonmark');
16+
const markdownWithTextarea = new RemarkableWithMap('commonmark');
17+
18+
const runTests = (name, input) => {
19+
input = input.replace(/→/g, '\\t');
20+
input.replace(/^\\.\\n([\\s\\S]*?)^\\.\\n([\\s\\S]*?)^\\.$/gm, function(__, md, html, offset, orig) {
21+
const result1 = html === markdownWithMap.render(md);
22+
const result2 = html === markdownWithTextarea.render(md);
23+
console.log(
24+
'%c %s %s %s',
25+
result1 === result2 ? 'color: green' : 'color: red',
26+
name,
27+
result1,
28+
result2
29+
);
30+
});
31+
};
32+
33+
runTests('test/fixtures/commonmark/good.txt', ${readFixture('test/fixtures/commonmark/good.txt')});
34+
${readFixtureDir('test/fixtures/remarkable')
35+
.map(([name, fixture]) => `runTests(${name}, ${fixture});`)
36+
.join('\n')}
37+
</script>
38+
`
39+
40+
fs.writeFileSync('index.html', content)

0 commit comments

Comments
 (0)