├── .gitignore ├── README.md ├── appveyor.yml ├── client └── app.js ├── package.json ├── public └── build │ └── bundle.js ├── rollup.config.js ├── rollup.config.ssr.js ├── server ├── build │ └── app.js ├── index.js └── templates │ └── index.html └── shared ├── App.html └── Nested.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svelte ssr bundling demo 2 | 3 | Simple toy demo for [sveltejs/svelte#196](https://github.com/sveltejs/svelte/issues/196). Clone, then do this... 4 | 5 | ```bash 6 | npm install 7 | npm start 8 | ``` 9 | 10 | ...then open [localhost:3000](http://localhost:3000). 11 | 12 | 13 | ## How this works 14 | 15 | First, we bundle `shared/App.html`. This gives us a module (`server/build/app.js`) which renders HTML. That module is used inside `server/index.js` to respond to requests with server-rendered HTML (and scoped CSS). 16 | 17 | Then, we bundle `client/app.js`, which imports `shared/App.html`. By initialising the client-side app with different data than the server uses, we can (for example) indicate that the app is now interactive, by rendering stuff differently. 18 | 19 | 20 | ## Notes 21 | 22 | * We don't yet have client-side hydration. The client-side app has to clear out the target element and recreate everything. That's not totally ideal 23 | * There's no client-side routing in this app. Would be easy enough to add in an ad-hoc way, but it might be nice to have a proper routing story, or maybe even a next.js-like framework 24 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # http://www.appveyor.com/docs/appveyor-yml 2 | 3 | version: "{build}" 4 | 5 | clone_depth: 10 6 | 7 | init: 8 | - git config --global core.autocrlf false 9 | 10 | environment: 11 | matrix: 12 | # node.js 13 | - nodejs_version: stable 14 | 15 | install: 16 | - ps: Install-Product node $env:nodejs_version 17 | - npm install 18 | 19 | build: off 20 | 21 | test_script: 22 | - node --version && npm --version 23 | - npm test 24 | 25 | matrix: 26 | fast_finish: false -------------------------------------------------------------------------------- /client/app.js: -------------------------------------------------------------------------------- 1 | import App from '../shared/App.html'; 2 | 3 | const match = /\/page\/(\d+)/.exec( window.location.pathname ); 4 | 5 | const target = document.querySelector( 'main' ); 6 | 7 | // simulate a loading delay before app becomes interactive 8 | setTimeout( () => { 9 | // Right now, we need to clear the target element. This is obviously 10 | // sub-optimal – we want to reuse the existing elements 11 | target.innerHTML = ''; 12 | 13 | window.app = new App({ 14 | target, 15 | data: { 16 | page: +match[1], 17 | loading: false 18 | } 19 | }); 20 | }, 1000 ); 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "rollup": "^0.37.0", 4 | "rollup-plugin-svelte": "^1.3.0" 5 | }, 6 | "scripts": { 7 | "start": "npm run build:server && npm run build:client && npm run serve", 8 | "build:server": "rollup -c rollup.config.ssr.js", 9 | "build:client": "rollup -c rollup.config.js", 10 | "serve": "node server/index.js", 11 | "test": "npm run build:client && npm run build:server" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /public/build/bundle.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function renderMainFragment$1 ( root, component ) { 5 | var div = document.createElement( 'div' ); 6 | div.setAttribute( 'svelte-3548289220', '' ); 7 | div.className = "pagination"; 8 | 9 | var p = document.createElement( 'p' ); 10 | p.setAttribute( 'svelte-3548289220', '' ); 11 | 12 | div.appendChild( p ); 13 | p.appendChild( document.createTextNode( "this is a nested component" ) ); 14 | div.appendChild( document.createTextNode( "\n\t" ) ); 15 | var ifBlock_anchor = document.createComment( "#if page > 1" ); 16 | div.appendChild( ifBlock_anchor ); 17 | 18 | function getBlock ( root ) { 19 | if ( root.page > 1 ) return renderIfBlock_0$1; 20 | return null; 21 | } 22 | 23 | var currentBlock = getBlock( root ); 24 | var ifBlock = currentBlock && currentBlock( root, component ); 25 | 26 | if ( ifBlock ) ifBlock.mount( ifBlock_anchor.parentNode, ifBlock_anchor ); 27 | div.appendChild( document.createTextNode( "\n\n\t" ) ); 28 | 29 | var a = document.createElement( 'a' ); 30 | a.setAttribute( 'svelte-3548289220', '' ); 31 | a.className = "next"; 32 | a.href = "/page/" + ( root.page + 1 ); 33 | 34 | div.appendChild( a ); 35 | a.appendChild( document.createTextNode( "page " ) ); 36 | var text4 = document.createTextNode( root.page + 1 ); 37 | a.appendChild( text4 ); 38 | 39 | return { 40 | mount: function ( target, anchor ) { 41 | target.insertBefore( div, anchor ); 42 | }, 43 | 44 | update: function ( changed, root ) { 45 | var _currentBlock = currentBlock; 46 | currentBlock = getBlock( root ); 47 | if ( _currentBlock === currentBlock && ifBlock) { 48 | ifBlock.update( changed, root ); 49 | } else { 50 | if ( ifBlock ) ifBlock.teardown( true ); 51 | ifBlock = currentBlock && currentBlock( root, component ); 52 | if ( ifBlock ) ifBlock.mount( ifBlock_anchor.parentNode, ifBlock_anchor ); 53 | } 54 | 55 | a.href = "/page/" + ( root.page + 1 ); 56 | 57 | text4.data = root.page + 1; 58 | }, 59 | 60 | teardown: function ( detach ) { 61 | if ( ifBlock ) ifBlock.teardown( false ); 62 | 63 | if ( detach ) { 64 | div.parentNode.removeChild( div ); 65 | } 66 | } 67 | }; 68 | } 69 | 70 | function renderIfBlock_0$1 ( root, component ) { 71 | var a = document.createElement( 'a' ); 72 | a.setAttribute( 'svelte-3548289220', '' ); 73 | a.className = "prev"; 74 | a.href = "/page/" + ( root.page - 1 ); 75 | 76 | a.appendChild( document.createTextNode( "page " ) ); 77 | var text1 = document.createTextNode( root.page - 1 ); 78 | a.appendChild( text1 ); 79 | 80 | return { 81 | mount: function ( target, anchor ) { 82 | target.insertBefore( a, anchor ); 83 | }, 84 | 85 | update: function ( changed, root ) { 86 | a.href = "/page/" + ( root.page - 1 ); 87 | 88 | text1.data = root.page - 1; 89 | }, 90 | 91 | teardown: function ( detach ) { 92 | if ( detach ) { 93 | a.parentNode.removeChild( a ); 94 | } 95 | } 96 | }; 97 | } 98 | 99 | function Nested ( options ) { 100 | options = options || {}; 101 | 102 | var component = this; 103 | var state = options.data || {}; 104 | 105 | var observers = { 106 | immediate: Object.create( null ), 107 | deferred: Object.create( null ) 108 | }; 109 | 110 | var callbacks = Object.create( null ); 111 | 112 | function dispatchObservers ( group, newState, oldState ) { 113 | for ( var key in group ) { 114 | if ( !( key in newState ) ) continue; 115 | 116 | var newValue = newState[ key ]; 117 | var oldValue = oldState[ key ]; 118 | 119 | if ( newValue === oldValue && typeof newValue !== 'object' ) continue; 120 | 121 | var callbacks = group[ key ]; 122 | if ( !callbacks ) continue; 123 | 124 | for ( var i = 0; i < callbacks.length; i += 1 ) { 125 | var callback = callbacks[i]; 126 | if ( callback.__calling ) continue; 127 | 128 | callback.__calling = true; 129 | callback.call( component, newValue, oldValue ); 130 | callback.__calling = false; 131 | } 132 | } 133 | } 134 | 135 | this.fire = function fire ( eventName, data ) { 136 | var handlers = eventName in callbacks && callbacks[ eventName ].slice(); 137 | if ( !handlers ) return; 138 | 139 | for ( var i = 0; i < handlers.length; i += 1 ) { 140 | handlers[i].call( this, data ); 141 | } 142 | }; 143 | 144 | this.get = function get ( key ) { 145 | return key ? state[ key ] : state; 146 | }; 147 | 148 | this.set = function set ( newState ) { 149 | var oldState = state; 150 | state = Object.assign( {}, oldState, newState ); 151 | 152 | dispatchObservers( observers.immediate, newState, oldState ); 153 | if ( mainFragment ) mainFragment.update( newState, state ); 154 | dispatchObservers( observers.deferred, newState, oldState ); 155 | }; 156 | 157 | this._mount = function mount ( target, anchor ) { 158 | mainFragment.mount( target, anchor ); 159 | }; 160 | 161 | this.observe = function ( key, callback, options ) { 162 | var group = ( options && options.defer ) ? observers.deferred : observers.immediate; 163 | 164 | ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); 165 | 166 | if ( !options || options.init !== false ) { 167 | callback.__calling = true; 168 | callback.call( component, state[ key ] ); 169 | callback.__calling = false; 170 | } 171 | 172 | return { 173 | cancel: function () { 174 | var index = group[ key ].indexOf( callback ); 175 | if ( ~index ) group[ key ].splice( index, 1 ); 176 | } 177 | }; 178 | }; 179 | 180 | this.on = function on ( eventName, handler ) { 181 | var handlers = callbacks[ eventName ] || ( callbacks[ eventName ] = [] ); 182 | handlers.push( handler ); 183 | 184 | return { 185 | cancel: function () { 186 | var index = handlers.indexOf( handler ); 187 | if ( ~index ) handlers.splice( index, 1 ); 188 | } 189 | }; 190 | }; 191 | 192 | this.teardown = function teardown ( detach ) { 193 | this.fire( 'teardown' ); 194 | 195 | mainFragment.teardown( detach !== false ); 196 | mainFragment = null; 197 | 198 | state = {}; 199 | }; 200 | 201 | this.root = options.root; 202 | this.yield = options.yield; 203 | 204 | var mainFragment = renderMainFragment$1( state, this ); 205 | if ( options.target ) this._mount( options.target ); 206 | } 207 | 208 | var template = (function () { 209 | return { 210 | data () { 211 | return { 212 | query: '???', 213 | loading: true 214 | }; 215 | }, 216 | 217 | components: { 218 | Nested 219 | }, 220 | 221 | methods: { 222 | showAlert () { 223 | alert( 'the page is now interactive' ); 224 | } 225 | } 226 | } 227 | }()); 228 | 229 | function renderMainFragment ( root, component ) { 230 | var h1 = document.createElement( 'h1' ); 231 | h1.setAttribute( 'svelte-3293511188', '' ); 232 | 233 | h1.appendChild( document.createTextNode( "Page " ) ); 234 | var text1 = document.createTextNode( root.page ); 235 | h1.appendChild( text1 ); 236 | var text2 = document.createTextNode( "\n\n" ); 237 | 238 | var p = document.createElement( 'p' ); 239 | p.setAttribute( 'svelte-3293511188', '' ); 240 | 241 | p.appendChild( document.createTextNode( "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." ) ); 242 | var text4 = document.createTextNode( "\n\n" ); 243 | 244 | var p1 = document.createElement( 'p' ); 245 | p1.setAttribute( 'svelte-3293511188', '' ); 246 | 247 | p1.appendChild( document.createTextNode( "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." ) ); 248 | var text6 = document.createTextNode( "\n\n" ); 249 | 250 | var div = document.createElement( 'div' ); 251 | div.setAttribute( 'svelte-3293511188', '' ); 252 | div.className = "interactive"; 253 | 254 | var ifBlock_anchor = document.createComment( "#if loading" ); 255 | div.appendChild( ifBlock_anchor ); 256 | 257 | function getBlock ( root ) { 258 | if ( root.loading ) return renderIfBlock_0; 259 | return renderIfBlock_1; 260 | } 261 | 262 | var currentBlock = getBlock( root ); 263 | var ifBlock = currentBlock && currentBlock( root, component ); 264 | 265 | if ( ifBlock ) ifBlock.mount( ifBlock_anchor.parentNode, ifBlock_anchor ); 266 | var text7 = document.createTextNode( "\n\n" ); 267 | 268 | var nested_initialData = { 269 | page: root.page 270 | }; 271 | var nested = new template.components.Nested({ 272 | target: null, 273 | root: component.root || component, 274 | data: nested_initialData 275 | }); 276 | 277 | return { 278 | mount: function ( target, anchor ) { 279 | target.insertBefore( h1, anchor ); 280 | target.insertBefore( text2, anchor ); 281 | target.insertBefore( p, anchor ); 282 | target.insertBefore( text4, anchor ); 283 | target.insertBefore( p1, anchor ); 284 | target.insertBefore( text6, anchor ); 285 | target.insertBefore( div, anchor ); 286 | target.insertBefore( text7, anchor ); 287 | nested._mount( target, anchor ); 288 | }, 289 | 290 | update: function ( changed, root ) { 291 | text1.data = root.page; 292 | 293 | var _currentBlock = currentBlock; 294 | currentBlock = getBlock( root ); 295 | if ( _currentBlock === currentBlock && ifBlock) { 296 | ifBlock.update( changed, root ); 297 | } else { 298 | if ( ifBlock ) ifBlock.teardown( true ); 299 | ifBlock = currentBlock && currentBlock( root, component ); 300 | if ( ifBlock ) ifBlock.mount( ifBlock_anchor.parentNode, ifBlock_anchor ); 301 | } 302 | 303 | var nested_changes = {}; 304 | 305 | if ( 'page' in changed ) nested_changes.page = root.page; 306 | 307 | if ( Object.keys( nested_changes ).length ) nested.set( nested_changes ); 308 | }, 309 | 310 | teardown: function ( detach ) { 311 | if ( ifBlock ) ifBlock.teardown( false ); 312 | nested.teardown( detach ); 313 | 314 | if ( detach ) { 315 | h1.parentNode.removeChild( h1 ); 316 | text2.parentNode.removeChild( text2 ); 317 | p.parentNode.removeChild( p ); 318 | text4.parentNode.removeChild( text4 ); 319 | p1.parentNode.removeChild( p1 ); 320 | text6.parentNode.removeChild( text6 ); 321 | div.parentNode.removeChild( div ); 322 | text7.parentNode.removeChild( text7 ); 323 | } 324 | } 325 | }; 326 | } 327 | 328 | function renderIfBlock_1 ( root, component ) { 329 | var button = document.createElement( 'button' ); 330 | button.setAttribute( 'svelte-3293511188', '' ); 331 | 332 | function clickHandler ( event ) { 333 | component.showAlert(); 334 | } 335 | 336 | button.addEventListener( 'click', clickHandler, false ); 337 | 338 | button.appendChild( document.createTextNode( "click me" ) ); 339 | 340 | return { 341 | mount: function ( target, anchor ) { 342 | target.insertBefore( button, anchor ); 343 | }, 344 | 345 | update: function ( changed, root ) { 346 | 347 | }, 348 | 349 | teardown: function ( detach ) { 350 | button.removeEventListener( 'click', clickHandler, false ); 351 | 352 | if ( detach ) { 353 | button.parentNode.removeChild( button ); 354 | } 355 | } 356 | }; 357 | } 358 | 359 | function renderIfBlock_0 ( root, component ) { 360 | var p = document.createElement( 'p' ); 361 | p.setAttribute( 'svelte-3293511188', '' ); 362 | 363 | p.appendChild( document.createTextNode( "loading..." ) ); 364 | 365 | return { 366 | mount: function ( target, anchor ) { 367 | target.insertBefore( p, anchor ); 368 | }, 369 | 370 | update: function ( changed, root ) { 371 | 372 | }, 373 | 374 | teardown: function ( detach ) { 375 | if ( detach ) { 376 | p.parentNode.removeChild( p ); 377 | } 378 | } 379 | }; 380 | } 381 | 382 | function App ( options ) { 383 | options = options || {}; 384 | 385 | var component = this; 386 | var state = Object.assign( template.data(), options.data ); 387 | 388 | var observers = { 389 | immediate: Object.create( null ), 390 | deferred: Object.create( null ) 391 | }; 392 | 393 | var callbacks = Object.create( null ); 394 | 395 | function dispatchObservers ( group, newState, oldState ) { 396 | for ( var key in group ) { 397 | if ( !( key in newState ) ) continue; 398 | 399 | var newValue = newState[ key ]; 400 | var oldValue = oldState[ key ]; 401 | 402 | if ( newValue === oldValue && typeof newValue !== 'object' ) continue; 403 | 404 | var callbacks = group[ key ]; 405 | if ( !callbacks ) continue; 406 | 407 | for ( var i = 0; i < callbacks.length; i += 1 ) { 408 | var callback = callbacks[i]; 409 | if ( callback.__calling ) continue; 410 | 411 | callback.__calling = true; 412 | callback.call( component, newValue, oldValue ); 413 | callback.__calling = false; 414 | } 415 | } 416 | } 417 | 418 | this.fire = function fire ( eventName, data ) { 419 | var handlers = eventName in callbacks && callbacks[ eventName ].slice(); 420 | if ( !handlers ) return; 421 | 422 | for ( var i = 0; i < handlers.length; i += 1 ) { 423 | handlers[i].call( this, data ); 424 | } 425 | }; 426 | 427 | this.get = function get ( key ) { 428 | return key ? state[ key ] : state; 429 | }; 430 | 431 | this.set = function set ( newState ) { 432 | var oldState = state; 433 | state = Object.assign( {}, oldState, newState ); 434 | 435 | dispatchObservers( observers.immediate, newState, oldState ); 436 | if ( mainFragment ) mainFragment.update( newState, state ); 437 | dispatchObservers( observers.deferred, newState, oldState ); 438 | 439 | while ( this.__renderHooks.length ) { 440 | var hook = this.__renderHooks.pop(); 441 | hook.fn.call( hook.context ); 442 | } 443 | }; 444 | 445 | this._mount = function mount ( target, anchor ) { 446 | mainFragment.mount( target, anchor ); 447 | }; 448 | 449 | this.observe = function ( key, callback, options ) { 450 | var group = ( options && options.defer ) ? observers.deferred : observers.immediate; 451 | 452 | ( group[ key ] || ( group[ key ] = [] ) ).push( callback ); 453 | 454 | if ( !options || options.init !== false ) { 455 | callback.__calling = true; 456 | callback.call( component, state[ key ] ); 457 | callback.__calling = false; 458 | } 459 | 460 | return { 461 | cancel: function () { 462 | var index = group[ key ].indexOf( callback ); 463 | if ( ~index ) group[ key ].splice( index, 1 ); 464 | } 465 | }; 466 | }; 467 | 468 | this.on = function on ( eventName, handler ) { 469 | var handlers = callbacks[ eventName ] || ( callbacks[ eventName ] = [] ); 470 | handlers.push( handler ); 471 | 472 | return { 473 | cancel: function () { 474 | var index = handlers.indexOf( handler ); 475 | if ( ~index ) handlers.splice( index, 1 ); 476 | } 477 | }; 478 | }; 479 | 480 | this.teardown = function teardown ( detach ) { 481 | this.fire( 'teardown' ); 482 | 483 | mainFragment.teardown( detach !== false ); 484 | mainFragment = null; 485 | 486 | state = {}; 487 | }; 488 | 489 | this.root = options.root; 490 | this.yield = options.yield; 491 | 492 | this.__renderHooks = []; 493 | 494 | var mainFragment = renderMainFragment( state, this ); 495 | if ( options.target ) this._mount( options.target ); 496 | 497 | while ( this.__renderHooks.length ) { 498 | var hook = this.__renderHooks.pop(); 499 | hook.fn.call( hook.context ); 500 | } 501 | } 502 | 503 | App.prototype = template.methods; 504 | 505 | const match = /\/page\/(\d+)/.exec( window.location.pathname ); 506 | 507 | const target = document.querySelector( 'main' ); 508 | 509 | // simulate a loading delay before app becomes interactive 510 | setTimeout( () => { 511 | // Right now, we need to clear the target element. This is obviously 512 | // sub-optimal – we want to reuse the existing elements 513 | target.innerHTML = ''; 514 | 515 | window.app = new App({ 516 | target, 517 | data: { 518 | page: +match[1], 519 | loading: false 520 | } 521 | }); 522 | }, 1000 ); 523 | 524 | }()); 525 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | 3 | export default { 4 | entry: 'client/app.js', 5 | dest: 'public/build/bundle.js', 6 | format: 'iife', 7 | plugins: [ 8 | svelte({ 9 | css: false // already present on page 10 | }) 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /rollup.config.ssr.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | 3 | export default { 4 | entry: 'shared/App.html', 5 | dest: 'server/build/app.js', 6 | format: 'cjs', 7 | plugins: [ 8 | svelte({ 9 | generate: 'ssr' 10 | }) 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /server/build/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Nested = {}; 4 | 5 | Nested.filename = "/www/SVELTE/misc/ssr-bundle/shared/Nested.html"; 6 | 7 | Nested.render = function ( root, options ) { 8 | root = root || {}; 9 | 10 | return ``; 14 | }; 15 | 16 | Nested.renderCss = function () { 17 | var components = []; 18 | 19 | components.push({ 20 | filename: Nested.filename, 21 | css: "\n\t.pagination[svelte-3548289220], [svelte-3548289220] .pagination {\n\t\twidth: 100%;\n\t}\n\n\tp[svelte-3548289220], [svelte-3548289220] p {\n\t\tcolor: red;\n\t\tfont-family: 'Comic Sans MS';\n\t\tfont-size: 2em;\n\t}\n\n\ta[svelte-3548289220], [svelte-3548289220] a {\n\t\tfont-family: 'Helvetica Neue';\n\t\tdisplay: block;\n\t\ttext-color: #999;\n\t\ttext-decoration: none;\n\t}\n\n\ta[svelte-3548289220]:hover, [svelte-3548289220] a:hover {\n\t\ttext-color: #333;\n\t}\n\n\t.prev[svelte-3548289220], [svelte-3548289220] .prev {\n\t\tfloat: left;\n\t}\n\n\t.next[svelte-3548289220], [svelte-3548289220] .next {\n\t\tfloat: right;\n\t}\n", 22 | map: null // TODO 23 | }); 24 | 25 | return { 26 | css: components.map( x => x.css ).join( '\n' ), 27 | map: null, 28 | components 29 | }; 30 | }; 31 | 32 | var escaped$1 = { 33 | '"': '"', 34 | "'": '&39;', 35 | '&': '&', 36 | '<': '<', 37 | '>': '>' 38 | }; 39 | 40 | function __escape$1 ( html ) { 41 | return String( html ).replace( /["'&<>]/g, match => escaped$1[ match ] ); 42 | } 43 | 44 | var template = (function () { 45 | return { 46 | data () { 47 | return { 48 | query: '???', 49 | loading: true 50 | }; 51 | }, 52 | 53 | components: { 54 | Nested 55 | }, 56 | 57 | methods: { 58 | showAlert () { 59 | alert( 'the page is now interactive' ); 60 | } 61 | } 62 | } 63 | }()); 64 | 65 | var App = {}; 66 | 67 | App.filename = "/www/SVELTE/misc/ssr-bundle/shared/App.html"; 68 | 69 | App.render = function ( root, options ) { 70 | root = Object.assign( template.data(), root || {} ); 71 | 72 | return `

Page ${__escape( root.page )}

73 | 74 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

75 | 76 |

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

77 | 78 |
${ root.loading ? `

loading...

` : `` }
79 | 80 | ${template.components.Nested.render({page: root.page})}`; 81 | }; 82 | 83 | App.renderCss = function () { 84 | var components = []; 85 | 86 | components.push({ 87 | filename: App.filename, 88 | css: "\n\th1[svelte-3293511188], [svelte-3293511188] h1 {\n\t\tfont-family: 'Helvetica Neue';\n\t\tfont-size: 2em;\n\t\tfont-weight: 200;\n\t}\n\n\tp[svelte-3293511188], [svelte-3293511188] p {\n\t\tmax-width: 40em;\n\t\tfont-family: 'Helvetica Neue';\n\t}\n\n\t.interactive[svelte-3293511188], [svelte-3293511188] .interactive {\n\t\tbackground-color: #eee;\n\t\theight: 4em;\n\t\tpadding: 1em;\n\t\tbox-sizing: border-box;\n\t\ttext-align: center;\n\t\tfont-family: 'Helvetica Neue';\n\t\tfont-weight: 900;\n\t}\n\n\t.interactive p[svelte-3293511188], .interactive [svelte-3293511188] p, .interactive[svelte-3293511188] p, [svelte-3293511188] .interactive p {\n\t\tmargin: 0;\n\t}\n\n\tbutton[svelte-3293511188], [svelte-3293511188] button {\n\t\tfont-family: inherit;\n\t\tfont-size: inherit;\n\t}\n", 89 | map: null // TODO 90 | }); 91 | 92 | var seen = {}; 93 | 94 | function addComponent ( component ) { 95 | var result = component.renderCss(); 96 | result.components.forEach( x => { 97 | if ( seen[ x.filename ] ) return; 98 | seen[ x.filename ] = true; 99 | components.push( x ); 100 | }); 101 | } 102 | 103 | addComponent( template.components.Nested ); 104 | 105 | return { 106 | css: components.map( x => x.css ).join( '\n' ), 107 | map: null, 108 | components 109 | }; 110 | }; 111 | 112 | var escaped = { 113 | '"': '"', 114 | "'": '&39;', 115 | '&': '&', 116 | '<': '<', 117 | '>': '>' 118 | }; 119 | 120 | function __escape ( html ) { 121 | return String( html ).replace( /["'&<>]/g, match => escaped[ match ] ); 122 | } 123 | 124 | module.exports = App; 125 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const http = require( 'http' ); 2 | const path = require( 'path' ); 3 | const fs = require( 'fs' ); 4 | 5 | const app = require( './build/app.js' ); 6 | const template = fs.readFileSync( path.join( __dirname, 'templates/index.html' ), 'utf-8' ); 7 | 8 | const server = http.createServer( ( req, res ) => { 9 | const match = /\/page\/(\d+)/.exec( req.url ); 10 | 11 | if ( match ) { 12 | const html = app.render({ page: +match[1] }); 13 | const { css } = app.renderCss(); 14 | 15 | res.end( template.replace( '/* CSS */', css ).replace( '', html ) ); 16 | } 17 | 18 | else if ( req.url === '/' ) { 19 | res.writeHead( 301, { 20 | Location: '/page/1' 21 | }); 22 | res.end(); 23 | } 24 | 25 | else { 26 | const filename = path.resolve( __dirname, '../public', req.url.slice( 1 ) ) 27 | const rs = fs.createReadStream( filename ); 28 | rs.on( 'error', err => { 29 | res.statusCode = 404; 30 | res.end( 'not found' ); 31 | }); 32 | rs.pipe( res ); 33 | } 34 | }); 35 | 36 | 37 | server.listen( 3000, () => { 38 | console.log( 'listening on localhost:3000' ); 39 | }); 40 | -------------------------------------------------------------------------------- /server/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SSR bundling demo 7 | 8 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /shared/App.html: -------------------------------------------------------------------------------- 1 |

Page {{page}}

2 | 3 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

4 | 5 |

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

6 | 7 |
8 | {{#if loading}} 9 |

loading...

10 | {{else}} 11 | 12 | {{/if}} 13 |
14 | 15 | 16 | 17 | 48 | 49 | 71 | -------------------------------------------------------------------------------- /shared/Nested.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 40 | --------------------------------------------------------------------------------