Coder Social home page Coder Social logo

webpack-pwa's Introduction

webpack-pwa

Super simple webpack PWA(Progressive Web App) example featuring

  • Routing with On Demand Loading
  • Offline support
  • Fetching some data from network
  • Two approaches
    • App Shell
    • Page Shell
  • By intent: No framework, only simple JavaScript and DOM
    • Yes with innerHTML and innerText
    • Feel free to imagine your favorite framework here.

Build and Run it

npm install
npm run build-shell
cd dist
npm install node-static -g
static
open http://localhost:8080/dashboard.html

This builds the App Shell version.

To build the Page Shell version: replace npm run build-shell with npm run build-page.

Architecture

app shell vs page shell

App Shell

  • Total size is smaller
  • Initial load requests three files: login.html, shell-1234.js, 3456.js
  • Initial load needs to load: Shell + Router + content
  • The shell is visible earlier than with Page Shell approach.

Page Shell

  • Total size is bigger (i. e. dashboard content is in dashboard-1234.js and 4567.js)
    • App takes longer to be offline ready.
  • Initial load requests only two files: login.html, login-2345.js
  • Initial load needs to load: Shell + content
  • The shell + content is visible earlier than with App Shell approach.

A hybrid approach can be used where shell and content is separated in two requests (see admin page as example). This makes sense when content is much bigger than shell and shell should be visible earlier.

webpack-pwa's People

Contributors

sdgluck avatar sokra avatar stipsan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

webpack-pwa's Issues

error running npm run build-shell in windows 10

Got an error running npm run-build shell in windows 10

C:\Projects\webpack-pwa-master>npm run build-shell

[email protected] build-shell C:\Projects\webpack-pwa-master
webpack -p --env.appShell

Hash: ce341eb5b8f22385368c
Version: webpack 2.2.1
Time: 1336ms
Asset Size Chunks Chunk Names
dashboard.html 202 bytes [emitted]
86a1c3085e135767dd99.js 298 bytes 0 [emitted]
2b1b28982bbce9a8fb90.js 1.43 kB 2 [emitted]
f57ea59692eeddb5f9ff.js 352 kB 3 [emitted] [big]
shell-5232f0bea5d8b88088f5.js 12 kB 4 [emitted] shell
afe436fe358f24997858.js 2.86 kB 1 [emitted]
login.html 202 bytes [emitted]
admin.html 202 bytes [emitted]
sw.js 7.83 kB [emitted]
appcache/manifest.appcache 121 bytes [emitted]
appcache/manifest.html 58 bytes [emitted]
[0] ./app/loading/page.js 217 bytes {4} [built]
[1] ./app/app.js 2.31 kB {4} [built]
[2] ./app/error/page.js 224 bytes {0} [built]
[3] ./app async ^./.*/page$ 160 bytes {4} [built]
[4] ./~/offline-plugin/runtime.js 1.63 kB {4} [built]
[5] ./app/shell.js 115 bytes {4} [built]
[6] ./app/admin/page.js 351 kB {3} [optional] [built]
[7] ./app/dashboard/page.js 1.05 kB {1} [optional] [built]
[8] ./app/login/page.js 415 bytes {2} [optional] [built]
[9] ./app/utils/fetch.js 285 bytes {1} [built]
[10] ./app/admin/page.html 190 bytes {3} [built]
[11] ./app/dashboard/page.html 328 bytes {1} [built]
[12] ./app/login/page.html 190 bytes {2} [built]

ERROR in afe436fe358f24997858.js from UglifyJs
SyntaxError: Unexpected token: punc ()) [afe436fe358f24997858.js:27,65]

ERROR in 2b1b28982bbce9a8fb90.js from UglifyJs
SyntaxError: Unexpected token: punc ()) [2b1b28982bbce9a8fb90.js:25,69]

ERROR in f57ea59692eeddb5f9ff.js from UglifyJs
SyntaxError: Unexpected token: punc ()) [f57ea59692eeddb5f9ff.js:25,69]

ERROR in shell-5232f0bea5d8b88088f5.js from UglifyJs
SyntaxError: Unexpected character '`' [shell-5232f0bea5d8b88088f5.js:153,42]
Child __offline_serviceworker:
[0] .//offline-plugin/empty-entry.js 0 bytes {0} [built]
[1] ./
/offline-plugin/lib/misc/sw-loader.js?json=%7B%22data_var_name%22%3A%22__wpo%22%2C%22loaders%22%3A%5B%5D%2C%22cacheMaps%22%3A%5B%5D%7D!.//offline-plugin/empty-entry.js 16.2 kB {0} [built]
Child html-webpack-plugin for "dashboard.html":
[0] ./
/lodash/lodash.js 540 kB {0} [built]
[1] (webpack)/buildin/global.js 509 bytes {0} [built]
[2] (webpack)/buildin/module.js 517 bytes {0} [built]
[3] .//html-webpack-plugin/lib/loader.js!.//html-webpack-plugin/default_index.ejs 538 bytes {0} [built]
Child html-webpack-plugin for "login.html":
[0] .//lodash/lodash.js 540 kB {0} [built]
[1] (webpack)/buildin/global.js 509 bytes {0} [built]
[2] (webpack)/buildin/module.js 517 bytes {0} [built]
[3] ./
/html-webpack-plugin/lib/loader.js!.//html-webpack-plugin/default_index.ejs 538 bytes {0} [built]
Child html-webpack-plugin for "admin.html":
[0] ./
/lodash/lodash.js 540 kB {0} [built]
[1] (webpack)/buildin/global.js 509 bytes {0} [built]
[2] (webpack)/buildin/module.js 517 bytes {0} [built]
[3] .//html-webpack-plugin/lib/loader.js!.//html-webpack-plugin/default_index.ejs 538 bytes {0} [built]

0 info it worked if it ends with ok 1 verbose cli [ 'C:\\Program Files\\nodejs\\node.exe', 1 verbose cli 'C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js', 1 verbose cli 'run', 1 verbose cli 'build-shell' ] 2 info using [email protected] 3 info using [email protected] 4 verbose run-script [ 'prebuild-shell', 'build-shell', 'postbuild-shell' ] 5 info lifecycle [email protected]~prebuild-shell: [email protected] 6 silly lifecycle [email protected]~prebuild-shell: no script for prebuild-shell, continuing 7 info lifecycle [email protected]~build-shell: [email protected] 8 verbose lifecycle [email protected]~build-shell: unsafe-perm in lifecycle true 9 verbose lifecycle [email protected]~build-shell: PATH: C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bin;C:\Projects\webpack-pwa-master\node_modules\.bin;C:\ProgramData\Oracle\Java\javapath;C:\windows\system32;C:\windows;C:\windows\System32\Wbem;C:\windows\System32\WindowsPowerShell\v1.0\;C:\windows\system32\config\systemprofile\.dnx\bin;C:\Program Files\Microsoft DNX\Dnvm\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\Client SDK\ODBC\130\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\130\DTS\Binn\;C:\Program Files (x86)\Microsoft SQL Server\130\Tools\Binn\ManagementStudio\;C:\Program Files\nodejs\;C:\Program Files\Git\cmd;C:\Users\rlloveras\AppData\Roaming\npm;C:\Program Files (x86)\Microsoft VS Code\bin 10 verbose lifecycle [email protected]~build-shell: CWD: C:\Projects\webpack-pwa-master 11 silly lifecycle [email protected]~build-shell: Args: [ '/d /s /c', 'webpack -p --env.appShell' ] 12 silly lifecycle [email protected]~build-shell: Returned: code: 2 signal: null 13 info lifecycle [email protected]~build-shell: Failed to exec build-shell script 14 verbose stack Error: [email protected] build-shell: webpack -p --env.appShell14 verbose stack Exit status 2 14 verbose stack at EventEmitter.<anonymous> (C:\Program Files\nodejs\node_modules\npm\lib\utils\lifecycle.js:255:16) 14 verbose stack at emitTwo (events.js:106:13) 14 verbose stack at EventEmitter.emit (events.js:191:7) 14 verbose stack at ChildProcess.<anonymous> (C:\Program Files\nodejs\node_modules\npm\lib\utils\spawn.js:40:14) 14 verbose stack at emitTwo (events.js:106:13) 14 verbose stack at ChildProcess.emit (events.js:191:7) 14 verbose stack at maybeClose (internal/child_process.js:877:16) 14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) 15 verbose pkgid [email protected] 16 verbose cwd C:\Projects\webpack-pwa-master 17 error Windows_NT 10.0.10586 18 error argv "C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "run" "build-shell" 19 error node v6.9.4 20 error npm v3.10.10 21 error code ELIFECYCLE 22 error [email protected] build-shell:webpack -p --env.appShell 22 error Exit status 2 23 error Failed at the [email protected] build-shell script 'webpack -p --env.appShell'. 23 error Make sure you have the latest version of node.js and npm installed. 23 error If you do, this is most likely a problem with the webpack-pwa package, 23 error not with npm itself. 23 error Tell the author that this fails on your system: 23 error webpack -p --env.appShell 23 error You can get information on how to open an issue for this project with: 23 error npm bugs webpack-pwa 23 error Or if that isn't available, you can get their info via: 23 error npm owner ls webpack-pwa 23 error There is likely additional logging output above. 24 verbose exit [ 1, true ]

Running build scripts blows up webpack

I get this error:

webpack -p --env.appShell

Hash: 6f893c8bbaf1e6baf97b
Version: webpack 2.2.0
Time: 1008ms
                        Asset       Size  Chunks                    Chunk Names
               dashboard.html  202 bytes          [emitted]
      339b7b0e50cbacf7a044.js  297 bytes       0  [emitted]
      c7fb370947354ed1aeff.js    1.43 kB       2  [emitted]
      3d00f0c7841f11a322d3.js     352 kB       3  [emitted]  [big]
shell-8edb2e35229150fb950e.js    11.4 kB       4  [emitted]         shell
      db6fbc4d7edd8e2fda51.js    2.86 kB       1  [emitted]
                   login.html  202 bytes          [emitted]
                   admin.html  202 bytes          [emitted]
                        sw.js    7.83 kB          [emitted]
   appcache/manifest.appcache  262 bytes          [emitted]
       appcache/manifest.html   58 bytes          [emitted]
   [0] ./app/loading/page.js 217 bytes {4} [built]
   [1] ./app/app.js 2.19 kB {4} [built]
   [2] ./app/error/page.js 222 bytes {0} [built]
   [3] ./app async ^\.\/.*\/page$ 160 bytes {4} [built]
   [4] ./~/offline-plugin/runtime.js 1.29 kB {4} [built]
   [5] ./app/shell.js 128 bytes {4} [built]
   [6] ./app/admin/page.js 351 kB {3} [optional] [built]
   [7] ./app/dashboard/page.js 1.05 kB {1} [optional] [built]
   [8] ./app/login/page.js 415 bytes {2} [optional] [built]
   [9] ./app/utils/fetch.js 285 bytes {1} [built]
  [10] ./app/admin/page.html 190 bytes {3} [built]
  [11] ./app/dashboard/page.html 328 bytes {1} [built]
  [12] ./app/login/page.html 190 bytes {2} [built]

ERROR in db6fbc4d7edd8e2fda51.js from UglifyJs
SyntaxError: Unexpected token: punc ()) [db6fbc4d7edd8e2fda51.js:27,65]

ERROR in c7fb370947354ed1aeff.js from UglifyJs
SyntaxError: Unexpected token: punc ()) [c7fb370947354ed1aeff.js:25,69]

ERROR in 3d00f0c7841f11a322d3.js from UglifyJs
SyntaxError: Unexpected token: punc ()) [3d00f0c7841f11a322d3.js:25,69]

ERROR in shell-8edb2e35229150fb950e.js from UglifyJs
SyntaxError: Unexpected character '`' [shell-8edb2e35229150fb950e.js:153,42]
Child __offline_serviceworker:
       [0] ./~/offline-plugin/empty-entry.js 0 bytes {0} [built]
       [1] ./~/offline-plugin/lib/misc/sw-loader.js?json=%7B%22data_var_name%22%3A%22__wpo%22%2C%22loaders%22%3A%5B%5D%2C%22cacheMaps%22%3A%5B%5D%7D!./~/offline-plugin/empty-entry.js 16.2 kB {0} [built]
Child html-webpack-plugin for "dashboard.html":
       [0] ./~/lodash/lodash.js 540 kB {0} [built]
       [1] (webpack)/buildin/global.js 509 bytes {0} [built]
       [2] (webpack)/buildin/module.js 517 bytes {0} [built]
       [3] ./~/html-webpack-plugin/lib/loader.js!./~/html-webpack-plugin/default_index.ejs 540 bytes {0} [built]
Child html-webpack-plugin for "login.html":
       [0] ./~/lodash/lodash.js 540 kB {0} [built]
       [1] (webpack)/buildin/global.js 509 bytes {0} [built]
       [2] (webpack)/buildin/module.js 517 bytes {0} [built]
       [3] ./~/html-webpack-plugin/lib/loader.js!./~/html-webpack-plugin/default_index.ejs 540 bytes {0} [built]
Child html-webpack-plugin for "admin.html":
       [0] ./~/lodash/lodash.js 540 kB {0} [built]
       [1] (webpack)/buildin/global.js 509 bytes {0} [built]
       [2] (webpack)/buildin/module.js 517 bytes {0} [built]
       [3] ./~/html-webpack-plugin/lib/loader.js!./~/html-webpack-plugin/default_index.ejs 540 bytes {0} [built]

npm ERR! Darwin 15.6.0
npm ERR! argv "/Users/amiterandole/.nvm/versions/node/v7.0.0/bin/node" "/Users/amiterandole/.nvm/versions/node/v7.0.0/bin/npm" "run" "build-shell"
npm ERR! node v7.0.0
npm ERR! npm  v3.10.8
npm ERR! code ELIFECYCLE
npm ERR! [email protected] build-shell: `webpack -p --env.appShell`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] build-shell script 'webpack -p --env.appShell'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the webpack-pwa package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     webpack -p --env.appShell
npm ERR! You can get information on how to open an issue for this project with:
npm ERR!     npm bugs webpack-pwa
npm ERR! Or if that isn't available, you can get their info via:
npm ERR!     npm owner ls webpack-pwa
npm ERR! There is likely additional logging output above.

npm ERR! Please include the following file with any support request:
npm ERR!     /Users/amiterandole/Documents/current/webdev/webpack-pwa/npm-debug.log

Here is my error log file:

0 info it worked if it ends with ok
1 verbose cli [ '/Users/amiterandole/.nvm/versions/node/v7.0.0/bin/node',
1 verbose cli   '/Users/amiterandole/.nvm/versions/node/v7.0.0/bin/npm',
1 verbose cli   'run',
1 verbose cli   'build' ]
2 info using [email protected]
3 info using [email protected]
4 verbose run-script [ 'prebuild', 'build', 'postbuild' ]
5 info lifecycle [email protected]~prebuild: [email protected]
6 silly lifecycle [email protected]~prebuild: no script for prebuild, continuing
7 info lifecycle [email protected]~build: [email protected]
8 verbose lifecycle [email protected]~build: unsafe-perm in lifecycle true
9 verbose lifecycle [email protected]~build: PATH: /Users/amiterandole/.nvm/versions/node/v7.0.0/lib/node_modules/npm/bin/node-gyp-bin:/Users/amiterandole/Documents/current/webdev/webpack-pwa/node_modules/.bin:/Users/amiterandole/Downloads/google-cloud-sdk/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/usr/local/sbin:/Users/amiterandole/.nvm/versions/node/v7.0.0/bin:/usr/local/heroku/bin:/Users/amiterandole/.rbenv/shims:/Users/amiterandole/Sites/larasites/yiisite/framework:vendor/bin:/Applications/Postgres.app/Contents/Versions/9.5/bin:/Users/amiterandole/.rbenv/shims:/usr/local/mysql/bin:/Users/amiterandole/.composer/vendor/bin:/usr/local/bin:/Users/amiterandole/Library/Haskell/bin:/Users/amiterandole/.local/bin:/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/bin:./node_modules/.bin:/Users/amiterandole/Library/Android/sdk/tools:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/share/dotnet:/usr/local/go/bin:/Users/amiterandole/bin:/Users/amiterandole/Library/Haskell/bin:/Users/amiterandole/.local/bin:/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/bin:./node_modules/.bin:/Users/amiterandole/Library/Android/sdk/tools:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/share/dotnet:/usr/local/go/bin:/Users/amiterandole/Sites/larasites/spark-installer:/Users/amiterandole/Library/Haskell/bin:/Users/amiterandole/.local/bin:/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/bin:./node_modules/.bin:/Users/amiterandole/Library/Android/sdk/tools:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/share/dotnet:/usr/local/go/bin:/Users/amiterandole/bin:/Users/amiterandole/Library/Haskell/bin:/Users/amiterandole/.local/bin:/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/bin:./node_modules/.bin:/Users/amiterandole/Library/Android/sdk/tools:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/share/dotnet:/usr/local/go/bin
10 verbose lifecycle [email protected]~build: CWD: /Users/amiterandole/Documents/current/webdev/webpack-pwa
11 silly lifecycle [email protected]~build: Args: [ '-c', 'webpack -p' ]
12 silly lifecycle [email protected]~build: Returned: code: 2  signal: null
13 info lifecycle [email protected]~build: Failed to exec build script
14 verbose stack Error: [email protected] build: `webpack -p`
14 verbose stack Exit status 2
14 verbose stack     at EventEmitter.<anonymous> (/Users/amiterandole/.nvm/versions/node/v7.0.0/lib/node_modules/npm/lib/utils/lifecycle.js:255:16)
14 verbose stack     at emitTwo (events.js:106:13)
14 verbose stack     at EventEmitter.emit (events.js:191:7)
14 verbose stack     at ChildProcess.<anonymous> (/Users/amiterandole/.nvm/versions/node/v7.0.0/lib/node_modules/npm/lib/utils/spawn.js:40:14)
14 verbose stack     at emitTwo (events.js:106:13)
14 verbose stack     at ChildProcess.emit (events.js:191:7)
14 verbose stack     at maybeClose (internal/child_process.js:877:16)
14 verbose stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5)
15 verbose pkgid [email protected]
16 verbose cwd /Users/amiterandole/Documents/current/webdev/webpack-pwa
17 error Darwin 15.6.0
18 error argv "/Users/amiterandole/.nvm/versions/node/v7.0.0/bin/node" "/Users/amiterandole/.nvm/versions/node/v7.0.0/bin/npm" "run" "build"
19 error node v7.0.0
20 error npm  v3.10.8
21 error code ELIFECYCLE
22 error [email protected] build: `webpack -p`
22 error Exit status 2
23 error Failed at the [email protected] build script 'webpack -p'.
23 error Make sure you have the latest version of node.js and npm installed.
23 error If you do, this is most likely a problem with the webpack-pwa package,
23 error not with npm itself.
23 error Tell the author that this fails on your system:
23 error     webpack -p
23 error You can get information on how to open an issue for this project with:
23 error     npm bugs webpack-pwa
23 error Or if that isn't available, you can get their info via:
23 error     npm owner ls webpack-pwa
23 error There is likely additional logging output above.
24 verbose exit [ 1, true ]

Switch to SW-based runtime caching

Nice job on this repo! It's great to see more architecture examples out there.

One thing to consider doing would be to switch from using localStorage to cache your "API" result to use a runtime caching strategy implemented inside the service worker instead.

You could pull a snippet of code from the offline cookbook (like network, falling back to cache), or you could use a library like sw-toolbox, which has helpers to implement common runtime caching strategies as well as set up routes to match URL patterns to which those strategies should be applied.

If you wanted to follow the current model in which you checked for a cached result from the context of the page prior to making a network request, you could use the Cache Storage API directly (it's exposed to pages via window.caches) instead of window.localStorage.

Relying on a SW-based approach to runtime caching scales much better that localStorage when you're dealing with real-world API requests, and allows you to think about all your offline data (both App Shell + dynamic data) from the context of the Cache Storage API, instead of a mix of the Cache Storage API and localStorage. localStorage also is a synchronous API whose reads and writes block the main thread, so using it for any substantial chunk of data is normally discouraged.

CC: @addyosmani

Feedback

First, it's awesome that you've been exploring different architecture patterns here @sokra. Thanks for reaching out for some thoughts.

Patterns 🖌

Here are the different patterns for PWAs that I'm aware of:

  • Server-side render of App Shell + use JS to fetch and populate content
  • Full server-side render of Page Shell
  • Full client-side render of Page Shell
  • Hydration. Server-side render of App Shell + content for the page - like Page Shell. Use JS to fetch content for any further routes by 'hydrating' the app into an SPA
  • Server-side render of App Shell with Streams for body content
  • Server-side render App Shell + use AMP as leaf nodes

In general, the Chrome team encourage optimizing for getting a page interactive really quickly (vs showing UI a user can't interact with), but YMMV depending on the metrics you care about.

App Shell

Pro: instantly load your UI on repeat visits, only fetching minimal payloads from the network as you navigate to different routes. Avoids refetching UI from page to page.

Con: For many, this requires a site-wide rearchitecture or shipping a new app. Can push out first contentful paint / first meaningful paint for first load as you're waiting on the network to fetch JS bundle which will then fetch your JSON data.

screen shot 2017-02-12 at 1 53 31 pm

In general, this approach works best for optimizing time-to-first-paint (sometimes meaningful paint) and structures your app a lot more like a native app. On repeat visits the whole UI gets loaded locally (from Service Worker) without touching the network and it becomes straight-forward to keep caching any JSON data or static resources the page needs to be useful. Pages only download the content they need instead of re-fetching pieces of UI, like toolbars and footers.

The downside of this approach is that at its most basic, you're giving a user a skeleton user-interface without any real content for the first load and then populating it using JavaScript. This can be less optimal on spotty networks where a delay of your Webpack bundles can mean the user is just waiting looking at the skeleton screen for a while.

The advice we've been giving folks is use code-splitting and route-based chunking (see PRPL) to keep your Webpack bundles for a route very small and hopefully that makes it easier to fetch both your JSON payloads and JS without too much of a wait on the network.

Page Shell

Pro: doesn't require a site-wide rearchitecture. Full-age caching possible and caching of static resources should also be fine. Could give you a faster time to first meaningful paint.
Con although page shells can be loaded on repeat visits from the SW Cache, each route needs to fetch the UI skeleton itself (toolbars, footers etc) meaning that it isn't quite as optimal for repeat visits as the App Shell model might be.

If you're working on a CMS or classic content site, it might be really hard to rearchitect for the App Shell model. You might find adding a simple SW for caching individual pages easier initially although we try to encourage AppShell's perf benefits where possible.

With Page Shell, you have a a bundle per route/page and are SW caching those, you can still give users the benefit of not having to refetch scripts for repeat visits but the HTML won't be cached quite as optimally. It's also harder to manage atomic updates when you're only updating smaller pieces of the UI.

Hybrid Shell

A hybrid model between App Shell and Page Shell offers an interesting combination of the benefits:

  • You server-side render your initial Application Shell prepopulated with data (Page Shell). This might have to be done for every route your user can land on. It increases the size of your HTML response, but means that 1) get meaningful text/content on the screen quicker, 2) not blocked on JavaScript/Webpack bundle loading before the user can read the page
  • When the server-side rendered page has completed rendering, you 'hydrate' it using your JavaScript bundle (as you would have with the App Shell). This effectively changes a static-side into an SPA - attach event handlers, routing etc. When the user navigates around the app now, it treats the Page Shell like an App Shell, reuses locally cached resources and just fetches JSON for your data instead of requiring a fully-server-rendered Page Shell for each route.
  • This can be harder to implement. It generally requires thinking about isomorphic data fetching and rendering, carefully looking at how much the SSR rendered Page Shell + hydration pushes out your metrics and I've seen fewer folks implement it. It is another option, though :)

screen shot 2017-02-12 at 1 56 14 pm

Comparisons

Thanks to Jake for putting the below demos together a while back. We’re going to quickly walk through a comparison of some of the above models.

Server render - 3G

image

image

http://www.webpagetest.org/video/compare.php?tests=160112_VA_KFA-r%3A8-c%3A0&thumbSize=200&ival=100&end=visual
First render: 0.8s
First content render: 1.7s

Repeat visits will load fully cached pages from the SW cache. However, each network fetch will re-request common “shell” UI blocks like headers and footers as they’re being served in the same page. The “app-shell” pattern doesn’t suffer from this problem.

App Shell render - 3G

image

image

http://www.webpagetest.org/video/compare.php?tests=160112_VA_KFA-r%3A4-c%3A1&thumbSize=200&ival=100&end=visual
First render: 0.4s
First content render: 3.7s

Repeat visits now of course don’t have to re-fetch the application shell or UI pieces that have already been fetched from the network, unlike the pure server-rendered version.

However, this demonstrates a flaw in the app shell approach. The shell loads from the cache, getting a quicker first render, then the JS fetches the content, then it writes it to the page. We have no access to the streaming parser from the page, so the content has to be fully downloaded before it can be displayed. The larger the content, the more you lose vs a streamed server render.

There are some hacks going on already to reduce the issue. The service worker will start fetching the content as soon as it serves the shell, so it starts the fetch earlier than the page's JS would. But there's another hack that helps…

App shell + partial content write, 3g

image

image

http://www.webpagetest.org/video/compare.php?tests=160112_ZW_KVN-r%3A4-c%3A1&thumbSize=200&ival=100&end=visual
First render: 0.2s
First content render: 2.5s

This hack streams the main content inside the page's JS. There's no access to the streaming parser, but this kind-of fakes it by streaming content until 9k is available (post-unzip), then writes the partial content to innerHTML. Once the rest of the content is fetched it writes to innerHTML again. This results in some elements being created twice, but the performance improvement is > 1s over 3g. Still not as fast as a server render though.

Jake hacked the same page together using streams, where the top & tail of the page are streamed from the cache, but the middle is streamed from the server…

Stream from service worker, 3g

image

image

http://www.webpagetest.org/video/compare.php?tests=160112_7B_M9B-r%3A6-c%3A1&thumbSize=200&ival=100&end=visual
First render: 0.3s
First content render: 1.5s (1.7 for full above-the-fold content)

So now we've got the quick first render, but without any cost to the content render, perhaps even faster, and it'll become even faster as more of the primitives land in more browsers (transform steams & piping). This approach also means that parsing/execution rules are as you'd expect when it comes to <script> etc.

In a streams-based model:

  • Start getting page content from cache or network
  • Start getting page head from cache
  • Start getting page footer from cache
  • Stream head to response
  • Stream content to response
  • Stream footer to response

In the case the SW effectively becomes your server, requiring very few changes on the client.

Further thoughts

With any of these models, there's going to be nuance. A lot of PWAs are fine with the App Shell approach, however if you're a content heavy side like a News publisher I could see the Hybrid model or Page Shell model being appealing. Some folks on our team are also hopeful that newer APIs like Streams will offer even better support for progressively rendering content in these types of models.

I personally suggest folks think about what metrics they are trying to optimize for and choose their architecture patterns accordingly :)

screen shot 2017-02-12 at 1 58 58 pm

The learnings from the app-shell and page-shell structure

Hi,

We have experimented with app-shell and are using page-shell currently (with a slight modification - loading the whole view in advance).

App Shell
It gave us better time to first paint but the time to first meaningful paint was delayed. Here's what we found in our experiment.

cxabh40uuaamded

As a user you get a faster first paint but the time to first meaningful content is what we are looking to optimize so we tried to fix that.

Solution
We got rid of app shell and implemented SSR with HTML streaming in combination with preload. And the result was :

201 1 - how we built it - part 1 housing

This way we were able to improve the time to first meaningful paint which made more sense than just improving the time to first paint.

Page Shell
Since we had implemented SSR and the idea of page-shell is same as that of app-shell, we used the idea rather than the implementation.

Case 1: Implement page-shell -> First meaningful paint still suffers. 2 JS files to load and when you navigate from another URL, loading multiple files (we also have separate CSS to prevent FOUC) didn't seem to be a good idea.

Case 2: Implement SSR -> Let the view have 2 files (JS and CSS). When the user lands directly on the page you don't need any shell due to SSR. When you are on a different page and the next intent of the user is to come on that page, load the view of current page in the idle time of previous page.

Implementing these we were able to solve for :

  • too many files
  • better time to first meaningful paint
  • faster route navigation

All of these were more about the implementation rather than webpack bundling.

webpack was mainly optimized to implement long term caching, manifest and chunking.

Intent based chunks
We implement route based chunks but we often ignore this. This is one important implementation that can reduce the size of a view bundle. There are certain parts of a view that are not always needed like a modal or anything that doesn't show up everytime. That can be made into a different chunk and loaded on demand. For example:

201 1 - how we built it - part 1 housing

The size in the above image is non-gzipped (around 9KB gzipped).

We are using 2 types of intent based chunks which are loaded in idle time or as per need.

Type 1 :
Those which have a chance of being visible but not immediately. They are loaded in idle time like some frequently visited modals. (driven by analytics)

Type 2:
Those which have less chance of being visible or once in many sessions. Load when actually required. Most of the times its not needed. (as per our analytics data)

We have to take care of bandwidth consumption of the user. Loading lot of chunks in idle time also may not be a good idea for people with slow internet connection.

I hope these are useful for developers who are trying to make a PWA.
So in both cases out time to first paint was around 2.2-2.3s so it was better that we implement the 2nd solution.

Its not always necessary that these will work for you so experiment and then implement.

Edit : Added clarity about intent based chunks.

build error `npm run build-shell`

I got below error.

ERROR in afe436fe358f24997858.js from UglifyJs
SyntaxError: Unexpected token: punc ()) [afe436fe358f24997858.js:27,65]

ERROR in 2b1b28982bbce9a8fb90.js from UglifyJs
SyntaxError: Unexpected token: punc ()) [2b1b28982bbce9a8fb90.js:25,69]

ERROR in f57ea59692eeddb5f9ff.js from UglifyJs
SyntaxError: Unexpected token: punc ()) [f57ea59692eeddb5f9ff.js:25,69]

ERROR in shell-5232f0bea5d8b88088f5.js from UglifyJs
SyntaxError: Unexpected character '`' [shell-5232f0bea5d8b88088f5.js:153,42]

hyper

My env is...

macOS 10.12.3

kntmrkm:~/code/javascript/webpack-pwa (master %=) $ npm -v
3.10.10
kntmrkm:~/code/javascript/webpack-pwa (master %=) $ node -v
v6.9.5

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.