├── .gitignore ├── .jshintignore ├── .travis.yml ├── test ├── fixtures │ ├── end-heading.md │ ├── end-heading.html │ ├── blockquote.md │ ├── blockquote.html │ ├── article.md │ └── article.html └── domador.js ├── windowContext.js ├── .editorconfig ├── bower.json ├── .jshintrc ├── virtualWindowContext.js ├── license ├── package.json ├── changelog.markdown ├── readme.markdown ├── dist ├── domador.min.js └── domador.js └── domador.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | Thumbs.db 5 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | dist 4 | example 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4' 4 | - '5' 5 | -------------------------------------------------------------------------------- /test/fixtures/end-heading.md: -------------------------------------------------------------------------------- 1 | some [text][1] 2 | 3 | ## some heading 4 | 5 | [1]: /foo 6 | -------------------------------------------------------------------------------- /test/fixtures/end-heading.html: -------------------------------------------------------------------------------- 1 |

some text

2 |

some heading

3 | -------------------------------------------------------------------------------- /windowContext.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (!window.Node) { 4 | window.Node = { 5 | ELEMENT_NODE: 1, 6 | TEXT_NODE: 3 7 | }; 8 | } 9 | 10 | function windowContext () { 11 | return window; 12 | } 13 | 14 | module.exports = windowContext; 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "domador", 3 | "version": "2.4.3", 4 | "description": "Dependency-free and lean DOM parser that outputs Markdown", 5 | "homepage": "https://github.com/bevacqua/domador", 6 | "main": "dist/domador.js", 7 | "authors": [ 8 | "Nicolas Bevacqua " 9 | ], 10 | "license": "MIT" 11 | } 12 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "newcap": true, 5 | "noarg": true, 6 | "noempty": true, 7 | "nonew": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "trailing": true, 12 | "boss": true, 13 | "eqnull": true, 14 | "strict": true, 15 | "immed": true, 16 | "expr": true, 17 | "latedef": "nofunc", 18 | "quotmark": "single", 19 | "validthis": true, 20 | "indent": 2, 21 | "node": true, 22 | "browser": true, 23 | "esnext": true 24 | } 25 | -------------------------------------------------------------------------------- /virtualWindowContext.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function jsdom () { 4 | try { 5 | return require('jsdom').jsdom; 6 | } catch (e) { 7 | throw new Error('domador requires you to install optional dependency `jsdom` to enable its server-side functionality.'); 8 | } 9 | } 10 | 11 | function virtualWindowContext (userOptions) { 12 | var options = { 13 | features: { 14 | FetchExternalResources: false 15 | } 16 | }; 17 | var _document = jsdom()(null, { url: userOptions.href }, options); 18 | var _window = _document.defaultView; 19 | return _window; 20 | } 21 | 22 | module.exports = virtualWindowContext; 23 | -------------------------------------------------------------------------------- /test/fixtures/blockquote.md: -------------------------------------------------------------------------------- 1 | title 2 | 3 | > Looking at the components that make up _\[the time to first tweet\]_ measurement, we discovered that the raw parsing and execution of JavaScript caused massive outliers in perceived rendering speed. In our fully client-side architecture, you don't see anything until our JavaScript is [downloaded][1] and executed. The problem is further exacerbated if you do not have a high-specification machine or if you're running an older browser. The bottom line is that a client-side architecture leads to slower performance because most of the code is being executed on our users' machines rather than our own. 4 | > 5 | > There are a variety of options for improving the performance of our JavaScript, but we wanted to do even better. We took the execution of JavaScript completely out of our render path. By rendering our page content on the server and deferring all JavaScript execution until well after that content has been rendered, we've dropped the time to first Tweet to one-fifth of what it was. 6 | 7 | [1]: /download 8 | -------------------------------------------------------------------------------- /test/fixtures/blockquote.html: -------------------------------------------------------------------------------- 1 |

title

Looking at the components that make up [the time to first tweet] measurement, we discovered that the raw parsing and execution of JavaScript caused massive outliers in perceived rendering speed. In our fully client-side architecture, you don’t see anything until our JavaScript is downloaded and executed. The problem is further exacerbated if you do not have a high-specification machine or if you’re running an older browser. The bottom line is that a client-side architecture leads to slower performance because most of the code is being executed on our users’ machines rather than our own.

There are a variety of options for improving the performance of our JavaScript, but we wanted to do even better. We took the execution of JavaScript completely out of our render path. By rendering our page content on the server and deferring all JavaScript execution until well after that content has been rendered, we’ve dropped the time to first Tweet to one-fifth of what it was.

2 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2015 Nicolas Bevacqua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "domador", 3 | "version": "2.4.4", 4 | "description": "Dependency-free and lean DOM parser that outputs Markdown", 5 | "main": "domador.js", 6 | "scripts": { 7 | "build": "jshint . && browserify -s domador -do dist/domador.js domador.js && uglifyjs -m -c -o dist/domador.min.js dist/domador.js", 8 | "deployment": "git add dist && npm version ${BUMP:-\"patch\"} --no-git-tag-version && git add package.json && git commit -m \"Autogenerated pre-deployment commit\" && bower version ${BUMP:-\"patch\"} && git reset HEAD~2 && git add . && git commit -am \"Release $(cat package.json | jq -r .version)\" && git push --tags && npm publish && git push", 9 | "deploy": "npm run build && npm run deployment", 10 | "test": "tape test/**/*.js" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/bevacqua/domador.git" 15 | }, 16 | "author": "Nicolas Bevacqua (http://bevacqua.io/)", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/bevacqua/domador/issues" 20 | }, 21 | "browser": { 22 | "./virtualWindowContext.js": "./windowContext.js" 23 | }, 24 | "homepage": "https://github.com/bevacqua/domador", 25 | "dependencies": { 26 | "jsdom": "9.9.1", 27 | "string.prototype.repeat": "0.2.0" 28 | }, 29 | "devDependencies": { 30 | "browserify": "^13.3.0", 31 | "jshint": "^2.9.4", 32 | "tape": "^4.6.3", 33 | "uglify-js": "^2.7.5", 34 | "watchify": "^3.8.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /changelog.markdown: -------------------------------------------------------------------------------- 1 | # v2.4.2 Rosetta Stone 2 | 3 | - Domador now detects code block languages using `fencinglanguage` in `` in addition to `
` tags
 4 | 
 5 | # v2.4.1 Pre-order Today
 6 | 
 7 | - Fixed an issue where `domador` wouldn't preserve whitespace in code blocks
 8 | 
 9 | # v2.4.0 Tableau
10 | 
11 | - Fixed an issue where `domador` would mess up tables that came after lists
12 | - List item output defaults to a prefix of `'- '` instead of the old `'* '` behavior
13 | 
14 | # v2.3.1 Bubble Wrap
15 | 
16 | - Fixed an issue where `domador` would pass the internal initial wrapper container to `transform` option
17 | 
18 | # v2.3.0 Pin Market
19 | 
20 | - Introduced support for `markers`, helpful in a variety of cases
21 | 
22 | # v2.2.1 Suspicious Clown
23 | 
24 | - Fixed a bug in client-side implementation of `` support
25 | 
26 | # v2.2.0 Murder Central
27 | 
28 | - Added `allowFrame` to enable cautious `'))}catch(t){}return}for(e=this.tables(t)||e,r=0;r"),"function"==typeof e&&(e=[e]);e&&e.length;)e.shift().call(this)}}},d.prototype.tables=function(t){if(this.options.tables!==!1){var e=t.tagName;if("TABLE"===e){var i;return i=this.inTable,this.inTable=!0,this.append("\n\n"),this.tableCols=[],function(t){return function(){return t.inTable=i}}(this)}return"THEAD"===e?function(){function t(t,e){return t+"-".repeat(e+2)+"|"}return this.append("|"+this.tableCols.reduce(t,"")+"\n")}:"TH"===e?[function(){this.tableCols.push(this.childBuffer.length)},this.td(!0)]:"TR"===e?(this.tableCol=0,this.output("|"),this.noTrailingWhitespace=!0,function(){this.append("\n")}):"TD"===e?this.td():void 0}},d.prototype.pushLeft=function(t){var e;return e=this.left,this.left+=t,this.atP?this.append(t):this.p(),function(t){return function(){return t.left=e,t.atLeft=t.atP=!1,t.p()}}(this)},d.prototype.replaceLeft=function(t){return this.atLeft?this.last?this.last=this.last.replace(/[ ]{2,4}$/,t):void 0:(this.append(this.left.replace(/[ ]{2,4}$/,t)),this.atLeft=this.noTrailingWhitespace=this.atP=!0)},d.prototype.isVisible=function(t){var e,i,n,s,r=!0,o=a(t,"style",!1),h=null!=o&&"function"==typeof o.match?o.match(T):void 0;if(null!=h)for(i=0;i1&&(e+=e),i>>=1;return n};t?t(String.prototype,"repeat",{value:e,configurable:!0,writable:!0}):String.prototype.repeat=e}()},{}],3:[function(t,e,i){"use strict";function n(){return window}window.Node||(window.Node={ELEMENT_NODE:1,TEXT_NODE:3}),e.exports=n},{}]},{},[1])(1)});


--------------------------------------------------------------------------------
/test/domador.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | var fs = require('fs');
  4 | var test = require('tape');
  5 | var domador = require('..');
  6 | 
  7 | function read (file) {
  8 |   return fs.readFileSync('./test/fixtures/' + file, 'utf8').trim();
  9 | }
 10 | 
 11 | function write (file, data) { /* jshint ignore:line */
 12 |   return fs.writeFileSync('./test/fixtures/' + file, data + '\n', 'utf8');
 13 | }
 14 | 
 15 | test('domador parses basic HTMl strings', function (t) {
 16 |   t.equal(domador('foo'), '**foo**');
 17 |   t.equal(domador('foo'), '_foo_');
 18 |   t.equal(domador('foo'), '_**foo**_');
 19 |   t.equal(domador('

foo

bar
'), 'foo\n\n> bar'); 20 | t.equal(domador('

foo

bar
baz
'), 'foo\n\n> bar \n> baz'); 21 | t.equal(domador('

foo

var bar = 1
'), 'foo\n \n var bar = 1'); 22 | t.end(); 23 | }); 24 | 25 | test('domador gets right', function (t) { 26 | t.equal(domador('foo'), 'foo'); 27 | t.equal(domador('foo bar baz'), 'foo bar baz'); 28 | t.equal(domador('foo bar baz '), 'foo _bar baz_ '); 29 | t.equal(domador('
col0
foo
'), '| col0 |\n|------|\n| _foo_ |'); 30 | t.end(); 31 | }); 32 | 33 | test('domador gets blockquotes right', function (t) { 34 | t.equal(domador('
bar
'), '> bar'); 35 | t.equal(domador('

bar

'), '> bar'); 36 | t.equal(domador('

bar

'), '> # bar'); 37 | t.equal(domador('

bar

bort

'), '> # bar\n> \n> bort'); 38 | t.equal(domador('

a

bar

bort

'), 'a\n\n> # bar\n> \n> bort'); 39 | t.equal(domador([ 40 | '

Creating point.

', 41 | '
', 42 | '

Click on the button to see this text come to life as HTML, the markup language of the web.

', 43 | '

– Nico

', 44 | '
' 45 | ].join('')), 'Creating point.\n\n> Click on the button to see this text come to life as HTML, the markup language of the web.\n> \n> -- Nico'); 46 | t.equal(domador([ 47 | '

Creating point.

', 48 | '
', 49 | '

Click on the button to see this text come to life as HTML, the markup language of the web.

', 50 | '

– Nico

', 51 | '
' 52 | ].join('\n')), 'Creating point.\n\n> Click on the button to see this text come to life as HTML, the markup language of the web.\n> \n> -- Nico'); 53 | t.end(); 54 | }); 55 | 56 | test('articles get the appropriate treatment', function (t) { 57 | t.ok(domador(read('article.html')) === read('article.md')); 58 | t.end(); 59 | }); 60 | 61 | test('blockquotes get the appropriate treatment', function (t) { 62 | t.ok(domador(read('blockquote.html')) === read('blockquote.md')); 63 | t.end(); 64 | }); 65 | 66 | test('end-heading get the appropriate treatment', function (t) { 67 | t.ok(domador(read('end-heading.html')) === read('end-heading.md')); 68 | t.end(); 69 | }); 70 | 71 | test('domador gets fencing', function (t) { 72 | t.equal(domador('

foo

var bar = 1
', { fencing: true }), 'foo\n\n```\nvar bar = 1\n```'); 73 | t.equal(domador('

foo

bar

', { fencing: true }), 'foo\n\n```\n

bar

\n```'); 74 | t.equal(domador('
    \n
  • foo
  • \n
  • bar
  • \n
', { fencing: true }), '```\n
    \n
  • foo
  • \n
  • bar
  • \n
\n```'); 75 | t.equal(domador('

foo

var bar = 1

baz

', { fencing: true }), 'foo\n\n```\nvar bar = 1\n```\n\nbaz'); 76 | t.equal(domador('

foo

var bar = 1;\nconsole.log(bar);
', { fencing: true }), 'foo\n\n```\nvar bar = 1;\nconsole.log(bar);\n```'); 77 | t.equal(domador('

foo

// Code could go here\nvar myVariable = 4;\n\n
', { fencing: true }), 'foo\n\n```\n// Code could go here\nvar myVariable = 4;\n\n```'); 78 | t.end(); 79 | }); 80 | 81 | test('domador gets fencing when spans and classes are involved.', function (t) { 82 | t.equal(domador('

foo

// Code could go here\nvar myVariable = 4;\n\n
', { fencing: true }), 'foo\n\n```\n// Code could go here\nvar myVariable = 4;\n\n```'); 83 | t.equal(domador('

foo

// Code could go here\n\n\nvar myVariable = 4;\n\n
', { fencing: true }), 'foo\n\n```\n// Code could go here\n\nvar myVariable = 4;\n\n```'); 84 | t.end(); 85 | }); 86 | 87 | test('domador gets fencing languages', function (t) { 88 | t.equal( 89 | domador( 90 | '

foo

var bar = 1;\nconsole.log(bar);
', 91 | { fencing: true, fencinglanguage: lang } 92 | ), 93 | 'foo\n\n```js\nvar bar = 1;\nconsole.log(bar);\n```' 94 | ); 95 | t.equal( 96 | domador( 97 | '

foo

var bar = 1;\nconsole.log(bar);
', 98 | { fencing: true, fencinglanguage: lang } 99 | ), 100 | 'foo\n\n```js\nvar bar = 1;\nconsole.log(bar);\n```' 101 | ); 102 | 103 | function lang (el) { 104 | var match = el.className.match(/md-lang-((?:[^\s]|$)+)/); 105 | if (match) { 106 | return match.pop(); 107 | } 108 | } 109 | t.end(); 110 | }); 111 | 112 | test('by default, domador just linkifies', function (t) { 113 | t.equal(domador([ 114 | '

Hey @bevacqua that\'s a nice thought.

' 115 | ].join('')), 'Hey [@bevacqua][1] that\'s a nice thought.\n\n[1]: /users/bevacqua'); 116 | t.equal(domador([ 117 | '

Hey @bevacqua that\'s a nice thought.

' 118 | ].join(''), {inline: true}), 'Hey [@bevacqua](/users/bevacqua) that\'s a nice thought.'); 119 | t.end(); 120 | }); 121 | 122 | test('if asked nicely, domador will do anything for you', function (t) { 123 | t.equal(domador([ 124 | '

Hey @bevacqua that\'s a nice thought.

' 125 | ].join(''), { 126 | transform: function (el) { 127 | if (el.tagName === 'A' && el.innerHTML[0] === '@') { 128 | return el.innerHTML; 129 | } 130 | } 131 | }), 'Hey @bevacqua that\'s a nice thought.'); 132 | t.end(); 133 | }); 134 | 135 | test('about absolute href', function (t) { 136 | t.equal(domador('foo', { absolute: true }), '[foo][1]\n\n[1]: /foo'); 137 | t.equal(domador('foo', { absolute: true, href: 'https://google.com/s/' }), '[foo][1]\n\n[1]: https://google.com/foo'); 138 | t.equal(domador('foo', { absolute: true, href: 'https://google.com/s/' }), '[foo][1]\n\n[1]: https://google.com/s/foo'); 139 | t.end(); 140 | }); 141 | 142 | test('tables are ignored when tables turned off', function (t) { 143 | t.equal(domador(` 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |
colu1colum2column3
foobarbaz
foodbarsbats
`, { tables: false }), 164 | `colu1\n**colum2**\ncolumn3\nfoo\n_bar_\nbaz\nfood\nbars\nbats`); 165 | t.end(); 166 | }); 167 | 168 | test('tables are parsed into gfm tables by default', function (t) { 169 | t.equal(domador(` 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 |
colu1colum2column3
foobarbaz
foodbarsbats
`), 190 | `| colu1 | colum2 | column3 | 191 | |-------|--------|---------| 192 | | foo | bar | baz | 193 | | food | bars | bats |`); 194 | t.end(); 195 | }); 196 | 197 | test('tables are parsed into gfm tables by default, after p, normally spaced', function (t) { 198 | t.equal(domador(` 199 |

Hi, there!

200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 |
onestwos
onetwo
`), 213 | `Hi, there! 214 | 215 | | ones | twos | 216 | |------|------| 217 | | one | two |`); 218 | t.end(); 219 | }); 220 | 221 | test('tables are parsed into gfm tables by default, after p, without extra spaces', function (t) { 222 | t.equal(domador(` 223 |

Hi, there!

224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 |
onestwos
onetwo
`), 237 | `Hi, there! 238 | 239 | | ones | twos | 240 | |------|------| 241 | | one | two |`); 242 | t.end(); 243 | }); 244 | 245 | test('tables are parsed into gfm tables separated from subsequent elements by only one newline', function (t) { 246 | t.equal(domador(` 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 |
onestwos
onetwo
259 |

Hi, there!

`), 260 | `| ones | twos | 261 | |------|------| 262 | | one | two | 263 | 264 | Hi, there!`); 265 | t.end(); 266 | }); 267 | 268 | test('tables that come right after a list item work as expected', function (t) { 269 | t.equal(domador(`
  • foo
270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 |
onestwos
onetwo
`), 282 | `- foo 283 | 284 | | ones | twos | 285 | |------|------| 286 | | one | two |`); 287 | t.end(); 288 | }); 289 | 290 | test('tables with complex content still get proper padding', function (t) { 291 | t.equal(domador(` 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 |
colu1colum2column3
foobarbaz
foodbarsbats
`), 312 | `| colu1 | **colum2** | column3 | 313 | |-------|------------|---------| 314 | | foo | _bar_ | baz | 315 | | food | bars | bats |`); 316 | t.end(); 317 | }); 318 | 319 | test('tables preserve html block or br elements in cells', function (t) { 320 | t.equal(domador(` 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 |
column1 with a very long headercolumn2
foo
bar
  • A list of one
bars
`), 338 | `| column1 with a very long header | column2 | 339 | |---------------------------------|---------| 340 | |
foo
| bar
| 341 | |
  • A list of one
| bars |`); 342 | t.end(); 343 | }); 344 | 345 | test('domador understands markers', function (t) { 346 | t.equal(domador('foo', { markers: [[0, '[START]'], [0, '[END]']] }), '[START][END]**foo**'); 347 | t.equal(domador('foo', { markers: [[4, '[START]'], [10, '[END]']] }), '**[START]fo[END]o**'); 348 | t.equal(domador('foo', { markers: [[6, '[START]'], [10, '[END]']] }), '**[START]fo[END]o**'); 349 | t.equal(domador('foo', { markers: [[8, '[START]'], [10, '[END]']] }), '**[START]fo[END]o**'); 350 | t.equal(domador('foo', { markers: [[8, '[START]'], [26, '[END]']] }), '`[START]f[END]oo`'); 351 | t.end(); 352 | }); 353 | 354 | test('domador converts double newlines into single newlines', function (t) { 355 | t.equal(domador('

Hello

\n

Hello

'), 'Hello\n\nHello'); 356 | t.equal(domador('

Hello

\n\n

Hello

'), 'Hello\n\nHello'); 357 | t.equal(domador('

Hello

\n\n\n

Hello

'), 'Hello\n\nHello'); 358 | t.equal(domador('

Hello

\n\n\n\n

Hello

'), 'Hello\n\nHello'); 359 | t.end(); 360 | }); 361 | -------------------------------------------------------------------------------- /test/fixtures/article.md: -------------------------------------------------------------------------------- 1 | The year is 2014, a ninja rockstar band goes up against the now long-forgotten [progressive enhancement][1] technique, forsaking the origins of the web and everything they once stood for. This article is where I rant about how **we are breaking the web**, the not-immediately-obvious reasons why we should stop doing this, and how _not breaking the web would be a great thing_. 2 | 3 | **TL;DR** _We are crushing the web. Dedicated client-side rendering sucks. Polyfills are used for all the wrong reasons. Those hideous-looking hash routers are bad and we should feel bad. We have been telling each other for years that progressive enhancement is great, and yet we're doing very little about it!_ 4 | 5 | Here's hoping the screenshot below corresponds merely to a publicity stunt, attempting to grab the tech media world by surprise. That being said, the fact that _we're not certain_ about whether this is a ruse or a permanent decision makes me cringe for the future of the web. 6 | 7 | ![tacobell.png][2] 8 | 9 | Taco Bell _#onlyintheapp_ --- is it a **clever publicity stunt to drive app downloads or a symptom of the profusely bleeding web?** 10 | 11 | _Disclaimer: This article is **not a rant about Angular 2.0**. I started forming these thoughts a while ago, before the Angular 2.0 revelations. The roadmap for Angular merely happened to coincide with the posting of this article. If anything, those news reinforce the [points others have made against it][3], but the statement behind this article goes far beyond the myriad of breaking changes in Angular's public API._ 12 | 13 | It makes me sad to point out that **we as a community have failed the web**. Whatever happened to [progressive enhancement][1]? You know, that simple rule where you are supposed to **put content at the forefront**. Everything else is secondary to content, right? People want to see your content first. Once the content is in place, maybe they'll want to be able to interact with it. However, if content isn't there first, because [your page is too slow][4], or because you load fonts synchronously before humans can read anything, or because you decide to use client-side rendering exclusively, then **humans are pretty much screwed**. _Right?_ 14 | 15 | > Sure, humans have faster Internet connections now. Or do they? A lot of humans access the web on **mobile connections such as 2G and 3G**, and they expect your site to be _just as fast as on desktop_. That'll hardly be the case if you're blocking content on a JavaScript download. 16 | 17 | Increasingly, this is becoming the norm. Fuck humans, we need all these awesome frameworks to make the web great. Wait, we do need humans. They have money, metadata, and stuff. Oh I know, let's give them client-side routing even if they're on IE6\. That's bound to make them happy, right? Oh, stupid IE6 doesn't support the history API. Well, screw IE6\. What? [IE9 doesn't support the history API][5] either? Well, I'll just support IE10\. Wait, that's bad, **I'll use a hash router** and support IE all the way down to IE6! Yes, what a wonderful world, let's make our site accessible through routes like `/#/products/nintendo-game-cube` and then require JavaScript to be enabled for our view router to work, and let's also render the views in the client-side alone. Yes, _that_ will do it! 18 | 19 | Meanwhile, we add tons of weight to our pages, levelling the field and making the experience in modern browsers worse as a result of attempting to make the experience in older browsers better. There's a problem with this fallacy, though. People using older browsers **are not expecting the newest features**. They're content with what they have. That's the whole reason why they're using an older browser in the first place. Instead of attempting to give those users a better experience (and usually failing miserably), you should enable features only if they're currently available on the target browser, instead of creating hacks around those limitations. 20 | 21 | Humans using older browsers would be more than fine with your site if you only kept the server-side rendering part, so they don't really need your fancy-and-terribly-complex [maintainability-nightmare][6] of a hash router. But no, wait! Hash routing is [so-and-so awesome][7], right? Who needs server-side rendering! 22 | 23 | Okay fine let's assume you agree with me. Hash routing sucks. It does nothing to help modern browsers _(except slowing down the experience, [it does do that!][8])_ and everything to complicate development and confuse humans who are using older browsers. 24 | 25 | ## Do we even care about the web as much as we say we do? 26 | 27 | Recently, someone published an article on Medium titled ["What's wrong with Angular.js"][3]. It infuriates me that we don't seem to care _at all_ about server-side rendering, as long as we are able to develop applications using our favorite framework. While every single other point was refuted in some way or another, the point about server-side rendering went almost unnoticed. As if nobody even cared or even understood the implications. 28 | 29 | > 6\. No server side rendering without obscure hacks. Never. You can't fix broken design. Bye bye [isomorphic web apps][9]. 30 | 31 | The only place where I would conceive using a framework that relies solely on client-side rendering is for developing prototypes or internal backend apps _(just like how we use Bootstrap mostly for internal stuff)_. In these cases, these negligent frameworks are great because they boost productivity at virtually no cost, since no humans get harmed in the process. Besides the few use cases where neglecting server-side rendering isn't going to affect any human beings, doing so is undisputably **slow, unacceptable, backwards, and negligent**. 32 | 33 | It is slow because the human now has to download all of your markup, your CSS, and your JavaScript before the JavaScript is able to render the view the user expected you to deliver in the first place. When did we agree to trade performance for frameworks? 34 | 35 | It is backwards because you should be **delivering the content in human-viewable form first**, and not after every single render blocking request out there finishes loading. This means that a human-ready HTML view should be rendered in the server-side and served to the human, then you can add your fancy JavaScript magic on top of that, while the user is busy making sense of the information you've presented them with. 36 | 37 | > Always keep humans busy, or they'll get uneasy. 38 | 39 | It is negligent because we have been telling each other to avoid this same situation for years, but using other words. We've been telling ourselves about the importance of deferring script loading by pushing `