Copy disabled (too large)
Download .txt
Showing preview only (12,262K chars total). Download the full file to get everything.
Repository: lavas-project/pwa-book
Branch: master
Commit: 529311ee4da8
Files: 536
Total size: 11.1 MB
Directory structure:
gitextract_nvjmu4af/
├── .editorconfig
├── .gitignore
├── LICENSE
├── README.md
├── SUMMARY.md
├── appendix01/
│ ├── 1-lighthouse.md
│ ├── 2-lighthouse-score-guide.md
│ └── 3-lighthouse-case.md
├── appendix01.md
├── book.json
├── chapter01/
│ ├── 1-how-was-pwa-born.md
│ ├── 2-what-is-pwa.md
│ ├── 3-what-are-key-techs.md
│ ├── 4-how-is-pwa-going.md
│ ├── 5-the-future-of-pwa.md
│ └── 6-your-first-pwa.md
├── chapter01.md
├── chapter02/
│ ├── 1-what-is-good-ux.md
│ ├── 2-design-and-tech.md
│ ├── 3-app-shell.md
│ ├── 4-app-skeleton.md
│ └── 5-responsive-design.md
├── chapter02.md
├── chapter03/
│ ├── 1-promise.md
│ ├── 2-async-function.md
│ ├── 3-fetch-api.md
│ ├── 4-cache-api.md
│ └── 5-indexeddb.md
├── chapter03.md
├── chapter04/
│ ├── 1-service-worker-introduction.md
│ ├── 2-service-worker-register.md
│ ├── 3-service-worker-dive.md
│ ├── 4-service-worker-debug.md
│ └── code/
│ ├── applicationCacheDemo/
│ │ ├── index.html
│ │ └── manifest.appcache
│ ├── serviceWorkerCacheDemo/
│ │ ├── data.json
│ │ ├── index.css
│ │ ├── index.html
│ │ ├── index.js
│ │ └── sw.js
│ ├── serviceWorkerDemo/
│ │ ├── index.html
│ │ └── sw.js
│ ├── serviceWorkerIndexeddbDemo/
│ │ ├── fruits.json
│ │ ├── index.html
│ │ └── sw.js
│ ├── serviceWorkerLifecycleDemo/
│ │ ├── index.html
│ │ └── sw.js
│ ├── serviceWorkerScopeDemo/
│ │ ├── a/
│ │ │ └── b/
│ │ │ └── sw.js
│ │ └── index.html
│ ├── serviceWorkerScopeDemo1/
│ │ ├── a/
│ │ │ ├── a-sw.js
│ │ │ └── index.html
│ │ ├── b/
│ │ │ └── index.html
│ │ └── root-sw.js
│ └── serviceWorkerUnregisterDemo/
│ ├── index.html
│ └── sw.js
├── chapter04.md
├── chapter05/
│ ├── 1-fetch-event-management.md
│ ├── 2-local-storage-management.md
│ ├── 3-respond-strategy.md
│ ├── 4-precache.md
│ └── 5-workbox.md
├── chapter05.md
├── chapter06/
│ ├── 1-manifest-json.md
│ ├── 2-credentials-api.md
│ ├── 3-notification-api.md
│ ├── 4-web-push-api.md
│ └── 5-payment-request-api.md
├── chapter06.md
├── chapter07/
│ ├── 1-https.md
│ ├── 2-CSP.md
│ ├── 3-policy.md
│ └── 4-vulnerability.md
├── chapter07.md
├── chapter08/
│ ├── 1-loading-performance.md
│ └── 2-rendering-performance.md
├── chapter08.md
├── chapter09/
│ ├── 1-search-engine-index.md
│ ├── 2-pwa-and-amp-and-mip.md
│ ├── 3-whole-site-amp-and-mip.md
│ └── 4-preload-pwa.md
├── chapter09.md
├── ci.yml
├── docs/
│ ├── LICENSE
│ ├── appendix01/
│ │ ├── 1-lighthouse.html
│ │ ├── 2-lighthouse-score-guide.html
│ │ └── 3-lighthouse-case.html
│ ├── appendix01.html
│ ├── chapter01/
│ │ ├── 1-how-was-pwa-born.html
│ │ ├── 2-what-is-pwa.html
│ │ ├── 3-what-are-key-techs.html
│ │ ├── 4-how-is-pwa-going.html
│ │ ├── 5-the-future-of-pwa.html
│ │ └── 6-your-first-pwa.html
│ ├── chapter01.html
│ ├── chapter02/
│ │ ├── 1-what-is-good-ux.html
│ │ ├── 2-design-and-tech.html
│ │ ├── 3-app-shell.html
│ │ ├── 4-app-skeleton.html
│ │ └── 5-responsive-design.html
│ ├── chapter02.html
│ ├── chapter03/
│ │ ├── 1-promise.html
│ │ ├── 2-async-function.html
│ │ ├── 3-fetch-api.html
│ │ ├── 4-cache-api.html
│ │ └── 5-indexeddb.html
│ ├── chapter03.html
│ ├── chapter04/
│ │ ├── 1-service-worker-introduction.html
│ │ ├── 2-service-worker-register.html
│ │ ├── 3-service-worker-dive.html
│ │ ├── 4-service-worker-debug.html
│ │ └── code/
│ │ ├── applicationCacheDemo/
│ │ │ ├── index.html
│ │ │ └── manifest.appcache
│ │ ├── serviceWorkerCacheDemo/
│ │ │ ├── data.json
│ │ │ ├── index.css
│ │ │ ├── index.html
│ │ │ ├── index.js
│ │ │ └── sw.js
│ │ ├── serviceWorkerDemo/
│ │ │ ├── index.html
│ │ │ └── sw.js
│ │ ├── serviceWorkerIndexeddbDemo/
│ │ │ ├── fruits.json
│ │ │ ├── index.html
│ │ │ └── sw.js
│ │ ├── serviceWorkerLifecycleDemo/
│ │ │ ├── index.html
│ │ │ └── sw.js
│ │ ├── serviceWorkerScopeDemo/
│ │ │ ├── a/
│ │ │ │ └── b/
│ │ │ │ └── sw.js
│ │ │ └── index.html
│ │ ├── serviceWorkerScopeDemo1/
│ │ │ ├── a/
│ │ │ │ ├── a-sw.js
│ │ │ │ └── index.html
│ │ │ ├── b/
│ │ │ │ └── index.html
│ │ │ └── root-sw.js
│ │ └── serviceWorkerUnregisterDemo/
│ │ ├── index.html
│ │ └── sw.js
│ ├── chapter04.html
│ ├── chapter05/
│ │ ├── 1-fetch-event-management.html
│ │ ├── 2-local-storage-management.html
│ │ ├── 3-respond-strategy.html
│ │ ├── 4-precache.html
│ │ └── 5-workbox.html
│ ├── chapter05.html
│ ├── chapter06/
│ │ ├── 1-manifest-json.html
│ │ ├── 2-credentials-api.html
│ │ ├── 3-notification-api.html
│ │ ├── 4-web-push-api.html
│ │ └── 5-payment-request-api.html
│ ├── chapter06.html
│ ├── chapter07/
│ │ ├── 1-https.html
│ │ ├── 2-CSP.html
│ │ ├── 3-policy.html
│ │ └── 4-vulnerability.html
│ ├── chapter07.html
│ ├── chapter08/
│ │ ├── 1-loading-performance.html
│ │ └── 2-rendering-performance.html
│ ├── chapter08.html
│ ├── chapter09/
│ │ ├── 1-search-engine-index.html
│ │ ├── 2-pwa-and-amp-and-mip.html
│ │ ├── 3-whole-site-amp-and-mip.html
│ │ └── 4-preload-pwa.html
│ ├── chapter09.html
│ ├── ci.yml
│ ├── gitbook/
│ │ ├── fonts/
│ │ │ └── fontawesome/
│ │ │ └── FontAwesome.otf
│ │ ├── gitbook-plugin-3-ba/
│ │ │ └── plugin.js
│ │ ├── gitbook-plugin-ace/
│ │ │ ├── ace/
│ │ │ │ ├── ace.js
│ │ │ │ ├── ext-beautify.js
│ │ │ │ ├── ext-chromevox.js
│ │ │ │ ├── ext-elastic_tabstops_lite.js
│ │ │ │ ├── ext-emmet.js
│ │ │ │ ├── ext-error_marker.js
│ │ │ │ ├── ext-keybinding_menu.js
│ │ │ │ ├── ext-language_tools.js
│ │ │ │ ├── ext-linking.js
│ │ │ │ ├── ext-modelist.js
│ │ │ │ ├── ext-old_ie.js
│ │ │ │ ├── ext-searchbox.js
│ │ │ │ ├── ext-settings_menu.js
│ │ │ │ ├── ext-spellcheck.js
│ │ │ │ ├── ext-split.js
│ │ │ │ ├── ext-static_highlight.js
│ │ │ │ ├── ext-statusbar.js
│ │ │ │ ├── ext-textarea.js
│ │ │ │ ├── ext-themelist.js
│ │ │ │ ├── ext-whitespace.js
│ │ │ │ ├── keybinding-emacs.js
│ │ │ │ ├── keybinding-vim.js
│ │ │ │ ├── mode-abap.js
│ │ │ │ ├── mode-abc.js
│ │ │ │ ├── mode-actionscript.js
│ │ │ │ ├── mode-ada.js
│ │ │ │ ├── mode-apache_conf.js
│ │ │ │ ├── mode-applescript.js
│ │ │ │ ├── mode-asciidoc.js
│ │ │ │ ├── mode-assembly_x86.js
│ │ │ │ ├── mode-autohotkey.js
│ │ │ │ ├── mode-batchfile.js
│ │ │ │ ├── mode-c9search.js
│ │ │ │ ├── mode-c_cpp.js
│ │ │ │ ├── mode-cirru.js
│ │ │ │ ├── mode-clojure.js
│ │ │ │ ├── mode-cobol.js
│ │ │ │ ├── mode-coffee.js
│ │ │ │ ├── mode-coldfusion.js
│ │ │ │ ├── mode-csharp.js
│ │ │ │ ├── mode-css.js
│ │ │ │ ├── mode-curly.js
│ │ │ │ ├── mode-d.js
│ │ │ │ ├── mode-dart.js
│ │ │ │ ├── mode-diff.js
│ │ │ │ ├── mode-django.js
│ │ │ │ ├── mode-dockerfile.js
│ │ │ │ ├── mode-dot.js
│ │ │ │ ├── mode-eiffel.js
│ │ │ │ ├── mode-ejs.js
│ │ │ │ ├── mode-elixir.js
│ │ │ │ ├── mode-elm.js
│ │ │ │ ├── mode-erlang.js
│ │ │ │ ├── mode-forth.js
│ │ │ │ ├── mode-ftl.js
│ │ │ │ ├── mode-gcode.js
│ │ │ │ ├── mode-gherkin.js
│ │ │ │ ├── mode-gitignore.js
│ │ │ │ ├── mode-glsl.js
│ │ │ │ ├── mode-golang.js
│ │ │ │ ├── mode-groovy.js
│ │ │ │ ├── mode-haml.js
│ │ │ │ ├── mode-handlebars.js
│ │ │ │ ├── mode-haskell.js
│ │ │ │ ├── mode-haxe.js
│ │ │ │ ├── mode-html.js
│ │ │ │ ├── mode-html_elixir.js
│ │ │ │ ├── mode-html_ruby.js
│ │ │ │ ├── mode-ini.js
│ │ │ │ ├── mode-io.js
│ │ │ │ ├── mode-jack.js
│ │ │ │ ├── mode-jade.js
│ │ │ │ ├── mode-java.js
│ │ │ │ ├── mode-javascript.js
│ │ │ │ ├── mode-json.js
│ │ │ │ ├── mode-jsoniq.js
│ │ │ │ ├── mode-jsp.js
│ │ │ │ ├── mode-jsx.js
│ │ │ │ ├── mode-julia.js
│ │ │ │ ├── mode-latex.js
│ │ │ │ ├── mode-lean.js
│ │ │ │ ├── mode-less.js
│ │ │ │ ├── mode-liquid.js
│ │ │ │ ├── mode-lisp.js
│ │ │ │ ├── mode-live_script.js
│ │ │ │ ├── mode-livescript.js
│ │ │ │ ├── mode-logiql.js
│ │ │ │ ├── mode-lsl.js
│ │ │ │ ├── mode-lua.js
│ │ │ │ ├── mode-luapage.js
│ │ │ │ ├── mode-lucene.js
│ │ │ │ ├── mode-makefile.js
│ │ │ │ ├── mode-markdown.js
│ │ │ │ ├── mode-mask.js
│ │ │ │ ├── mode-matlab.js
│ │ │ │ ├── mode-maze.js
│ │ │ │ ├── mode-mel.js
│ │ │ │ ├── mode-mips_assembler.js
│ │ │ │ ├── mode-mipsassembler.js
│ │ │ │ ├── mode-mushcode.js
│ │ │ │ ├── mode-mysql.js
│ │ │ │ ├── mode-nix.js
│ │ │ │ ├── mode-objectivec.js
│ │ │ │ ├── mode-ocaml.js
│ │ │ │ ├── mode-pascal.js
│ │ │ │ ├── mode-perl.js
│ │ │ │ ├── mode-pgsql.js
│ │ │ │ ├── mode-php.js
│ │ │ │ ├── mode-plain_text.js
│ │ │ │ ├── mode-powershell.js
│ │ │ │ ├── mode-praat.js
│ │ │ │ ├── mode-prolog.js
│ │ │ │ ├── mode-properties.js
│ │ │ │ ├── mode-protobuf.js
│ │ │ │ ├── mode-python.js
│ │ │ │ ├── mode-r.js
│ │ │ │ ├── mode-rdoc.js
│ │ │ │ ├── mode-rhtml.js
│ │ │ │ ├── mode-ruby.js
│ │ │ │ ├── mode-rust.js
│ │ │ │ ├── mode-sass.js
│ │ │ │ ├── mode-scad.js
│ │ │ │ ├── mode-scala.js
│ │ │ │ ├── mode-scheme.js
│ │ │ │ ├── mode-scss.js
│ │ │ │ ├── mode-sh.js
│ │ │ │ ├── mode-sjs.js
│ │ │ │ ├── mode-smarty.js
│ │ │ │ ├── mode-snippets.js
│ │ │ │ ├── mode-soy_template.js
│ │ │ │ ├── mode-space.js
│ │ │ │ ├── mode-sql.js
│ │ │ │ ├── mode-sqlserver.js
│ │ │ │ ├── mode-stylus.js
│ │ │ │ ├── mode-svg.js
│ │ │ │ ├── mode-swift.js
│ │ │ │ ├── mode-swig.js
│ │ │ │ ├── mode-tcl.js
│ │ │ │ ├── mode-tex.js
│ │ │ │ ├── mode-text.js
│ │ │ │ ├── mode-textile.js
│ │ │ │ ├── mode-toml.js
│ │ │ │ ├── mode-twig.js
│ │ │ │ ├── mode-typescript.js
│ │ │ │ ├── mode-vala.js
│ │ │ │ ├── mode-vbscript.js
│ │ │ │ ├── mode-velocity.js
│ │ │ │ ├── mode-verilog.js
│ │ │ │ ├── mode-vhdl.js
│ │ │ │ ├── mode-xml.js
│ │ │ │ ├── mode-xquery.js
│ │ │ │ ├── mode-yaml.js
│ │ │ │ ├── snippets/
│ │ │ │ │ ├── abap.js
│ │ │ │ │ ├── abc.js
│ │ │ │ │ ├── actionscript.js
│ │ │ │ │ ├── ada.js
│ │ │ │ │ ├── apache_conf.js
│ │ │ │ │ ├── applescript.js
│ │ │ │ │ ├── asciidoc.js
│ │ │ │ │ ├── assembly_x86.js
│ │ │ │ │ ├── autohotkey.js
│ │ │ │ │ ├── batchfile.js
│ │ │ │ │ ├── c9search.js
│ │ │ │ │ ├── c_cpp.js
│ │ │ │ │ ├── cirru.js
│ │ │ │ │ ├── clojure.js
│ │ │ │ │ ├── cobol.js
│ │ │ │ │ ├── coffee.js
│ │ │ │ │ ├── coldfusion.js
│ │ │ │ │ ├── csharp.js
│ │ │ │ │ ├── css.js
│ │ │ │ │ ├── curly.js
│ │ │ │ │ ├── d.js
│ │ │ │ │ ├── dart.js
│ │ │ │ │ ├── diff.js
│ │ │ │ │ ├── django.js
│ │ │ │ │ ├── dockerfile.js
│ │ │ │ │ ├── dot.js
│ │ │ │ │ ├── eiffel.js
│ │ │ │ │ ├── ejs.js
│ │ │ │ │ ├── elixir.js
│ │ │ │ │ ├── elm.js
│ │ │ │ │ ├── erlang.js
│ │ │ │ │ ├── forth.js
│ │ │ │ │ ├── ftl.js
│ │ │ │ │ ├── gcode.js
│ │ │ │ │ ├── gherkin.js
│ │ │ │ │ ├── gitignore.js
│ │ │ │ │ ├── glsl.js
│ │ │ │ │ ├── golang.js
│ │ │ │ │ ├── groovy.js
│ │ │ │ │ ├── haml.js
│ │ │ │ │ ├── handlebars.js
│ │ │ │ │ ├── haskell.js
│ │ │ │ │ ├── haxe.js
│ │ │ │ │ ├── html.js
│ │ │ │ │ ├── html_elixir.js
│ │ │ │ │ ├── html_ruby.js
│ │ │ │ │ ├── ini.js
│ │ │ │ │ ├── io.js
│ │ │ │ │ ├── jack.js
│ │ │ │ │ ├── jade.js
│ │ │ │ │ ├── java.js
│ │ │ │ │ ├── javascript.js
│ │ │ │ │ ├── json.js
│ │ │ │ │ ├── jsoniq.js
│ │ │ │ │ ├── jsp.js
│ │ │ │ │ ├── jsx.js
│ │ │ │ │ ├── julia.js
│ │ │ │ │ ├── latex.js
│ │ │ │ │ ├── lean.js
│ │ │ │ │ ├── less.js
│ │ │ │ │ ├── liquid.js
│ │ │ │ │ ├── lisp.js
│ │ │ │ │ ├── live_script.js
│ │ │ │ │ ├── livescript.js
│ │ │ │ │ ├── logiql.js
│ │ │ │ │ ├── lsl.js
│ │ │ │ │ ├── lua.js
│ │ │ │ │ ├── luapage.js
│ │ │ │ │ ├── lucene.js
│ │ │ │ │ ├── makefile.js
│ │ │ │ │ ├── markdown.js
│ │ │ │ │ ├── mask.js
│ │ │ │ │ ├── matlab.js
│ │ │ │ │ ├── maze.js
│ │ │ │ │ ├── mel.js
│ │ │ │ │ ├── mips_assembler.js
│ │ │ │ │ ├── mipsassembler.js
│ │ │ │ │ ├── mushcode.js
│ │ │ │ │ ├── mysql.js
│ │ │ │ │ ├── nix.js
│ │ │ │ │ ├── objectivec.js
│ │ │ │ │ ├── ocaml.js
│ │ │ │ │ ├── pascal.js
│ │ │ │ │ ├── perl.js
│ │ │ │ │ ├── pgsql.js
│ │ │ │ │ ├── php.js
│ │ │ │ │ ├── plain_text.js
│ │ │ │ │ ├── powershell.js
│ │ │ │ │ ├── praat.js
│ │ │ │ │ ├── prolog.js
│ │ │ │ │ ├── properties.js
│ │ │ │ │ ├── protobuf.js
│ │ │ │ │ ├── python.js
│ │ │ │ │ ├── r.js
│ │ │ │ │ ├── rdoc.js
│ │ │ │ │ ├── rhtml.js
│ │ │ │ │ ├── ruby.js
│ │ │ │ │ ├── rust.js
│ │ │ │ │ ├── sass.js
│ │ │ │ │ ├── scad.js
│ │ │ │ │ ├── scala.js
│ │ │ │ │ ├── scheme.js
│ │ │ │ │ ├── scss.js
│ │ │ │ │ ├── sh.js
│ │ │ │ │ ├── sjs.js
│ │ │ │ │ ├── smarty.js
│ │ │ │ │ ├── snippets.js
│ │ │ │ │ ├── soy_template.js
│ │ │ │ │ ├── space.js
│ │ │ │ │ ├── sql.js
│ │ │ │ │ ├── sqlserver.js
│ │ │ │ │ ├── stylus.js
│ │ │ │ │ ├── svg.js
│ │ │ │ │ ├── swift.js
│ │ │ │ │ ├── swig.js
│ │ │ │ │ ├── tcl.js
│ │ │ │ │ ├── tex.js
│ │ │ │ │ ├── text.js
│ │ │ │ │ ├── textile.js
│ │ │ │ │ ├── toml.js
│ │ │ │ │ ├── twig.js
│ │ │ │ │ ├── typescript.js
│ │ │ │ │ ├── vala.js
│ │ │ │ │ ├── vbscript.js
│ │ │ │ │ ├── velocity.js
│ │ │ │ │ ├── verilog.js
│ │ │ │ │ ├── vhdl.js
│ │ │ │ │ ├── xml.js
│ │ │ │ │ ├── xquery.js
│ │ │ │ │ └── yaml.js
│ │ │ │ ├── theme-ambiance.js
│ │ │ │ ├── theme-chaos.js
│ │ │ │ ├── theme-chrome.js
│ │ │ │ ├── theme-clouds.js
│ │ │ │ ├── theme-clouds_midnight.js
│ │ │ │ ├── theme-cobalt.js
│ │ │ │ ├── theme-crimson_editor.js
│ │ │ │ ├── theme-dawn.js
│ │ │ │ ├── theme-dreamweaver.js
│ │ │ │ ├── theme-eclipse.js
│ │ │ │ ├── theme-github.js
│ │ │ │ ├── theme-idle_fingers.js
│ │ │ │ ├── theme-iplastic.js
│ │ │ │ ├── theme-katzenmilch.js
│ │ │ │ ├── theme-kr_theme.js
│ │ │ │ ├── theme-kuroir.js
│ │ │ │ ├── theme-merbivore.js
│ │ │ │ ├── theme-merbivore_soft.js
│ │ │ │ ├── theme-mono_industrial.js
│ │ │ │ ├── theme-monokai.js
│ │ │ │ ├── theme-pastel_on_dark.js
│ │ │ │ ├── theme-solarized_dark.js
│ │ │ │ ├── theme-solarized_light.js
│ │ │ │ ├── theme-sqlserver.js
│ │ │ │ ├── theme-terminal.js
│ │ │ │ ├── theme-textmate.js
│ │ │ │ ├── theme-tomorrow.js
│ │ │ │ ├── theme-tomorrow_night.js
│ │ │ │ ├── theme-tomorrow_night_blue.js
│ │ │ │ ├── theme-tomorrow_night_bright.js
│ │ │ │ ├── theme-tomorrow_night_eighties.js
│ │ │ │ ├── theme-twilight.js
│ │ │ │ ├── theme-vibrant_ink.js
│ │ │ │ ├── theme-xcode.js
│ │ │ │ ├── worker-coffee.js
│ │ │ │ ├── worker-css.js
│ │ │ │ ├── worker-html.js
│ │ │ │ ├── worker-javascript.js
│ │ │ │ ├── worker-json.js
│ │ │ │ ├── worker-lua.js
│ │ │ │ ├── worker-php.js
│ │ │ │ ├── worker-xml.js
│ │ │ │ └── worker-xquery.js
│ │ │ ├── ace.css
│ │ │ ├── ace.js
│ │ │ └── pdf.css
│ │ ├── gitbook-plugin-advanced-emoji/
│ │ │ ├── LICENSE-IMAGES.md
│ │ │ ├── LICENSE.md
│ │ │ ├── emoji-book.css
│ │ │ └── emoji-website.css
│ │ ├── gitbook-plugin-anchor-navigation-ex/
│ │ │ ├── lib/
│ │ │ │ ├── config.js
│ │ │ │ ├── log.js
│ │ │ │ └── plugin.js
│ │ │ └── style/
│ │ │ └── plugin.css
│ │ ├── gitbook-plugin-anchors/
│ │ │ └── plugin.css
│ │ ├── gitbook-plugin-chart/
│ │ │ └── highcharts/
│ │ │ └── highcharts.js
│ │ ├── gitbook-plugin-disqus/
│ │ │ ├── plugin.css
│ │ │ └── plugin.js
│ │ ├── gitbook-plugin-edit-link/
│ │ │ └── plugin.js
│ │ ├── gitbook-plugin-emphasize/
│ │ │ └── plugin.css
│ │ ├── gitbook-plugin-expandable-chapters-small/
│ │ │ ├── expandable-chapters-small.css
│ │ │ └── expandable-chapters-small.js
│ │ ├── gitbook-plugin-fontsettings/
│ │ │ ├── fontsettings.js
│ │ │ └── website.css
│ │ ├── gitbook-plugin-github/
│ │ │ └── plugin.js
│ │ ├── gitbook-plugin-page-footer-ex/
│ │ │ ├── lib/
│ │ │ │ └── plugin.js
│ │ │ └── style/
│ │ │ └── plugin.css
│ │ ├── gitbook-plugin-prism/
│ │ │ ├── prism-coy.css
│ │ │ ├── prism-dark.css
│ │ │ ├── prism-funky.css
│ │ │ ├── prism-okaidia.css
│ │ │ ├── prism-solarizedlight.css
│ │ │ ├── prism-tomorrow.css
│ │ │ ├── prism-twilight.css
│ │ │ └── prism.css
│ │ ├── gitbook-plugin-search-plus/
│ │ │ ├── search.css
│ │ │ └── search.js
│ │ ├── gitbook-plugin-sectionx/
│ │ │ ├── sectionx.css
│ │ │ └── sectionx.js
│ │ ├── gitbook-plugin-sharing-plus/
│ │ │ └── buttons.js
│ │ ├── gitbook-plugin-splitter/
│ │ │ ├── splitter.css
│ │ │ └── splitter.js
│ │ ├── gitbook.js
│ │ ├── style.css
│ │ └── theme.js
│ ├── index.html
│ ├── package.json
│ ├── scripts/
│ │ └── build.sh
│ ├── search_plus_index.json
│ └── thanks.html
├── package.json
├── scripts/
│ └── build.sh
└── thanks.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .gitignore
================================================
# Referenced from https://github.com/github/gitignore/blob/master/Node.gitignore
_book
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
# other stuff
.DS_Store
Thumbs.db
# IDE configurations
.idea
.vscode
# build assets
/output
/dist
/dll
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: README.md
================================================
<img src="./img/pwa.png" width="50%">
# 《PWA 应用实战》
欢迎走进 PWA 世界!!
## 简介
本书围绕着 PWA 以及周边技术,从概念入手,以实战的方式给读者讲述如何编写 PWA,以及如何编写体验最好、速度最快、安全的 PWA 站点。
本书主要从以下几个部分讲述 PWA。
- 设计与体验
- 基础技术
- Service Worker
- 离线与缓存
- 用户留存
- 安全
- 性能
## 关于作者
作者:**百度 Web 生态团队**
百度 Web 生态团队是 2017 年组建,旨在帮助国内 Web 的发展,提升 Web 的用户体验,目前已有多个产品,如 [LAVAS](https://lavas.baidu.com) 和 [MIP](https://www.mipengine.org)。
## 为什么写这本书
我们团队从成立到现在,已有 2 年,推出 LAVAS 和 MIP 也是我们的尝试之一,积极参与 Web 方向上的技术讨论,也经常去国内的技术会议上进行分享,虽不能说真给国内 Web 发展带来了多大的贡献,但我们至少做了很多尝试。
因此,我们希望将我们过去两年积累的 PWA 方面的经验写下来,不仅帮我们自己梳理知识结构,也希望这本书能帮助到正在读的您。
## 为什么开源
PWA 它还在不断进步,本书的内容随时需要更新,电子书对于我们来说,迭代起来很方便。
同时也欢迎对 Web 和 PWA 有浓厚兴趣的读者加入我们,一起来维护这本书,我们由衷感谢。
## 版权
本书为开源书,读者可以自由地分享。只要遵守惟下列条件:
- **署名** — 您必须给出适当的署名,提供指向本许可协议的链接,同时标明是否(对原始作品)作了修改。您可以用任何合理的方式来署名,但是不得以任何方式暗示许可人为您或您的使用背书。
- **非商业性使用** — 您不得将本作品用于商业目的。
- **禁止演绎** — 如您再混合、转换、或者基于该书的创作,您不可以分发修改作品。
- **禁止附加限制** — 您不得使用法律术语或采用技术措施限制其他人做许可协议允许的事情。
本书采用 [知识共享署名-非商业性使用 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode) 进行许可
## 最后
本书还在勘误阶段,可能会存在一定的问题,请您在分享的过程一定要注明来源,帮助所有读者溯源,让他们能够获得最新的修改内容版本。如果您在阅读的过程发现了书中的问题,欢迎提交 [Issue](https://github.com/lavas-project/pwa-book/issues) 反馈给我们,或者直接发送 [Pull Request](https://github.com/lavas-project/pwa-book/pulls) 帮助我们修正,感谢您对本书的支持和贡献!
================================================
FILE: SUMMARY.md
================================================
# Summary
<!-- * [书写规范](standard.md)
* [前言](chapter01/11-why.md) -->
* [HOME](README.md)
* [第1章 PWA 介绍](chapter01.md)
* [1.1 为什么会出现 PWA](chapter01/1-how-was-pwa-born.md)
* [1.2 什么是 PWA](chapter01/2-what-is-pwa.md)
* [1.3 PWA 的核心技术](chapter01/3-what-are-key-techs.md)
* [1.4 PWA 的发展](chapter01/4-how-is-pwa-going.md)
* [1.5 PWA 的未来](chapter01/5-the-future-of-pwa.md)
* [1.6 你的第一个 PWA](chapter01/6-your-first-pwa.md)
* [第2章 设计与体验](chapter02.md)
* [2.1 什么是好的用户体验](chapter02/1-what-is-good-ux.md)
* [2.2 设计与技术](chapter02/2-design-and-tech.md)
* [2.3 App Shell](chapter02/3-app-shell.md)
* [2.4 骨架屏](chapter02/4-app-skeleton.md)
* [2.5 响应式布局](chapter02/5-responsive-design.md)
* [第3章 基础技术简介](chapter03.md)
* [3.1 Promise](chapter03/1-promise.md)
* [3.2 Async 函数](chapter03/2-async-function.md)
* [3.3 Fetch API](chapter03/3-fetch-api.md)
* [3.4 Cache API](chapter03/4-cache-api.md)
* [3.5 IndexedDB](chapter03/5-indexeddb.md)
* [第4章 Service Worker](chapter04.md)
* [4.1 Service Worker 简介](chapter04/1-service-worker-introduction.md)
* [4.2 Service Worker 注册](chapter04/2-service-worker-register.md)
* [4.3 Service Worker 工作原理](chapter04/3-service-worker-dive.md)
* [4.4 Service Worker 调试](chapter04/4-service-worker-debug.md)
* [第5章 离线与缓存](chapter05.md)
* [5.1 资源请求的拦截代理](chapter05/1-fetch-event-management.md)
* [5.2 本地存储管理](chapter05/2-local-storage-management.md)
* [5.3 资源请求响应策略](chapter05/3-respond-strategy.md)
* [5.4 预缓存方案](chapter05/4-precache.md)
* [5.5 使用 Workbox](chapter05/5-workbox.md)
* [第6章 用户留存](chapter06.md)
* [6.1 Web 应用清单](chapter06/1-manifest-json.md)
* [6.2 凭证管理 API](chapter06/2-credentials-api.md)
* [6.3 桌面通知](chapter06/3-notification-api.md)
* [6.4 网络推送](chapter06/4-web-push-api.md)
* [6.5 Payment Request API](chapter06/5-payment-request-api.md)
* [第7章 安全](chapter07.md)
* [7.1 使用 HTTPS 保护站点安全](chapter07/1-https.md)
* [7.2 内容安全策略](chapter07/2-CSP.md)
* [7.3 同源策略](chapter07/3-policy.md)
* [7.4 典型的安全漏洞](chapter07/4-vulnerability.md)
* [第8章 性能](chapter08.md)
* [8.1 加载性能 ](chapter08/1-loading-performance.md)
* [8.2 渲染性能 ](chapter08/2-rendering-performance.md)
* [第9章 PWA 与搜索](chapter09.md)
* [9.1 搜索引擎收录](chapter09/1-search-engine-index.md)
* [9.2 PWA 与 AMP/MIP](chapter09/2-pwa-and-amp-and-mip.md)
* [9.3 全站 AMP/MIP](chapter09/3-whole-site-amp-and-mip.md)
* [9.4 AMP/MIP 预加载 PWA](chapter09/4-preload-pwa.md)
* [附录1 使用 Lighthouse 测评 PWA](appendix01.md)
* [1 使用 Lighthouse 测评 PWA](appendix01/1-lighthouse.md)
* [2 Lighthouse 评分指南](appendix01/2-lighthouse-score-guide.md)
* [3 Lighthouse 使用案例](appendix01/3-lighthouse-case.md)
* [感谢](thanks.md)
================================================
FILE: appendix01/1-lighthouse.md
================================================
## 使用 Lighthouse 测评 PWA
Lighthouse 是 Google Chrome 推出的一个开源自动化工具,能够对 PWA 多方面的效果指标进行评测,并给出最佳实践的建议以帮助开发者改进 PWA 的质量。它的使用方法也非常简单,我们只需要提供一个要测评的网址,它将针对此页面运行一系列的测试,然后生成一个有关页面性能的报告。通过报告我们就可以知道需要采取哪些措施来改进应用的性能和体验。
### 使用入门
针对不同的适用场景,我们可以通过多种方式来安装并使用 Lighthouse:
- Chrome 浏览器插件。Chrome 插件的形式提供了更加友好的用户界面,方便读取报告。
- Chrome DevTools。该工具集成在最新版本的 Chrome 浏览器中,无需安装即可使用。
- Lighthouse CLI 命令行工具。方便将 Lighthouse 集成到持续集成系统中。
- 编程的方式。我们也能通过 Node.js 模块引入 Lighthouse 工具包,以编程的形式来使用它。
下面我们依次介绍上述这几种使用方法。
### Chrome 插件
下载 Google Chrome 52 或更高版本,并安装 [Ligthouse Chrome 插件](https://chrome.google.com/webstore/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk)。打开需要进行测评的页面,并点击 Chrome 工具栏上的 Lighthouse 图标如下图:

如果在工具栏上没有看到此图标,它可能隐藏在 Chrome 的主菜单中。点击此图标后,我们会看到一个展开菜单,如下图

在 `Options` 选项里,可以选择需要的测评项,包括性能、无障碍访问性、最佳实践、SEO、PWA,默认情况下,选择全部即可。

点击 Generate report 按钮以针对当前打开的页面运行 Lighthouse 测试。
在完成测评后,Lighthouse 将打开一个新标签,并在页面的结果上显示一个报告。

在这里,我们就能看到关于 PWA, Performance, Accessibility, Best Practices 四个方面存在的问题以及相关建议。根据这些去优化你的站点吧!
### Chrome DevTools
在最新版本的 Chrome 浏览器中,Lighthouse 已经直接集成到了调试工具 DevTools 中了,因此不需要进行任何安装或下载。我们先打开需要进行测试的页面,打开 Chrome DevTools,选择 `Audits` 面板,就能看到 Lighthouse 工具的一些配置选项,选择需要的配置后,点击 `Run audits`,工具就会对当前页面进行性能的测评。

### 命令行工具(Lighthouse CLI)
上述两种工具提供了较为基础、快捷的使用方式,针对进阶的高级开发者,Lighthouse 的命令行工具更为合适。它提供了多种详细的使用参数,通过参数的配置,能够更灵活地进行测评并输出报告结果。此外,在一些自动化的持续集成场景中,命令行工具显得十分有用。
#### 安装
安装 [Node](https://nodejs.org/zh-cn/download/),需要 Node 8 LTS(8.9) 及以上版本。
以全局方式安装:
```shell
npm install -g lighthouse
# or use yarn:
# yarn global add lighthouse
```
#### 运行 CLI
针对一个页面运行 `lighthouse` 命令,进行测评:
```shell
lighthouse https://www.example.com/
```
> 注意:
> CLI 相较于插件和 DevTools 存在一些尚未修复的问题,比如对 Ubuntu 系统支持不好,会抛 NO_FCP 的错误。对于某些网站支持不好,这些网站可能存在以下特征导致 NO_FCP 错误的发生:
> 1. 初始渲染的时候页面不包含文本,而只包含一个 spinner
> 2. 你的站点的启动和渲染主要内容在 5-10s 的延迟之后发生
> [点击](https://github.com/GoogleChrome/lighthouse/issues/6154)查看最新解决方案
默认情况下,运行报告将以 html 的格式输出在当前目录的 `./<HOST>_<DATE>.report.html` 文件中,如上述命令结果将存储在 www.example.com_2019-04-02_16-51-15.report.html。我们也可以指定输出的格式与路径,如:
```shell
lighthouse https://example.com/ --output html --output-path ./report.html
```
如果需要将结果在标准输出流中以 json 格式输出:
```shell
lighthouse https://example.com/ --output json
```
将结果以 json 文件的形式输出到本地目录:
```shell
lighthouse https://example.com/ --output json --output-path ./myfile.json
```
输入 `--help` 选项可以查看可用的输入、输出选项
```shell
lighthouse --help
```
#### 生命周期
Lighthouse 运行测评的过程有一套完整的生命周期,可以划分成三个主要流程:首先是 Collecting 流程,这一步会调用内置的 Driver,其作用是通过 Chrome DevTools Protocol 调起浏览器,并创建新的 tab 请求待测评的站点,通过浏览器采集站点数据并将结果(称之为 Artifacts)保存在本地临时目录。然后进入 Auditing 流程,读取 Artifacts 数据,根据内置的评判策略逐条进行检查并计算出各项的数字形式得分。最后进行 Report 流程,将评分结果按照 PWA、性能、无障碍访问、最佳实践等纬度进行划分,以 JSON、HTML 等格式输出。如下图:

命令行工具基于此提供了生命周期的选项,我们可以让 CLI 只运行整个测评过程的一个或多个特定生命周期。比如,使用 --gather-mode(-G)只进行资源采集的生命周期,命令行工具将会启动浏览器,采集被测试站点的相关数据,并将结果以 json 的形式存储到本地,默认是 `./latest-run/` 目录,然后退出进程:
```shell
lighthouse https://example.com/ -G
```
如果想要跳过浏览器的交互,直接从本地读取页面的临时数据,运行测评和产出结果报告,则可以使用 --audit-mode(-A),默认将从 `./latest-run/` 目录读取:
```shell
lighthouse https://example.com/ -A
```
两个选项同时使用,就会运行整个测评的生命周期,与直接运行 lighthouse 命令相比,会在当前目录保存一份测试站点的数据。
```shell
lighthouse https://example.com -GA
```
如果不想使用默认的 `./latest-run/` 目录,我们也能自定义站点的 json 数据的保存目录,如:
```
lighthouse -GA=./mycustomfolder https://example.com
```
### 编程的方式使用 Lighthouse
除了上述几种方式之外,Lighthouse 也提供了 NPM 包,我们能够以 Node.js 模块的形式引入到项目代码中,结合另一个 NPM 模块 `chrome-launcher` 用于调起浏览器,就可以随心所欲地以编程的方式使用了。
首先要在项目中安装依赖模块:
```shell
npm install lighthouse chrome-launcher
# yarn add lighthouse chrome-launcher
```
在使用 lighthouse 接口之前,需要先用启动一个 chrome,所以我们可以封装一个名为 launchChromeAndRunLighthouse 的方法,来完成这两步。chromeLauncher 工具包提供了 launch 方法,接收一个启动所需的参数 opts,这个参数用于配置 chrome 的启动环境、启动方式等,在返回的 promise 对象中,我们能获取到 chrome 实例。再将待测试 url 及 opts 提供给 lighthouse,测试完成后需要关掉 chrome 实例,然后就能拿到我们需要的测试报告 results 了。
```javascript
const lighthouse = require('lighthouse')
const chromeLauncher = require('chrome-launcher')
function launchChromeAndRunLighthouse(url, opts, config = null) {
return chromeLauncher.launch({chromeFlags: opts.chromeFlags}).then(chrome => {
opts.port = chrome.port
return lighthouse(url, opts, config).then(results => {
return chrome.kill().then(() => results)
})
})
}
const opts = {
chromeFlags: ['--show-paint-rects']
}
launchChromeAndRunLighthouse('https://example.com', opts).then(results => {
// Use results!
})
```
值得一提的是这里的 result 包含了多种类型的数据格式,如 `results.lhr` 是 javascript 对象(Lighthouse Result Object),而 `results.report` 则是用于 HTML/JSON/CSV 输出结果的字符串,可以按需选择。
#### 性能报告
对于一些只关注应用性能的使用场景,可以用 onlyCategories 配置项来设定只运行性能这个维度的测试:
```javascript
// ...
const flags = {onlyCategories: ['performance']}
launchChromeAndRunLighthouse(url, flags).then(results => {
// Use performance results!
})
```
除此之外,我们可以自定义自己需要的测评维度,Lighthouse 的 [Github 仓库](https://github.com/GoogleChrome/lighthouse/tree/master/docs/recipes/custom-audit)提供了一些自定义的示例和方法。
注意,用编程的方式使用 lighthouse 时,在上述代码的 flags 参数的使用上,与 CLI 的方式有所区别。考虑到以 node 模块引入的情况下,开发者自己来控制这些选项更为简单和灵活,部分 flag 选项值只在 CLI 的模式下生效。node 模块和 CLI 中都能使用的选项值,可以参考 [官方的定义](https://github.com/GoogleChrome/lighthouse/blob/888bd6dc9d927a734a8e20ea8a0248baa5b425ed/typings/externs.d.ts#L82-L119)。
#### 开启测试日志
如果运行期间需要查看测试的日志,可以引入 lighthouse-logger 模块,它支持打印不同级别的日志。用法如下:
```javascript
const log = require('lighthouse-logger')
const flags = {logLevel: 'info'}
log.setLevel(flags.logLevel)
launchChromeAndRunLighthouse('https://example.com', flags).then(...)
```
### 自定义配置
你可能已经注意到 lighthouse 在调用的时候还有第三个参数:`lighthouse(url, opts, config)`,这个 config 参数能够帮助我们用更细粒度的控制整个测评。这种细粒度的配置在 CLI 中也是支持的,只是使用方式略有区别:
我们可以指定一个配置文件 custom-config.js 如下,它在继承默认的配置的基础上进行了一定的自定义:
```javascript
module.exports = {
extends: 'lighthouse:default',
settings: {
onlyAudits: [
'first-meaningful-paint',
'speed-index-metric',
'estimated-input-latency',
'first-interactive',
'consistently-interactive',
],
},
}
```
如果使用 CLI,需要用 --config-path 指向该配置文件:
```bash
lighthouse --config-path=path/to/custom-config.js https://example.com
```
对于 Node 模块的形式,也基本相似,将配置对象当做 lighthouse 方法的第三个参数传入即可:
```javascript
const lighthouse = require('lighthouse')
const config = require('./path/to/custom-config.js')
lighthouse('https://example.com/', {port: 9222}, config)
```
如果评测过程中有这种比较高阶的配置需求,可以详细了解相关的[属性文档](https://github.com/GoogleChrome/lighthouse/blob/master/docs/configuration.md),这里我们就不作展开了。
除了这些官方提供的工具之外,一些开源项目也将 Lighthouse 进行了集成。例如使用 Webpack 构建的应用,可以使用 `webpack-lighthouse-plugin`,在构建的过程中完成 Lighthouse 测评;`lighthouse-mocha-example` 则能够在 Mocha 的测试用例中增加 Lighthouse 的测评项等等,整个工具生态日渐繁荣,相信对于我们测评 PWA 性能,构建更完美的 PWA 大有裨益。
================================================
FILE: appendix01/2-lighthouse-score-guide.md
================================================
## Lighthouse 评分指南
使用 Lighthouse 对网站进行测评后,我们会得到一份评分报告,它包含了性能(Performance),PWA(Progressive Web App),访问无障碍(Accessibility),最佳实践(Best Practice),搜索引擎优化(SEO)等几个部分。这里我们将介绍一下 Lighthouse 是如何计算这几个评分的。

### 性能评分
性能评分的分值区间是0到100,如果出现0分,通常是在运行 Lighthouse 时发生了错误,如果反复出现了0分的情况,可以在 Lighthouse 的 GitHub 仓库去[反馈 bug](https://github.com/GoogleChrome/lighthouse/issues/new)。满分100分代表了站点已经达到了98分位值的数据,而50分则是75分位值的数据。
#### 影响评分的性能指标
性能测试指标分成了 Metrics,Diagnostic,Opportunities 三部分。通常情况下,只有 Metrics 部分的指标项会对分数产生直接影响,Lighthouse 会衡量以下性能指标项:
- 首次内容绘制(First Contentful Paint)。即浏览器首次将任意内容(如文字、图像、canvas 等)绘制到屏幕上的时间点。
- 首次有效绘制(First Meaningful Paint)。衡量了用户感知页面的主要内容(primary content)可见的时间。对于不同的站点,首要内容是不同的,例如:对于博客文章,标题及首屏文字是首要内容,而对于购物网站来说,图片也会变得很重要。
- 首次 CPU 空闲(First CPU Idle)。即页面首次能够对输入做出反应的时间点,其出现时机往往在首次有效绘制完成之后。该指标目前仍处于实验阶段。
- 可交互时间(Time to Interactive)。指的是所有的页面内容都已经成功加载,且能够快速地对用户的操作做出反应的时间点。该指标目前仍处于实验阶段。
- 速度指标(Speed Index)。衡量了首屏可见内容绘制在屏幕上的速度。在首次加载页面的过程中尽量展现更多的内容,往往能给用户带来更好的体验,所以速度指标的值约小越好。
- 输入延迟估值(Estimated Input Latency)。这个指标衡量了页面对用户输入行为的反应速度,其基准值应低于 50ms。
#### 性能指标评分的计算
每一项性能指标对评分的贡献都有其计算逻辑,Lighthouse 会将原始的性能值映射成为 0-100 之间的数字。评分使用 HTTPArchive 上的真实站点性能数据作为样本,统计出对数正态分布。例如,FMP(First Meaningful Paint)的原始值是从页面初始化开始到主要内容渲染成功的耗时,根据真实站点的数据,顶级性能的站点的 FMP 值约为 1220ms,这个值会被映射成 Lighthouse 的 99 分。
针对不同的评分,Lighthouse 用了不同的颜色进行标注,分值区间和颜色的对应关系如下:
- 0 - 49(慢):红色
- 50 - 89(平均值): 橙色
- 90 - 100(快): 绿色
#### 性能评分的分配权重
各个指标对性能评分的贡献并不相同,Lighthouse 提供了[评分详情表](https://docs.google.com/spreadsheets/d/1Cxzhy5ecqJCucdf1M0iOzM8mIxNc7mmx107o5nj38Eo/edit#gid=0),形式如下图,来查阅具体权重分配情况。权重较大的指标,对性能评分的影响更大一些,最终的总体性能评分是这些性能指标分数的加权平均值。我们可以用这个分数计算的[表格](https://docs.google.com/spreadsheets/d/1dXH-bXX3gxqqpD1f7rp6ImSOhobsT1gn_GQ2fGZp8UU/edit?ts=59fb61d2#gid=283330180)作为参考,以了解不同的指标是如何影响最终评分的。

从表中可以看出,性能的指标项权重分配如下:
- 3X - 首次内容绘制
- 1X - 首次有效绘制
- 2X - 首次 CPU 空闲
- 5X - 可交互时间
- 4X - 速度指标
- 0X - 输入延迟估值
这些权重的分配方式仍在探究之中,Lighthouse 的团队也在努力试图收集更多站点的数据样本,来将这套计算方法规范化。
#### 减少性能评分的波动
当我们使用 Lighthouse 对实际站点进行测试时,难以避免会有一些影响评分的变量存在。每次访问,站点可能会加载不同的广告、脚本等,网络状况也可能不同。杀毒软件、浏览器插件以及一些其他对页面加载有干预的程序都可能导致评分的较大波动。所以,我们应尽量避免上述因素的影响,以得到更加一致性的数据。可以考虑使用持续集成系统或者第三方服务(如 [WebPageTest](https://webpagetest.org/easy))进行测试,往往会有更准确的数据。
#### 提高性能评分
前面提到性能评分分为了三部分,Metrics 部分的指标项会直接影响分数,可以作为我们的主要参考点。另外的两部分中, Opportunities 提供了详细的建议和文档,来解释低分的原因,帮助我们具体进行实现和改进。Diagnostics 部分的列表项则为进一步改善性能的实验和调整,给出了指导。这两者不会纳入分数的计算。
### PWA 评分
#### 基准指标项
PWA(Progressive Web App)评分的分值区间也是0-100。Lighthouse 使用 PWA 基准检查项列表(Baseline PWA Checklist)进行测评,测评结果将这些指标项分成了四个类别,共包含12个自动测试项和3个手动测试项,其中各个自动测试项的评分权重是相同的。PWA 的评测指标对我们来说非常重要,我们可以从这四个类别详细了解一下基准指标项。
##### 快速可靠
1. 页面在移动网络条件下能够快速加载。
2. 在离线条件下页面能够返回状态码200。这里我们可以通过 Service Worker 来实现离线可用。
3. start_url 在离线条件下返回状态码200。start_url 是前面章节我们提到过的 manifest.json 中的一个属性,它指定了用户打开该 PWA 时加载的 URL。
##### 可安装
1. 始终使用 HTTPS。
2. 注册 Service Worker 来缓存页面以及 start_url。
3. 使用 manifest 文件来实现安装 PWA 的需求,浏览器能够主动通知用户将应用添加到桌面,增加留存率。
##### PWA 优化
1. 将 HTTP 流量重定向到 HTTPS。
2. 配置自定义启动画面。
3. 设置地址栏主题颜色。
4. 页面内容针对视口大小自适应,对移动用户的展示更友好。
5. 使用了 `<meta name="viewport">` 标签,并设置了 width 或 initial-scale 属性。
6. 当 JavaScript 文件不可用时,提供降级措施,页面能显示基本内容而不出现白屏。
##### 手动测试项
1. 站点跨浏览器可用,如主流浏览器 Chrome, Edge, Firefox 及 Safari 等。
2. 页面间切换流畅,即使在较差的网络环境下,切换动画也应该简洁顺畅,这是提高用户感知体验的关键。
3. 保证每个页面都有独一无二的 URL,能够在新的浏览器窗口打开,且方便在社交媒体上进行分享。
#### 示范性指标项
除了上述基准指标项之外,为了让 PWA 的体验更加完美,还有 Lighthouse 未实现检查的进阶指标,也就是可以作为示范性参考的 PWA 的指标,这些指标大多需要人工进行确认,它们也分成了几个类别,我们可以针对性地改进 PWA。
##### 可索引 & 社交支持
1. 站点的内容可以被 Baidu,Google 等搜索引擎收录索引。
2. 提供必要的 `Schema.org` 标记数据,搜索引擎都会依据这些标记来优化搜索结果,让人们更容易找到正确的网页。
3. 提供必要的社交标记数据,如 Twitter 的 `<meta name="twitter:card" content="summary" />` 标签,能够便于被这些社交站点的爬虫抓取。
4. 提供 Canonical URL,一般适用于页面内容能使用多个 URL 访问的场景。
5. 页面使用 History API,对于单页应用,确保 URL 不要使用片段标识符,如 `https://example.com/#!user/26601` 中 #!后面的部分。
##### 用户体验
1. 当页面加载时,确保页面内容不跳动或闪烁。
2. 从详情页返回列表页时,记录之前的滚动位置。
3. 输入文字时,键盘不会挡住输入框。
4. 在 Standalone 模式下(从主屏幕启动的情况),页面内容可分享。
5. 在不同的移动设备及桌面电脑上,站点能够自适应。
6. 不要过度使用安装 APP 的推送。
7. 在适当的时候显示添加到桌面的推送,避免打断用户正常使用流程。
##### 性能
1. 首次加载速度尽可能快,即使是在慢速 3G 移动网络下,可交互时间也应该在 5s 以内(在基准指标中,这个时间是 10s)。
##### 缓存
1. 站点使用 cache-first (缓存优先)的缓存策略。尽可能地使用这种策略,确保在慢网络环境和离线环境的加载速度始终相当。
2. 用户处于离线状态时,应当适当地给出通知。可以使用 Network Information API 来实现。
##### 推送通知
1. 告知用户通知功能的使用背景和原因,保证得到许可。
2. 建议用户开启推送通知的 UI 应当尽量友好,不能太强势。
3. 站点显示权限请求时,屏幕的其他主体内容应当适度变暗,让用户更清楚地看到站点需要开启通知权限的理由。
4. 推送通知的内容应当及时、准确,且具有较强的关联性。
5. 给用户提供开启、关闭通知的操作权限。
##### 附加功能点
1. 使用 Credential Management API,帮助用户实现跨设备登录。
2. 使用 Payment Request API,帮助用户方便地调起更友好的原生界面(native UI)进行支付操作。
### 访问无障碍评分
访问无障碍评分的分值由相关指标的加权平均值计算而来。可以在[评分详情](https://docs.google.com/spreadsheets/d/1Cxzhy5ecqJCucdf1M0iOzM8mIxNc7mmx107o5nj38Eo/edit#gid=0)查阅每项指标的具体权重。同理,较大权重的指标项对分数的影响较大。无障碍性的每个指标项测试结果为`pass`或者`fail`,与性能指标项的计算方式不同,当页面只是部分通过某项指标时,页面的这项指标将不会得分。例如,如果页面中的一些元素有`屏幕阅读器友好`的命名,而其他的元素没有,那么这个页面的 screenreader-friendly-names 指标项得分为0。
### 最佳实践评分
最佳实践评分的分数区间为0-100。影响这项评分的指标项的权重都是相同的。如果我们想计算某个指标项对总体评分的贡献,用指标项的数量除以100即可。指标项和权重同样可以参考上文提供的评分详情表,我们可以结合测评结果一一进行优化。
================================================
FILE: appendix01/3-lighthouse-case.md
================================================
## Lighthouse 使用案例
前面两节我们对 Lighthouse 的使用方法和性能评分计算的原理有了一定的了解,下面我们使用 Lighthouse 对一个实际站点 [https://lavas.baidu.com/](https://lavas.baidu.com/) 进行测试,看看 Lighthouse 能对站点提供哪些方面的改进指导。
### 对站点进行测试
为了更直观地展示测试结果,我们使用 Chrome Devtools 中集成的 Lighthouse 工具进行测试。
1. 在 Chrome 中打开 https://lavas.baidu.com/
2. 打开 Chrome Devtools,并选择 `Audits` 面板
3. Lighthouse 提供了不同的模拟网络环境选项,我们可以根据需求进行选择,点击 `Run Audits` 开始测试。
### 测试结果
稍等片刻,Chrome 会新建一个新的会话窗口,将包含各项评分指标的 Lighthouse 测试报告展示出来,我们可以依次来看:
#### 总体评分
报告的最上方是总体评分,如下图,可以看到站点在性能、PWA、最佳实践及 SEO 方面的表现还是不错的,在无障碍访问指标项上,评分稍低,需要重点进行改进。

具体到每一个方面,都能查看指标项的细节:
#### Performance 性能评分
在选择网络环境为 `No throttling` 的条件下,FCP,FMP,首次交互时间等指标都能得到详细的统计,`View Trace` 还能看到页面每一帧的渲染过程。

Metrics 部分的指标与性能评分关联紧密,可以看到各个指标项都显示绿色,意味着它们都得到了 90-100 的分值。Opportunities 和 Diagnostics 部分的指标项虽然不纳入评分的计算,但也给我们改进提供了参考。可以看到站点存在字体文件加载优化、静态资源缓存等问题。

#### PWA 评分
在基准指标检查中,有一项未通过:Lighthouse 建议站点需要对 JavaScript 脚本不能正确加载的情况作降级处理。我们需要改进的是,页面的核心渲染脚本加载失败时,仍能够展示基本的静态内容,而不是空白页面,这一点在客户端渲染的 SPA 应用中尤其需要引起重视。除此之外,我们可以看到站点的其他 PWA 自动检查项都已经通过,然后手动检查一下跨浏览器兼容性、页面切换流畅度、页面 URL 等检查项即可。

#### 无障碍访问评分

此项评分偏低,仅得到62分,说明站点在这部分存在的问题较多。展开可以看到问题的详情,如 DOM 元素使用不规范、结构不合理,页面颜色对比度不够,Meta 信息使用不正确等等,如果需要提高站点的无障碍可访问性,我们可以从这些方面对症下药进行完善。
#### Best Practices 最佳实践评分
站点在这部分的表现中规中矩,通过了15个检查项中的12个,暴露了3个问题,资源推荐使用 http2,跨域的跳转链接需要使用 rel 标识,不能使用废弃的 API。不通过检查测试,开发中我们很难注意到这些细节问题。

#### SEO 搜索引擎优化评分

结果显示站点的 robots.txt 文件缺失,需要改进。
### 小结
通过上述简单演示,我们对站点的各项指标就能有较为直观的了解,评分高的指标项值得积累开发经验,评分低的指标,对照着测试报告给出的建议,我们可以逐一完善。
Lighthouse 是一个开源的项目,主要由 Google Chrome 团队开发维护,它包含了强大的功能和较为繁荣的周边生态,本章只是就其使用方式、打分机制、测评指标等作了简要的介绍,在实际的 PWA 工程中,我们可能会运用到更详细的配置选项和测试参数,或者需要深入了解其运行原理,这些都可以通过 Github 上的项目文档进一步学习和实践。工欲善其事,必先利其器,有了 Lighthouse 这把利器的帮助,我们打造体验更好的 PWA 站点将不再是难事!
================================================
FILE: appendix01.md
================================================
# 使用 Lighthouse 测评 PWA
完成了 PWA 站点的开发之后,我们需要对站点进行评估和测试,了解其性能是否达标、是否符合 PWA 规范等。本章中,我们将介绍一款 Web App 的测评工具 —— Lighthouse 的使用,通过它的帮助,我们能够进一步了解 PWA 的缺陷和不足,有针对性地对站点进行完善和改进。
================================================
FILE: book.json
================================================
{
"title": "PWA 应用实战",
"description": "PWA 开发实战",
"author": "lavas <lavas@baidu.com>",
"language": "zh-hans",
"gitbook": "3.2.3",
"root": ".",
"structure": {
"readme": "README.md"
},
"plugins": [
"-lunr",
"-search",
"anchors",
"search-plus",
"-livereload",
"simple-page-toc",
"prism",
"prism-themes",
"-highlight",
"advanced-emoji",
"include-codeblock",
"-sharing",
"sharing-plus",
"ace",
"emphasize",
"splitter",
"page-footer-ex",
"expandable-chapters-small",
"sectionx",
"local-video",
"anchor-navigation-ex",
"favicon",
"todo",
"edit-link",
"3-ba",
"chart",
"disqus",
"github"
],
"pluginsConfig": {
"edit-link": {
"base": "https://github.com/lavas-project/pwa-book/edit/master",
"label": "编辑此页"
},
"theme-default": {
"showLevel": false
},
"prism": {
"css": [
"prismjs/themes/prism-okaidia.css"
]
},
"include-codeblock": {
"template": "ace",
"unindent": true,
"edit": true
},
"sharing": {
"douban": true,
"facebook": false,
"google": false,
"hatenaBookmark": false,
"instapaper": false,
"line": false,
"linkedin": true,
"messenger": false,
"pocket": false,
"qq": false,
"qzone": false,
"stumbleupon": false,
"twitter": false,
"viber": false,
"vk": false,
"weibo": true,
"whatsapp": false,
"all": [
"facebook",
"google",
"twitter",
"weibo",
"instapaper",
"linkedin",
"pocket",
"stumbleupon",
"qq",
"qzone"
]
},
"page-footer-ex": {
"copyright": "Copyright ©2019 [百度 Web 生态团队](https://github.com/lavas-project) <br/> [](http://creativecommons.org/licenses/by-nc-nd/4.0/) 本书采用 [知识共享署名-非商业性使用 4.0 国际许可协议](http://creativecommons.org/licenses/by-nc-nd/4.0/) 进行许可。<br/> 本书仍在勘误阶段,请勿以任何形式私自传播,我们保留对本书的版权",
"markdown": true,
"update_label": "该文件修订时间:",
"update_format": "YYYY-MM-DD HH:mm:ss"
},
"anchor-navigation-ex": {
"showLevel": false,
"isRewritePageTitle": false,
"tocLevel1Icon": "fa fa-hand-o-right",
"tocLevel2Icon": "fa fa-hand-o-right",
"tocLevel3Icon": "fa fa-hand-o-right"
},
"sectionx": {
"tag": "b"
},
"favicon": {
"shortcut": "favicon.ico",
"bookmark": "favicon.ico"
},
"disqus": {
"shortName": "lavas-project-github-io"
},
"terminal": {
"copyButtons": true,
"fade": false,
"style": "flat"
},
"3-ba": {
"token": "c45e843439f6364ead89fd505f1abb68"
},
"github": {
"url": "https://github.com/lavas-project/pwa-book"
}
}
}
================================================
FILE: chapter01/1-how-was-pwa-born.md
================================================
# 为什么会出现 PWA
PWA 是 Progressive Web Apps 的缩写,翻译为渐进式网络应用。早在 2014 年, W3C 就公布过 Service Worker 的相关草案,但是其在生产环境被 Chrome 支持是在 2015 年。因此,如果我们把 PWA 的关键技术之一 Service Worker 的出现作为 PWA 的诞生时间,那就应该是 2015 年。在关注 PWA 是什么之前,先来了解一下为什么会出现 PWA,它是在什么样的背景下被提出来的。
回顾一下历史,在 2015 年之前的那段时间,作为前端开发人员,我们主要精力花在哪里,对于我来说,移动站点的性能优化是投入精力很大的一部分,例如提升首屏速度,动画的流畅度,经过一段时间的优化,性能确实有不小的提升,但是无论怎么优化,还是比 Native App 要差很多,始终无法突破移动设备上 WebView 给 Web 的枷锁,这就是我们想说的第一个问题,**Web 的用户体验**。
除开用户体验问题之外,还有一个非常重要的问题,那就是**用户留存**。Native App 安装完毕后会在用户手机桌面上有一个入口,让用户打开 App 只需一次点击,而 Web App 在移动时代最主要的入口还是搜索引擎,用户从浏览器到站点需要经过搜索引擎,如果想访问上次同样的内容甚至还需要记住上次的搜索词,用户也可以记住 URL 并进行输入,但这些对于移动用户来说,无疑成本巨大,这就导致 Web 站点和用户之间的粘性非常脆弱。Native App 还能够通过发送通知让用户再次回到应用中来,而 Web 没有这个能力。
最后要说的一个问题是 0 和 1 的问题,**Device API 的不完善**。Android 和 iOS 提供了非常丰富的设备 API,Native App 只需获取用户授权就可以使用,而在 Web App 中,WebView 没有提供这样的 API,完全没法使用,如果我们开发一个需要使用 NFC 的 App,你一定不会考虑 Web,因为近场通信 API 在 Web 中还没有。虽然在近年来,W3C 已经提出了很多新的标准,但是浏览器对于 Device API 的支持仍然很不完善。
Google 在一篇名为《[Why Build Progressive Web Apps](https://developers.google.com/web/ilt/pwa/why-build-pwa)》的文章中披露过这样的一组数据,Web 站点每个月的 UV 是 Native App 的 3 倍,然而用户在 Native App 花费的时间却是 Web 的 20 倍,如下图所示,这之间巨大的反差,和上面所说的三个原因息息相关。

就在这样的背景条件下,PWA 以及支撑 PWA 的一系列关键技术应运而生。
================================================
FILE: chapter01/2-what-is-pwa.md
================================================
# 什么是 PWA
Google 提出 PWA 的时候,并没有给它一个准确的定义,经过我们的实践和总结,
PWA 它不是特指某一项技术,而是应用多项技术来改善用户体验的 Web App,其核心技术包括 Web App Manifest,Service Worker,Web Push 等,用户体验才是 PWA 的核心。
PWA 主要特点如下:
* 可靠 - 即使在网络不稳定甚至断网的环境下,也能瞬间加载并展现
* 用户体验 - 快速响应,具有平滑的过渡动画及用户操作的反馈
* 用户黏性 - 和 Native App 一样,可以被添加到桌面,能接受离线通知,具有沉浸式的用户体验
PWA 本身强调**渐进式**(Progressive),可以从两个角度来理解渐进式,首先,PWA 还在不断进化,Service Worker,Web App Manifest,Device API 等标准每年都会有不小的进步;其次,标准的设计向下兼容,并且侵入性小,开发者使用新特性代价很小,只需要在原有站点上新增,让站点的用户体验渐进式的增强。
Google 在官网一篇名为《[Progressive Web App Checklist](https://developers.google.cn/web/progressive-web-apps/checklist)》的文章中给出了 PWA 的基准线,也给出了体验更好的示范性 PWA 的 Checklist,下面列出了 PWA 的最低要求。
* 站点需要使用 HTTPS
* 页面需要响应式,能够在平板和移动设备上都具有良好的浏览体验
* 所有的 URL 在断网的情况下有内容展现,不会展现浏览器默认页面
* 需要支持 Wep App Manifest,能被添加到桌面
* 即使在 3G 网络下,页面加载要快,可交互时间要短
* 在主流浏览器下都能正常展现
* 动画要流畅,有用户操作反馈
* 每个页面都有独立的 URL
## PWA 的特性
PWA 本质上还是 Web App,借助了新技术具备了一些 Native App 的特性,所以它兼具 Web App 和 Native App 的优点,同时在安全、体验和用户黏性三个方面都有很大的提升。总结下来,PWA 具有如下特性。
* **渐进式** - 适用于所有浏览器,因为它是以渐进式增强作为宗旨开发的
* **连接无关性** - 能够借助 Service Worker 在离线或者网络较差的情况下正常访问
* **类原生应用** - 由于是在 App Shell 模型基础上开发,因此应具有 Native App 的交互,给用户 Native App 的体验
* **持续更新** - 始终是最新的,无版本和更新问题
* **安全** - 通过 HTTPS 协议提供服务,防止窥探,确保内容不被篡改
* **可索引** - manifest 文件和 Service Worker 可以让搜索引擎索引到,从而将其识别为『应用』
* **黏性** - 通过推送离线通知等,可以让用户回流
* **可安装** - 用户可以添加常用的 Web App 到桌面,免去到应用商店下载的麻烦
* **可链接** - 通过链接即可分享内容,无需下载安装
PWA 的这些新特性给 Web App 注入了活力,而 Native App 却没能很好的弥补自己的劣势。对于 Native App来说,最大的痛点是由于其天生封闭的基因,内容无法被索引,这会导致 Native App 很难被分发,例如,用户想知道红烧肉的做法,还需要先知道应用的名称,下载应用之后才能获取内容,这个流程十分不合理,根据 Google 的统计,用户每个月安装的应用个数约等于 0,再加上用户 80% 的时间被 Top3 的超级应用占据,应用分发成本也因此越来越高。相对于 Native App 的封闭,PWA 完全是开放的,PWA 的所有技术都是遵循开放的标准,因此能够被浏览器快速支持,被开发者接受。
下表列出了传统 Web App,Native App 和 PWA 在各特性的对比。
| |是否可安装|是否可链接访问|用户体验|用户黏性|
|---|---|---|---|---|
|传统 Web|无法安装|可链接访问|体验一般|黏性差|
|Native App|可安装|不可链接访问|体验好|黏性强|
|PWA|可安装|可链接访问|体验好|黏性强|
PWA 能给站点体验带来飞跃式的提升,我们可以用移动设备上的浏览器,如 Chrome, 访问 [LAVAS 官网](https://lavas.baidu.com) 体验一下,并添加到桌面,还可以在断网的情况下使用。现在在国内也有很多 PWA 站点,比如饿了么和新浪微博的移动版,不用耗费流量下载几十兆的应用,就能有和原生应用一样的体验,不妨尝试一下。
在后面的章节中,我们会从体验、安全和性能三个角度来分析如何打造一个好的 PWA。
================================================
FILE: chapter01/3-what-are-key-techs.md
================================================
# PWA 的核心技术
前文提到,PWA 的核心是用户体验,能让 PWA 达到原生应用的体验并不仅仅依赖于某一项技术,而是多管齐下,进行改进,从而在安全、性能和体验上都获得很大的提升。下面将简单介绍几个 PWA 应用中经常使用到的技术,后面的章节会进行更详细的讲解。
## Web App Manifest
Web App 是依附于浏览器的,在一般情况下,用户需要先打开浏览器,然后输入网址或点击收藏的书签,才能够访问到网页内容,相比在移动设备主屏上拥有一席之地的 Native App,Web App 使用起来太麻烦了,这也是 Native App 相比 Web App 用户黏性更好的原因之一。因此 Web App 也需要这个能力,Web 应用清单(Web App Manifest)能够帮助我们实现这一点,也是 PWA 最核心的功能之一,开发者可以定义用户添加到主屏的图标、应用名称等,也许有人会说,有些移动设备上的浏览器可以在主屏上添加网页的快捷方式,但其实用户体验区别很大,Web App Manifest 允许开发者配置隐藏浏览器多余的 UI(地址栏,导航栏等),让 PWA 具有和 Native App 一样的沉浸式体验。
Web App Manifest 体现在代码上主要是一个 JSON 文件:`manifest.json`,开发者可以在这个 JSON 文件中配置 PWA 的相关信息,应用名称、图标、启动方式、背景颜色、主题颜色等等。添加到桌面后,PWA 并不是一个快捷方式,而是能够在系统中作为一个独立的 App 存在的,用户可以设置它的权限,清除它的缓存,就和 Native App 一样。
添加主屏的好处是显而易见的,首先它缩短了用户和站点的距离,用户可以在主屏直达站点;其次是能够让网站具有更加接近 Native App 的体验,具有启动画面、沉浸式浏览体验;最后,PWA 会被系统的应用商店收录,目前只有 Windows 10 这样做了,但是可以预见在不远的将来,其他的主流平台也会进行收录。
## Service Worker
在前文中,频繁的提及 Service Worker 是因为它真的很重要,毫不夸张的说,Service Worker 就像人体中心脏一样的存在,如果没有它,PWA 就像没有了动力,无法寸进。
Service Worker,直白的翻译就是服务工作线程,但一般我们不会这么做。它是浏览器在后端独立于网页主进程运行的脚本,它可以拦截网络请求,可以操作本地缓存,还可以接受服务器推送的离线消息,它的功能很丰富,并且 Service Worker 可扩展性很强,想象空间比较大,未来 PWA 很多的特性会基于 Service Worker 来设计,这也是笔者为什么说它是 PWA 的心脏。
简单归纳一下,Service Worker 的特点,如下:
* 一个特殊的 worker 线程,独立于当前网页主线程,有自己的执行上下文
* 一旦被安装,就永远存在,除非显式取消注册
* 使用到的时候浏览器会自动唤醒,不用的时候自动休眠
* 可拦截并代理请求和处理返回,可以操作本地缓存,如 CacheStorage,IndexedDB 等
* 离线内容开发者可控
* 能接受服务器推送的离线消息
* 异步实现,内部接口异步化基本是通过 Promise 实现
* 不能直接操作 DOM
* 必须在 HTTPS 环境下才能工作

Service Worker 在 PWA 中最重要的功能就是离线与缓存,在本书第三章,还会有很多笔墨来介绍 Service Worker 如何实现站点离线。
## 离线通知
Service Worker 是 PWA 很多功能的基础,正是有了 Service Worker,其他功能才能发挥更大的作用,离线通知就是其中之一。
离线通知是指在用户没有打开 PWA 站点的情况下,也能接受到服务器推送过来的通知并展现给用户,其中包括了两部分,离线推送和展现通知,分别是 Web Push 和 Notification API。
推送通知是一种时效性非常强的与用户沟通的方式,即使在 PWA 没有打开的情况下,依然可以触达用户,能够立即引起用户的注意,对于一些突发事件、限时活动、重大升级等时效性要求很高的场景,推送通知总是最好的选择,这也是过去 Native App 强于 Web App 的原因之一。因此 PWA 提供了 Web Push 和 Notification API 补全了这一功能。
浏览器在接受到对应的消息服务中心推送过来的离线消息时,会唤醒对应站点注册的 Service Worker,开发者可以在 Service Worker 文件中处理接受到的请求,显示通知。
Web Push 和 Notification API 在后面的章节也会重点介绍。
## App Shell 和骨架屏
App Shell 是 PWA 强调的一个非常重要的设计理念,它能够缩短用户进入页面时的白屏时间,让用户一进入 PWA 就能快速看到 PWA 的整体框架,就和 Native App 一样。从概念上讲,App Shell 是 PWA 界面展示所需的最小资源集合,即让页面能够正常运行起来的最小的 HTML、CSS 和 JavaScript 等静态资源集,每个页面都需要加载这一部分资源。利用 Service Worker 把这部分资源缓存在本地,就能够在打开 PWA 时不需要从服务器端获取这部分资源,从而能够瞬间渲染出页面框架,不仅提升了首屏的速度,还减小了站点流量的消耗。
骨架屏(App Skeleton),也是提升首屏体验的有效方式。它的原理是在真实内容渲染完成之前,使用一些能够快速渲染的静态图片/样式/色块/部分真实内容进行占位,让用户对真实内容区域有心理预期。App Shell 和骨架屏都是提升首屏体验的绝好妙招。
App Shell 和骨架屏相辅相成,App Shell 显示页面的外框部分,初始内容就用骨架屏来填充,保证主体内容区域不会留白,它的特点是:
* 在页面加载初期预先渲染内容,提升感官上的体验
* 一般情况骨架屏和实际内容的结构是类似的,因此之后的切换不会过于突兀。这点和传统的 Loading 动图不同,可以认为是其升级版
* 只需要简单的 CSS 支持 (涉及图片懒加载可能还需要 JS ),不要求 HTTPS 协议,没有额外的学习和维护成本
* 如果页面采用组件化开发,每个组件可以根据自身状态定义自身的骨架屏及其切换时机,同时维持了组件之间的独立性
App Shell 和骨架屏在提升首屏体验上发挥了重要作用,本书第二章会首先介绍,在了解了这部分内容之后,就能够很好的理解 App Shell 结合 Service Worker 的重要性。
以上四点是笔者认为对应 PWA 非常重要的技术,因此列出来,并且后续也会重点讲述,其他在这里没有提到的技术对于 PWA 来说也同样重要,在本书中不会重点讲述,但也会有所提及,开发者可以去阅读一些相关的文档或者标准,比如 [MDN](https://developer.mozilla.org/en-US/) 站点。
================================================
FILE: chapter01/4-how-is-pwa-going.md
================================================
# PWA 的发展
从 PWA 被提出到现在,已经过去了 4 年,PWA 取得的成绩有目共睹,特别是在国外,在网络速度不够快或者相对贫困的地区,PWA 非常受欢迎,因为它不需要很高的硬件配置,也很省流量,比如在印度,就有一个很成功的案例,Flipkart。
## Flipkart
[Flipkart](https://www.flipkart.com) 是印度最大的电商公司,在 2015 年的时候,他们关停了 Web App,尝试将用户导流到 Native App,后来发现在产品快速迭代和好的用户体验之间很难做到两者兼得,因此他们决定将 Web App 和 Native App 整合为 PWA,通过 Service Worker,Web App Manifest 等技术的使用,PWA 不仅在体验上达到了他们的标准,同时还具有了 Web App 的产品迭代速度。
采用 PWA 后,取得的成绩斐然,关键收益如下:
* 用户时长增加了 3 倍,传统 Web 是 70 秒,而 PWA 达到了 3.5 分钟。
* 用户回流率提升了 40%。
* 添加到主屏的用户转化率提高了 70%。
* 用户数据流量的消耗降低了 3 倍。

### 阿里速卖通(AliExpress)
[阿里速卖通](https://m.aliexpress.com)是阿里巴巴旗下的一款产品,对外销售来自中国的各种产品。AliExpress 的困境在于,用户不愿意下载安装他们的 Native App,即使在 Web App 中对 Native App 有足够的引流,导致获客成本很高。
最后,AliExpress 选择了 PWA,他们的出发点在于提升 Web App 的体验和用户黏性。带来的收益也非常的明显,如下:
* 在 PWA 中,新用户的转化率提升了 104%。
* 在一次会话中,用户访问的页面数量增加了 2 倍。
* 用户时长增加了 74%。
### 饿了么
不仅在国外,国内同样也有 PWA 的案例,[饿了么](https://ele.me),作为国内最早一批尝试 PWA 的站点,同样也取得了不小的收益。
饿了么做 PWA 站点的出发点和 Flipkart、AliExpress 不完全一样,它几乎纯粹是从性能优化的角度接入的 PWA,当时国内的浏览器市场比较混乱,对 PWA 支持的不是很完善。饿了么 PWA 采取的是多页应用(Multi-Page Application),和 PWA 推荐的 SPA(Single Page Application)不一样,主要是考虑到多个团队合作共同开发同一个站点,不过在体验上并没有打折扣,现在是 Google 对外展示的一个成功案例。
看一下饿了么 PWA 的收益:
* 预缓存的页面加载时间缩短了 11.6%。
* 所有页面平均加载时间缩短了 6.35%。
* 在 3G 网络下,第一次加载首次可交互时间缩短了 4.93 秒。

## 标准的支持
PWA 采用的最新技术,当前浏览器还没有达到完全支持的程度,很多技术在 W3C 还没有定稿,不过这也意味着这些技术的还有很大的想象空间。
根据 [Can I Use](https://caniuse.com) 的统计(包括 PC 和移动端,截至 2019 年 4 月 2 日),PWA 的关键技术在浏览器中的支持度如下:
* Web App Manifest 的支持度达到 80.63%。
* Service Worker 的支持度达到 89.84%。
* Notifications API 的支持度达到 75.17%。
* Push API 的支持度达到 78.06%。
随着标准的进一步完善,国内外各大浏览器都会逐步支持,拥抱标准。Chrome 自不必说,Apple 从 iOS 11.3 版本开始在 Safari 上支持 Service Worker,iOS 12.2 版本修复了 PWA 很多致命的体验问题,支持了 Web Share API 等。可见大家都在拥抱标准,拥抱开放。
Can I Use 的统计由于一些原因在国内不是很适用,为此百度 Web 生态团队维护了一份列表,开发者可以在上面查看国内各主流浏览器对 PWA 主要技术的支持程度,[https://lavas.baidu.com/ready](https://lavas.baidu.com/ready)。
================================================
FILE: chapter01/5-the-future-of-pwa.md
================================================
# PWA 的未来
从 Google 最初提出 PWA 到现在,PWA 已经有不小的改变了,这就是 Web 的魅力,遵循标准且完全开放的魅力,来自世界各地的开发者参与标准的制定,它还在不断进化,Web 即使已经 30 岁了,它还依旧是被广泛应用的技术之一。
关注 Web 标准化的开发者会在标准文档里发现很多有意思的提案,有 Web 蓝牙、Web XR 等,在 [TPAC Lyon 2018](https://www.w3.org/2018/10/TPAC/) 上,Intel 的开发者演示了他们开发的 Web Machine Learning 的 DEMO,Web 也能直接利用 NPU 来进行深度学习的计算,让我觉得 Web 还能再战 30 年,我对此充满信心。
在国外,PWA 已经被广泛应用,也被用户所接受。在笔者刚从事 Web 生态相关工作的时候,国内才刚刚接触到 PWA 这个概念,UC 浏览器的内核版本还是 Chrome 3x,连 ES6 都支持的不全,更别说是 Service Worker、Web App Manifest 等 PWA 技术了。UC 浏览器并不是个例,国内厂商 App 内核版本几乎都不支持 Service Worker。不过也就在不到一年的时间里,这些浏览器就都支持了 Service Worker,让人不得不感叹国内互联网进步之快,国内主流浏览器对 Service Worker 的支持度如下图所示。

除了 Service Worker 等主流 PWA 技术外,W3C 也一直在推进 Device API 的标准。在 MDN,有一个 Web API 的索引,[WebAPI](https://developer.mozilla.org/zh-CN/docs/WebAPI),里面列出了大部分的现存的 Device API 和其他的 API。
[Accelerated Shape Detection in Imagges](https://wicg.github.io/shape-detection-api/) 是形体检测的 API,在最新的 Chrome 中已经支持,如下代码所示。
> 需要将 chrome://flags/#enable-experimental-web-platform-features 设置为 Enabled。
```javascript
// 人脸识别
const faceDetector = new FaceDetector({fastMode: true, maxDetectedFaces: 1})
// 假设 theImage 是 <img> 标签中的内容或者一个 Blob 对象
faceDetector.detect(theImage)
.then(detectedFaces => {
for (const face of detectedFaces) {
console.log(
` Face @ (${face.boundingBox.x}, ${face.boundingBox.y}),` +
` size ${face.boundingBox.width}x${face.boundingBox.height}`);
}
}).catch(() => {
console.error("Face Detection failed, boo.");
})
```
不断有新的 Device API 被支持,W3C 等标准组织有一群对 Web 怀抱希望,希望 Web 成为开放技术的人,他们在努力推进 Web 用户体验的提升,虽然由于 W3C 的组织方式和对安全、隐私、性能的考虑,推进速度不是很快,但不久也会被所有浏览器支持。我喜欢 Web 的开放,喜欢它的简单。
================================================
FILE: chapter01/6-your-first-pwa.md
================================================
# 你的第一个 PWA
本书中大部分示例均基于下面的这个模板展开,开发者可以跟随书中示例逐步操作,加深理解。在这个示例里,我们一起来实现一个能添加到桌面并且离线可用的 PWA。
## 准备工作
在准备编写第一个 PWA 前,有一些准备工作需要准备,需要安装一些必备的软件,如下:
* 一台可以正常联网的计算机并已安装较新版本的 Node.js,npm,Git
* 一个方便调试并支持 Service Worker 的浏览器,推荐使用 Google Chrome
* 一部安卓手机,开启添加到桌面的权限,推荐安装好 Chrome 浏览器
* 一个自己习惯的文本编辑器,如 Visual Studio Code, Sublime Text 等等
## 下载代码
在完成上面的准备工作后,接下来下载笔者准备的示例代码。本书的示例代码均托管在 GitHub 上,地址是 [https://github.com/lavas-project/pwa-book-demo](https://github.com/lavas-project/pwa-book-demo)。
> 本书所有的 JS 代码均符合 [JavaScript Standard Style](https://standardjs.com/) 规范。
那么接下来的第一步,我们先下载代码到本地,在命令行中运行如下命令。
```bash
# 从 GitHub 下载代码到本地 pwa-book-demo 目录
$ git clone https://github.com/lavas-project/pwa-book-demo.git
# 进入到 chapter01 目录
$ cd chapter01
# 安装 npm 依赖
$ npm install
# 安装成功后启动 chapter01 示例
$ npm run server
```
在看到命令行中输出 `Server start on: http://127.0.0.1:8088`,意味着已经成功启动,这时,打开浏览器,访问 `http://127.0.0.1:8088` 能看到如下图所示的页面。
<img src="./img/chapter01_demo.png" width="50%" alt="PWA Chapter01 Demo 截图" title="PWA Chapter01 Demo 示意图">
接下来,开发者可以根据下面的步骤逐步开启 Web App Manifest 和 Service Worker,开始体验自己的第一个 PWA。
## 添加到主屏
根据前面的章节介绍,增加用户黏性最好的方式是把这个 PWA 放在用户的主屏上,它背后的技术就是 Web App Manifest,接下来,我们就来看看如何使用。
第一步,站点需要新增一个文件:`manifest.json`,这个文件中包含站点的名称、图标地址、入口地址、显示模式等信息,并且通过一个地址能够访问到该文件,在我们下载下来的代码中,这个文件在 `chapter01/public/` 目录下,启动调试服务器后,可以通过 `http://127.0.0.1:8088/manifest.json` 访问到。
```json
{
"name": "PWA Chapter01 Demo",
"short_name": "Chapter01 Demo",
"icons": [
{
"src": "assets/images/icons/icon_144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "assets/images/icons/icon_152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "assets/images/icons/icon_192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/images/icons/icon_512x512.png",
"sizes": "256x256",
"type": "image/png"
}
],
"start_url": "/index.html",
"display": "standalone",
"background_color": "#fff",
"theme_color": "#1976d2"
}
```
第二步,在 HTML 页面中添加对 `manifest.json` 文件的引用,在示例中,打开 `chapter01/public/index.html` 文件,添加如下代码到 `<head>` 中。
```html
<!-- chapter01/public/index.html -->
<!-- 对 manifest.json 的应用 -->
<link rel="manifest" href="./manifest.json">
```
第三步,在移动端浏览器 Chrome 中打开启动的地址,如果在同一个局域网内,可以通过电脑的 IP 地址访问,比如笔者的电脑的局域网 IP 是 `192.168.0.100`,那么就可以通过 `http://192.168.0.100:8088/` 访问。访问成功后,点击添加到桌面按钮,PWA 就会出现在主屏上,如下图所示。

点击 PWA 的图标,不仅具有启动画面,还具有完全沉浸式的体验,如下图所示。

Web App Manifest 的内容远不止这些,可以阅读本书的后续章节,会详细介绍。
> 如果添加到主屏始终不成功,可以阅读本书的第五章,相信会解决您的问题。
## 离线可用
离线可用依赖于 Service Worker 的应用,接下来来看看在示例代码中如何启用 Service Worker。
第一步,您需要一个 Service Worker 文件,在示例中,可以查看 `chapter01/public/sw.js` 文件,并且可以通过 `http://127.0.0.1:8088/sw.js` 访问到该文件,sw.js 文件具体的内容会在后续章节逐步讲解。
第二步,在 HTML 页面中注册 Service Worker。打开 `chapter01/public/index.html` 文件,找到下面的代码,并将注释打开。
```html
<!-- service worker -->
<script>
// 判断浏览器是否支持 Service Worker
if ('serviceWorker' in navigator) {
// 在 load 事件触发后注册 Service Worker,确保 Service Worker 的注册不会影响首屏速度
window.addEventListener('load', function () {
// 注册 Service Worker
navigator.serviceWorker.register('/sw.js').then(function (registration) {
// 注册成功
console.log('ServiceWorker registration successful with scope: ', registration.scope)
}).catch(function (err) {
// 注册失败 :(
console.warn('ServiceWorker registration failed: ', err)
})
})
}
</script>
```
第三步,接下来可以通过打开电脑上的 Chrome 来验证是否注册成功,并且是否离线可用。
通过 Chrome 访问 `http://127.0.0.1:8088` 打开我们的第一个 PWA,并且打开调试工具,点击 `Application` 菜单栏,再点击调试工具左边的 Service Worker 选项,会看到 `sw.js` 的注册信息,如下图所示:

那么,我们检查一下是否真的离线可用,勾选上图中的 `Offline` 复选框,让 Chrome 在这个标签页下保持断网状态。

接下来,刷新页面,您会发现页面依然能正常渲染,这就是 PWA 的离线可用,可以利用 Service Worker 做很多事情,缓存页面框架和骨架屏,提升页面首屏速度,甚至可以缓存部分数据。Service Worker 将在本书的后续章节会重点讲述。
## 总结
以上就是您的第一个 PWA,麻雀虽小,五脏俱全,能被添加到主屏,能离线可用,短短的几行代码就在原站点的基础上实现了这两个功能,并且没有侵入性,代价也很小,它确实奉行渐进式的原则。
================================================
FILE: chapter01.md
================================================
# 什么是 PWA
随着移动互联网的发展,Native App 开始兴起,那么 Web App 落伍淘汰了吗?很显然答案是没有,它依然生机勃勃,和 Native App 互相依存,还衍生出很多 Native App 和 Web App 相结合的技术,比如 Hybrid App,React Native 等,Angular/ReactJS/Vue 也都是在这期间才发展起来的优秀的框架,可见,Web App 仍在不断进化,PWA(Progressive Web Apps)就是在这背景下诞生的。
本书第一章会着重介绍 PWA 的基础概念,让读者对 PWA 有个大致的了解,后续章节会再深入讲解。本章会探讨为什么会出现 PWA,PWA 的出现解决了哪些问题,然后再看 PWA 的主要技术,并且会结合实际的例子让读者能够创建一个简单的 PWA。
================================================
FILE: chapter02/1-what-is-good-ux.md
================================================
# 什么是好的用户体验
PWA 的核心是用户体验,它的核心技术(如 Service Worker,Web App Manifest 等)都是为了提升 Web App 用户体验,但“体验”其实是个很主观的感受,我们很难用一个或几个量化指标来轻易的衡量用户体验,判断优劣,甚至不同的人有不同的理解,不过体验好的站点都有一些共性,包括不限于下面列出来的一些特征。
* 首屏速度快
* 顺滑流畅的动画效果
* 有用户操作的反馈
* 比较简单的操作步骤
* 主体内容比较在最显眼的位置
* 整站体验一致
* 无障碍访问,不同的人群均可使用
用户体验的核心是用户,设计师需要站在用户的角度思考用户需要什么,在做设计的时候需要做充分的调研。移动设备上的用户目的性很强,需要在巴掌大小的屏幕上快速找到自己想要的内容。
很多公司都沉淀了自己的一套设计原则和方案,百度在 2018 年发布了《[百度移动搜索落地页体验白皮书 4.0](https://ziyuan.baidu.com/college/documentinfo?id=2492)》,这里面列出了很多体验上的指导原则。Google 也在开发者网站上列出了他们总结的 25 项设计原则,《[What Makes a Good Mobile Site?](https://developers.google.cn/web/fundamentals/design-and-ux/principles/)》。
笔者将他们整合了一下,并且加入了一些自己认为重要的设计原则,如下:
1. 主操作区域要放在显眼位置
2. 不要出现巨大的弹层盖住主要内容,比如下载条幅可以放在顶部并且添加关闭按钮
3. 推送的通知需要满足准确、准时和相关三个特征
4. 尽量减少用户的输入或者减小输入的代价,自动帮用户填写已知的数据,选择合适的 `input` 类型
5. 缩短不必要的流程,让整个转化流程更简洁
6. 响应式布局,让站点在不同尺寸的浏览器上都有好的体验
7. 图片清晰,且点击可放大查看更清晰的图片
8. 避免不必要的切换,让用户在一个浏览窗口内完成所有的操作
9. 如果需要申请设备权限,如定位、通知等,在申请前需要明确的告诉用户为什么需要这些设备权限
10. 可点击区域的宽度和高度不应小于 48px
11. 整站体验需要一致,页面框架、主色调等需要保持统一
好的设计原则远不止上面列出来的这些,好的设计不仅需要美观,还需要方便易用。
那么站在开发者的角度,哪些需要重点关注呢?
================================================
FILE: chapter02/2-design-and-tech.md
================================================
# 设计与技术
好的设计没有好的技术来实现同样无法达到最佳的用户体验。
设计与技术并不只是实现的关系,而是需要互相配合,再完美的设计如果不能实现,那也只是白费,而一些技术上的优化也需要设计给予配合才能更加完美。
上一节中,列出了很多设计原则,有些是比较容易实现的,但有些也比较复杂,复杂到需要很大的篇幅来描述。下面我们总结了几个比较重要的设计与技术相结合的问题。
## 首屏速度
首先是首屏速度,它对于站点的重要性不言而喻,特别是移动端,下图是页面加载时间与跳出率和会话的关系。

那么技术上如何提升首屏速度呢?首选我们会想到将 CSS 放在头部,JS 放在页面底部等技术手段,这些方法确实卓有成效,但是有没有更好的方法呢?
在这里就要讲到设计与技术相辅相成的一种 Web 的设计,那就是 App Shell,以及和 App Shell 配套使用的骨架屏(App Skeleton),本章后续会详细介绍。
## 响应式布局
移动设备种类越来越多,屏幕大小不一,有小到高宽不足 40 毫米的智能手表,也有高度接近 300 毫米的 iPad。尽管如此,大部分的 Web 站点都没有对尺寸不一的设备做过适配,导致在移动端体验很差,这时就凸显响应式布局的重要性。
响应式布局不是一个纯技术的问题,在设计之初就需要设计师考虑众多问题,例如在不同尺寸的设备上,字体大小设置多少,页面如何布局,主体内容放在哪里,叠起来的内容如何使用户更方便的找到,小尺寸屏幕如何展现列数较多的表格,等等。
而开发者需要考虑如何实现根据屏幕尺寸大小动态调整字体大小或者布局,其中涉及到的知识点很多,在本章后续小节中会详细介绍。
## 流畅的动画
动画是体验非常重要的一部分,站点展现速度再快,没有任何交互动画,也会显得没有生气,它赋予了站点“生命”。但是如果动画掉帧,造成卡顿,反而会让用户觉得站点体验很差,所以,我们需要的是流畅的动画。
动画的设计没有想象中的简单,过多的动画和没有动画体验同样糟糕,动画过多会让用户觉得烦躁,因此需要设计师在初期考虑到参与动画的元素、动画的类型、动画的持续时间等,而开发者需要考虑如何实现流畅的动画。在本章后续小节中同样会展开介绍。
================================================
FILE: chapter02/3-app-shell.md
================================================
# App Shell
在第一章中已经简单介绍过 App Shell,这个小节我们会更深入学习如何构建和使用 App Shell。它并不是一种新的技术或者 API,而是设计与技术相结合产生的一种整站设计方案,减少用户进入页面的等待时间,用户能够快速看到页面的主体结构,虽然这时主体内容没有渲染出来,但是给用户感官上的体验是这个页面渲染很快,那么来看一下什么是 App Shell。
## 什么是 App Shell
PWA 多数采用单页应用(Single Page Application)的方式编写,这样能减少页面跳转带来的开销,并且开发者可以在页面切换时增加过渡动画,避免出现加载时的白屏。那么在页面切换时页面上固定不动的内容就是 App Shell 的一部分。
应用从显示内容上可以粗略的划分为内容部分和外壳部分。App Shell 就是外壳部分,即页面的基本结构,如下图所示:

上面只是 App Shell 显示的部分,那么是谁去加载内容区域呢,是谁接管了页面的状态呢,是谁初始化页面的样式呢?
也是 App Shell,它不仅包括用户能看到的页面框架部分,还包括用户看不到的代码逻辑。因此,我们可以总结一下 App Shell 的定义,App Shell 是页面能够展现所需的最小资源集合,即支持用户界面所需的最小的 HTML、CSS 和 JavaScript 等静态资源集合。采用 App Shell 的站点,每个页面都会先加载 App Shell 的内容,再由 App Shell 根据当前页面 URL 渲染对应的主体内容。
上图给出了基本的 App Shell 例子,它包含头部导航,侧边栏等,对于一个应用来说它有一个相对稳定的结构。但是一个站点可以有多个 App Shell,以电商网站举例,不同品类的商品主题馆,颜色不同,甚至结构也会有一些区别,这就需要开发者灵活处理,可以分为多个 Shell,或者在一些页面去掉 Shell。
## 如何正确使用 App Shell
单独使用 App Shell 并不是一个很好的主意,对于速度要求比较高的站点来说,它会导致用户看到主要内容的时间延后,那么有没有办法呢?这个时候开发者会提到使用浏览器的 HTTP 缓存,这确实是一个不错的主意,但它也有一些很难解决的问题。
* 难以确定合适的 HTTP 缓存设置时间,也不适合更新比较频繁的站点
* HTTP 缓存只有在 URL 相等的情况下才能起作用,这不符合 PWA 每个页面有唯一 URL 的要求
那么有没有更好的缓存方案呢?在第一章中,我们提到了 Service Worker 可以拦截网络请求和操作本地缓存,这给了我们足够的想象空间,可以结合 App Shell 和 Service Worker 来解决缓存问题。
1. 使用 Service Worker 预缓存 App Shell 的静态资源
2. 用户访问 Web 站点时,通过 Service Worker 拦截请求
3. Service Worker 返回缓存中的 App Shell 给浏览器
4. App Shell 根据当前的 URL 再去请求对应的数据来渲染
这样可以解决上面提到的所有问题。下面是 App Shell 和 Service Worker 结合使用的收益。
* 第二次访问速度极快且稳定。由于 App Shell 的内容已经缓存在本地缓存中,用户第二次访问会在极短的时间内渲染出 App Shell
* 为用户节省流量。用户在后续的访问都不会再请求 App Shell 的内容,而是只请求主体内容,不用加载一些公用的静态文件。
* 具有 Native App 的用户体验。无论是第一次访问还是后续页面的切换,都具有唯一不变的区域,没有传统 Web 页面切换的白屏。
## App Shell 的案例
[https://lavas.baidu.com](https://lavas.baidu.com) 是百度 Web 生态团队开发的 Lavas 的官网。这是一个体验比较好的 PWA 站点,那么来分析一下这个站点是如何结合 Service Worker 和 App Shell 的。
下图右侧就是 Service Worker 缓存在 [Cache Storage](https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage) 中的 App Shell 内容,左侧顶上的 banner 就是 App Shell 的可见区域。整个页面的外壳部分被缓存起来,在下次访问时由 Service Worker 取出并直接返回,如果把网络断开再刷新页面,页面依然能够正常渲染。

经过我们前面的分析,App Shell 和 Service Worker 结合首屏展现速度会非常快,那么在这个案例中,首屏的性能到底如何呢,来看一下下面这张图,这张渲染性能的图是在 PC Chrome 上统计的。

从上图中,能看到大概在时间轴 200ms 的时候,已经渲染出了 App Shell。查看浏览器统计的详细 Event Log,上图中页面首次绘制是在 230ms,再减去上一个页面的销毁时间大概 80ms,实际上当前页面从开始发送请求到首屏渲染出来所花掉的时间是 **150ms**,对于用户来说,页面几乎是瞬间呈现,没有白屏时间。并且从上图中,也能看出渲染的顺序是先 App Shell,然后再渲染出主体内容,和我们预想的一样。
如果您觉得这个站点过于简单,无法体验 Service Worker 和 App Shell 结合的优势,下面列出了该团队提供的另一个较复杂的使用了 App Shell 模型的例子,也同样是体验非常良好的 PWA 站点。
[https://lavas.baidu.com/12306/](https://lavas.baidu.com/12306/),这是百度 Web 生态团队开发的一个 demo,参考中国铁路在线售票系统 12306。
> 注意,这个站点只是 demo,不是 12306 官方的网站,不要输入密码等个人信息。
## 如何在 Vue 项目中使用 App Shell + Service Worker
第一章的例子也采用了 App Shell + Service Worker 的方式,但是没有采用任何框架辅助,在实际项目中,这样不借助任何框架编写 SPA 是比较少见的。在现代项目中,采用 Angular/React/Vue 来编写复杂 SPA 的项目占大多数,那么如何在这些框架中使用 App Shell 和 Service Worker 呢,下面我们用一个实际的例子来演示一下。
### 启动示例
和第一章的例子一样,您需要准备 Git,Node.js 和 npm 环境,准备完成后,可以通过下面的命令下载我们的示例,这个示例是根据 `vue-cli` 创建的很基本的 Webpack 模板,可以参考 npm 上的文档 [vue-cli 简介](https://www.npmjs.com/package/vue-cli)。
```bash
# 从 GitHub 下载代码到本地 pwa-book-demo 目录
$ git clone https://github.com/lavas-project/pwa-book-demo.git
# 进入到 chapter02/appshell 目录
$ cd chapter02/appshell
# 安装 npm 依赖
$ npm install
# 启动 chapter02 appshell 示例
$ npm run dev
```
在命令行中看到输出 `Your application is running here: http://localhost:8080` 后,代表服务已经启动成功,打开浏览器,访问 `http://localhost:8080`,能看到下图所示的页面。
<img src="./img/chapter02_demo_screenshot.png" width="50%" title="App Shell demo 启动成功示意图" alt="App Shell demo 启动成功示意图">
这时,如果您打开 Chrome 调试工具,并定位到 Application 栏目,会发现没有注册 Service Worker,这是因为我们在开发模式下没有生成 `service-worker.js` 文件,根据我们的经验,在开发模式下启用 Service Worker 对开发调试会带来很大的影响,因为如果 Service Worker 写的不够完善,会导致开发中修改后的文件得不到及时的更新,没有经验的开发者会花较多的时间来排查,因此我们的 demo 只有在编译之后才会生成 `service-worker.js` 文件。
```bash
# 编译
$ npm run build
# 进入到编译后的 dist 目录
$ cd dist
# 安装静态文件调试服务
$ npm install -g edp
# 在 dist 目录中启动本地静态文件服务
$ edp ws start
```
在命令行输出了下面的文本后,即代表服务已经启动。
```
mockservice
edp INFO EDP WebServer start, http://xxx.xxx.xxx.xxx:8848
```
打开浏览器,输入 `http://localhost:8848`,能看到调试模式下相同的页面。不同的地方在于,这个时候已经安装了 Service Worker,打开 Chrome 调试工具,定位到 Application -> Service Worker,如下图所示。

点击 Chrome 调试工具左侧的 Cache -> Cache Storage,能看到有两个 Cache Storage 实例,其中一个以 `sw-precache-` 开头的实例里面预缓存了 App Shell 所需的静态文件,如下图。

### 示例解析
#### 哪些区域是 App Shell
在这个 Vue 的示例里,可见部分哪些是 App Shell 呢,我们可以查看 `src/App.vue` 文件。
```html
<template>
<div id="app">
<img src="./assets/logo.png">
<router-view/>
<bottom-navigation></bottom-navigation>
</div>
</template>
```
熟悉 Vue 的开发者都知道 `<router-view>` 标签在 Vue 项目里是用来嵌入子路由的,`Home.vue` 和 `NextPage.vue` 是这个例子中的两个页面。
可以简单的认为,在 `<router-view>` 标签之外的内容都可以认为是 App Shell,不仅包括上面的 img 标签,下面的 `bottom-navigation` 组件,还包括在 `<div id="app">` 标签外面的内容,如 `index.html` 文件中的其他部分。

#### 如何缓存 App Shell 内容
在这个示例中,`service-worker.js` 文件是通过 SW Precache Webpack Plugin 插件生成的,可以参考 [sw-precache-webpack-plugin](https://github.com/goldhand/sw-precache-webpack-plugin#readme) 文档。代码如下所示:
```javascript
// build/webpack.prod.js #13 行
// 引入 sw-precache-webpack-plugin 插件
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
// build/webpack.prod.js #121 行
// 添加 sw-precache-webpack-plugin 插件生成 Service Worker 文件
new SWPrecacheWebpackPlugin(config.swPrecache.build)
```
它的配置写在 `config/sw-preache.js` 文件中,开发者可以对它做一定的修改。
编译之后,会在 `dist` 目录中生成 `service-worker.js` 文件,第四章中有对 Service Worker 详细的介绍,在这里我们不必关心这个文件中过多的细节。插件生成的 Service Worker 做了很多事情,如下:
* 列出了项目中静态文件列表
* 预缓存了 App Shell 的静态文件
* 具有预缓存动态更新机制
* Service Worker 的更新机制
```javascript
// 预缓存文件列表,其中包含 App Shell 所有的文件
var precacheConfig = [["/index.html","cb8786927330e5bdd417a47314a7300b"],["/static/css/app.5be76b7d213b43df9723e8ab15122efb.css","5b0aa7a24708300b7176f0304923ab39"],["/static/images/icons/icon_144x144.png","4bf0757895bd1cda44ee9204fe06a661"],["/static/images/icons/icon_152x152.png","c69908d502224c5317453f8ae725bbf0"],["/static/images/icons/icon_192x192.png","c69908d502224c5317453f8ae725bbf0"],["/static/images/icons/icon_512x512.png","8eaee831d59442821083453b174df0f6"],["/static/js/0.2dd5b370dbfb9afda866.js","5455b40e8991749d4aeb904f09389394"],["/static/js/1.f740f9813d49b7661238.js","65b7858bacef211f345d0d88fda81092"],["/static/js/app.e9b2b9e109561a7201f5.js","a28433e856b3e149bac022df934eb94e"],["/static/js/manifest.69fbe521a6bc723a8ce1.js","c6b6fbf7902cd5325b5b1d43045db622"],["/static/js/vendor.7bb72363822163e0f3b3.js","38d1baa4e38eeab57826462846900d69"],["/static/manifest.json","a521180656694782bc69a54ee5900dfb"]];
var cacheName = 'sw-precache-v3-sw-cache-chapter02-appshell-' + (self.registration ? self.registration.scope : '');
```
## 总结
总结来说,App Shell 把站点内容划分为“变”和“不变”两个部分,再辅以 Service Worker 技术将“不变”的部分缓存起来,以达成快速加载页面的效果。
通过 App Shell 和 Service Worker,我们有办法让“不变”的部分快速展现,那么针对“变”的部分,我们有什么办法让它展现得更快呢,或者让它“看起来”更快?在这里我们再提出另一种方案,它虽然不能在实质上提升页面的加载速度,但可以让它在感官上更快,它就是下一节要介绍的骨架屏。
================================================
FILE: chapter02/4-app-skeleton.md
================================================
# 骨架屏
在前面的章节,我们说过,首屏速度对于用户留存率至关重要。
很多站点都会在完成基本功能后(或者同时)进行性能优化,常见的性能优化手段包括静态资源合并,压缩文件,使用 CDN,包括上一小节介绍的 App Shell 等,这些的确能够显著地缩短加载时间。但是我们想象一下,在首次打开时,主体内容渲染完成之前,页面基本上是空白的,这对于用户体验是非常不友好的。如果我们用能够快速渲染的静态图片/样式/色块进行占位,让用户对后续会渲染的内容有一定的预期,这要比白屏等待要好的多,这就是骨架屏。如上一节的结尾所述,它本质上并不提升页面的加载速度,却能提升感官体验,让网页“看起来”更快。
## 骨架屏长什么样
您可能没听说过骨架屏这个名字,但应该很早就在其他 App 上看到过,骨架屏常见的种类有列表、图片和两者的混合。
下面的图是[饿了么 Web 站点](https://h5.ele.me/msite/) 骨架屏的效果,这种效果是比较常见的类型,它使用各种形状的色块来模拟图片和文字,有些图标也会使用圆形色块。为了追求效果,还可以在色块表面添加动画效果,如波纹,看起来就像具有 loading 效果的骨架屏。

在图片居多的站点,使用低像素的图片进行占位也是一个不错的选择,图片配色及变化和原图相近即可,如果觉得生成低像素的图片成本较高,可以降级使用纯色色块代替,但色块的颜色最好和图片主体颜色相似,如果觉得这个成本还是很高,那么可以直接采用上面例子中统一颜色的色块。

骨架屏的精髓,并不是用什么来占位,而是无论什么内容占位,一定要保持渲染前和渲染后结构相似,不能差距太大,最好保持色块/图片间距一样,避免页面渲染后内容跳动。
## 骨架屏能用在哪里
现在的 Web 应用,从架构上来说分为前端渲染(CSR)和后端渲染(SSR)两种,骨架屏适用于前端渲染的页面,而后端渲染的页面渲染首屏时所有内容都已经存在了,因此无需骨架屏。但是,即使是后端渲染的页面有时也会存在前端渲染的区域,比如列表的加载,只要是用到 JavaScript 来渲染内容的地方,都可以选择性的使用骨架屏来占位。
由于近几年 Angular/React/Vue 的推出,前端渲染的站点越来越多,它们让开发复杂 Web App 变的很简单。它们的流行也大大提高了 App Shell 和骨架屏的普适性,大部分此类站点都可以采用这种模式。
## 在 Vue 项目中使用骨架屏
我们以 Vue 开发的单页应用为例,在其它框架上,实现思路是一致的。在这个示例中,为了方便调试,笔者将 Service Worker 的注册逻辑删除了。
开发者可以下载示例代码并启动,如下所示:
```bash
# 从 GitHub 下载代码到本地 pwa-book-demo 目录
$ git clone https://github.com/lavas-project/pwa-book-demo.git
# 进入到 chapter02/appskeleton-01 目录
$ cd chapter02/appskeleton-01
# 安装 npm 依赖
$ npm install
# 启动 chapter02 appskeleton-01 示例
$ npm run dev
```
为了方便调试,我们将 `src/main.js` 中 Vue 实例的挂载时间推迟了 2s,在这期间,我们能看到骨架屏的效果。
```javascript
// src/main.js
const app = new Vue({
router,
components: { App },
template: '<App/>'
})
router.onReady(() => {
// 将 mount 时间延后 2s,便于查看效果
setTimeout(() => app.$mount('#app'), 2000)
})
```
然后,我们需要在 `index.html` 中添加骨架屏,如下:
```html
<!-- skeleton 的内联样式 -->
<style>
body {
margin: 0;
}
.skeleton {
text-align: center;
padding-top: 60px;
}
.skeleton-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 56px;
box-shadow: 0 3px 14px 2px rgba(0, 0, 0, .12);
display: flex;
}
</style>
...
<!-- Vue 实例挂载点 -->
<div id="app">
<div class="skeleton">
<!-- 上半部分图片的占位采用缩小的图片 -->
<img src="data:image/png;base64,..."/>
<div class="skeleton-section">
<!-- 中间内容区域采用文字类型的骨架占位 -->
<img src="data:image/svg+xml;base64,...">
<img src="data:image/svg+xml;base64,...">
</div>
<!-- 底部导航栏采用空的结构占位 -->
<div class="skeleton-bottom"></div>
</div>
</div>
```
填写完成之后,刷新页面您能看到下图所示的骨架屏。
<img src="./img/chapter02_demo_skeleton.png" width="50%" alt="Chapter02 demo 骨架屏示意图" title="Chapter02 demo 骨架屏示意图">
## 进阶优化:更快的展现骨架屏
在上面的例子中,为了让骨架屏尽早展现,我们需要做到以下两点:
1. 把骨架屏的 HTML 内联在 `index.html` 中,而不是用 JavaScript 来渲染
2. 骨架屏的 CSS 最好内联,保证骨架屏在最短的时间内渲染
可惜的是,尽管我们这么做了,也不一定能取得想要的结果,通过实际测试会发现骨架屏很难被用户看到,或者说它展示的时间不如预期的早。
### 现状分析
我们在上面的示例基础上来分析一下原因是什么。用 Chrome 调试工具中的 Performance 记录渲染流程,为了简单一些,我们可以将网络设置成 Slow 3G,会比较明显的发现骨架屏出现的时间非常短。
> 为了更接近生产环境,先将示例编译之后再调试。
```bash
# 在 chapter02/appskeleton-01 目录中
# 编译
$ npm run build
# 进入到编译后的 dist 目录
$ cd dist
# 安装静态文件调试服务
$ npm install -g edp
# 在 dist 目录中启动本地静态文件服务
$ edp ws start
```
打开 Chrome 浏览器访问 `http://localhost:8848`。下图是笔者用调试工具记录的页面渲染流程,发现页面第一次渲染是在 `app.*.css` 加载完成之后。

按照预想,骨架屏应该在 HTML 加载完成之后立刻渲染出来,也就是在浏览器获取外链资源的同时,这也是为什么我们把骨架屏的 HTML 和样式都内联的原因,然而事与愿违,浏览器并不买账。
### 浏览器做了什么
熟悉浏览器的开发者很快就能理解,这与浏览器的渲染顺序有关。
相信大家都整理过行李箱。在整理行李箱时,会根据每个行李的大小合理安排,大的和小的配合,填满一层再整理上面一层。如果突然有人跑来和你说电脑不用带了,要多带两件衣服,这时除了想打他之外,你还需要重新安排行李。在浏览器中,这个过程叫做重排(reflow),而那个馊主意就是新加载的样式文件。显而易见,重排的开销是很大的,需要尽力避免。
既然每个 CSS 文件都可能会触发重排和重绘,那索性等待 `<head>` 中所有的外链样式文件加载完成之后再渲染,这个流程本身是没有问题的,却在骨架屏的应用中出了一些问题。
页面从加载到展现的大致顺序如下:
1. 加载 HTML 文件
2. 解析 DOM
3. 并行加载 CSS/JS 资源
4. 如果 `<head>` 中存在外链的样式,则阻塞渲染等待样式文件加载并解析完成
5. 如果 `<head>` 中存在外链的 script,则阻塞渲染等待 script 文件加载并执行完成
在 Vue 的项目编译完成之后,`<head>` 标签中的结构如下,在 `<head>` 标签中插入了一个外链的样式文件,导致骨架屏渲染推后。
```html
<head>
<meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<link rel=manifest href=/static/manifest.json> <title>PWA Chapter02 Demo - App Skeleton</title>
<style>
body {
margin: 0;
padding-top: 60px;
}
.skeleton {
text-align: center;
}
.skeleton-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 56px;
box-shadow: 0 3px 14px 2px rgba(0, 0, 0, .12);
display: flex;
}
</style>
<!-- 编译后插入在 head 中的外链样式文件 -->
<link href=/static/css/app.5be76b7d213b43df9723e8ab15122efb.css rel=stylesheet>
</head>
```
为了尽早展现骨架屏,我们将骨架屏渲染所需的样式和 HTML 内联,却被页面中其他的外链样式文件阻塞了渲染。拿上一节中的 Vue 示例来举例,由于浏览器解析完 DOM 之后是并行加载外链资源的,所以在样式文件加载完成之后,JavaScript 文件也基本已经加载完成,因此在骨架屏真的渲染出来之后没多久就被 JavaScript 渲染的真正内容取代,这就是为什么骨架屏出现非常靠后,效果大打折扣。
所以,我们需要告诉浏览器,请放心大胆的先渲染骨架屏。
### 避免样式文件的加载阻塞骨架屏的渲染
Webpack 编译的 Vue 项目,会在 `index.html` 的 `<head>` 插入外链的样式文件,`<link ref="stylesheet" href="http://xxxx">`,这无疑会阻塞骨架屏的渲染。浏览器还提供了预加载机制,使用方法非常简单,只需将 `rel="stylesheet"` 改为 `rel="preload"`,浏览器会在空闲的时候加载并缓存,之后再使用就不用重复加载。
这看似无关的技术,在骨架屏的应用里将起到很大的作用,因为**预加载的资源不会阻塞渲染**.
我们通过这种方式告诉浏览器,先不要管 `app.xxx.css`,直接渲染后续内容,在 `app.xxx.css` 文件加载完成之后,再将它重新设置为样式文件,如下代码所示:
```html
<link rel="preload" href="/static/css/app.5be76b7d213b43df9723e8ab15122efb.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
```
方法的核心是通过改变 `rel` 让浏览器重新认定这个 `<link>` 标签是样式文件,这样既不阻塞骨架屏的渲染,也能正常应用外链样式文件。
### 这样就完了吗?
如果不将 `<link>` 标签 `rel="stylesheet"` 改为 `rel="preload"`,浏览器会根据资源的书写顺序来顺序执行,即先应用外链样式,再执行外链 JavaScript 文件渲染主体内容。但是根据上面的步骤,我们使用预加载来加载样式文件,这样做的结果就是我们无法保证浏览器会先应用样式再运行 JavaScript 渲染内容,一旦 JavaScript 先执行并渲染出了内容,再应用外链样式,会导致页面重排和重绘,用户会先看到排版完全是乱的页面,再看到正常的页面,对用户体验是不小的伤害。
因此,我们还需要考虑到文件加载顺序的问题,在样式文件加载完成前,即使 JavaScript 已经渲染好了内容,也先不要替换掉骨架屏,等待样式文件加载完成后,再触发 JavaScript 进行挂载。
在 Vue 的项目中需要修改 `src/main.js` 和 `index.html` 文件。
```javascript
// src/main.js
const app = new Vue({
router,
components: { App },
template: '<App/>'
})
/**
* 挂载 Vue 渲染好的 HTML 元素到 #app 中,替换掉骨架屏
*/
window.mount = function () {
app.$mount('#app')
}
```
```html
<!-- index.html -->
<link rel="preload" href="/static/css/app.5be76b7d213b43df9723e8ab15122efb.css" as="style" onload="this.onload=null;this.rel='stylesheet';window.mount()">
```
这样就好了吗?
还不够完善,如果样式文件在 JavaScript 文件之前加载,那么 `mount` 函数还没有声明,执行会出错,最终也导致主体内容没有渲染到页面上。还需要完善,增加一个标记告诉 JavaScript 样式是否加载完成,经过改造代码如下,同样还是 `src/main.js` 和 `index.html` 文件。
```javascript
// src/main.js
const app = new Vue({
router,
components: { App },
template: '<App/>'
})
/**
* 挂载 Vue 渲染好的 HTML 元素到 #app 中,替换掉骨架屏
*/
window.mount = function () {
app.$mount('#app')
}
// 如果样式文件已经加载完成了,直接挂载
if (window.STYLE_READY) {
window.mount()
}
```
```html
<!-- index.html -->
<link rel="preload" href="/static/css/app.5be76b7d213b43df9723e8ab15122efb.css" as="style" onload="this.onload=null;this.rel='stylesheet';window.STYLE_READY=1;window.mount&&window.mount();">
```
考虑到浏览器不支持 JavaScript 的情况,那么还需要增加一个 `<noscript>` 标签。
```html
<link rel="preload" href="/static/css/app.5be76b7d213b43df9723e8ab15122efb.css" as="style" onload="this.onload=null;this.rel='stylesheet';window.STYLE_READY=1;window.mount&&window.mount();">
<noscript><link href="/static/css/app.5be76b7d213b43df9723e8ab15122efb.css" rel="stylesheet"></noscript>
```
经过这番改造之后,我们再来看一下在 Chrome 中的表现,这个示例是在 Fast 3G 网络环境下展现的,如下图所示。骨架屏在 650ms 左右的时刻渲染完成,而样式文件是在 1200ms 左右才加载完成,没有阻塞骨架屏的渲染,符合期望。

### 如何在 Vue 项目中使用骨架屏
如果让开发者在 Vue + Webpack 项目中进行上面的改写,代价是比较高的,为此,百度 Web 生态团队写了一个 Webpack 插件,[vue-skeleton-webpack-plugin](https://github.com/lavas-project/vue-skeleton-webpack-plugin)。
## 总结
骨架屏从优化关键渲染路径思路出发,配合 App Shell 和 Service Worker 等技术,进一步优化页面在加载阶段的感知体验。通过本节学习示例,我们看到配合 Vue/Webpack 等一系列技术,为应用添加骨架屏可以变得更加简单。
================================================
FILE: chapter02/5-responsive-design.md
================================================
# 响应式布局
自从进入移动互联网时代,响应式布局这个词经常出现在 Web 设计和开发领域,它让 Web 页面在不同尺寸的设备上都具有良好的浏览体验。
## 开始之前
在讲解响应式布局之前,需要先了解一下基础知识,只有对它们都有一定的了解,才能在做响应式布局时选取合适的技术方案。
### 像素
像素这个单位很常见,指的是图像中最小的单位,一个不可再分割的点,在计算机屏幕上一般指屏幕上的一个光点。例如常见的描述中 iPhone X 的分辨率是 1125x2436,一般指的是在长和宽上像素点的个数。但是在 Web 开发中,我们知道 iPhone X 的像素是 375x812,那么这又是怎么回事呢?这里需要讲到设备像素(Device Pixels)和虚拟像素,也可以叫 CSS 像素(CSS Pixels)或者逻辑像素,后面我们统一使用 CSS 像素这个称呼,在 Android 开发中可以叫设备无关像素(Device Independent Pixel,简写 dip)。设备像素很好理解,对应屏幕上光点的数量。
在科技发展到今天,屏幕分辨率已经达到人眼无法区分单个像素的程度,人眼无法在 iPhone X 宽不到 7cm 的屏幕上数出 1125 个像素点。Web 开发人员眼中的 1px 可能对应多个设备像素,Peter-Paul Koch 在他的博文中有详细的讲解《[A pixel is not a pixel is not a pixel](https://www.quirksmode.org/blog/archives/2010/04/a_pixel_is_not.html)》。
比如在 iPhone X 上,设备像素是 1125x2436,而 CSS 像素是 375x812,那么一个 CSS 像素对应的是长和宽各 3 个设备像素,9个设备像素点。
1 css pixel = 3 x 3 device pixels
那个这个比值 3 就是我们平时所说的设备像素比(Device Pixel Ratio),简称为 DPR。DPR 它并不是一个单位,而是一个比值,这个比值可以在浏览器中通过 JavaScript 代码获取。
```javascript
// 设备像素比,在 iPhone X 中等于 3,在 iPhone 6 中等于 2
window.devicePixelRatio
```
### EM vs REM
#### EM
EM 是相对单位,相对于元素自身的 `font-size`,它不像像素是固定的单位,因此很适合用来做响应式布局。
```html
<style>
h1 {
font-size: 20px;
margin: 1em; /* 1em = 20px */
}
p {
font-size: 14px;
padding: 1em; /* 1em = 14px */
}
.outer {
font-size: 12px;
}
.inner {
font-size: 2em;
padding: 1em; /* 1em = 24px*/
}
</style>
<div class="outer">
<div class="inner"></div>
</div>
```
如果当前元素没有设置 `font-size`,那么 1em 实际大小是多少?
```css
p {
padding: 1em; /* 1em 等于多少像素*/
}
```
在上面的代码中没有设置 `<p>` 的 `font-size`,它会从继承父元素的字体大小,如果父元素也没有设置字体大小,会一直找到根元素 `<html>`,而 `<html>` 元素的默认 `font-size` 一般是 16px。有的元素有默认的字体大小,比如 `<h1>` 的 `font-size` 默认等于 2em,最终计算还是会追溯到最外层。
```html
<html>
<head></head>
<body>
<p>1em = 16px</p>
</body>
</html>
```
#### REM
REM = Root EM,顾名思义就是相对于根元素的 EM,是根据根元素来计算出CSS 像素点的大小。根元素就是 `<html>`,而它的默认字体大小是 16px。
```css
h1 {
font-size: 20px;
margin: 1rem; /* 1rem = 16px */
}
p {
font-size: 1rem; /* 1rem = 16px */
}
```
所以,如果我们改变根元素的字体大小,页面上所有使用 rem 的元素都会被重新计算元素属性并重绘。
#### EM vs REM
EM 和 REM 都是相对单位,两者都可以用来做响应式布局的单位。根据它们的特性,EM 和 REM 互有优劣。
* EM - 对于模块化的页面元素比较好,比如 Web Components 标签,标签内的元素都根据父元素计算像素大小,只需设置最外层父元素的字体大小可同时影响子元素,保持自定义元素具有一定的模块封闭性。但,EM 比较难以追溯,需要逐层向上排查显示设置了字体大小的元素。
* REM - 方便是 REM 最大的好处,只需知道 `<html>` 的字体大小即可计算当前的实际像素大小。
有的开发者全部都用 REM,有些开发者全部用 EM,这其实都是不合理的用法。开发者应该视情况不同采用不同的单位,但在现在的环境下,REM 使用的更广泛一些。
开发者根据设计师提供的 UE 图进行开发时,测量出来的大小单位一般是像素,如果需要转换为 REM,可以采用 PostCSS 的插件 `postcss-px2rem` 自动转换为 rem 单位。
### `vw`, `vh`,百分比
#### `vw` 和 `vh`
vw 和 vh 现在还不常见,但也逐渐开始被开发者使用,特别是在布局上。
* vw - viewport width,视口宽度,1vw = 1% 视口宽度
* vh - viewport height,视口高度,1vh = 1% 视口高度
vw 和 vh 的逻辑比较简单,100vw = 100% 视口宽度,视口(viewport)会在后面详细讲解。下面的代码演示如何在 iPhone X 上计算 vw 的实际 CSS 像素大小,vh 的计算方法和 vw 一样。
```html
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
p {
width: 50vw; /* 1vw = 1 / 100 * 375px = 3.75px */
}
</style>
</head>
<body>
<p>50vw = 50% viewport width = 50% * 375px = 187.5px</p>
</body>
</html>
```
浏览器对 vw 和 vh 支持相对较晚,目前在 Android 4.4 以下的 Android Browser 上还不支持,但是国内主流应用的 WebView 内核都是自己定制的,内核版本都高于系统自带的,因此在国内 vw 和 vh 的支持度比 Can I Use 统计的要高很多,而且随着版本的推移,vw 和 vh 会更流行。

#### `vw`, `vh` vs 百分比
现在我们知道了,1vw = 1% 视口宽度,那么它们是不是等价呢?我们先来看一下下面的代码,同样还是以 iPhone X 为例。
```html
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.p1 {
width: 50vw; /* 1vw = 1 / 100 * 375px = 3.75px */
}
.outer {
width: 80vw;
}
.p2 {
width: 50%;
}
</style>
</head>
<body>
<p class="p1">50vw = 187.5px</p>
<section class="outer">
<p class="p2">50% = 150px</p>
</section>
</body>
</html>
```
将上面的代码在浏览器中运行,发现第二个 `<p>` 标签的实际宽度为 150px,不是 187.5px。其实原理非常简单,就和 EM 和 REM 一样,百分比相对于父元素的宽度来计算,而 `vw` 根据视口的宽度来计算。
所以再回顾上面的例子中的第二个 `<p>` 标签,`.outer` 元素的宽度为 80vw = 300px,那么其子 `<p>` 标签的宽度为 300px * 50% = 150px。
除 vw 和 vh 外,还有 vmin 和 vmax 元素,这里就不继续展开,感兴趣的开发者可以访问 《[Fun with Viewport Units](https://css-tricks.com/fun-viewport-units/)》了解更多,这篇文章中对 vw 和 vh 讲的很详细,还有不少示例。
Web 开发中还有很多其他的单位,如 in(英尺),mm(毫米),cm(厘米)等,但因为使用的不多,开发者仅作了解就可以。
### 弹性框
上面我们讲到了百分比和 vw/vh,它们都可以用来实现响应式的布局,但是不如我们接下来要讲的弹性框灵活,它不是单位,而是一种布局方式。
区别于传统的布局方式,如标准文档流、浮动布局和定位布局,弹性框(flexbox)布局更加灵活,弹性框中的元素可以弹性伸缩,可以定义排版方向,还可以指定 flex 元素的顺序。下面是一个简单的例子。
```html
<style>
.container {
display: flex; /* 设置容器为弹性布局 */
}
.box {
width: 100px;
height: 30vh;
}
.b1 {
background: #009;
}
.b2 {
background: #06c;
}
.b3 {
background: #39f;
}
.b4 {
background: #6cf;
width: 50px;
}
</style>
<div class="container">
<div class="box b1"></div>
<div class="box b2"></div>
<div class="box b3"></div>
<div class="box b4"></div>
</div>
```
上面的例子在浏览器中的表现如下图所示,我们能看到,每个 `<div>` 元素都是横排,这是因为弹性布局默认排列为横向排列,我们可以通过 `flex-direction` 属性决定排列方向,同时在小于 350px 宽的浏览器里,会按比例自动缩小每个 `<div>` 的宽度。

从上面的例子中,能发现,有两个重要的角色需要开发者关注,一个是容器,一个是其子元素。
#### 容器样式属性
容器指的是 `display: flex` 的元素,它可以定义其他的属性,决定子元素的排列,如下。
* `flex-direction` - 定义主轴方向,即子元素的排列方向,取值为 `row`, `row-reverse`, `column` 和 `column-reverse`,默认为 `row`,即水平从左到右
* `flex-wrap` - 默认情况下,弹性布局会将所有元素都压缩到一行,可以通过设置 `flex-wrap` 告诉浏览器在适当时候换行,取值为 `nowrap`, `wrap` 和 `wrap-reverse`,默认为 `nowrap`
* `flex-flow` - 这个属性值是 `flex-direction` 和 `flex-wrap` 的简写,如 `flex-flow: row nowrap`,等价于 `flex-direction: row; flex-wrap: nowrap`
* `justify-content` - 定义子元素在主轴上对齐方式,取值为 `flex-start`, `flex-end`, `center`, `space-between`, `space-around`,默认为 `flex-start`
* `align-items` - 定义子元素在垂直于主轴的交叉轴的排列方式,取值为 `stretch`, `flex-start`, `flex-end`, `center`, `baseline`,默认为 `stretch`,即如果没设置高度,将填满交叉轴方向
* `align-content` - 定义了子元素在多条轴线上的对齐方式,如果只使用了一条轴线,那该属性不起作用,取值为 `flex-start`, `flex-end`, `center`, `space-between`, `space-between`, `space-around` 和 `stretch`,默认为 `stretch`
在弹性布局之前,开发者如果要实现子元素水平和垂直居中会比较麻烦,在弹性布局中,非常容易实现,只需要在容器上设置轴线对齐方式,如下代码所示。
```css
.container {
display: flex; /* 设置容器为弹性布局 */
justify-content: center; /* 设置在主轴上居中对齐 */
align-items: center; /* 设置在交叉轴上居中对齐 */
}
```
#### 子元素样式属性
同样,子元素也有很多新增的样式属性,如下:
* `order` - 设置子元素在主轴方向上的顺序,取值为数字,从小到大排列,默认为 0
* `flex-grow` - 定义子元素的放大比例,取值为数字,默认为 0
* `flex-shrink` - 定义子元素的缩小比例,取值为数字,默认为 1
* `flex-basis` - 定义在分配多余空间之前,子元素的默认大小,默认为 `auto`
* `flex` - 是 `flex-grow`, `flex-shrink` 和 `flex-basis` 的简写,默认值为 `0 1 auto`
* `align-self` - 覆盖父元素的 `align-items` 属性,可以让子元素自身采用不同的对齐方式,默认为 `auto`,继承父元素的 `align-items`
弹性布局非常灵活,属性值也足够应对大部分复杂的场景。可以阅读这篇文章查看详细的介绍《[A Complete Guide to Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)》。
那么,开始之前需要了解的内容就到这了,接下来看如何实现响应式布局。
## 设置 viewport
支持响应式第一步,需要做的是设置页面的 `viewport`。移动端网页会在头部书写 viewport 的元标签,它告诉浏览器页面多大尺寸,是否需要缩放。
```html
<meta name="viewport" content="width=device-width, initial-scale=1">
```
想要理解 viewport 可以阅读 Peter-Paul Koch 写的三篇文章,《[A tale of two viewports — part one](https://www.quirksmode.org/mobile/viewports.html)》,《[A tale of two viewports — part two](https://www.quirksmode.org/mobile/viewports2.html)》,《[Meta viewport](https://www.quirksmode.org/mobile/metaviewport/)》。
在早期,移动设备经常需要打开 PC 端的网页,早期的移动设备设备像素比较低,多为 320px,而 PC 端的网页宽度一般都很大,所以,如果将 PC 端的网页在移动设备上打开,会因为页面太窄而导致布局错乱。为了解决这个问题,浏览器会将页面默认 viewport 设置为一个较大的值(Safari 默认是 980px),所以 PC 端的网页在移动设备浏览器上都能正常打开,只是元素看上去比较小。
在上面的例子中,viewport 的值 `width=device-width`,告诉浏览器用 屏幕宽度(单位为 CSS 像素)来作为页面宽度渲染,在 iPhone X 下是 375px,不同的设备宽度可能不一样。这个视口被 Peter-Paul Koch 称为理想视口(ideal viewport),也是体验最好的视口大小。

viewport 元标签的取值有 6 种,如下表所示
|字段名|取值|说明|
|----- |----|---|
|`width`|正整数,device-width|定义视口的宽度,单位是 CSS 像素,如果等于 device-width,则为理想视口宽度|
|`height`|正整数,device-height|定义视口的高度,单位是 CSS 像素,如果等于 device-height,则为理想视口高度|
|`initial-scale`|0 - 10|初始缩放比例,允许小数点|
|`minimum-scale`|0 - 10|最小缩放比例,必须小于等于 maximum-scale|
|`maximum-scale`|0 - 10|最大缩放比例,必须大于等于 minimum-scale|
|`user-scalable`|yes/no|是否允许用户缩放页面,默认是 yes|
## 确保内容不会超出 viewport
设置了 viewport 为理想视口,如果在 iPhone X 上,有元素的宽度超出了 375px,那么就会溢出到视口外面,导致出现横向滚动条。无论是在 PC 端,还是移动端,用户的都习惯上下滚动,而不是左右滚动,强迫用户横向滚动或者缩小页面来浏览全部的内容,体验很不好。
因此,不能期望设置 viewport 宽度能解决适配问题,还需要开发者记住以下原则。
* 不要使用大的固定宽度的元素,如果不考虑穿戴式设备,不要设置大于 320px 的宽度
* 不应该让内容在某一个特定宽度的 viewport 下才能正常显示
* 使用相对单位或者媒体查询让元素在不同大小的视口下适配
对于图片或者视频等嵌入式的元素,可以在站点 CSS 中添加下面的代码。
```css
img, embed, object, video {
max-width: 100%; /* 设置 img 等元素最大宽度威 100% */
}
```

## 使用媒体查询
媒体查询(media query)让开发者可以有选择性的应用不同 CSS,媒体查询提供了简单的判断方法,可以根据不同的设备特征应用不同样式,比如设备的宽度、类型、方向等,可以参考 MDN 上的文档[《CSS 媒体查询》](https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Media_queries)。
```html
<!-- 在 viewport 宽度大于 600px 时,应用 example.css 中的样式 -->
<link ref="stylesheet" href="example.css" media="min-width: 600px">
<style>
/* 如果设备类型为屏幕并且 viewport 小于 800px 宽,设置 body 背景颜色为灰色 */
@media screen and (max-width: 800px) {
body {
background: #ccc;
}
}
</style>
```
如果需要使用媒体查询应用的样式比较多,可以独立为一个文件,通过在 `<link>` 标签中设置媒体查询条件。
媒体查询支持很多设备特征,常用的主要是 viewport 的宽高和设备方向,如下表所示。
|设备特征|取值|说明|
|-------|----|----|
|`min-width`|数值,如 600px|视口宽度大于 min-width 时应用样式|
|`max-width`|数值,如 800px|视口宽度小于 max-width 时应用样式|
|`orientation`|portrait|landscape|当前设备方向,portrait 垂直,landscape 水平|
如何选择 `min-width` 和 `max-width` 的取值,我们称为选择断点,主要取决于产品设计本身,没有万能媒体查询的代码。但经过实践,我们也总结了一套比较具有代表性的设备断点,代码如下。
```css
/* 很小的设备(手机等,小于 600px) */
@media only screen and (max-width: 600px) { }
/* 比较小的设备(竖屏的平板,屏幕较大的手机等, 大于 600px) */
@media only screen and (min-width: 600px) { }
/* 中型大小设备(横屏的平板, 大于 768px) */
@media only screen and (min-width: 768px) { }
/* 大型设备(电脑, 大于 992px) */
@media only screen and (min-width: 992px) { }
/* 超大型设备(大尺寸电脑屏幕, 大于 1200px) */
@media only screen and (min-width: 1200px) { }
```
如果要对细分屏幕大小进行适配,可以查看文章,列出了详细的常见设备的媒体查询条件,《[media queries for common device breakpoints](https://responsivedesign.is/develop/browser-feature-support/media-queries-for-common-device-breakpoints/)》。
## 最佳实践
### 响应式文字
大多数用户阅读都是从左到右,如果一行文字太长,用户阅读下一行时容易出错,或者用户只会读一行文字的前半部分,而略读后半部分。在上世纪就有研究表明,一行 45 ~ 90 个英文字符是最好的,当然这要看是什么字体,一个中文汉字一般对应两个英文字符,所以,对于中国用户来说,一行文字合理的数量应该是 22 ~ 45 个字符。
字体大小对阅读体验同样重要,基本字体一般不小于 16px,行高大于 1.2em。
```css
p {
font-size: 16px;
line-height: 1.2em; /* 1.2em = 19.2px */
}
```
而设备的尺寸多种多样,如果设计师希望在平板上将字体设置为 18px,开发者可以使用前面讲到的 REM 和媒体查询,代码如下。
```css
/* 在屏幕宽度大于 600px 的设备上使用下面的样式 */
@media only screen and (min-width: 600px) {
p {
font-size: 1.125rem; /* 1.125rem = 16px * 1.125 = 18px */
}
}
```
### 响应式图片
一图胜千言,图片占网页流量消耗的 60%,可见其在 Web 的重要性。在上文提到图片不要超出视口的宽度,给图片设置 `max-width: 100%`,这确实非常有作用,那还有没有其他需要我们注意的呢。
#### 图片的质量
现代设备的 DPR (设备像素比)都很高,iPhone X 的 DPR 是 3,因此如果我们用 375px 宽的图片在 iPhone X 上显示,实际只能利用它三分之一的设备像素点,会让图片看起来很模糊,视觉体验较差。如果我们都用 3 倍分辨率的图片来显示,实际屏幕较小的设备无法完全显示如此高清晰度的图片,就会在显示时进行压缩,这对于实际屏幕比较小的设备来说会浪费较多带宽。
为此,图片质量也需要能响应式。
```html
<!-- 响应式图片 -->
<img
srcset="example-320w.jpg 320w,
example-480w.jpg 480w,
example-800w.jpg 800w"
sizes="(max-width: 320px) 280px,
(max-width: 480px) 440px,
800px"
src="example-800w.jpg" alt="An example image">
```
这里 `sizes` 和 `srcset` 很多开发者比较陌生。在兼容性不好的浏览器里,会继续使用默认 `src` 属性中的图片。
**`srcset`**
定义了几组图片和对应的尺寸,格式比较简单,主要的两个部分是图片地址和图片固有宽度,单位为像素,但是这里使用 `w` 代替 `px`。
**`sizes`**
定义了一组媒体查询条件,并且指名了如果满足媒体查询条件之后,使用特定尺寸的图片。
如果开发者书写了上面代码中的图片,浏览器会根据下面的顺序加载图片。
1. 获取设备视口宽度
2. 从上到下找到第一个为真的媒体查询
3. 获取该条件对应的图片尺寸
4. 加载 `srcset` 中最接近这个尺寸的图片并显示
所以,如果我们在视口宽度为 375px 的设备上,会采用最接近 440px 的图片,`example-480w.jpg`。
如果对 `srcset` 和 `sizes` 还想了解更多,可以访问 MDN 的文档[《响应式图片》](https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)。
#### 图片艺术方向
我们提到将图片的 `max-width` 设置为 100%,图片就会在手机屏幕上压缩到视口的宽度,如果这张图片实际上很大,图片中的内容就会看不清,特别是如果图片主要内容集中在中间,如人像,浏览效果会比较差。遇到这样的情况,最好的方式是在不同的屏幕尺寸下采用不同的图片,让主要内容保持在视口中间,如下图。

HTML 标准中有一个标签 `<picture>`,允许我们在其中设置多个图片来源,就和 `<video>`,`<audio>` 标签一样。
```html
<picture>
<source media="(max-width: 799px)" srcset="example-480w-portrait.jpg">
<source media="(min-width: 800px)" srcset="example-800w.jpg">
<img src="example-800w.jpg" alt="An example img">
</picture>
```
`<picture>` 标签的作用和上面在 `<img>` 中设置 `sizes` 和 `srcset` 一样,都能在不同的设备宽度下显示不同的图片,笔者更建议使用 `<picture>` 实现此效果。
#### 图片的其他注意事项
响应式布局在 PWA 中是非常重要的概念,在实现响应式的同时,还需要关注响应式图片是否会带来性能问题。在开发过程中,还需要注意下面几个问题。
1. 对图片进行懒加载
2. 对于小的简单的图片,可以使用矢量图或者字体,保证在不同尺寸设备下都很清晰
3. 对于尺寸小的图片,可以使用 Data URI 的方式,将图片转成 base64 内联在 CSS 或者 HTML 中,避免请求,但这样同样无法利用 HTTP 缓存,因此一般只对小于 1.5K 的图片做处理
4. 挑选恰当的图片格式,PNG,JPEG 等,可以在 Android 下使用 WebP 格式
5. 对图片进行压缩和优化
6. 采用 CSS 和 CSS 动画代替一些简单的图片和动态图,如加载中 GIF 图
### 响应式布局
利用相对单位、flexbox、媒体查询等技术,开发者能应付各种类型的页面和布局,为了方便开发者能够快速上手,下面介绍 5 种常见的响应式布局模式。这些模式最初由 [Luke Wroblewski](https://www.lukew.com/ff/entry.asp?1514) 总结并提出。
为了更好的理解这些响应式布局,笔者准备了 5 个例子。
```bash
# 从 GitHub 下载代码到本地 pwa-book-demo 目录
$ git clone https://github.com/lavas-project/pwa-book-demo.git
# 进入到 chapter02/responsive-web-design 目录
$ cd chapter02/responsive-web-design
# 安装 npm 依赖
$ npm install -g edp
# 启动 chapter02 responsive-web-design 示例
$ edp ws start
```
启动完成之后,访问 `http://localhost:8848/` 能看到 5 个不同的目录,如 mostly-fluid,点击目录里的 `index.html` 就能看到不同模式的效果,尝试拖动改变浏览器的大小吧。
#### 第一种:大体流动(Mostly Fluid)
大体流动布局的主要特点是在大屏幕上,内容区域宽度是固定的,因此在多数设备上,主要布局结构并没有很大改变,如果屏幕宽度大于内容区域,就在内容左右留白。而在视窗宽度较窄时,会逐渐掉落呈堆放,如下图所示。

大体流动布局比较简单,往往只需要少量的媒体查询就可以实现,如下代码所示。
```html
<style>
.box {
width: 100%;
height: 150px;
}
/* 设置各个区块的颜色 */
.b1 {
background: #009;
}
.b2 {
background: #06c;
}
.b3 {
background: #39f;
}
.b4 {
background: #6cf;
}
.b5 {
background: #cff;
}
/* 在大于 450px 时,将 .b2, .b3 宽度设置为 50% */
@media screen and (min-width: 450px) {
.b2, .b3 {
width: 50%;
}
}
/* 在大于 800px 时,将 .b1, .b2 宽度设置为 50%,让 .b3, .b4, .b5 平分一行 */
@media screen and (min-width: 800px) {
.b1, .b2 {
width: 50%;
}
.b3, .b4, .b5 {
width: 33.333333%;
}
}
/* 定义最大宽度为 980px */
@media screen and (min-width: 980px) {
.container {
max-width: 980px;
margin: auto;
}
}
</style>
...
<div class="container">
<div class="box b1"></div>
<div class="box b2"></div>
<div class="box b3"></div>
<div class="box b4"></div>
<div class="box b5"></div>
</div>
```
#### 第二种:列掉落 (Column Drop)
列掉落,布局中的每列随着宽度变小而逐个掉落,在视口最小的时候,每个元素都纵向堆放。和大体流动布局不同点在于,列掉落布局不会设置最大宽度,如果视口足够大,列掉落布局也会填满整个页面。媒体查询的断点选择需要根据网页本身的内容来选择。

列掉落比大体流动布局要简单,代码如下。
```html
<style>
/* 在大于 450px 时,将 .b1 和 .b2 放置在同一行,分别占据 30% 和 70% */
@media screen and (min-width: 450px) {
.b1 {
width: 30%;
}
.b2 {
width: 70%;
}
}
/* 在大于 800px 时,.b1, .b3 各占 20% 区域,.b2 在中间占 60% */
@media screen and (min-width: 800px) {
.b1, .b3 {
width: 20%;
}
.b2 {
width: 60%;
}
}
</style>
...
<div class="container">
<div class="box b1"></div>
<div class="box b2"></div>
<div class="box b3"></div>
<div class="box b4"></div>
<div class="box b5"></div>
</div>
```
#### 第三种:布局移位(Layout Shifter)
布局移位是最灵活的布局方式,它不仅仅将元素按照从前到后,从上到下排列,有时还会改变元素的位置。

代码如下:
```html
<style>
/* 在大于 800px 时,将 .b1 和 .b4 放置在同一行,分别占据 20% 和 80% */
@media screen and (min-width: 800px) {
.b1 {
width: 20%;
}
.b4 {
width: 80%;
}
}
</style>
...
<div class="container">
<div class="box b1"></div>
<div class="box b4">
<div class="box b2"></div>
<div class="box b3"></div>
</div>
</div>
```
#### 第四种:微调(Tiny Tweaks)
微调,意思就是在视口发生变化时,对内容进行一些小的调整,比如调整字体、图片大小或者元素间距等。

```html
<style>
.b1 {
background: #39f;
text-align: center;
padding-top: 10%;
font-size: 1em;
}
@media screen and (min-width: 450px) {
.b1 {
font-size: 2em;
}
}
@media screen and (min-width: 800px) {
.b1 {
font-size: 4em;
}
}
</style>
...
<div class="container">
<div class="box b1">字体大小</div>
</div>
```
#### 第五种:画布溢出(Off Canvas)
在画布溢出布局中,内容不是从上到下的,而是将不常用的内容,比如应用菜单和导航栏,折叠起来,留下一个打开的入口,只有当屏幕足够大的时候才显示。画布溢出布局很常见,不仅在 Web App 中,在 Native App 中使用更多。这样的布局一般都需要配合 JavaScript 使用。

在示例中,我们通过 `transform: translate(-275px, 0)` 将左侧侧边栏隐藏在视口外,点击菜单后,菜单栏会从左侧划出。在页面视口大于 800 时,将布局改为 `flexbox` 弹性布局,直接显示左侧菜单栏,代码如下。
```html
<style>
nav {
width: 275px;
height: 100%;
position: absolute;
/* 将菜单栏隐藏起来 */
transform: translate(-275px, 0);
transition: transform 0.3s ease-in-out;
background: #39f;
z-index: 2;
}
nav.open {
transform: translate(0, 0);
}
/* 在视口大于 800px 时,将菜单栏直接显示出来 */
@media screen and (min-width: 800px) {
nav {
position: relative;
transform: translate(0, 0);
}
body {
display: flex;
flex-flow: wrap;
}
main {
width: auto;
flex: 1;
}
}
</style>
<nav></nav>
<main>
<a id="menu">...</a>
</main>
<div id="mask"></div>
<script>
let mask = document.querySelector('#mask')
let nav = document.querySelector('nav')
let menu = document.querySelector('a')
// 点击菜单,显示或隐藏菜单栏
menu.addEventListener('click', event => {
nav.classList.toggle('open')
mask.classList.toggle('open')
})
// 点击遮罩,隐藏菜单栏
mask.addEventListener('click', event => {
nav.classList.remove('open')
mask.classList.remove('open')
})
</script>
```
以上就是比较常见的 5 种响应式布局模式,大多数情况下都需要多种模式同时使用。
## 总结
响应式布局从设计角度出发,借助视口设置、CSS 媒体查询等方法,使开发者可以更易于维护适用于不同尺寸屏幕的网页。在本节中,我们介绍了一些文字、图片以及布局方面常见的响应式设计最佳实践,开发者应用这些经验,可以更好的优化 PWA 在不同尺寸大小设备的用户体验。
================================================
FILE: chapter02.md
================================================
# 设计与体验
PWA 的提出包含了很多新的技术,如 Service Worker 等,但**用户体验**才是它的核心,用户体验包括很多方面,速度、顺滑度、阅读体验等,这不是单靠设计师能做到的,需要设计与技术互相配合,设计配合技术,技术实现设计。设计师需要考虑如何缩短用户转化流程,如何布局,让用户能方便快捷的获取信息,还需要考虑如何配合技术达到最佳用户体验;开发人员同样需要考虑采用使用什么技术来达到体验最优。
本章会从体验原则、App Shell、骨架屏和响应式布局来分析设计和技术如何互相配合来达到最佳体验。
================================================
FILE: chapter03/1-promise.md
================================================
# Promise
在深入介绍 Service Worker 之前,先来了解一下 Promise API。因为 Service Worker 的所有的异步接口内部都采用 Promise 来实现,因此学习了 Promise 讲能够有助于对 Service Worker 的理解。此外,本文还介绍了 Promise
的可靠性,链式调用的原理,并行执行的原理等较为深入的内容,感兴趣的读者也可以通过本文加深对 Promise 的理解。
## 什么是 Promise
Promise 是 ES6 引入的一种异步编程的解决方案,通过 Promise 对象来提供统一的异步状态管理方法。
过去我们通常使用注册异步回调函数的形式来进行异步编程,这里的异步回调实际上是具体的异步函数与开发者的接口约定,并不存在任何的标准,因此回调的注册形式、触发方式、异步状态管理等等都得不到统一且稳定的保证。同时这种异步回调的写法不利于状态管理,在处理多个异步过程的时候容易走进回调地狱,因此 JavaScript 异步编程需要一个统一且可靠的方案来进行异步状态管理,因此 Promise 应运而生。事实上 Promise 是社区推动的产物,在早期就出现了比如 $.Deferred、Bluebird 等库用于解决异步状态管理和回调地狱的问题,并最终促进并推动了 Promise 写进了 ES6 规范当中。
## Promise 基本用法
一般在使用 Promise 对象的时候,首先需要对其进行实例化:
```js
let promise = new Promise((resolve, reject) => {
if (/* 操作成功 */) {
resolve(value)
} else {
reject(error)
}
})
```
其中实例化的 promise 对象为异步状态的管理容器,`resolve()` 和 `reject()` 则是用于控制 promise 状态的方法。
Promise 具有三种状态:
- 'pending':初始状态,代表异步过程仍在进行中,尚未判定成功或者失败;
- 'fulfilled':操作成功。通过调用 `resolve()` 方法,promise 状态将由 'pending' 变更为 'fulfilled';
- 'rejected':操作失败。通过调用 `reject()` 方法,promise 状态将变更为 'rejected'。
在调用 `resolve()` 或 `reject()` 方法的时候可以传入任意值,比如 `resolve('操作成功')`、`reject(Error('操作失败'))` 等等,这个值会作为监听状态变更的回调函数的参数透传出去。
Promise 提供了 `.then(onFulfilled, onRejected)` 和 `.catch(onRejected)` 等原型链方法用于注册状态变更所触发的回调函数。其中 `.catch(onRejected)` 等价于 `.then(null, onRejected)`,因此为了行文方便,在没有特殊说明的情况下,后续所提到的 `.then()` 方法均用于指代 `.then()` 或 `.catch()`。
下面的示例演示了 Promise 的基本使用方式。在这个例子中创建了一个 Promise 对象,并且利用 `setTimeout()` 方法在 1 秒后触发 Promise 的状态变更,状态变更后便会触发 `onFulfilled` 回调函数并在控制台打印出 Promise 的返回值。
```js
let promise = new Promise(resolve => {
setTimeout(() => {
resolve('执行完成!')
}, 1000)
})
// 1 秒后打印“执行完成”
promise.then(value => {
console.log(value)
})
// 此时不会执行 onRejected 回调
promise.catch(error => {
console.log(error)
})
```
同理,1 秒后将 Promise 状态变更为失败则是调用 `reject()` 方法,可采用 `.then()` 和 `.catch()` 方法进行 onRejected 回调的注册:
```js
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('操作失败!')
}, 1000)
})
promise.then(
// 不会进入 onFulfilled 回调
value => {
console.log(value)
},
// 1 秒后打印“操作失败![1]”
error => {
console.log(error + '[1]')
}
)
// 1 秒后打印“操作失败![2]”
promise.catch(error => {
console.log(error + '[2]')
})
```
当回调函数执行出错时,promise 的状态同样会变更为 'rejected':
```js
let promise = new Promise((resolve, reject) => {
throw Error('操作失败!')
})
promise.catch(error => {
// 打印“操作失败!”
console.log(error.message)
})
```
在一些复杂的异步场景当中,我们还可以使用变量将 resolve 和 reject 缓存下来,等到需要变更 promise 状态的时候再去触发它们,这种情形在配合上各种闭包写法,可以实现很多神奇的功能:
```js
let resolve
let reject
let promise = new Promise((res, rej) => {
resolve = res
reject = rej
})
promise.then(value => {
console.log(value)
})
/* 一些神仙操作 */
if (/* 异步操作成功 */) {
resolve(value)
} else {
reject(error)
}
```
Promise 提供了 `Promise.resolve(value)` 和 `Promise.reject(error)` 来快速获得一个确定状态以及返回值的 Promise 对象,在一些特定的使用场景下,这两个函数能够起到简化代码的作用。
```js
let p1 = Promise.resolve(12345)
// 等价于
let p1 = new Promise(resolve => {
resolve(12345)
})
let p2 = Promise.reject(Error('出错了'))
// 等价于
let p2 = new Promise((resolve, reject) => {
reject(Error('出错了'))
})
```
## Promise 的可靠性
Promise 作为异步状态的管理方案,首先要解决的是状态管理的可靠性问题,这里包括操作的可靠性和状态的可靠性两个方面,Promise 通过以下特点来依次解决这些可靠性问题。
### 统一的格式
Promise 对象统一了异步状态管理的格式,经过 Promise 包装的异步过程将具有统一的状态变更方式,统一的 API 以及统一的回调函数格式。这样就再也不需要为过去不同形式的回调函数所困扰。
我们可以做个对比,在过去采用回调函数的机制进行异步编程时,写法五花八门:
```js
// ajax 风格的回调写法
run({
success (value) {
console.log('执行成功!')
},
error (error) {
console.log('执行失败!')
}
})
// nodejs 风格的回调写法
run((error, result) => {
if (error) {
console.log('执行失败!')
} else {
console.log('执行成功!')
}
})
// 事件监听风格的回调写法
run.onsuccess = (result) => {
console.log('执行成功!')
}
run.onfail = (error) => {
console.log('执行失败!')
}
run()
```
而 Promise 只有一种写法,完成了格式上的统一,这也为下一节将要介绍的 Promise 链式调用提供基础:
```js
let promise = run()
promise.then(result => {
console.log('执行成功!')
})
promise.catch(result => {
console.log('执行失败!')
})
```
### Promise 状态不受外部影响
Promise 只能通过 `resolve()` 和 `reject()` 方法控制 Promise 的状态,这个状态无法被外部直接访问,也没有提供任何方法从外部修改状态,这就保证了 Promise 状态不受外部影响。
### Promise 状态具有确定性
Promise 对象一旦从初始状态(pending)变更为执行成功(fulfilled)或者执行失败(rejected),那么这个状态就被完全确定下来了,不会被后续的任何操作所影响,即便在此后多次调用 `resolve()` 或 `reject()`,这个 Promise 对象的状态也将永远是这个初次变更时的状态。同时,初次调用 `resolve` 或者 `reject` 所传入的参数也将会固定下来。
```js
let promise = new Promise((resolve, reject) => {
// 初次触发状态变更为 fulfilled,
// 同时记录返回值为 1 并触发 onFulfilled 回调函数
resolve(1)
// 后续的操作都不会影响状态,
// 也不会覆盖掉返回值,
// 也不会多次触发 onFulfilled 回调
reject(2)
resolve(3)
reject(4)
})
// 打印 1
promise.then(value => {
console.log(value)
})
// 不会进入该 onRejected 回调
promise.catch(error => {
console.log(error)
})
```
Promise 的这一特性确保了异步过程最终状态的确定性,不用担心这个状态在后续的任何时候发生变更。
### Promise 回调函数是一次性的
由于 Promise 对象上注册的回调函数只会至多触发一次,这个特点规避了过去基于基于回调函数的异步编程当中回调函数执行次数不受控制的问题。在 Promise 的这套机制下,希望触发几次回调,就注册几个回调函数即可。
```js
// 假设异步函数的实现机制如下,会存在多次调用 callback 的情况
function run (callback) {
setInterval(callback, 1000)
}
// 采用 Promise 进行包装,就能够避免这一问题
let promise = new Promise(resolve => {
run(resolve)
})
// 只会触发一次
promise.then(() => {
console.log('执行完成!')
})
```
### Promise 不存在回调过早问题
由于 Promise 的状态具有确定性,一旦固定下来后便不会发生任何更改,因此在任何时候注册回调函数都可以监听到 Promise 的状态。如果回调函数在状态变更前注册,则会等待状态变更时触发;当注册时状态已经确定下来,那么 Promise
会立即调用这个函数并传入相应的返回值。这就解决了过去回调函数机制可能存在的回调过早问题(即事件在回调注册前触发导致回调监听失效),在 Promise 机制的保证下,这种问题不会发生。
下面举个例子演示后注册的 onFulfilled 回调获取返回值的情况:
```js
let promise = new Promise((resolve, reject) => {
// 1 秒时触发状态变更为 fulfilled
setTimeout(() => {
resolve('操作成功!')
}, 1000)
})
// 0 秒时注册 onFulfilled
promise.then(value => {
console.log(value + '[1]')
})
// 2 秒时注册 onFulfilled
setTimeout(() => {
promise.then(value => {
console.log(value + '[2]')
})
}, 2000)
```
这段代码的控制台输出结果为:
```bash
# (...1s)
操作成功![1]
# (...2s)
操作成功![2]
```
可以看到,第 0 秒注册的回调函数在第 1 秒 promise 对象状态变更的时候触发,同时第 2 秒注册的的回调函数会立即触发并成功获得返回值。
这一特性确保了在任何时候注册 promise 的回调函数都不会错过异步返回的结果,这个点在回调函数的年代很难被保证的。
### Promise 的回调函数之间不会相互影响
同一个 Promise 上注册的回调函数彼此相互隔离,因此个别回调函数执行出错并不会影响到其他回调函数的正常执行。
```js
let promise = new Promise(resolve => {
setTimeout(() => {
resolve('操作成功!')
}, 1000)
})
// 1 秒后执行回调并抛错
promise.then(value => {
throw Error('出错了')
})
// 永远不会进到 onRejected 回调中
// 因为onFulfilled 执行出错不会影响 promise 的状态
promise.catch(error => {
console.log(error)
})
// 1 秒后打印“操作成功!”
promise.then(value => {
console.log(value)
})
```
### Promise 回调函数执行的时序是确定的
首先举个例子来说明问题。假设目前存在一个函数 `run()`,它可以传入回调函数作为参数,那么相应的代码如下所示:
```js
console.log('a')
run(() => {
console.log('b')
})
console.log('c')
```
在不知道 run 函数的内部实现之前,我们完全无法预测这段代码的执行结果。比如以下这两种实现方式,其打印的结果是完全不一样的:
```js
function run (callback) {
callback()
}
// 打印 a b c
/*****/
function run (callback) {
setTimeout(callback)
}
// 打印 a c b
```
但如果 run 函数通过 Promise 的方式来实现,并且回调函数放到 `.then` 方法当中执行,那么我们就可以很明显地知道打印结果一定是“a c b”:
```js
console.log('a')
run().then(() => {
console.log('b')
})
console.log('c')
// 打印 a c b
```
这里涉及到 microtask、JavaScript 事件循环机制相关 的概念,感兴趣的同学可以搜索相应关键字进行深入了解。
### 小节
总的来说,Promise 通过一系列特性解决了过去异步编程当中存在的可靠性问题,使得我们能够以一种更为简单而规整的方式去获取和管理异步状态。
## Promise 的串行执行与链式调用
在开篇 Promise 的演示当中一个最为亮眼的特点就是,通过一连串的 `.then()` 链式调用来实现多个异步方法的顺序执行问题:
```js
run1()
.then(run2)
.then(run3)
.then(run4)
.catch(error => {
console.log('执行出错')
})
```
那么接下来我们将从 `.then()` 出发,一步一步地弄明白其中的 Promise 传递过程,并最终理解 Promise 的链式调用机制。
### Promise.prototype.then
`.then(onFulfilled, onRejected)` 是 Promise 的原型链方法,用于注册 Promise 对象状态变更时的回调函数。它接受两个回调函数作为参数,分别在 Promise 变更为不同状态时触发,其中 `onRejected` 可以缺省。
```js
promise.then(
result => {
console.log('执行成功!')
},
error => {
console.log('执行失败!')
}
)
```
`.then()` 方法会创建并返回一个新的 Promise 对象(用 p2 指代,当前监听的 Promise 对象用 p1 指代),用于表征回调函数的执行情况。这个过程满足如下规则:
- p1 的状态只决定了何时执行回调以及执行哪种类型的回调,并不会影响到 p2 状态;
- p2 的初始状态为 'pending',当回调函数执行成功时状态变更为 'fulfilled',如果回调执行过程抛出异常则变更为 'rejected';
- 回调函数的返回值 value 将作为 p2 触发状态变更时 `resolve(value)` 的参数将其传递下去。
这里存在一个有意思的地方,由于回调函数可以返回任何的结果,因此返回一个 Promise 对象也是可行的。我们在这里用 p3 来指代这个 Promise 对象,在这种情况下首先明确 p2 与 p3 两个不同的 Promise 对象,但是 p2 与 p3 的状态是一致的,这里的“一致”包括最终的状态、状态触发的时机以及返回值的一致性。我们来举例说明这个过程:
```js
let p1 = new Promise(resolve => {
resolve('[p1]')
})
let p2 = new Promise(resolve => {
resolve(p1)
})
// 打印 false
console.log(p1 === p2)
// 打印 “[p1]”
p2.then(value => {
console.log(value)
})
```
- 当 p1 需要调用的回调函数不存在时,则会调用 p2 的 `resolve(p1)` 方法,将这个状态持续传递下去;
```js
// 产生一个 rejected 状态的 Promise 对象
let p1 = new Promise((resolve, reject) => {
reject('[p1]')
})
// 当前注册的 onFulfilled 回调不会触发
// 同时 onRejected 回调并未注册,因此 p1 的状态会继续向下传递:
let p2 = p1.then(value => {
console.log(value)
})
// 打印 '[p1]'
p2.catch(error => {
console.log(error)
})
```
以上这些就给异步状态提供了可传递性,为 Promise 的链式调用提供了状态传递的基础。
下面通过一些例子来说明 `.then()` 方法在不同情况下的执行结果。
#### 1. 正常顺序执行
```js
// 获取初始 promise 对象
let promise = new Promise(resolve => {
setTimeout(() => {
resolve('执行成功!')
}, 1000)
})
// onFulfilled 回调执行完成
// 因此 p1 状态变更为 'fulfilled'
let p1 = promise.then(result => {
return result + '[1]'
})
// 1 秒后打印“执行成功![1]”
let p2 = p1.then(result => {
console.log(result)
})
```
#### 2. 错误处理
```js
// 获取初始 promise 对象
let promise = new Promise(resolve => {
// 1 秒后触发执行失败
setTimeout(() => {
reject('执行失败!')
}, 1000)
})
// 1 秒后打印“执行失败”
// 同时由于 onRejected 回调执行完成
// p1 状态变更为 'fulfilled'
let p1 = promise.catch(error => {
console.log(error)
})
// 打印 undefined,因为 p1 注册的回调没有任何返回
let p2 = p1.then(value => {
console.log(value)
})
```
#### 3. 执行回调时抛出异常
```js
// 获取初始 promise 对象
let promise = new Promise(resolve => {
// 1 秒后触发执行成功
setTimeout(() => {
resolve('执行成功!')
}, 1000)
})
// 1 秒后执行回调并抛出异常
// 此时 p1 状态变更为 'rejected'
let p1 = promise.then(value => {
throw Error('执行异常!')
})
// 打印“执行异常!”并返回字符串
// 由于该回调执行完成因此 p2 状态变更为 'fulfilled'
let p2 = p1.catch(error => {
console.log(error.message)
return '恢复正常!'
})
// 打印“恢复正常!”
// 同时 p3 状态变更为 'fulfilled'
let p3 = p2.then(value => {
console.log(value)
})
```
#### 4. 回调函数返回 Promise 对象
```js
// 初始 Promise 对象,2 秒后执行成功并返回 '[p1]'
let p1 = new Promise(resolve => {
setTimeout(() => {
resolve('[p1]')
}, 2000)
})
let p2 = p1.then(result => {
return new Promise(resolve => {
setTimeout(() => {
resolve('[p3]')
}, 1000)
})
})
// 3 秒后打印 '[p3]'
p2.then(result => {
console.log(result)
})
```
通过这个机制就实现了多个异步过程的串行执行,只需要将所有的异步过程统一使用 Promise 进行包裹,并且将下一个异步过程的 Promise 对象作为上一个异步过程 Promise 对象的 `onFulfilled` 回调函数的返回值即可。
### Promise 的链式调用
通过前面的举例可以看到 `.then()` 方法是 Promise 对象的原型链方法,并且其返回值同样也是个 Promise 对象,因此只要把前面例子中一些无关紧要的中间变量去除掉,就实现 Promise 的链式调用了。
```js
new Promise(resolve => {
setTimeout(() => {
resolve('执行成功!')
}, 1000)
})
.then(result => {
console.log('步骤 [1]')
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(Error('执行异常'))
}, 1000)
})
})
.catch(error => {
console.log(error.message)
return '恢复正常'
})
.then(result => {
console.log(result)
})
```
链式调用的好处就是,可以非常直观地将多个需要按顺序执行的异步过程以一种自上而下的线性组合方式实现,在降低编码难度的同时,也增加了代码的可读性。
同时基于注册在同一 Promise 对象的回调函数彼此不相干扰的特性,我们可以在任何需要的地方进行链分叉。在下面的例子当中,假设对于初始 Promise 对象的不同状态将采取两种完全不一样的异步操作的时候,就可以这么实现:
```js
let promise = new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
resolve()
} else {
reject()
}
})
promise.then(run1)
.then(run2)
.then(run3)
// ...
promise.catch(run4)
.then(run5)
.then(run6)
// ...
promise.then(run7)
.then(run8)
// ...
```
## Promise 并行执行与管理
在 JavaScript 当中,异步任务本身就是并行执行的。前面所提到的基于 Promise 的异步任务串行执行,本质上是通过 `.then()` 方法去控制上一个异步任务完成之后再触发下一个异步任务的执行,所以如果要改造成并行执行,只需要同步地创建这些异步任务,并对它们的 Promise 对象进行相应的管理即可。
下面的例子展示了并行获取异步数据 x 和 y,并且在 x 和 y 全部获取之后输入它们的相加结果,其中 `getX()` 和 `getY()` 分别是 x 和 y 的异步获取方法,`getXAndY()` 用于同步返回 x 和 y 的结果:
```js
function getX () {
return new Promise(resolve => {
setTimeout(() => {
resolve(1)
}, 1000)
})
}
function getY () {
return new Promise(resolve => {
setTimeout(() => {
resolve(10)
}, 2000)
})
}
function getXAndY([promiseX, promiseY]) {
let results = []
return promiseX
.then(x => {
results.push(x)
return promiseY
})
.then(y => {
result.push(y)
return results
})
}
getXAndY([
getX(),
getY()
])
.then(results => {
console.log(x + y)
})
```
执行结果如下:
```bash
# (...2s)
11
```
可以看到 2s 后控制台输出了结果 11,说明 `getX()` 和 `getY()` 是并行执行的,并且在两个 Promise 状态全部成功之后,再最终返回两者的相加结果。
这里的 `getXAndY()` 就属于一种并行状态管理的方案。事实上 Promise 已经提供了 `Promise.all()` 方法来实现同样的功能。因此上述代码可修改为使用 `Promise.all()` 的形式:
```js
Promise.all([
getX(),
getY()
])
.then(results => {
console.log(x + y)
})
```
除了 `Promise.all()`,Promise 还提供了 `Promise.race()` 方法,用于获取第一个发生状态变更的 Promise 对象:
```js
Promise.race([
getX(),
getY()
])
.then(value => {
// 打印“1”,因为 x 的结果最先返回
console.log(value)
})
Promise.race([
getX(),
new Promise((resolve, reject) => {
reject('error')
})
])
// 不会进入 onFulfilled
.then(value => {
console.log(value)
})
// 打印“error”
// 因为这个 Promise 最先返回
.catch(error => {
console.log(error)
})
```
假如 `Promise.all()` 和 `Promise.race()` 都无法满足应用场景,我们也可以基于 Promise 的原理与特性,自行开发相应的并行执行管理方案,在这里就不做赘述了。
## 总结
这篇文章介绍了 Promise 基本用法,介绍了 Promise 对象所具有的特性如何解决异步状态的可靠性问题,最后介绍了基于 Promise 的串行和并行执行的实现原理。Promise 是前端异步编程的基础,随着前端生态的不断完善,网站功能的前后端交互将会变得越来越复杂,Promise
也将会在各种复杂的异步编程当中发挥着越来越重要的作用。
================================================
FILE: chapter03/2-async-function.md
================================================
# Async 函数
上一节介绍了 Promise 对象,我们可以很方便地利用 Promise 将过去基于回调函数的异步过程改造成基于链式调用实现,这样更符合我们线性的思维习惯。但实践过程中发现,这种链式调用的异步方案仍然不够直观,我们更希望采用类似于同步函数的书写方式来实现异步。因此在 ES2017 标准中引入了 Async 函数(Async Functions)用于进一步简化异步编程。
需要注意的是,由于 Async 函数语法比较新,目前只在最新版的浏览器上得到了支持,因此在项目中如果使用了 Async 函数,可能需要准备 Babel 等代码编译工具,将 Async 函数语法转换成 ES5 语法实现。

首先我们通过一个简单的例子来演示 Async 函数的作用。
在这之前首先准备一个异步函数 `sleep()`,其作用是将 setTimeout 方法用 Promise 对象进行包装:
```js
function sleep (time) {
return new Promise(function (resolve) {
setTimeout(resolve, time)
})
}
```
通过上一节的学习我们知道可以通过链式调用 `Promise.then` 方法来实现异步过程。比如下面的例子当中,在执行 `main()` 1 秒之后将在控制台打印出“结束”的文案的实现如下所示:
```js
function main () {
console.log('开始:' + new Date())
return sleep(1000)
.then(() => {
console.log('结束:' + new Date())
})
}
```
接下来改用 Async 函数来实现同样功能的函数:
```js
async function main () {
console.log('开始:' + new Date())
await sleep(1000)
console.log('结束:' + new Date())
}
```
可以看到,通过使用 `async` 和 `await` 修饰符改写之后的 `main()` 就不再需要书写复杂的 Promise 链式调用了,同时 Async 函数的语法也更为接近同步函数,无论是书写体验还是阅读体验都得到了较大的提升。
## 语法说明
### Async 函数定义
Async 函数需要通过 `async` 修饰符进行定义,下面所举例的定义方式都是合法的:
```js
// 普通函数
async function foo (/* 参数 */) {/* 函数体 */}
// 匿名函数
const foo = async function () {}
// 箭头函数
const foo = async () => {}
// 对象方法简写
const obj = {
async foo () {}
}
// 函数作为参数
list.map(async () => {})
```
Async 函数会将函数体的所有执行结果通过一个隐式的 Promise 对象返回:
```js
async function foo () {}
// 等价于
function foo () {
return new Promise(resolve => resolve())
}
async function foo () {
return 'Hello World'
}
// 等价于
function foo () {
return new Promise(resolve => resolve('Hello World'))
}
async function foo () {
let promise = new Promise(resolve => resolve('Hello World'))
return promise
}
// 等价于
function foo () {
let promise = new Promise(resolve => resolve('Hello World'))
return new Promise(resolve => resolve(promise))
}
```
### Async 函数错误处理
假如 Async 函数的函数体在执行过程中存在未捕获的错误,那么返回的 Promise 对象将会通过 reject 方法将异常值传递下去:
```js
async function foo () {
throw Error('出错了')
}
// 等价于
function foo () {
return new Promise((resolve, reject) => reject(Error('出错了')))
}
```
假如 Async 函数返回了异步的错误,也就是返回的 Promise 对象状态变更为 rejected,
```js
async function foo () {
return Promise.reject('出错了')
}
// 等价于
function foo () {
return new Promise(resolve => resolve(
Promise.reject('出错了')
))
}
```
这样一来都可以通过链式调用来捕获异常:
```js
foo().then(
() => {},
e => {
// 打印 '出错了'
console.log(e)
}
)
// 或
foo().catch(e => {
// 打印 '出错了'
console.log(e)
})
```
### await 表达式定义
Async 函数的函数体中可能存在 await 表达式。await 表达式非常简单,只需要在 Promise 对象前增加 `await` 关键字即可,同时 await 表达式的返回值就是 Promise 通过 resolve() 所返回的结果:
```js
async function main () {
// sleep(1000) 返回 Promise 对象,并在 1s 后 resolve
await sleep(1000)
// val1 === 'Hello World'
let val1 = await Promise.resolve('Hello World')
// 等待 1s 后对 val2 进行赋值
// val2 === 'Hello World'
let val2 = await sleep(1000).then(() => 'Hello World')
}
```
await 表达式可以作为 Async 函数的返回结果:
```js
async function main () {
return await sleep(1000).then(() => 'Hello World')
}
main().then(result => {
// 打印 Hello World
console.log(result)
})
```
当 `await` 关键字后面跟的不是 Promise 对象,会自动将其转换为 Promise 对象的返回结果:
```js
// 以下代码从 Async 函数体内节选
let val = await 'Hello World'
// 等价于
let val = await Promise.resolve('Hello World')
```
当 Async 函数执行到 await 表达式的时候会暂停执行,等待 await 表达式的 Promise 对象状态发生变更之后,再去执行后续的步骤。
### await 表达式错误用法
需要强调的是,await 表达式只能在 Async 函数中使用,如果在这个范围之外使用,程序将会报语法错误(SynaxError)。下面的例子举例了一些常见的错误用法:
```js
// 错误,await 表达式必须在 Async 函数中执行
await sleep(1000)
function foo () {
// 错误,foo 不是 Async 函数
await sleep(1000)
}
async function main () {
const foo = () => {
// 错误,因为该匿名函数不是 Async 函数
await sleep(1000)
}
}
async function bar () {
let intervals = [1000, 1000, 2000]
intervals.forEach(interval => {
// 错误,因为该匿名函数不是 Async 函数
await sleep(1000)
})
}
```
### await 表达式异常捕获
`await` 关键字后面跟的 Promise 对象可能会执行 reject,这时 await 表达式就会抛出异常,异常值就是 reject 方法所回传的值。我们可以通过 `try/catch` 捕获这个异常并进行处理:
```js
async function foo () {
try {
await Promise.reject('发生错误')
} catch (e) {
// 打印 '发生错误'
console.log(e)
}
}
```
其效果与直接对 Promise 对象的异常进行捕获是等价的:
```js
async function foo () {
await Promise.reject('发生错误')
// 打印 '发生错误'
.catch(e => console.log(e))
}
```
如果不对 await 表达式的抛错进行捕获处理,那么这个错误会继续向外传递,并最终以 Promise.reject 的方式将错误抛到 Async 函数外部:
```js
async function foo () {
await Promise.reject('发生错误')
}
// 打印 '发生错误'
foo().catch(e => console.log(e))
```
## Async 函数用法举例
通过上面的学习对 Async 函数的语法和功能有了一定的了解之后,接下来我们准备几个示例来加深理解。
### 常规用法
在本示例中,将演示如何定义并使用异步函数、读取异步数据、捕获异步异常等等。
这个示例演示了这样一个过程,首先执行 getRandomNumber() 异步地获取一个 0 - 1 之间的随机数,然后送入 shouldLargerThan() 方法进行检查,当随机数小于给定的数值 0.5 时,抛出异常,反之则通过。
首先简单实现 getRandomNumber 和 shouldLargerThan 的功能:
```js
// 一秒后返回一个 0 - 1 的随机数
async function getRandomNumber () {
await sleep(1000)
return Math.random()
}
// 一秒后查看传入的数字是否大于期望值 spec
async function shouldLargerThan (spec, num) {
await sleep(1000)
// 当数值小于 0.5 时抛出异常
if (num < spec) {
throw '小于 ' + spec
}
console.log('大于等于 ' + spec)
}
```
接下来就可以定义执行整个异步过程的 Async 函数 `run()`:
```js
async function run () {
// 获取异步数据
let num = await getRandomNumber()
console.log(num)
try {
await shouldLargerThan(0.5, num)
} catch (e) {
// 捕获异常
// 打印 '小于 0.5'
console.error(e)
}
console.log('结束')
}
run().then(() => console.log('任务全部执行完毕'))
// ... (等待 1s)
// 0.3(假设生成的随机数为 0.3)
// ... (等待 1s)
// 小于 0.5
// 结束
// 任务全部执行完毕
```
### 顺序执行异步操作
首先我们定义 3 个异步执行的任务,他们都会在任务开始的时候打印任务开始信息,等待一秒之后再打印任务结束信息。
```js
async function task1 () {
console.log('Task1 开始')
await sleep(1000)
console.log('Task1 结束')
}
async function task2 () {
console.log('Task2 开始')
await sleep(1000)
console.log('Task2 结束')
}
async function task3 () {
console.log('Task3 开始')
await sleep(1000)
console.log('Task3 结束')
}
```
如果我们需要按顺序依次执行这些任务,根据前面所学内容,可以利用 await 表达式实现:
```js
async function main () {
await task1()
await task2()
await task3()
}
main()
// Task1 开始
// ... (等待 1s)
// Task1 结束
// Task2 开始
// ... (等待 1s)
// Task2 结束
// Task3 开始
// ... (等待 1s)
// Task3 结束
```
我们可以使用 for 循环来简化这一过程,下面的示例展示了使用 for 循环实现同样的效果,读者可以自行尝试使用 for...of 或者 while 等循环语句实现:
```js
async function main () {
const tasks = [task1, task2, task3]
for (let i = 0; i < tasks.length; i++) {
await tasks[i]()
}
}
```
需要注意的是,这里的 for 循环无法用 forEach 代替,这是因为 forEach 只会同步执行它的回调函数,不会受到 await 的阻塞影响:
```js
tasks.forEach(async task => await task())
// 等价于
for (let task of tasks) {
task()
}
```
### 并发执行异步操作
假设我们需要这些任务并行执行,那么不使用 await 表达式就能够实现:
```js
function main () {
task1()
task2()
task3()
}
main()
// Task1 开始
// Task2 开始
// Task3 开始
// ... (等待 1s)
// Task1 结束
// Task2 结束
// Task3 结束
```
上面的函数可以使用 for/while/forEach 等等各种循环方法来进行简化:
```js
function main () {
const tasks = [task1, task2, task3]
tasks.forEach(task => task())
}
```
假设我们需要在所有的任务全部完成之后去执行某些操作,那么可以结合 Promise.all 方法实现:
```js
async function main () {
await Promise.all([
task1(),
task2(),
task3()
])
console.log('任务全部执行完毕')
}
main()
// Task1 开始
// Task2 开始
// Task3 开始
// ... 等待 1s
// Task1 结束
// Task2 结束
// Task3 结束
// 任务全部执行完毕
```
我们也可以利用 `Array.map` 来简化这一过程:
```js
async function main () {
const tasks = [task1, task2, task3]
const promises = tasks.map(task => task())
await Promise.all(promises)
console.log('任务全部执行完毕')
}
```
================================================
FILE: chapter03/3-fetch-api.md
================================================
# Fetch API
Fetch API 是目前最新的异步请求解决方案,它在功能上与 XMLHttpRequest(XHR)类似,都是从服务端异步获取数据或资源的方法。对于有过 AJAX 开发经验的读者应该深有体会,基于 XHR 的异步请求方法在实现上比较复杂。下面简单演示如何通过 XHR 发送异步请求:
```js
// 实例化 XMLHttpRequest
let xhr = new XMLHttpRequest()
// 定义加载完成回调函数,打印结果
xhr.onload = function () {
console.log('请求成功')
}
// 定义加载出错时的回调函数,打印错误
xhr.onerror = function (err) {
console.error('请求失败')
}
// 设置请求目标
xhr.open('GET', '/path/to/text', true)
// 开始发起请求
xhr.send()
```
从上面的代码当中可以感受到,基于事件回调机制的 XHR 在编程实现的思路上非常反思维,要实现这样一个简单的 GET 请求所需代码较多,一旦功能变得复杂很容易会造成混乱。因此在实际应用当中,一般会选择封装好的函数进行使用,比如较为常见的是 jQuery 所提供的 $.ajax 方法。
接下来使用 Fetch API 来实现上述功能:
```js
fetch('/path/to/text', {method: 'GET'})
.then(response => {
console.log('请求成功')
})
.catch(err => {
console.error('请求失败')
})
```
经过对比可以发现,在使用 Fetch API 之后,代码逻辑变得更清晰,所需的代码也变得更少。当然 Fetch API 的优点还不止这些,在本节的内容当中,将逐步对 Fetch API 进行更加深入的学习。
## 兼容性
Fetch API 的标准目前由 WHATWG 组织进行制定与维护,虽然尚未纳入 W3C 规范当中,但从 Can I Use 网站的统计数据来看,各大主流浏览器已经基本上实现了对 Fetch API 的支持。

对于尚未支持或支持度不完整的浏览器,开源社区也提供了相关 Polyfill,开发者可以通过 npm 进行安装和使用:
```shell
npm install --save whatwg-fetch
```
安装完成之后,只需在 JS 入口文件引入 Polyfill 即可:
```js
import 'whatwg-fetch'
// 引入 polyfill 之后,就可以正常使用 Fetch API 了
window.fetch(/* 相关参数 */)
```
对于不使用 npm 的项目,也可以到 whatwg-fetch 的 [GitHub 主页](https://github.com/github/fetch)直接下载并使用 fetch.umd.js 文件。
## 概念和用法
Fetch API 首先提供了网络请求相关的方法 `fetch()`,其次还提供了用于描述资源请求的 Request 类,以及描述资源响应的 Response 对象,这样就能够以一种统一的形式将资源的请求与响应过程应用到更多的场景当中。
### fetch()
Fetch API 提供了 `fetch()` 用来发起网络请求并获得资源响应。它的使用方法非常简单,相关语法如下所示:
```js
fetch(request).then(response => {/* 响应结果处理 */})
```
可以看到,`fetch()` 需要传入一个 Request 对象作为参数,`fetch()` 会根据 request 对象所描述的请求信息发起网络请求;由于网络请求过程是个异步过程,因此 `fetch()` 会返回 Promise 对象,当请求响应时 Promise 执行 resolve 并传回 Response 对象。
除了直接以 Request 对象作为参数之外,`fetch()` 还支持传入请求 URL 和请求配置项的方式,`fetch()` 会自动根据这些参数实例化 Request 对象之后再去发起请求,因此以下代码所展示的请求方式都是等价的:
```js
fetch(new Request('/path/to/resource', {method: 'GET'}))
// 等价于
fetch('/path/to/resource', {method: 'GET'})
```
需要注意的是,`fetch()` 只有在网络错误或者是请求中断的时候才会抛出异常,此时 Promise 对象会执行 reject 并返回错误信息。因此对于 `fetch()` 来说,服务端返回的 HTTP 404、500 等状态码并不认为是网络错误,因此除了检查 Promise 是否 resolve 之外,还需要检查 Response.status、Response.ok 等属性以确保请求是否成功响应。下面的示例代码通过检查响应 status 是否为 200 来判断请求是否成功:
```js
fetch('/path/to/resource').then(response => {
if (response.status === 200) {
// 请求成功
} else {
// 请求失败
}
})
.catch(err => {
// 网络请求失败或请求被中断
})
```
### Request
Request 是一个用于描述资源请求的类,通过 Request() 构造函数可以实例化一个 Request 对象,其语法如下所示:
```js
let request = new Request(input, init)
```
其中,input 代表想要请求的资源,可以是资源的 URL,或者是描述资源请求的 Reqeust 对象;init 为可选参数,可以用来定义请求中的其他选项。可以注意到,Request 构造函数所需参数与 `fetch()` 方法的参数是一样的。下面将通过一些例子来演示一些常见请求类型的实例化方法:
1.GET 请求,请求参数需要写到 URL 当中。
```js
let getRequest = new Request('/api/hello?name=lilei', {
method: 'GET'
})
```
2.POST 请求,请求参数需要写到 body 当中。
```js
let postRequest = new Request('/api/hello', {
method: 'POST',
// body 可以是 Blob、FormData、字符串等等
body: JSON.stringify({
name: 'lilei'
})
})
```
3.自定义请求的 Headers 信息。
```js
let customRequest = new Request('/api/hello', {
// 这里展示请求 Content-Type 为 text/plain 的资源
headers: new Headers({
'Content-Type': 'text/plain'
})
})
```
4.设置发起资源请求时带上 cookie。
```js
let cookieRequest = new Request('/api/hello', {
credentials: 'include'
})
```
init 对象还可以配置其他参数,此处先不做展开,在后续的内容当中会针对一些特定参数做进一步说明。
由于在后面实现资源请求的拦截代理时,需要对拦截的请求进行判断分类,也就是对 Request 对象的属性进行检查,因此介绍一下 Request 对象常用的几个属性:
- url:String 类型,只读,请求的 url;
- method:String 类型,只读,请求的方法,如 'GET','POST' 等;
- headers:Headers 类型,只读,请求的头部,可通过 get() 方法获取 'Content-Type','User-Agent' 等信息。
下面举例使用以上属性对请求进行判断:
```js
if (request.url === 'https://example.com/data.txt') {
// ...
}
if (request.method === 'POST') {
// ...
}
if (reuqest.headers.get('Content-Type') === 'text/html') {
// ...
}
```
### Response
Response 类用于描述请求响应数据,通过 Response() 构造函数可以实例化一个 Response 对象,其实例化语法如下所示:
```js
let response = new Response(body, init)
```
其中 body 参数代表请求响应的资源内容,可以是字符串、FormData、Blob 等等;init 为可选参数对象,可用来设置响应的 status、statusText、headers 等内容。下面举例说明如何构造一个 index.js 的响应:
```js
let jsResponse = new Response(
// index.js 的内容为,在控制台打印 "Hello World!"
'console.log("Hello World!")',
{
// 定义状态码为请求成功
status: 200,
// 通过 headers 定义 JS 的 Content-Type
headers: new Headers({
'Content-Type': 'application/x-javascript'
})
}
)
```
在实际应用当中,我们一般会通过 `fetch()`、Cache API 等等获得请求响应对象,然后再对响应对象进行操作。
#### 判断请求是否成功
前面在介绍 `fetch()` 时提到,对于服务端返回 HTTP 404、500 等错误码 `fetch()` 不会将其当成网络错误,这时就需要对 Response 对象的相关属性进行检查。
- status:Number 类型,包含了 Response 的状态码信息,开发者可以直接通过 status 属性进行状态码检查,从而排除服务端返回的错误响应;
- statusText:String 类型,包含了与状态码一致的状态信息,一般用于解释状态码的具体含义;
- ok:Boolean 类型,只有当状态码在 200-299 的范围时,ok 的值为 true。
除了上述提到的属性之外,也同样可以借助 headers 等属性进行辅助判断,具体检查方式与实际需求有关。下面举例如何使用 ok 和 status 进行判断:
```js
if (response.ok || response.status === 0) {
// status 为 0 或 200-299 均代表请求成功
} else {
// 请求失败
}
```
#### 读取响应体
Fetch API 在设计的时候就采用了数据流的形式去操作请求体和响应体,这样在传输大数据或大文件时会非常有优势。Response 的 body 属性暴露了一个 ReadableStream 类型的响应体内容。Response 提供了一些方法来读取响应体:
- text():解析为字符串;
- json():解析为 JSON 对象;
- blob():解析为 Blob 对象;
- formData():解析为 FormData 对象;
- arrayBuffer():解析为 ArrayBuffer 对象
这些方法读取并解析响应体的数据流属于异步操作,因此这些方法均返回 Promise 对象,当读取数据流并解析完成时,Promise 对象将 resolve 并同时返回解析好的结果。下面的示例将简单演示如何读取 JSON 格式的响应体:
```js
// 构造 Response 对象
let response = new Response(JSON.stringify({name: 'lilei'}))
// 通过 response.json() 读取请求体
response.json().then(data => {
console.log(data.name) // 打印 'lilei'
})
```
由于 Response 的响应体是以数据流的形式存在的,因此只允许进行一次读取操作。通过检查 bodyUsed 属性可以知道当前的 Response 对象是否已经被读取:
```js
let response = new Response(JSON.stringify({name: 'lilei'}))
console.log(response.bodyUsed) // false
response.json().then(data => {
console.log(response.bodyUsed) // true
})
```
由于二次读取响应体内容会导致报错,因此为了保险起见,可以在进行响应体读取前首先判断 bodyUsed 属性再决定下一步操作。
#### 拷贝 Response
Response 提供了 clone() 方法来实现对 Response 对象的拷贝:
```js
let clonedResponse = response.clone()
```
clone() 是一个同步方法,克隆得到的新对象在所有方面与原对象都是相同的。在这里需要注意的是,如果 Response 对象的响应体已经被读取,那么在调用 clone() 方法时会报错,因此需要在读取响应体读取前进行克隆操作。
## Fetch API 与 XHR 的对比
通过上面的介绍可以发现,从功能上看,Fetch API 和 XHR 做的事情都是相似的,都实现了异步请求与资源获取。但从 API 的具体使用和实现方式上,两者存在着较大区别:
1.Fetch API 的异步机制更为先进
XHR 采用回调机制实现异步,这种机制不太符合人脑线性的思维方式,在较为复杂的异步场景中如果存在大量的回调,很容易带来代码可读性差的问题。当然我们也可以利用 Promise 将 XHR 包装成返回 Promise 对象的函数来解决上述问题,但这种封装的函数毕竟不如原生方法来得简洁。
Fetch API 则直接采用 Promise 实现异步机制,通过链式调用 Promise.then() 方法,就能够直接按照线性的思维去组织异步操作中的每个步骤,同时借助 Promise.all、Promise.race 等方法,还能够高效地组织多个异步操作来实现更为复杂的功能。
2.Fetch API 更为简洁
在使用 XHR 进行异步请求时会发现,XHR 实例属性包含了请求描述、响应描述,以及各种事件、请求操作方法等等,显得相当混乱。
Fetch API 在设计的时候不仅仅实现了 `fetch()` 这个方法,还根据异步请求中所需要的数据格式拆分出 Request、Response、Headers、Body 等一系列原生对象,彼此各司其职,符合关注点分离原则,因此在使用上会显得更加简洁,更加语义化。
3.Fetch API 的应用范围更广
目前 XHR 已经无法在 Service Worker 作用域下进行使用,在 Service Worker 作用域当中发起异步请求的方法只有 Fetch API。这也许只是个开始,XHR 在过去已经很好地完成它的历史使命,但由于 XHR 在设计上已经逐渐不适应现代编程理念,因此在未来 XHR 的应用范围将可能会变得越来越窄,而 Fetch API 这类基于新理念和新技术所设计的 API 将逐渐发挥出越来越重要的作用。
## Fetch API 处理跨域请求
当涉及到前后端通信问题的时候,就不得不提请求跨域的情况。由于受到 Web 同源策略的影响,在使用 Fetch API 默认配置情况下发送异步请求,会受到跨域访问限制而导致资源请求失败。
我们通常采用跨域资源共享机制(CORS)来解决这个问题。在跨域服务端支持 CORS 的前提下,通过将 `fetch()` 的请求模式设置为“cors”,就可以简单地实现跨域请求。在这种请求模式下,返回的请求响应是完全可访问的:
```js
// 假设当前页面 URL 为 https://current.com
fetch('https://other.com/data.json', {
mode: 'cors'
})
.then(response => {
console.log(response.status) // 200
console.log(response.type) // 'cors'
console.log(response.bodyUsed) // false
return response.json()
})
.then(data => {
console.log(data.name) // 'lilei'
})
```
对于图片、JS、CSS 等等这些类型的静态资源,如果通过对应的 HTML 标签加载这类跨域资源,是不会受到同源策略限制的,因此一般来说,存放静态资源的服务器并不需要设置 CORS。这就会对 Fetch API 请求这类静态资源带来影响。在默认情况下 `fetch()` 的请求模式为“no-cors”,在这种模式下请求跨域资源并不会报错,但是返回的 Response 对象将变得不透明,type 属性将变成“opaque”,无论服务端所返回的真实 status 是多少,在这种情况下都会变成 0,其他属性也都无法正常访问:
```js
// 假设当前页面 URL 为 https://current.com
fetch('https://other.com/data.json', {
mode: 'no-cors'
})
.then(response => {
console.log(response.status) // 0
console.log(response.type) // 'opaque'
console.log(response.headers) // Headers {}
console.log(response.body) // null
})
```
此时唯一能正常工作的方法是 clone(),即实现对 Response 对象的拷贝,当然拷贝得到的新对象也同样是不透明的。这种模式比较适用于在 Service Worker 线程中拦截静态资源请求并复制一份缓存到本地,只要将这类不透明的请求响应返回主线程,依然是能够正常工作的。下面的代码演示了 Service Worker 拦截跨域图片资源并将资源缓存到本地,然后在 `fetch()` 出错的时候再从缓存中读取资源:
```js
// service-worker.js
self.addEventListener('fetch', event => {
// 判断当前拦截到的请求为跨域图片资源
if (event.request.url === 'https://other-site.com/pic.jpg') {
event.respondWith(
// 优先发送网络请求获取最新的资源
fetch(event.request.url, {mode: 'no-cors'})
.then(response => {
// 将请求得到的响应进行缓存
// 此时缓存的资源是不透明的
caches.open('cache-storage')
.then(cache => cache.put(event.request.url, response.clone()))
// 返回请求响应结果
return response
})
.catch(
// 请求失败时再使用缓存资源进行兜底
() => caches.open('cache-storage')
.then(cache => cache.match(event.request.url))
)
)
)
}
})
```
在这种情况下,图片资源的 Response 对象是不透明的,因此整个操作过程无法对图片资源响应做任何检查判断,只能直存直取。这就有可能将真实状态码为 404、500 等错误响应给缓存下来,因此在“no-cors”模式下缓存的跨域资源的可信度不高,最好作为各类请求策略的兜底资源进行使用。
================================================
FILE: chapter03/4-cache-api.md
================================================
# Cache API
在上一节 Fetch API 的介绍当中提到,Fetch API 提供了 Request、Response 等偏底层的类对象,这样就能够以统一的形式将资源的请求与响应过程应用到更多的场景当中。本节所介绍的 Cache API 就属于另一种资源请求与响应的场景,Cache API 提供了一系列方法实现了请求响应对象的缓存管理,因此它可以作为资源请求响应的缓存仓库,为 Service Worker 实现离线缓存提供基础支持。
接下来将介绍 Cache API 的使用方法。
## 兼容性检测
截止本书定稿之前,除了 IE 之外几乎所有主流浏览器的最新版本都支持了 Cache API,但保险起见,我们可以在主线程或者 Worker 线程中通过判断全局变量 `caches` 是否存在来检测浏览器是否支持 Cache API:
```js
if ('caches' in self) {
console.log('当前环境支持 Cache API')
}
```
## 打开 Cache 对象
通过 `caches.open()` 方法可以打开一个 Cache 对象,其语法为:
```js
caches.open(cacheName).then(cache => {/* 获得 Cache 对象 */})
```
其中参数 cacheName 表示要打开的 Cache 对象的名称。该方法是异步方法,返回的 Promise 对象在 resolve 时会返回成功打开的 Cache 对象。打开 Chrome 开发者工具,切换到 Application - Cache Storage 选项卡可以观察到,在执行 `caches.open()` 方法时,会在 Cache Storage 下边建立同名仓库,每个仓库里面的内容就是操作对应的 Cache 对象后写入的资源缓存。

## 添加缓存
Cache 对象提供了 `put()`、`add()`、`addAll()` 三个方法来添加或者覆盖资源请求响应的缓存。需要注意的是,这些添加缓存的方法只会对 GET 请求起作用。
### Cache.put(request, response)
资源请求响应在通过 Cache API 进行存储的时候,会以请求的 Request 对象作为键,响应的 Response 对象作为值,因此 `put()` 方法需要依次传入资源的请求和响应对象,然后生成键值对并缓存起来。下面举例说明它的使用方法:
```js
// 假设 cache 由 caches.open('v1') 打开
cache.put(
new Request('/data.json'),
new Response(JSON.stringify({name: 'lilei'}))
)
```
这样就给 v1 仓库写入了 '/data.json' 请求与响应的缓存。通过开发者工具可以明显地看到仓库当中新增的缓存条目信息:

同样,我们可以结合 Fetch API 来获取并存储服务端所返回的资源:
```js
fetch('/data.json').then(response => {
if (response.ok) {
cache.put(new Request('/data.json'), response)
}
})
```
在 Fetch API 的章节中介绍了 Request 和 Response 都基于数据流实现,因此在进行缓存的时候需要格外留意 Response 对象的响应体数据是否已经被读取。
### Cache.add(request) 和 Cache.addAll(requests)
`add()` 和 `addAll()` 方法的功能类似于 Fetch API 结合 `put()` 方法实现对服务端资源的抓取和缓存。`add()` 和 `addAll()` 的区别在于,`add()` 只能请求和缓存一个资源,而 `addAll()` 能够抓取并缓存多个资源。有了这两个方法,缓存服务端资源将变得更为简单:
```js
cache.add('/data.json').then(() => {/* 缓存成功 */})
cache.addAll([
'/data.json',
'/info.txt'
])
.then(() => {/* 缓存成功 */})
```
`add()` 和 `addAll()` 方法会缓存 Response.ok 为 true 的响应。同时请求跨域资源返回了不透明的 Response 对象,同样也会缓存下来。
## 查找缓存
`cache.match()` 和 `cache.matchAll()` 可以实现对缓存的查找。其中 `match()` 会返回第一个匹配条件的缓存结果,而 `matchAll()` 则会返回所有满足匹配条件的缓存结果。下面举例说明如何查找“/data.json”的缓存资源,相关代码如下所示:
```js
// 使用 match() 进行查找
cache.match('/data.json').then(response => {
if (response == null) {
// 没有匹配到任何资源
}
else {
// 成功匹配资源
}
})
// 使用 matchAll() 进行查找
cache.matchAll('/data.json').then(responses => {
if (!responses.length) {
// 没有匹配到任何资源
}
else {
// 成功匹配到资源
}
})
```
上述查找方法可以传入第二参数来控制匹配过程,比如设置 ignoreSearch 参数,会在匹配过程中忽略 URL 中的 Search 部分,下面通过代码举例说明这一匹配过程:
```js
// 假设缓存的请求 URL 为 /data.json?v=1
cache.match('/data.json?v=2', {ignoreSearch: true}).then(response => {
// 匹配成功
})
```
在上面的例子当中,缓存的 URL 和用于匹配的 URL 都带有 Search 参数,但由于配置了 ignoreSearch 值为 true,因此最终仍然匹配成功。
## 获取匹配的请求
前面介绍的 `match()`、`matchAll()` 方法会返回匹配到的响应,但如果需要获取匹配到的请求,可以通过 `cache.keys()` 方法实现:
```js
cache.keys('/data.json', {ignoreSearch: true}).then(requests => {
// requests 可能包含 /data.json、/data.json?v=1、/data.json?v=2 等等请求对象
// 如果匹配不到任何请求,则返回空数组
})
```
如果没有传入任何参数,`cache.keys()` 会默认返回当前 Cache 对象中缓存的全部请求:
```js
cache.keys().then(requests => {
// 返回全部请求对象
})
```
## 删除缓存
通过 `cache.delete()` 方法可以实现对缓存的清理。其语法如下所示:
```js
cache.delete(request, options).then(success => {
// 通过 success 判断是否删除成功
})
```
比如要删除前面添加成功的“/data.json”请求,相关代码如下所示:
```js
cache.delete('/data.json').then(success => {
// 将打印 true,代表删除成功
console.log(success)
})
```
假如删除一个未被缓存的请求,则执行删除后返回的 success 为 false:
```js
cache.delete('/no-cache.data').then(success => {
// 将打印 false,代表删除失败
console.log(success)
})
```
在调用 `cache.delete()` 时可以传入第二参数去控制删除操作中如何匹配缓存,其格式与 `match()`、`matchAll()` 等匹配方法的第二参数一致。因此下面举例的删除过程能够忽略 Search 参数:
```js
// 假设缓存的请求 URL 为 /data.json?v=1.0.1
// 那么设置 ignoreSearch 之后同样也回删除该缓存
cache.delete('/data.json', {ignoreSearch: true}).then(success => {
// /data.json?v=1.0.1 已被成功删除
})
```
================================================
FILE: chapter03/5-indexeddb.md
================================================
# IndexedDB
Cache Storage 是一种缓存管理的缓存空间,前面了解到了 Cache Storage 是基于键值对的方式缓存数据,是适用于存储和检索网络请求及响应的存储系统,不能提供搜索功能,不能建立自定义的索引。IndexedDB 是浏览器环境提供的本地数据库,允许存储大量的数据,提供查询接口,还能创建索引等等。在存储结构上,数据库是存储一系列相关数据的容器,在每个域名下都可以新建多个数据库。IndexedDB 是一个非关系型的数据库,和平时所知道的关系型数据库(如 MySQL 等)有一定的区别,关系型数据库的内容是以记录为单位存储的,也就是说一条记录代表一条数据,而数据中的结构管理是通过记录的字段来指定存储的,而 IndexedDB 里面没有表和记录的概念,它的数据的最小单位是 JavaScript 对象(object),object 在 IndexedDB 里的地位就跟关系型数据库里面的记录一样,是数据的最终体现形式。
数据库存储结构上,关系型数据库和 IndexedDB 都可以划分为三个层次。
- 第一层:两者都有 database 的概念,要存储数据,首先要创建一个数据库。
- 第二层:两者就有了区别,关系型数据库有表的概念,而 IndexedDB 对应的是 objectStore。简单的说就是,在数据库中开辟一块 store 用来存储 object,同样,一个数据库中可以有多个(甚至无限个)objectStore。
- 第三层:关系型数据库有记录的概念,而 IndexedDB 直接存放 JavaScript 的 object 数据对象。
IndexedDB 存储的 object 是结构化数据。简单理解就是,不能存 function 等非结构化的数据,object 必须是以键值对组成的字面对象。并且支持嵌套结构,也就是说 object 里面嵌套了 object,和 JavaScript 实现无缝对接。而同样是本地化存储的 localStorage 却需要对数据格式化为字符串后才能保存。
HTML5 API 规范提供了一套 IndexedDB API, 可以使用 `indexedDB.open()` 方法来打开或者创建数据库,如下面代码所示:
```js
// 如果有 mydb 这个数据库,就直接打开
// 如果没有,就会创建 mydb 数据库
let request = window.indexedDB.open('mydb', 1)
```
`indexedDB.open()` 方法有两个参数,第一个参数为数据库名,第二个参数为数据库版本。
## IndexedDB 简介
IndexedDB 和关系型数据库的不同,主要体现在数据库存储结构设计上和数据操作方式上。下面介绍一些 IndexedDB 一些基本的概念,可以快速的了解 IndexedDB 的大致架构设计。
### 数据库版本
从 `indexedDB.open()` 方法的参数来看,很容易注意到 IndexedDB 存在版本的概念。例如:当数据库的 version 为 `1` 时,创建了一些 objectStore,当需要添加新的 objectStore 或者修改某些 objectStore 的时候,就需要升级 version。这时可能有两个不同的 version `1` 和 `2`. 此时用 `indexedDB.open()` 方法打开一个 version 的时候,得到的 db 容器对应的 objectStore 是不同的,如果此时还要打开 version 为 `1` 的数据库,那么在 version 为 `2` 中创建的 objectStore 和新增的 object 都是不存在的。由其可见新的 version 一般包含了老的 version。
通常在代码操作中,你要时刻保证你使用对了 version,它的使用场景只有两种:
- 当需要修改 objectStore 时
- 当需要添加新的 objectStore 时
从代码的层面来看,并非这两个事情发生才触发了 version 的改变,恰恰相反,如果要修改或添加 objectStore,必须通过传递新的 version 参数到 `indexedDB.open()` 方法中,触发 `onupgradeneeded` 事件,在 `onupgradeneeded` 的回调函数中才能实现目的。从项目的开发上讲,只会在重新发布代码时去升级 version,而不会在程序运行过程中通过程序去更改 version。升级 version,是为了对数据库结构进行修改。
### 数据库容器
IndexedDB 中非常重要的概念是 transaction(事务),不过会在后面具体介绍事务。这里只需要了解一下调用 `indexedDB.open()` 方法之后如何操作数据库。要想操作 IndexedDB 数据,必须先创建一个数据库容器。操作数据库的存储结构以及数据的内容,都是要在数据库容器的基础上进行的,那怎么获得数据库容器呢?如下代码所示:
```js
let request = window.indexedDB.open('mydb', 1)
request.onsuccess = e => {
// db 就是数据库容器
let db = e.target.result
// 使用 db 数据库容器,可以接着做一些数据处理 ...
}
```
IndexedDB 数据库的事件回调中都会在事件对象中带有数据库容器对象,可以通过 `event.target.result` 获取,在这个例子中是在 IndexedDB 数据库打开或者创建成功后通过 `onsuccess` 事件回调获取到了数据库容器。
### 对象仓库
objectStore 是 IndexedDB 中非常核心的概念,在前面的介绍中,也知道了它是数据的存储仓库,一个 objectStore 类似于关系型数据库中的表,存放着相关的所有数据。所谓的 “相关” 是指,这些 object 必须具备相同的一个属性名,也就是**主键** ,在 IndexedDB 中被称之为 keyPath。这还有点像关系型数据库中的 primaryKey,不过关系型数据库中不必一定有 primaryKey,而 objectStore 中的 keyPath 必须有。
如果存入的某个 object 不存在那个属性,而该属性在 IndexedDB 中又不是 autoIncrement,那么就会报错,如果 autoIncrement 被设置为 `true`,在没有该 key 的情况下,存入数据库的时候,会被自动添加上,这个效果跟关系型数据的自增字段是一样的。
在使用事务对 objectStore 进行操作前,需要创建对应的 objectStore。创建 objectStore 和修改 objectStore 都只能在 db 的 `onupgradeneeded` 事件中进行,因此要创建 objectStore 必须在 `indexedDB.open()` 操作之后来进行,如下代码所示:
```js
let request = window.indexedDB.open('mydb', 1)
request.onupgradeneeded = e => {
let db = e.target.result
db.createObjectStore('mystore', {keyPath: 'id'})
}
```
上面的代码中使用 `db.createObjectStore()` 方法来实现 objectStore 的创建。但是需要注意的是,一个 db 中是不允许同名的 objectStore 的,因此,如果第二次通过 `createObjectStore()` 创建相同名的 objectStore,程序会报错。
另外,一旦一个 objectStore 被创建,它的 name 和 keyPath 是不能修改的。可以通过 `db.objectStoreNames` 属性来判断是否已经存在同名的 objectStore 可以避免这个问题,如下代码所示:
```js
let request = window.indexedDB.open('mydb', 2)
request.onupgradeneeded = e => {
let db = e.target.result
let objectStore
// 如果不存在同名的 Store,就创建一个
if (!db.objectStoreNames.contains('mystore')) {
objectStore = db.createObjectStore('mystore', {keyPath: 'id'})
} else {
// 如果存在同名的 store,就直接取出来
objectStore = e.target.transaction.objectStore('mystore')
}
}
```
### 索引
在 IndexedDB 中也存在索引,但和关系型数据库中索引的作用不同,关系型数据库中的索引是对指定字段进行特殊记录,以方便在检索时提高检索性能。IndexedDB 中的索引,是指在除了设置的 keyPath 之外,提供其他的检索方式。在 IndexedDB 中,`objectStore.get()` 方法用来获取某一条数据,但是它的默认的参数是 keyPath 对应的值。而如果要用其他的字段来检索某个 object,那就麻烦了,所以 IndexedDB 提供了索引的方式,通过一个 index 方法来实现索引检索。所以看起来 objectStore 的索引,等效于关系型数据库中的表的字段。
前面反复提到 keyPath 这个概念。在前面的代码里面可以发现在 `db.createObjectStore()` 的时候,可以指定一个 keyPath。实际上,keyPath 的概念非常简单,它规定了必须要把 object 属性作为检索的入口。如 objectStore 中有一堆对象,如下所示:
```js
{
id: 1,
name: 'data1'
}
{
id: 2,
name: 'data2'
}
```
如上代码所示,设置的 keyPath 为 `id`, 可以通过 `objectStore.get(1)` 方法来获取 keyPath 为 `id = 1` 的那条数据,因此,id 对于所有 object 而言是应该是唯一的。需要在建立索引的时候,传入一个 `unique` 参数确保唯一,所以实际上 `db.createObjectStore()` 的时候传入的 keyPath 是一个特殊的索引。创建索引实际上是对 `objectStore` 进行修改,因此,只能在数据库的 `onupgradeneeded` 事件中处理,如下代码所示:
```js
let request = window.indexedDB.open('mydb', 3)
request.onupgradeneeded = e => {
let db = e.target.result
// 注意这里应该进行判断是否已经存在这个 objectStore,在这里略过
let objectStore = db.createObjectStore(
'mystore',
{keyPath: 'id'}
)
// 创建 id 为索引
objectStore.createIndex('id', 'id', {unique: true})
}
```
objectStore 对象有一个 `createIndex()` 方法,它可以创建索引。它有三个参数:
- 第一个参数是这个索引的 name。
- 第二个参数是 key,这个 key 对应的就是 object 的属性名,name 是可以自己定的,它会用在后面的 index 方法中进行检索,也会被记录在 objectStore 的 indexNames 属性里面,但是 key 必须和 object 的属性对应。
- 第三个参数是 options,其中 unique 选项被放在这里面。
objectStore 本身的信息是不能修改的,例如 name 和 keyPath 都是不能修改的,但是它所拥有的索引可以被修改,修改其实就是删除或添加操作。删除用到的就是 `objectStore.deleteIndex()` 这个方法,如果想修改一个索引,要做的就是先删除掉原来的同名索引,然后添加新的索引,如下面代码所示:
```js
let request = window.indexedDB.open('mydb', 4)
request.onupgradeneeded = e => {
// 从事务中获取已经存在的 objectStore
let objectStore = e.target.transaction.objectStore('mystore')
let indexNames = objectStore.indexNames
// 先删除对应的索引
if (indexNames.includes('name')) {
objectStore.deleteIndex('name')
}
// 再重新创建一个新的同名索引
objectStore.createIndex('name', 'name', {unique: false})
}
```
### 事务
所有数据库中都有事务这个概念,它是为了确保当某些操作部分执行时不致混乱。举个简单的例子,当你转账给别人的时候,发起了一个请求,你的银行就操作从数据库里把相应的钱扣掉,但是这时候银行机房出问题了,你朋友的银行并没有收到这个转入的请求,那岂不是会出现你的钱已经扣了,但是别人并没有收到的情况?
数据库系统为了避免这种情况,采用事务机制,如果出错那就回滚,把你打出去但对方没收到的钱回到你账上,重新再执行一次打钱的操作,这样就保证了数据库增删改有序不混乱。
IndexedDB 里面的事务也是一样,保证了所有操作(特别是写入操作)是按照一定的顺序进行,不会导致同时写入的问题。另外,IndexedDB 强制规定了任何 object 读写的操作都必须在一个事务中进行。从前面的代码里面你也看到了,对 objectStore 的修改其实也是在一个事务中进行。
在代码层面必须通过 `db.transaction()` 方法向数据库容器提出事务要求,才能对具体的 objectStore 进行数据处理:
```js
let request = window.indexedDB.open('mydb', 5)
request.onsuccess = e => {
let db = e.target.result
let transaction = db.transaction(
['myObjectStore'],
'readonly'
)
let objectStore = transaction.objectStore('myObjectStore')
let objectRequest = objectStore.get('111')
objectRequest.onsuccess = e => {
// 获取到的数据
let object = e.target.result
}
}
```
上面这段代码的操作,得到了具体要进行操作的 objectStore,这与直接通过 `db.objectStore('myObjectStore')` 这样简洁的方法完全不同,IndexedDB 中不能这么直接去获取 objectStore,而必须通过 `db.transaction()` 方法。`db.transaction()` 方法有两个参数:
- objectStores:事务打算对哪些 objectStore 进行操作,因此是一个数组
- mode:对进行操作的 objectStore 的模式,即读写权限控制,readonly | readwrite
而通过 `transaction.objectStore()` 方法可以获取想要操作的 objectStore ,但是它的参数必须存在于 `db.transaction()` 方法指定的 objectStores 参数数组中,毕竟这个事务已经规定了要对哪些 objectStore 进行操作。
因为 objectStore 是在事务中获取,因此一个 objectStore 实例,如果有一个 transaction 属性的话,那么可以通过这个属性找出它的事务的实例。在 IndexedDB 中,只能在事务中得到一个 objectStore 实例,如果通过 db 的话,最多只能得到 objectStore 的名字列表,如果要获得 objectStore 的实例,必须在 transaction 中。
### 操作请求
Request 是在事务过程中,发起某项操作的请求。一个事务过程中,可以有多个 Request,Request 一定存在于事务中,因此它肯定会有一个 transaction 属性来获取它所属于的那个事务的容器。我们可以把 transaction 当做一个队列,在这个队列中,Request 进行排队,每一个 Request 都只包含一个操作,比如添加,修改,删除数据之类的。这些操作不能马上进行,比如修改操作,如果马上进行,就会导致大家同时修改怎么办的问题,把多个修改操作放在 Request 中,这些 Request 在 transaction 中排队,一个一个处理,这样就会有执行的顺序,修改就有前后之分。同时,transaction 都可以被中断,这样当一系列的操作被放弃之后,后续的操作也不会进行。
而且有意思的是,Request 是异步的,它是有状态的。一个 Request 处于什么状态,可以通过 readyStates 属性查到,这对开发者而言也更可控。目前,在 IndexedDB 中,有四种情形产生 Request:`open database`,`objectStore request`, `cursor request`, `index request`。
### 游标
所谓游标,简单的理解,就是“**一个用来记录数组正在被操作的某个下标位置的变量**”,举个例子:对数组 `[1, 2, 3, 4]` 进行遍历,可以使用 `forEach()` 方法,那么 `forEach()` 方法怎么知道上次操作到第几个元素,又怎么知道现在应该操作第几个元素呢?就是通过游标来判断。
游标是一个机制,无法把游标打印出来看,可以通过游标得到你当前操作的元素,换句话说,游标有着类似 `next()` 的方法,可以用来移动游标到下一个位置。
当数据量巨大的时候,想要获取一个 objectStore 中的全部 object 可不是一件简单的事。IndexedDB 没有直接提供类似的方法来获取。但是可以利用游标来解决,如下代码所示:
```js
let request = window.indexedDB.open('mydb', 10)
request.onsuccess = e => {
let db = e.target.result
let transaction = db.transaction(
['myObjectStore'],
'readonly'
)
let objectStore = transaction.objectStore('myObjectStore')
// 打开一个游标
let cursorRequest = objectStore.openCursor()
let results = []
cursorRequest.onsuccess = e => {
let cursor = e.target.result
if (cursor) {
results.push(cursor.value)
cursor.continue()
} else {
// 遍历之后的 object 数据列表的结果
console.log(results)
}
}
}
```
通过 `objectStore.openCursor()` 方法打开游标机制,该方法返回一个 Request 对象,在这个 Request 对象的 `onsuccess` 回调中,如果 cursor 没有遍历完所有 object,那么通过执行 `cursor.continue()` 来让游标滑动到下一个 object,`onsucess` 回调会被再次触发。而如果所有的 object 都遍历完了,cursor 变量会是 `undefined`。
注意上面的 results 变量,它的声明必须放在 `onsuccess` 回调函数的外部,因为该回调函数会在遍历过程中反复执行。
在 Firefox 浏览器中自主实现了一个 `getAll()` 方法可以获取 objectStore 中所有的 object,但是它不是标准的 IndexedDB 的接口,因此不推荐使用,而本例的操作方法,通常是获取全部 object 的标准做法。由此可以总结出游标就是对已知的集合对象(比如 objectStore 或 indexView)进行遍历,在 `onsuccess` 回调中使用 `cursor.continue()` 来进行控制。
### 主键范围
可以使用 IDBKeyRange 对象定义索引的范围。此对象有四种方法用于定义范围的限制:`upperBound()`、`lowerBound()`、`bound()` 和 `only()`。`upperBound()` 和 `lowerBound()` 方法指定了范围的上限和下限。可以通过 `IDBKeyRange.lowerBound(indexKey)` 方法指定索引的下边界,也可以使用 `IDBKeyRange.upperBound(indexKey)` 方法指定索引的上边界。当然还可以使用 `bound()` 方法同时指定上下边界:`IDBKeyRange.bound(lowerIndexKey, upperIndexKey)`。
接下来看一个代码示例:在 myObjectStore 对象库中的 price 属性上创建了一个索引,并添加了一个带有两个输入的小型表格,用于为游标设置范围的上限和下限。代码如下所示:
```js
function searchItems(lower, upper) {
if (lower === '' && upper === '') {
return
}
// 设置
let range
if (lower !== '' && upper !== '') {
range = IDBKeyRange.bound(lower, upper)
} else if (lower === '') {
range = IDBKeyRange.upperBound(upper)
} else {
range = IDBKeyRange.lowerBound(lower)
}
let request = window.indexedDB.open('mydb', 11)
request.onsuccess = e => {
let db = e.target.result
let transaction = db.transaction(
['myObjectStore'],
'readonly'
)
let store = transaction.objectStore('myObjectStore')
let index = store.index('price')
// 索引打开带有主键集合的游标
let cursorRequest = index.openCursor(range)
let results = []
cursorRequest.onsuccess = e => {
let cursor = e.target.result
if (cursor) {
console.log('游标位置在: ', cursor.key)
results.push(cursor.value)
cursor.continue()
} else {
// 遍历之后的 object 数据列表的结果
console.log(results)
}
}
}
}
```
## IndexedDB 的增删改查
和任何数据库一样,IndexedDB 也是进行数据存储,并提供一些方式让开发者可以对数据进行查询、添加、删除、修改。当一个事务开始之后,在它的生命周期以内,可以对 objectStore 进行数据操作,下面会通过一些简单的示例对 IndexedDB 的增删改查操作进行介绍。
### 获取数据
前面介绍过如何获取事务中的 objectStore,现在就用获取到的 objectStore 进行数据操作,如下代码所示。
```js
let request = window.indexedDB.open('mydb', 6)
request.onsuccess = e => {
let db = e.target.result
let transaction = db.transaction(
['myObjectStore'],
'readonly'
)
let objectStore = transaction.objectStore('myObjectStore')
let objectRequest = objectStore.get('100001')
objectRequest.onsuccess = e => {
// 获取到的数据
let object = e.target.result
}
}
```
在 IndexedDB 事务机制下进行操作是很麻烦的,上面代码中使用了 `objectStore.get()` 方法获取主键值为 `100001` 的 object,但是获取过程是一个 Request 对象,只有在其 `onsuccess` 事件中才能得到获取到的结果。
### 添加数据
```js
let request = window.indexedDB.open('mydb', 7)
request.onupgradeneeded = e => {
let db = e.target.result
let transaction = db.transaction(
['myObjectStore'],
'readwrite'
)
let objectStore = transaction.objectStore('myObjectStore')
// 写入一条数据
objectStore.add({
id: '100002',
name: 'Zhang Fei',
})
}
```
添加数据使用 `objectStore.add()` 方法,传入一个 object。但是这个 object 有限制,它的主键值,也就是 id 值,不能是已存在的,如果 objectStore 中已经有了这个 id,那么会报错。因此,在某些程序中为了避免这种情况的发生,通常会使用 `objectStore.put()` 方法。
### 更新数据
```js
let request = window.indexedDB.open('mydb', 8)
request.onupgradeneeded = e => {
let db = e.target.result
let transaction = db.transaction(
['myObjectStore'],
'readwrite'
)
let objectStore = transaction.objectStore('myObjectStore')
// 更新一条数据
objectStore.put({
id: '100002',
name: 'Zhang San',
})
}
```
`objectStore.put()` 方法和 `objectStore.add()` 方法有两大区别。
- 如果 objectStore 中已经有了该id,则表示更新这个object,如果没有,则添加这个 object。
- 在另一种情况下,也就是设置了 autoIncrement 为 true 的时候,也就是主键自增的时候,`objectStore.put()` 方法必须传第二个参数,第二个参数是主键的值,以此来确定你要更新的是哪一个主键对应的 object,如果不传的话,可能会直接增加一个 object 到数据库中。从这一点上讲,自增字段确实比较难把握,因此我建议开发者严格自己在传入时保证 object 中存在主键值。
### 删除数据
```js
let request = window.indexedDB.open('mydb', 9)
request.onupgradeneeded = e => {
let db = e.target.result
let transaction = db.transaction(
['myObjectStore'],
'readwrite'
)
let objectStore = transaction.objectStore('myObjectStore')
// 删除一条数据
objectStore.delete('100001')
}
```
`objectStore.delete()` 方法将传入的主键值对应的 object 从数据库中删除。
## 利用 IndexedDB 实现 DB 类
接下来利用 IndexedDB 实现一个 DB 类,将 IndexedDB 的数据存储模式简化为键值对的形式,并实现一些常用的 setItem/getItem/getAllItems/removeItem 等方法。这样我们就可以通过 DB 类的实例,以类似 localStorage 的 API 去使用 IndexedDB 了。
### 构造函数
在初始化时,需要传入 dbName、version、storeName 三个参数,分别对应数据库名、数据库版本号、对象仓库名:
```js
class DB {
constructor ({
dbName = 'db',
version = 1,
storeName
}) {
this.dbName = dbName
this.storeName = storeName
this.version = version
}
// ...
}
```
其中 dbName 和 version 我们设置了默认值,因此在实例化 DB 类的时候,只需要传入 storeName 即可:
```js
const db = new DB({storeName: 'test'})
```
### 获取数据库实例
接下来封装 `getDB()` 方法来获得数据库实例,并且在数据库初始化时创建对象仓库,由于在这里我们使用键值对的存储形式,因此规定存储对象结构为:`{key, value}` ,其中 `key` 存放数据的键名,value 存放值。同时由于 IndexedDB 采用回调函数的异步机制,我们可以通过实现简单的 `promisify` 方法将回调修改成 Promise 的异步形式。具体实现如下所示:
```js
class DB {
// ...
async getDB () {
// 优先返回缓存的数据库实例
if (this.db) {
return this.db
}
// 打开数据库
let request = indexedDB.open(this.dbName, this.version)
// 当数据库初始化或升级时创建仓库
request.onupgradeneeded = event => {
let db = event.target.result
// 当仓库不存在时创建仓库,同时规定 key 为索引
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName, {keyPath: 'key'})
}
}
let event = await promisify(request)
this.db = event.target.result
return this.db
}
}
```
其中 `promisify()` 方法实现如下:
```js
function promisify (request) {
return new Promise((resolve, reject) => {
request.onsuccess = resolve
request.onerror = reject
})
}
```
这样我们就可以通过 getDB() 方法初始化好仓库,并最终获得数据库实例,接下来就可以实现其他操作数据库的方法了。
### 写入数据 setItem()
`setItem()` 用于将数据写入 indexedDB,它接收两个参数分别为 key 和 value,其中 key 要求为字符串类型,value 为 IndexedDB 允许存储的类型即可。
```js
class DB {
// ...
async setItem (key, value) {
// 获取数据库
let db = await this.getDB()
// 创建事务,指定使用到的仓库名以及读写权限
let transaction = db.transaction([this.storeName], 'readwrite')
// 获取仓库实例
let objectStore = transaction.objectStore(this.storeName)
// 将 key 和 value 包装成对象 {key, value} 并存入仓库
let request = objectStore.put({key, value})
// 异步执行结果通过 Promise 返回
return promisify(request)
}
}
```
通过 `setItem()` 方法,我们就可以方便地写入数据了:
```js
// 存入数字
db.setItem('number', 1)
.then(() => {console.log('写入成功!')})
// 存入 Plain Object
db.setItem('/path/to/data', {status: 0, data: 'Hello World'})
.then(() => {console.log('写入成功!')})
```
### 读取数据 getItem() 与 getAll()
#### getItem()
`getItem()` 用于获取数据,它接收参数 key,作为查找资源的标识:
```js
class DB {
// ...
async getItem (key) {
// 获取数据库实例
let db = await this.getDB()
// 创建事务,并指定好仓库名以及操作的只读权限
let transaction = db.transaction([this.storeName], 'readonly')
// 获取仓库实例
let objectStore = transaction.objectStore(this.storeName)
// 查找对应的数据并通过 Promise 对象包装后返回
let request = objectStore.get(key)
let event = await promisify(request)
return event.target.result && event.target.result.value
}
}
```
使用 getItem() 读取数据的方法也非常简单:
```js
db.getItem('number')
// 打印 1
.then(value => console.log(value))
db.getItem('/path/to/data')
// 打印 {status: 0, data: 'Hello World'}
.then(value => console.log(value))
```
#### getAll()
`getAll()` 用于获取数据库的全部数据,返回的结果为 Promise 包装的 Map 对象:
```js
class DB {
// ...
async getAll () {
// 获取数据库实例
let db = await this.getDB()
// 创建事务,并指定好仓库名以及操作的只读权限
let transaction = db.transaction([this.storeName], 'readonly')
// 获取仓库实例
let objectStore = transaction.objectStore(this.storeName)
// 读取仓库全部数据
let request = objectStore.getAll()
let event = await promisify(request)
let result = event.target.result
// 当数据为空时,返回空
if (!result || !result.length) {
return
}
// 数据不为空时,将数据包装成 Map 对象并返回
let map = new Map()
for (let {key, value} of result) {
map.set(key, value)
}
return map
}
}
```
这样通过 getAll() 方法就可以异步获取仓库中存储的全部数据了:
```js
db.getAll()
// 打印 Map(2) {
// 'number' => 1,
// '/path/to/data': {status: 0, data: 'Hello World'}
// }
.then(map => console.log(map))
```
### 删除数据 removeItem()
`removeItem()` 用于删除数据,通过参数 key 进行数据匹配并删除:
```js
class DB {
// ...
async removeItem (key) {
// 获取数据库实例
let db = await this.getDB()
// 创建事务,并指定好仓库名以及删除操作的读写权限
let transaction = db.transaction([this.storeName], 'readwrite')
let objectStore = transaction.objectStore(this.storeName)
// 删除数据,并用 Promise 进行包裹
let request = objectStore.delete(key)
return promisify(request)
}
}
```
这样删除数据操作可以简化为如下形式:
```js
db.removeItem('number')
// 数据删除成功时 Promise 对象执行 resolve
.then(() => console.log('删除成功'))
```
================================================
FILE: chapter03.md
================================================
# 基础技术简介
PWA 是建立在现代前端技术和标准之上的,因此在介绍 PWA 特别是 Service Worker 相关的内容时,会频繁地使用一些方法和对象。它们有的不属于 PWA 的范畴,有的不一定要配合 Service Worker 使用,但它们都属于 PWA 的基础,对它们的学习将有助于后面章节理解,因此有必要花些篇幅进行介绍。
在本章内容当中,将首先介绍 ES6 标准给出的异步编程解决方案 Promise,它是 PWA 所有异步方案的基础,然后进一步介绍更直观的异步方案 Async 函数。在有了最新的异步编程概念之后,紧接着介绍如何使用 Fetch API 发送异步请求,并与 XMLHttpRequest 进行比对。最后介绍了对资源进行缓存的方法,包括用于缓存资源请求响应的 Cache API 和本地非关系型数据库 IndexedDB,为后面利用 Service Worker 实现离线缓存功能提供了基础。
================================================
FILE: chapter04/1-service-worker-introduction.md
================================================
# Service Worker 简介
丢失网络连接是一个困扰 Web 用户多年的难题,即使是世界上最好的 Web App,如果因为网络原因访问不了它,那体验也是非常糟糕的。本小节要介绍的 Service Worker 能提供一种良好的统筹机制对资源缓存和网络请求进行缓存和处理,是 PWA 实现离线可访问、稳定访问、静态资源缓存的一项重要技术。
通常所讲的 Service Worker 指的是 Service Worker 线程。了解浏览器工作原理的开发者都知道浏览器中执行的 JavaScript 文件是运行在一个单一线程上,称之为 **主线程**。而 Service Worker 是一种独立于浏览器主线程的 **工作线程**,与当前的浏览器主线程是完全隔离的,并有自己独立的执行上下文(context)。
首先借一个简单的例子来了解一下什么是 Service Worker,假如现在有一个最简单的前端项目 serviceWorkerDemo ,目录结构如下:
```bash
.
└── serviceWorkerDemo
├── index.html
└── sw.js
```
`index.html` 文件的内容如下:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Demo</title>
</head>
<body>
<script>
navigator.serviceWorker.register('./sw.js')
</script>
</body>
</html>
```
HTML5 提供的一个 Service Worker API,能够进行 Service Worker 线程的注册、注销等工作,在该示例中,通过 `navigator.serviceWorker.register()` 方法就能够注册一个 Service Worker,在当前的浏览器主线程的基础上新起一个 Service Worker 线程。
在示例项目的目录中还有一个 `sw.js`,有时候开发者会将这个 JavasScript 脚本文件称之为 Service Worker,这种说法不是很严谨,通常将可以被 `navigator.serviceWorker.register()` 方法注册的 JavaScript 文件称之为 Service Worker 文件,可以是任何命名,在这个示例中命名为 `sw.js`,其内容就是在 Service Worker 线程上下文中执行的内容(如果文件为空代表 worker 线程什么也不会做),由于 Service Worker 线程是独立于主线程的工作线程,所以在 `sw.js` 中的任何操作都不会影响到主线程。
接下来,我们来运行一下上面简易的示例,可以借助 local-web-server 工具在示例项目根目录下启动一个本地服务器,帮助我们查看一下 Service Worker 的具体运行状态,如下操作:
```bash
$ npm install -g local-web-server
$ ws
```
> 提示:
> 为了方便介绍,本章所有的 Demo 示例在提到 “运行” 的时候,都指的是在 Demo 项目的根目录通过 `ws` 命令启动 local-web-server,其默认 host 为 `127.0.0.1`,默认端口号为 `8000`。
使用 Chrome 浏览器访问示例站点 `http://127.0.0.1:8000` 的,可以在开发者模式的 `Applications > Service Worker` 面板中看到当前 Service Worker 线程的状态,在完成了 Service Worker 注册安装之后,结果如下图所示:

当调节当前的网络状态为「离线」,依然可以看到 Service Worker 还是生效状态,通过这个例子可以发现,Service Worker 不仅是一个独立于主线程的的一个工作线程,并且还是一个可以在离线环境下运行的工作线程,这样就为 PWA 的离线与缓存功能提供了可行性基础。
## 为什么有 Service Worker
在了解了 Service Worker 是一个工作线程的本质之后,接下来继续了解一下为什么会有 Service Worker 这个技术出现呢?W3C(国际万维网联盟)早在 2014 年 5 月就提出了 Service Worker HTML5 API 草案,用来进行 Web 资源和请求的持久离线缓存。Service Worker 的来历可以从两个方面来介绍。
一方面,浏览器中的 JavaScript 都是运行在一个单一主线程上的,在同一时间内只能做一件事情。随着 Web 业务不断复杂,在 JavaScript 中的代码逻辑中往往会出现很多耗资源、耗时间的复杂运算过程。这些过程导致的性能问题在 Web App 日益增长的复杂化过程中更加凸显出来。所以 W3C 提出了 Web Worker API 来专门解放主线程,Web Worker 是脱离在主线程之外的工作线程,开发者可以将一些复杂的耗时的工作放在 Web Worker 中进行,工作完成后通过 postMessage 告诉主线程工作的结果,而主线程通过 onmessage 得到 Web Worker 的结果反馈,从而释放了主线程的性能压力。
代码执行性能问题好像是解决了,但 Web Worker 是临时存在的,每次做的事情的结果不能被持久存下来,如果下次访问 Web App 同样的复杂工作还是需要被 Web Worker 重新处理一遍,这同样是一件消耗资源的事情,只不过不是在主线程消耗罢了。那能不能有一个 Worker 线程是一直是可以持久存在的,并且随时准备接受主线程的命令呢?基于这样的需求 W3C 推出了最初版本的 Service Worker,Service Worker 在 Web Worker 的基础上加上了持久离线缓存能力,可以通过自身的**生命周期**特性保证复杂的工作只处理一次,并持久缓存处理结果,直到修改了 Service Worker 的内在的处理逻辑。
而另一方面,为了解决 Web 网络连接不稳定的问题,W3C 在很早的时候提出了 ApplicationCache 机制来解决离线缓存的问题,做法是在 HTML 页面中可以指定一个清单文件 `manifest.appcache`,清单中指定需要离线缓存的静态资源,ApplicationCache 能够解决离线可访问的问题。假设已经存在一个简单的项目 applicationCacheDemo,项目目录如下:
```bash
.
└── applicationCacheDemo/
├── index.html
└── manifest.appcache
```
下面是一个简单的 `manifest.appcache` 配置文件内容:
```bash
CACHE MANIFEST
# version xx.xx.xx
CACHE:
cached.png
cached.js
NETWORK:
noCached.html
noCached.css
FALLBACK:
/ 404.html
```
`CACHE` 字段配置了需要当前页面离线缓存的静态资源,`NETWORK` 字段配置了当前页面不需要离线缓存的静态资源,`FALLBACK` 字段指定了一个后备页面,当资源无法访问时,浏览器会使用该页面。该段落的每条记录都列出两个 URI,第一个表示资源,第二个表示后备页面。两个 URI 都必须使用相对路径并且与清单文件同源。可以使用通配符。有了 `manifest.appcache` 文件之后,可以在 `index.html` 的 HTML 文件中的 `<html>` 标签进行引入从而指定当前页面的静态资源离线缓存的情况,如下面代码所示:
```html
<!DOCTYPE html>
<html manifest="./manifest.appcache">
<!--...-->
</html>
```
虽然通过 ApplicationCache 机制能够解决 Web App 的离线缓存的问题,但是同时也带来了不小的问题:
- 在 manifest.appcache 文件中定义的资源全部被成功加载后,这些资源文件连同引用 manifest.appcahe 文件的 HTML 文档一并被移动到永久离线缓存中。所以如果想只缓存 JS、CSS、图片等文件,而不希望缓存 HTML 文档以保持获得最新内容的情况来说,是个非常大的问题。
- 根据 ApplicationCache 的加载机制,如果仅仅修改被缓存资源文件的内容(没有修改资源文件的路径或名称),浏览器将直接从本地离线缓存中获取资源文件。所以在每次修改资源文件的同时,需要修改 manifest.appcache 文件,以触发资源文件的重新加载和缓存,维护成本太高。
- 靠一个 manifest.appcache 配置文件来维护一个复杂的站点的缓存策略实在是一件非常艰难的工作,毕竟单纯靠配置是非常不灵活的。
- 对动态请求无法处理。
通过一段时间的实践后,W3C 决定废弃 ApplicationCache,虽然其仍然保留在 HTML 5.0 Recommendation 中,但会在 HTML 后续版本中移除。一些主流浏览器甚至已经将 ApplicationCache 标注为不推荐使用,并引导开发者使用 Service Worker。Service Worker 就很好的解决了 ApplicationCache 的痛点问题,它能够通过非常多的缓存策略来灵活的管理 Web App 的离线缓存,大大降低维护成本(我们会在后面章节详细的讲解这部分的内容)。
基于 Woker 工作线程的离线能力和离线缓存机制的双重迫切需求,通过不断的实践和发展,W3C 最终提出的 Service Worker API 可以以独立工作线程的方式运行,结合持久缓存调度策略,能够很好的解决离线缓存问题。并且可以以非侵入的方式与现存的 Web App 结合使用,从可以实现 PWA 渐进式的离线与缓存的效果。
## Service Worker 的特点
Service Worker 功能虽然强大,但是使用 Service Worker 还是有一定的条件以及一些专有的特点的。
出于安全的考虑 Service Worker **必须运行在 HTTPS 协议下**,Github 提供的 [git page](https://pages.github.com/)是个用来测试 Service Worker 的好地方,因为它就直接就支持 HTTPS,直接就可以测试静态页面和静态资源,为了便于本地开发,`localhost`、`127.0.0.1` 这种非 HTTPS 协议也被浏览器认为是安全源。
Service Worker 线程**有自己完全独立的执行上下文**。**一旦被安装成功就永远存在,除非线程被程序主动解除**,而且 Service Worker 在访问页面的时候可以直接被激活,如果关闭浏览器或者浏览器标签的时候会自动睡眠,以减少资源损耗。
Service Worker 是完全异步实现的,内部的接口的异步化都是通过 Promise 实现,并且在 Service Worker 中**不能直接操作 DOM**,出于安全和体验的考虑,UI 的渲染工作必须只能在主线程完成。
Service Worker **可以拦截并代理请求,可以处理请求的返回内容**,可以持久化缓存静态资源达到离线访问的效果,和 ApplicationCache 不同,Service Worker 的所有的离线内容**开发者完全可控**,甚至是可以控制动态请求,第三方静态资源等。
由于 Service Worker 可以离线并且在后台工作,所以可以进行 **推送消息**(第六章会详细说明)、**后台同步**资源等功能,在不久的将来,利用 Service Worker 的这一特性,甚至可以衍生出更多的 Web App 原生化的功能。
## 浏览器支持程度
由于 W3C 标准或草案的提出之后各大浏览器的实现步伐是不一样的,参考 [Can I Use](https://caniuse.com) 截止 2019-04-02 的数据,如下图所示当前各大浏览器对 Service Worker 的支持情况如下。

从上图可以看出,Service Worker 的支持程度已经达到 `89.84%`。其中 Chrome 作为开路先锋早早的在 V40 版本就已经支持 Service Worker,并在 Devtools 中还提供了完善的 Debug 方案,Apple 方面从 MacOS Safari 11.1 和 iOS Safari 11.3 开始全面支持,IE Edge 从 17 版本开始也全面支持。
目前 Apple 和微软都已经支持了 Service Worker,所以对于 “离线可访问” 这样的 PWA 特性来讲,几乎可以在任何的现代浏览器中被实现。
更详细的 Service Worker 浏览器支持信息,可以在 [Jake Archibald 的 Is ServiceWorker Ready](https://jakearchibald.github.io/isserviceworkerready/) 网站上查看所有浏览器的支持情况。
由于 Service Worker 的功能是渐进式的,如果浏览器不支持 Service Worker,在架构设计上 Web App 也应该能够正常运行,为了防止 JavaScript 报错,所以通常在注册之前需要进行嗅探处理。修改 serviceWorkerDemo 的 `index.html` 代码如下所示:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Demo</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js')
}
</script>
</body>
</html>
```
## 小结
本节介绍了 Service Worker 的一些基本概念和 Service Worker 的一些背景和功能,但是对注册的细节,Service Worker 生命周期等细节没有做深入介绍,这些内容对于使用 Service Worker 对 Web App 进行离线缓存处理有重要作用。接下来的章节将会对 Service Worker 的这些细节做更加详细的介绍。
================================================
FILE: chapter04/2-service-worker-register.md
================================================
# Service Worker 注册
通过前面对 Service Worker 概念的介绍,我们对 Service Worker 的一些概念和原理有了一定的了解,在本节将会重点介绍 Service Worker 注册的相关内容。主要会介绍如何为 Web App 注册一个 Service Worker、在不同的项目架构下注册 Service Worker 的方法、Service Worker 注册的一些细节和注意点等。
## 作用域
Service Worker 是有自己的作用域的,Service Worker 作用域是一个 URL path 地址,指的是 Servcie Worker 能够控制的页面的范围,例如:某个 Service Worker 的作用域为 `https://somehost/a/b/`,那这个 Service Worker 能控制 `https://somehost/a/b/` 目录下的所有页面,可以包含下面列出的页面:
- `https://somehost/a/b/index.html`
- `https://somehost/a/b/c/index.html`
- `https://somehost/a/b/anothor.html`
- ...
所谓的 “控制页面” 指的是 Service Worker 可以处理这些页面里面的资源请求和网络请求,然后通过 Service Worker 自身的调度机制构建离线缓存策略。如果页面不在 Service Worker 的作用域范围内,Service Worker 就无法处理页面的任何资源或请求。
为了加深对 Service Worker 作用域的理解,接下来还是来看下 serviceWorkerDemo 这个示例,在 `index.html` 中修改一下代码如下所示:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Demo</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js')
.then(reg => {
console.log(reg)
})
}
</script>
</body>
</html>
```
首先从上面代码可以看出 `navigator.serviceWorker.register()` 方法返回的是一个 Promise,这个 Promise 中 resolve 返回的是 Service Worker 注册成功后返回的 ServiceWorkerRegistration 对象。运行之后将这个对象打印出来的结果如下图所示。

ServiceWorkerRegistration 对象中的 scope 的值就是当前的 Service Worker 的作用域,在这个示例中为 `http://127.0.0.1:8000/`。
为了更直观的看到 Service Worker 作用域的工作原理,接下来新建一个 serviceWorkerScopeDemo 项目,项目目录结构如下:
```bash
.
└── serviceWorkerScopeDemo
├── a
│ └── b
│ └── sw.js
└── index.html
```
将 `sw.js` 放入 `/a/b/` 目录下,将 `index.html` 中的注册 Service Worker 逻辑修改一下,代码如下所示:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Scope Demo</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./a/b/sw.js')
.then(reg => {
console.log(reg.scope)
// http://127.0.0.1:8000/a/b/
})
}
</script>
</body>
</html>
```
将 `navigator.serviceWorker.register()` 方法的 Service Worker 文件 URL 改成 `./a/b/sw.js`,运行结果打印出来的 scope 结果为 `http://127.0.0.1:8000/a/b/`。通常情况下在注册 `sw.js` 的时候会忽略 Service Worker 作用域的问题,Service Worker 默认的作用域就是注册时候的 path, 例如:Service Worker 注册的 path 为 `/a/b/sw.js`,则 scope 默认为 `/a/b/`。
也可以通过在注册时候在 `navigator.serviceWorker.register()` 方法中传入 `{scope: '/some/scope/'}` 参数的方式自己指定作用域,如下代码所示:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Scope Demo</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./a/b/sw.js', {
// 手动指定一个作用域
scope: '/a/b/c/'
}).then(reg => {
console.log(reg.scope)
// http://127.0.0.1:8000/a/b/c/
})
}
</script>
</body>
</html>
```
将 scope 配置 `{scope: '/a/b/c/'}` 传入 `navigator.serviceWorker.register()` 方法,运行后打印出来的内容为 `http://127.0.0.1:8000/a/b/c/`。也就是说可以通过参数为 Service Worker 指定一个作用域。当然,这个自定义作用域是不可以随意指定的,可以通过如下代码修改 `index.html`:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Scope Demo</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./a/b/sw.js', {
scope: '/a/'
}).then(reg => {
console.log(reg.scope)
})
}
</script>
</body>
</html>
```
上面代码将作用域指定为 `/a/`,运行后浏览器会报错,报错的内容如下图所示。

通过报错信息知道 `sw.js` 文件所在的 URL 的 path 是 `/a/b/`,则默认的作用域和最大的作用域都是 `/a/b/`,不允许指定超过最大作用域范围的 `/a/` 为作用域。
通俗的讲,Service Worker 最多只能在 Service Worker 文件 URL path 范围内发挥作用,以上面代码为例,`/a/b/`,`/a/b/c/`,`/a/b/c/d/` 下的页面都可以被注册的 Service Worker 控制。但是 `/a/`、`/e/f/` 目录下面的页面是不受注册的 Service Worker 的控制的(当然浏览器也会抛出错误告知开发者)。也就是说,在最大作用域的基础上才能通过 scope 配置在注册 Service Worker 的时候指定自定义的作用域。
> 注意:
> 类似于 Ajax 的跨域请求可以通过对请求的 Access-Control-Allow-Origin 设置,我们也可以通过服务器对 sw.js 这个文件的请求头进行设置,就能够突破作用域的限制,只需要在服务端对 sw.js 请求设置 Service-Worker-Allowed 请求头为更大控制范围或者其他控制范围的 scope 即可。如:`Service-Worker-Allowed: /a/`。
## Service Worker 作用域污染
通过对 Service Woker 作用域的了解会发现一个问题:**会不会存在多个 Service Worker 控制一个页面的情况呢?** 接下来再新建 serviceWorkerScopeDemo1 项目来了解注册多个 Service Worker 的情况下会有些什么神奇的情况发生。项目目录如下所示:
```bash
.
└── serviceWorkerScopeDemo1
├── a/
│ ├── a-sw.js
│ └── index.html
├── b/
│ └── index.html
└── root-sw.js
```
如果 `/a/index.html` 页面是如下方式注册 Service Worker:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Scope DEMO1 PageA</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./a-sw.js')
}
</script>
</body>
</html>
```
而 `/b/index.html` 页面是如下方式注册 Service Worker:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Scope DEMO1 PageB</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('../root-sw.js')
}
</script>
</body>
</html>
```
`http://127.0.0.1:8000/a/index.html` 页面(称为 A 页面)在 `/a/` 作用域下注册了一个 Service Worker,而 `http://127.0.0.1:8000/b/index.html` 页面(称为 B 页面)在 `/` 作用域下注册了一个 Service Worker,这种情况下 B 页面的 Service Worker 就可以控制 A 页面,因为 B 页面的作用域是包含 A 页面的最大作用域的,这个时候这种情况就称之为**作用域污染**,这时候就会出现如下图所示的情况,A 页面被两个 Service Worker 所控制。

在开发环境开发者在 Chrome 浏览器还可以通过 Devtools 进行手动 “unregister” 来清除掉污染的 Service Worker,但是如果在线上环境被安装了 Service Worker 之后这就是个持久的过程。除非用户手动清除存储的缓存(这个也是不可能的),否则就会出现 Service Worker 交叉控制页面的问题。
当然,线上出现作用域污染的情况也是有办法解决的,比较合理的一种做法是在 A 页面新上线的 `/a/index.html` 版本中注册 Service Worker 之前借助 `navigator.serviceWorker.getRegistrations()` 方法将污染的 Service Worker 先注销掉,然后在注册自己的所在作用域的 Service Worker。具体做法还是看下示例,将 serviceWorkerScopeDemo1 项目的 `/a/index.html` 文件修改后代码如下所示:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Scope Demo1 PageA</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations()
.then(regs => {
for (let reg of regs) {
// 注销掉不是当前作用域的所有的 Service Worker
if (reg.scope !== 'https://127.0.0.1:8000/a/') {
reg.unregister()
}
}
// 注销掉污染 Service Worker 之后再重新注册自己作用域的 Service Worker
navigator.serviceWorker.register('./a-sw.js')
})
}
</script>
</body>
</html>
```
通过这样的方式,运行 serviceWorkerDemo 项目会发现,A 页面只会有一个被自己注册的 Service Worker 生效,在复杂的项目架构中,Service Worker 的作用域污染问题会经常发生,在设计 Service Worker 注册逻辑的时候,尤其是大型的 Web App 项目的时候需要考虑到这点。
## Service Worker 注册设计
由于 Service Worker 注册会有意想不到的作用域污染问题,而 Web App 项目又有多种形式存在,有 SPA(单页面应用),MPA(多页面应用)等架构方式,那到底怎么进行 Service Worker 注册才合适呢?
### SPA 注册 Service Worker
SPA 在工程架构上只有一个 `index.html` 的入口,站点的内容都是异步请求数据之后在前端渲染的,应用中的页面切换都是在前端路由控制的。
通常会将这个 `index.html` 部署到 `https://somehost`,SPA 的 Service Worker 只需要在 `index.html` 中注册一次。所以一般会将 `sw.js` 直接放在站点的根目录保证可访问,也就是说 Service Worker 的作用域通常就是 `/`,这样 Service Worker 能够控制 `index.html`,从而控制整个 SPA 的缓存。
SPA 每次路由的切换都是前端渲染的,这个过程本质上还是在 `index.html` 上的前端交互。通常 Service Worker 会预先缓存 SPA 中的 AppShell 所需的静态资源以及 `index.html`。当然有一种情况比较特殊,当用户从 `https://somehost/a` 页面切换到 `https://somehost/b` 页面的时候,这时候刷新页面首先渲染的还是 `index.html`,在执行 SPA 的路由逻辑之后,通过 SPA 前端路由的处理,继续在前端渲染相应的路由对应的渲染逻辑,这部分的逻辑都是在已经缓存的 JavaScript 中完成了。
### MPA 注册 Service Worker
MPA 这种架构的模式在现如今的大型 Web App 非常常见,这种 Web App 相比较于 SPA 能够承受更重的业务体量,并且利于大型 Web App 的后期维护和扩展。MPA 可以理解为是有多个 HTML 文件对应着多个不同的服务端路由,也就是说 `https://somehost/a` 映射到 `a.html`,`https://somehost/b` 映射到 `b.html`。
那么 MPA 架构下怎么去注册 Service Worker 呢?是不同的页面注册不同的 Service Worker,还是所有的页面都注册同一个 Service Worker?结论是:需要根据实际情况来定。
#### MPA 注册单个 Service Worker
在每个页面之间的业务相似度较高,或者每个页面之间的公共静态资源或异步请求较多,这种 MPA 是非常适合在所有的页面只注册一个 Service Worker。
例如 `https://somehost/a` 和 `htps://somehost/b` 之间的公共内容较多,则通常情况下在 `/` 作用域下注册一个 Service Worker。这样这个 Service Worker 能够控制 `https://somehost` 域下的所有页面。
MPA 维护单个 Service Worker 有如下特点:
- 可以统一管理整个站点的缓存。
- 不会造成页面之间的作用域污染。
- 后期维护成本相对较低。
#### MPA 注册多个 Service Worker
MPA 注册多个 Service Worker 适用于主站非常庞大的 Web App,并且是以 path 分隔的形式铺展垂类子站的大型 Web App,这种情况下就不适合只在 `/` 作用域下只注册一个 Service Worker 了。
例如:`https://somehost/a` 和 `https://somehost/b` 几乎是两个站点,其中公共使用的静态资源或异步请求非常少,则比较适合每个子站注册维护自己的 Service Worker,`https://somehost/a` 注册 Servcie Worker 的作用域为 `/a/`,最好是存在 `/a/sw.js` 对应的 Service Worker 文件 URL 可访问,尽量不要使用某一个公用的 `/sw.js` 并使用 scope 参数来自定义作用域,这样会增加后期的维护成本以及增加出现 bug 的风险。
子站在实现上还要考虑一点是,防止其他页面的 Service Worker 对自身页面造成污染,需要在注册子站 Service Worker 之前将不是子站 path 作用域的 Service Worker 先注销掉。
注册多个 Service Worker 有如下特点:
- 需要严格要求每个子站管理好自己的 `sw.js` 及作用域。
- 防止对其他子站的 Service Worker 造成影响。
- 相比较整个站点只注册一个 Service Worker,这种维护多个 Service Worker 的方式更加灵活。
- 随着子站的增多,风险相对会更加大,也更加难以维护。
## Service Worker 更新
当在页面中通过 `sw.js` 注册了一个 Service Worker 之后,如果 `sw.js` 内容发生了变更,Service Worker 该如何更新呢?
拿 SPA 为例,作为 AppShell 的载体 `index.html` 是会被缓存起来的,AppShell 的静态资源也都会被缓存起来的,由于 Service Worker 的注册入口必须是在主线程完成,所以 Service Worker 的注册必然是需要在 `index.html` 的 `<script></script>` 标签或者被缓存住的 JavaScript 文件中来实现的。
如果 Web App 功能发生了升级更新,我们预期的结果是当用户刷新页面的时候希望浏览器立即更新当前页面的缓存,并且立即加载最新的内容和资源,呈现最新的效果给用户看到。可是用户在刷新页面的时候看到的还是之前缓存的老的内容,这时候该如何处理呢?
通常在每次进行 Web App 升级的时候,都必须伴随着 Service Worker 文件 `sw.js` 的升级,当浏览器检测到 `sw.js` 的升级之后,就会重新触发注册、安装、激活、控制页面的流程,并在这个过程中就会更新当前 Web App 的离线缓存为最新的上线内容。
在执行 `navigator.serviceWorker.register()` 方法注册 Service Worker 的时候,浏览器通过自身 diff 算法能够检测 `sw.js` 的更新包含两种方式:
- Service Worker 文件 URL 的更新
- Service Worker 文件内容的更新
在实际项目中,在 Web App 新上线的时候,通常是在注册 Service Worker 的时候,通过修改 Service Worker 文件的 URL 来进行 Service Worker 的更新,一般采用以下代码所示的方式处理:
```js
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js?v=20190401235959')
}
```
每次 Web App 上线构建的时候,维护一个最新的唯一构建版本号,将构建版本号写入 Service Worker 文件 URL 的版本号参数中,这样的话,就能够保证每次 Web App 有最新上线功能的时候,都能够有最新的 Service Worker 文件 diff 让浏览器能够检测到。当然,除了改变 Service Worker 文件 URL,还可以改变 Service Worker 文件的内容,如下代码所示:
```js
// sw.js
self.version = '20190401235959'
```
> 注意:
> 在 sw.js 中,`self` 为 Service Worker 线程的全局命名空间,类似于主线程的 `window`,在 sw.js 中是访问不到 `window` 命名空间的。
在 Web App 每次上线新的功能,项目进行构建的时候,可以将最新的唯一构建版本号写在 `sw.js` 文件内,这样也能保证每次 Web App 都能够有最新的 Service Worker 文件 diff 被浏览器检测到。
## Service Worker 容错
由于 Service Worker 一旦上线就会永久生效,如果发现线上 Service Worker 有 bug 该怎么办呢?有一种亡羊补牢的方法是重新上一次线,注销掉有 bug 的 Service Worker,假如现在有一个现存的项目 serviceWorkerUnregisterDemo,项目目录如下:
```bash
.
└── serviceWorkerUnregisterDemo/
├── index.html
└── sw.js
```
如果需要紧急下线该项目的 Service Worker,则 `index.html` 代码如下所示:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Unregister Demo</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations()
.then(regs => {
for (let reg of regs) {
// 注销掉所有的 Service Worker
reg.unregister()
}
})
}
</script>
</body>
</html>
```
这种方法是在发现 Service Worker 出现问题之后,必须重新上线 Web App 来解决问题,这样的成本比较高。一般大型 Web App 上线的过程也非常复杂,上线周期长,所以这种止损效果较差,不是很可取。还有一种方法可以避免重新上线 Web App,只需要在 Service Worker 注册的时候通过一个 “**开关请求**” 做一个容错降级的处理,这个开关请求需要满足几个条件:
- 能够快速上线,和 Web App 的上线解耦
- 不能被缓存(无论是 HTTP 缓存还是 Service Worker 缓存)
在实际项目中,通常开关请求会维护成一个 JavaScript 文件(当然也可以是任何一种请求类型,只不过 JavaScript 文件通常比较好维护,而且无需考虑请求跨域的问题),放在某一个可以快速上线的静态资源服务器。那么现在可以修改 serviceWorkerUnregisterDemo 项目的 `index.html` 代码来看看具体如何解决问题的,代码如下面所示:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Unregister Demo</title>
</head>
<body>
<script>
if ('serviceWorker' in navigator) {
// 正常进行注册 Service Worker
navigator.serviceWorker.register('./sw.js?v=20190401235959')
let script = document.createElement('script')
// 假设这个 JS 中存在 Service Worker 开关全局变量
script.src = 'https://some-static-cdn-host/sw-on-off.js'
script.async = true
script.onload = () => {
// Service Worker 开关全局变量的名称
if (window.SW_TURN_OFF) {
navigator.serviceWorker.getRegistrations()
.then(regs => {
for (let reg of regs) {
// 注销掉所有的 Service Worker
reg.unregister()
}
})
}
}
document.body.appendChild(script)
}
</script>
</body>
</html>
```
假如在 `https://some-static-cdn-host/sw-on-off.js` 静态资源服务器维护了一个开关 JavaScript 文件,那这个文件正常情况下的代码内容如下所示:
```js
/**
* @file https://some-static-cdn-host/sw-on-off.js
*/
// 当 Web App 线上出现紧急问题的时候将值设为 true 并上线
window.SW_TURN_OFF = false
```
## 小结
本节从注册 Service Worker 的角度出发,详细的介绍了在注册 Service Worker 的过程中需要考虑哪些问题,在不同的项目架构或者不同的情况下,注册 Service Worker 的考量点都是不一样的,接来下将会详细介绍 Service Worker 的技术细节,了解 Service Worker 到底是如何进行 PWA 的离线缓存的。
================================================
FILE: chapter04/3-service-worker-dive.md
================================================
# Service Worker 工作原理
前面已经介绍了 Service Worker 是一个工作线程的本质,也了解了 Service Worker 可以离线工作,还介绍了 Service Worker 在主线程中是如何被注册的。但是到现在为止还是不知道 Service Worker 具体怎么在实际项目中应用。也不知道如何去开发和维护一个 Service Worker 文件。我们已经知道了 Service Worker 是可以对 Web App 的资源和请求进行离线缓存,那它到底是如何进行离线缓存控制的呢?
在本节,我们会深入的介绍一下 Service Worker 的工作原理,Service Worker 的工作原理主要体现在它的生命周期上,一个 Service Worker 从被注册开始,就会经历自身的一些生命周期的节点,而在这些节点都可以去做一些特定的事情,比如一些复杂的计算、缓存的写入、缓存的读取等操作。通过这些生命周期节点的联合调度,Service Worker 才能完成复杂的资源离线缓存的工作。而开发者只有了解了 Service Worker 的生命周期,才能通过设计相关逻辑,并开发 Service Worker 文件 `sw.js` ,让 Service Worker 去完成 PWA 离线缓存策略。
## 生命周期
先来了解下什么是 Service Worker 的生命周期,每个 Service Worker 都有一个独立于 Web 页面的生命周期,其示意图如下图所示。

1. 在主线程成功注册 Service Worker 之后,开始下载并解析执行 Service Worker 文件,执行过程中开始安装 Service Worker,在此过程中会触发 worker 线程的 install 事件。
2. 如果 install 事件回调成功执行(在 install 回调中通常会做一些缓存读写的工作,可能会存在失败的情况),则开始激活 Service Worker,在此过程中会触发 worker 线程的 activate 事件,如果 install 事件回调执行失败,则生命周期进入 Error 终结状态,终止生命周期。
3. 完成激活之后,Service Worker 就能够控制作用域下的页面的资源请求,可以监听 fetch 事件。
4. 如果在激活后 Service Worker 被 unregister 或者有新的 Service Worker 版本更新,则当前 Service Worker 生命周期完结,进入 Terminated 终结状态。
Service Worker 生命周期是一个比较复杂的知识点,其中有较多的细节需要深入理解,为了更清楚的进行介绍,接下来新建一个项目 serviceWorkerLifecycleDemo,项目目录结构如下:
```bash
.
└── serviceWorkerLifecycleDemo/
├── imgs/
│ └── dog.jpg
├── index.html
└── sw.js
```
首先,需要有一个 Service Worker 的注册入口,所以 `index.html` 的代码内容如下所示:
```html
<!DOCTYPE html>
<head>
<title>Service Worker Lifecycle Demo</title>
</head>
<body>
<img src="./imgs/dog.jpg" alt="demo image" />
<script>
if ('serviceWorker' in navigator) {
// 由于 127.0.0.1:8000 是所有测试 Demo 的 host
// 为了防止作用域污染,将安装前注销所有已生效的 Service Worker
navigator.serviceWorker.getRegistrations()
.then(regs => {
for (let reg of regs) {
reg.unregister()
}
navigator.serviceWorker.register('./sw.js')
})
}
</script>
</body>
</html>
```
> 注意:
> 由于 Service Worker 一旦注册后就会永久生效,而生效的控制范围是根据作用域来控制的,我们所有的测试 host 都为 `127.0.0.1:8000`,这样会导致新的项目还没注册 Service Worker 却已经被之前注册的 Service Worker 所控制,所以通常在注册新的 Service Worker 的时候,为了**彻底防止作用域污染**的做法就是在注册前将所有现存控制当前页面的 Service Worker 全部注销掉,或者在 Chrome Devtools 中每次都将老的 Service Worker 手动 unregister 掉。
这次在 serviceWorkerLifecycleDemo 项目的 HTML 文件中加入一个 `<img>` 标签来加载一张图片,主要是用来理解 Service Worker 如何在生命周期中进行离线与缓存处理的。
虽然空的 Service Worker 文件也是可以通过注册来新开一个 Service Worker 线程,但是通常 Service Worker 文件中需要编写一些 JavaScript 代码逻辑来完成 Web App 的离线与缓存的策略设计。接下来我们会一步步的详细讲解这些代码该如何编写,首先先给 `sw.js` 写入以下代码,用来理解 Service Worker 的生命周期:
```js
// sw.js
console.log('service worker 注册成功')
self.addEventListener('install', () => {
// 安装回调的逻辑处理
console.log('service worker 安装成功')
})
self.addEventListener('activate', () => {
// 激活回调的逻辑处理
console.log('service worker 激活成功')
})
self.addEventListener('fetch', event => {
console.log('service worker 抓取请求成功: ' + event.request.url)
})
```
这段代码一开始是直接通过 `console.log()` 打印输出一段内容,然后绑定了三个事件,分别是 `install`、`activate`、`fetch` 事件,用来响应 Service Worker 生命周期的事件触发。
接下来用 Chrome 浏览器来测试一下 serviceWorkerLifecycleDemo 这个例子,为了更好的理解测试结果,在打开测试页面 `http://127.0.0.1:8000` 之前需要将所有的浏览器标签关闭(后面会详细解释为什么需要如此操作)。不出意外的话,**第一次**访问 `http://127.0.0.1:8000` 页面的时候 Chrome Devtools Console 控制台的打印结果如下:
```bash
service worker 注册成功
service worker 安装成功
service worker 激活成功
```
当我们**第二次**刷新页面的时候,这时候控制台的打印结果如下:
```bash
service worker 抓取请求成功:http://127.0.0.1:8000/imgs/dog.jpg
```
从这个执行结果来看,初步能够说明以下几点:
- Service Worker 文件只在首次注册的时候执行了一次。
- 安装、激活流程也只是在首次执行 Service Worker 文件的时候进行了一次。
- 首次注册成功的 Service Worker 没能拦截当前页面的请求。
- 非首次注册的 Service Worker 可以控制当前的页面并能拦截请求。
Service Worker 在内部都有一系列的工作流程,这些工作流程决定了开发者可以在 Service Worker 文件中如何进行开发。下图展示的是 Service Worker 工作流程图。

实际上 Service Worker 首次注册或者有新版本触发更新的时候,才会重新创建一个 worker 工作线程并解析执行 Service Worker 文件,在这之后并进入 Service Worker 的安装和激活生命周期。
而在首次注册、安装、激活之后,Service Worker 已经拿到了当前页面的控制权了,但为什么首次刷新却没有拦截到网络请求呢?主要是因为在 Service Worker 的注册是一个异步的过程,在激活完成后当前页面的请求都已经发送完成,因为时机太晚,此时是拦截不到任何请求的,只能等待下次访问再进行。
而第二次刷新页面,由于当前站点的 Service Worker 是处于激活状态,所以不会再次新建 worker 工作线程并执行 Service Worker。也就是说激活状态的 Service Worker 在一个站点只会存在一个 worker 工作线程,除非 Service Worker 文件发生了变化(手动 unregister Service Worker 也会注销掉 worker 工作线程),触发了浏览器更新,才会重新开启生命周期。而由于 Service Worker 工作线程的离线特性,只要处于激活状态,在后续的任何访问中,都会通过 fetch 事件监听器拦截当前页面的网络请求,并执行 `fetch` 事件回调。
## waitUntil 机制
如果 Service Worker 安装失败会导致 Service Worker 生命周期终止。由于 Service Worker install 回调是在用户首次访问注册的时候才会触发,所以在项目设计的时候,会将 Web App 一些只有上线才会改变的静态资源会在 install 阶段进行缓存,让用户更快的体验到缓存加速的好处。如果缓存成功了才算是 Service Worker 安装完成,如果这些静态资源缓存失败了,那 Service Worker 安装就会失败,生命周期终止。
什么情况下才算是 Service Worker 安装失败呢?如果在 Service Worker 文件中的 install 回调中写一段错误逻辑会不会导致安装失败呢?接下来修改一下 serviceWorkerLifecycleDemo 的 `sw.js`,代码如下:
```js
// sw.js
console.log('service worker 注册成功')
self.addEventListener('install', () => {
// 一段一定会报错的代码
console.log(a.undefined)
console.log('service worker 安装成功')
})
self.addEventListener('activate', () => {
// 激活回调的逻辑处理
console.log('service worker 激活成功')
})
self.addEventListener('fetch', event => {
console.log('service worker 抓取请求成功: ' + event.request.url)
})
```
在 install 事件回调中,插入了一段一定会报错的代码,看看是不是会导致 Service Worker 的安装失败呢?
> 注意:
> 前面介绍过,由于修改了 sw.js,所以会触发 Service Worker 更新机制,而这次测试是纯粹介绍首次安装失败的情况,为保证实验的纯粹性,需要在 Chrome DevTools 中将存在的 Service Worker 手动 unregister 掉,在后面介绍 Service Worker 更新机制的时候会详细解释其原理。
示例运行结果如下图所示:

从运行结果看,当 install 回调中的逻辑报错了,并不会影响 Service Worker 的生命周期继续向后推进,因为运行结果还是有 `激活成功`,甚至第二次刷新发现还能正常拦截请求。
所以说并不是 intall 回调中出错了就会导致生命周期中断。由于 Service Worker 生命周期异步触发的特性,并不是像同步执行模式,如果报错就会中断执行。Service Worker 事件回调的参数是一个 ExtendableEvent 对象,在 Service Worker 中需要使用 `ExtendableEvent.waitUntil()` 方法来保证生命周期的执行顺序。该方法接收一个 Promise 参数,开发者通常会将安装的回调执行逻辑(如缓存的写入)封装在一个 Promise 里,如果操作报错应该通过 Promise 来 reject 错误,这样 Service Worker 就知道了安装失败,然后 Service Worker 就能中断生命周期。接下来修改 `sw.js` 代码如下所示:
```js
// sw.js
console.log('service worker 注册成功')
self.addEventListener('install', event => {
// 引入 event.waitUntil 方法
event.waitUntil(new Promise((resolve, reject) => {
// 模拟 promise 返回错误结果的情况
reject('安装出错')
// resolve('安装成功')
}))
})
self.addEventListener('activate', () => {
// 激活回调的逻辑处理
console.log('service worker 激活成功')
})
self.addEventListener('fetch', event => {
console.log('service worker 抓取请求成功: ' + event.request.url)
})
```
这时候运行刷新页面的时候发现 Service Worker 的生命周期中断,而且没有执行 activate 事件回调。当将 `reject('安装失败')` 改成 `resolve('安装成功')` 的时候,会发现 Service Worker 能够顺利激活。事实上,`ExtendableEvent.waitUntil()` 方法扩展了事件的生命周期。在服务工作线程中,延长事件的寿命能够阻止浏览器在事件中的异步操作完成之前终止 worker 工作线程。
在 install 事件回调被调用时,它把即将被激活的 worker 线程状态延迟为 installing 状态,直到传递的 Promise 被成功地 resolve。这主要用于确保:Service Worker 工作线程在所有依赖的核心 cache 被缓存之前都不会被安装。
不只是 install 事件回调可以调用这个方法,如果在 activate 事件回调被调用时,它把即将被激活的 worker 线程状态延迟为 activating 状态,直到传递的 Promise 被成功地 resolve。这主要用于确保:任何功能事件不会被分派到 ServiceWorkerGlobalScope 对象,直到它升级数据库模式并删除过期的缓存条目。
当 `ExtendableEvent.waitUntil()` 运行时,如果 Promise 是 resolved,任何事情都不会发生;如果 Promise 是 rejected,installing 或者 activating 的状态会被设置为 redundant。
> 注意:
> 如果在 ExtendableEvent 处理程序之外调用 `waitUntil()`,浏览器会抛出一个InvalidStateError 错误。
> 如果多个调用将会堆叠,所产生的所有 promise 将被添加到**延长生命周期的 promise** 等待执行完成。
## 终端
在运行 serviceWorkerLifecycleDemo 示例的时候,提到了需要关闭所有浏览器标签再打开测试页面,其中主要的原因是涉及到 Service Worker 的终端(clients)的概念。
最直接的解释是每一个打开 `http://127.0.0.1:8000` 页面的浏览器标签都是一个终端,如下图所示。

在手机端或者 PC 端浏览器,每新打开一个已经激活了 Service Worker 的页面,那 Service Worker 所控制的终端就新增一个,每关闭一个包含已经激活了 Service Worker 页面的时候(不包含手机端浏览器进入后台运行的情况),则 Service Worker 所控制的终端就减少一个,如上图打开了三个浏览器标签,则当前 Service Worker 控制了三个终端,通过 Chrome 浏览器 Devtools 的 `Applications -> ServiceWorker` 标签可以查看如下图所示 Service Worker 控制的三个终端。

当刷新其中一个浏览器标签的时候,会发现一个奇怪的现象,当前的浏览器标签的控制台打印了一条信息如下所示:
```bash
service worker 抓取请求成功: http://127.0.0.1:8000/imgs/dog.jpg
```
而并没有对其他的两个浏览器标签进行刷新,但是它们的控制台也出现了打印信息:
```bash
service worker 抓取请求成功: http://127.0.0.1:8000/
service worker 抓取请求成功: http://127.0.0.1:8000/imgs/dog.jpg
```
这主要是因为,所有的终端共用一个 worker 工作线程,当在 worker 线程中执行 `console.log()` 方法打印内容的时候,会作用到所有的终端,worker 工作线程和终端的关系如下图 4-12 所示。

`console.log` 是浏览器提供的一种特殊的 I/O 操作,并不是常规操作。通常开发者不会这样来应用这种终端机制,一般而是借助 postMessage 机制来通过 worker 工作线程控制终端,worker 线程在某个生命周期回调 postMessage 给各个终端,终端预先绑定 onmessage 事件,回调处理 worker 线程发送过来的指令,可以做一些后台统计的相关工作,甚至可以用这种机制在 Service Worker 线程中,集中对所有终端的 UI 进行统一处理。
### clients.claim() 方法
如果使用了 skipWaiting 的方式跳过 waiting 状态,直接激活了 Service Worker,可能会出现其他终端还没有受当前终端激活的 Service Worker 控制的情况,切回其他终端之后,Service Worker 控制页面的效果可能不符合预期,尤其是如果 Service Worker 需要动态拦截第三方请求的时候。
为了保证 Service Worker 激活之后能够马上作用于所有的终端,通常在激活 Service Worker 后,通过在其中调用 `self.clients.claim()` 方法控制未受控制的客户端。`self.clients.claim()` 方法返回一个 Promise,可以直接在 `waitUntil()` 方法中调用,如下代码所示:
```js
self.addEventListener('activate', event => {
event.waitUntil(
self.clients.claim()
.then(() => {
// 返回处理缓存更新的相关事情的 Promise
})
)
})
```
> 注意:
> 很多开发者默认就在 Service Worker 文件中使用 `self.clients.claim()`。不建议这么绝对,还是要根据具体项目而定,主要看是否有激活 Service Worker 之后马上控制所有终端的需求。
## Service Worker 更新原理
在运行 serviceWorkerLifecycleDemo 的时候,之前提到过,在每次修改 Service Worker 文件的时候,如果需要刷新页面验证效果,都应提前在 Chrome Devtools 中手动 unregister 当前的 Service Worker,主要是因为修改 Service Worker 文件都会触发其更新,而 Service Worker 的更新过程比较复杂,为了区分首次安装、激活和更新触发的安装、激活,保证效果的一致性,所以才有此建议。那接下来重点地讲解一下 Service Worker 的更新原理,看看里面到底有什么门道。
修改 serviceWorkerLifecycleDemo 的 `index.html` 中注册 `sw.js` 部分的逻辑,用于触发 Service Worker 的更新(当然也可以修改 Service Worker 文件的某些内容),如下所示:
```js
// 触发 Service Worker 的更新
navigator.serviceWorker.register('./sw.js?v=20190401235959')
```
刷新页面之后控制台打印的内容只有 `注册成功`,说明更新 Service Worker 会重新解析执行 Service Worker 的 JavaScript 代码,会触发安装回调,但是没有完成激活。查看 Chrome Devtools 的 Service Worker 面板发现 Service Worker 确实卡在激活状态了,状态为 `waiting to activate`,如下图所示:

这就是更新 Service Worker 和首次安装 Service Worker 的一个区别所在。下面通过下图了解一下 Service Worker 更新的原理。

当浏览器监测到新的 Service Worker 更新之后,会重新进行注册、安装,当检测到当前的页面被激活态的 Service Worker 控制着的话,会进入 waiting 状态,之后可以有两种选择:
1. 通过 skipWaiting 跳过 waiting 状态
2. 在所有终端保持 waiting 状态,直到 Service Worker 对**所有**终端失去控制(关闭所有终端的时候)
通过运行 serviceWorkerLifecycleDemo 可以发现,将之前启动的三个终端全部关闭掉,然后再新开一个浏览器标签打开 `http://127.0.0.1:8000` 之后,会发现新的 Service Worker 已经激活成功。
还可以有另外一种方法,就是在 Chrome Devtools 中点击 “**skipWaiting**” 按钮,这样就会发现 Service Worker 直接进入了激活状态(反复测试 Demo,记得修改 Service Worker 内容或 URL 以触发 Service Worker 的更新)。
### skipWaiting
Service Worker 一旦更新,需要等所有的终端都关闭之后,再重新打开页面才能激活新的 Service Worker,这个过程太复杂了。通常情况下,开发者希望当 Service Worker 一检测到更新就直接激活新的 Service Worker。如果不想等所有的终端都关闭再打开的话,只能通过 skipWaiting 的方法了,但是总不能让用户自己去浏览器中点击 “skipWaiting” 按钮吧?
Service Worker 在全局提供了一个 `skipWaiting()` 方法,`skipWaiting()` 在 waiting 期间调用还是在之前调用并没有什么不同。一般情况下是在 install 事件中调用它,接下来验证一下效果,可以通过如下代码所示的方式修改 `sw.js` 代码。
```js
// sw.js
console.log('service worker 注册成功')
self.addEventListener('install', event => {
// 跳过等待
self.skipWaiting()
// 引入 event.waitUntil 方法
event.waitUntil(new Promise((resolve, reject) => {
// 模拟 promise 返回错误结果的情况
// reject('安装出错')
resolve('安装成功')
console.log('service worker 安装成功')
}))
})
self.addEventListener('activate', () => {
// 激活回调的逻辑处理
console.log('service worker 激活成功')
})
self.addEventListener('fetch', event => {
console.log('service worker 抓取请求成功: ' + event.request.url)
})
```
通过调用 `skipWaiting()` 方法,运行 Demo 之后刷新任何一个页面都会发现,新的 Service Worker 被激活了。这种方式也被普遍应用在 Service Worker 的更新策略中,主要是为了让用户能够最快的体验到站点的升级和变化。
> 注意:
> skipWaiting() 意味着新 Service Worker 可能会控制使用较旧 Service Worker 控制的页面。这意味着页面提取的部分数据将由旧 Service Worker 处理,而新 Service Worker 处理后来提取的数据。如果预期到缓存数据不一致的现象会导致问题,则不要使用 skipWaiting() 跳过 waiting 状态。
### 手动更新
当刷新页面重新执行 register 方法的时候,浏览器检测到 Service Worker 文件更新就会触发 Service Worker 更新,但是如果站点在浏览器后台长时间没有被刷新,则浏览器将自动检查更新,通常是每隔 24 小时检查一次,但是 24 小时也太长了,所以也可以在代码中手动触发更新,通常做法如下代码所示:
```js
navigator.serviceWorker.register('/sw.js')
.then(reg => {
setInterval(() => {
reg.update()
}, 60 * 60 * 1000)
})
```
如果开发者期望用户可以长时间使用您的网站而不必重新加载,您需要按一定间隔(如每小时)调用 `update()` 方法。
### update on reload
Service Worker 生命周期是专为用户构建的,这就给开发工作带来一定的困难。幸运的是,我们可借助 Chrome 的 Devtools 的 “update on reload” 功能,在开发调试 Service Worker 生命周期的时候非常友好。如下图所示。

通过 update on reload 功能,开发者可以做到以下几点:
1. 重新提取 Service Worker。
2. 即使字节完全相同,也将其作为新版本安装,这表示运行 install 事件并更新缓存。
3. 跳过 waiting 阶段,直接激活新 Service Worker。
4. 浏览页面,每次浏览时(包括刷新)都将进行更新,无需重新加载两次或关闭标签。
所以在测试 serviceWorkerLifecycleDemo 的时候,不妨试一下 update on reload 功能吧。
## 小结
本节介绍了 Service Worker 的生命周期以及更新机制,了解了 Service Worker 具体的运作方式。虽然目前对 Service Worker 技术点有了全面的了解,但是还是没有涉及到任何离线与缓存相关的东西,为了更加系统深入了解 PWA 离线缓存机制,在下一章中会对 Service Worker 缓存管理进行详细介绍。
================================================
FILE: chapter04/4-service-worker-debug.md
================================================
# Service Worker 调试
在开发 Service Worker 文件的过程中,如何调试呢?怎么才能确保线下开发的 Service Worker 文件在经过注册后到线上去运行是符合预期的呢?在这小节中将详细介绍如何调试 Service Worker。
Service Worker 作为独立于主线程的独立线程,在调试方面其实和常规的 JavaScript 开发类似,通常开发者关注的点大概有如下几点:
- Service Worker 文件 JavaScript 代码是否有报错。
- Service Worker 能否顺利安装、激活或者更新。
- 在不同机型上的兼容性是不是有问题。
- 不同类型资源和请求的缓存策略的验证。
## debug 环境下的开发跳过等待状态
根据 Service Worker 生命周期的特性,如果浏览器还在使用旧的 Service Worker 版本,即使有 Service Worker 新的版本也不会立即被浏览器激活,只能进行安装并进入等待状态,直到浏览器 Tab 标签被重新关闭打开。
在开发调试 Service Worker 时,肯定希望重新加载后立即激活,通常开发者不希望每次都重新打开当前页面调试,为此可以在 `install` 事件发生时通过 `skipWaiting()` 来跳过 Service Worker 的 waiting 状态。这样每次 Service Worker 安装后就会被立即激活,通常在 `sw.js` 中实现如下代码所示:
```js
self.addEventListener('install', () => {
if (ENV === 'development') {
self.skipWaiting()
}
})
```
## 借助 Chrome Devtool 进行调试
使用 Chrome 浏览器,可以通过进入控制台 `Application -> Service Workers` 面板查看和调试。其效果如下图所示:

如果 Service Worker 线程已安装到当前打开的页面上,接下来会看到它将列示在此窗格中。例如:在上图中,展示的是在 `https://lavas-project.github.io/lavas-demo/news-v2/#/` 的作用域内安装了一个 Service Worker 线程。
为了更熟练的运用 Chrome Devtools 调试 Service Worker,首先需要熟悉以下这些选项:
- **Offline**: 复选框可以将 DevTools 切换至离线模式。它等同于 Network 窗格中的离线模式。
- **Update on reload**:复选框可以强制 Service Worker 线程在每次页面加载时更新。
- **Bypass for network**:复选框可以绕过 Service Worker 线程并强制浏览器转至网络寻找请求的资源。
- **Update**:按钮可以对指定的 Service Worker 线程执行一次性更新。
- **Push**:按钮可以在没有负载的情况下模拟推送通知。
- **Sync**:按钮可以模拟后台同步事件。
- **Unregister**:按钮可以注销指定的 Service Worker 线程。
- **Source**:告诉当前正在运行的 Service Worker 线程的安装时间,链接是 Service Worker 线程源文件的名称。点击链接会将定向并跳转至 Service Worker 线程来源。
- **Status**:告诉 Service Worker 线程的状态。此行上的数字指示 Service Worker 线程已被更新的次数。如果启用 `update on reload` 复选框,接下来会注意到每次页面加载时此数字都会增大。在状态旁边会看到 `start` 按钮(如果 Service Worker 线程已停止)或 `stop` 按钮(如果 Service Worker 线程正在运行)。Service Worker 线程设计为可由浏览器随时停止和启动。 使用 stop 按钮明确停止 Service Worker 线程可以模拟这一点。停止 Service Worker 线程是测试 Service Worker 线程再次重新启动时的代码行为方式的绝佳方法。它通常可以揭示由于对持续全局状态的不完善假设而引发的错误。
- **Clients**:告诉 Service Worker 线程作用域的原点。如果已启用 `show all` 复选框,`focus` 按钮将非常实用。 在此复选框启用时,系统会列出所有注册的 Service Worker 线程。如果这时候点击正在不同标签中运行的 Service Worker 线程旁的 `focus` 按钮,Chrome 会聚焦到该标签。
如果 Service Worker 文件在运行过程中出现了任何的错误,将显示一个 `Error` 新标签,如下图所示。

当然也可以直接访问 `Chrome://serviceworker-internals` 来打开 serviceWorker 的配置面板,查看所有注册的 Service Worker 情况。
> 注意:
> 如无必要,不要选中顶部的 `Open DevTools window and pause javaScript execution on Service Worker startup for debugging` 复选框,否则每当刷新页面调试时都会弹出一个开发者窗口来。
在 Firefox 中,可以通过 `Tools -> Web Developer -> Service Workers` 打开调试面板。也可以访问 `about:debugging#workers` 直接进入该面板。
## 查看 Service Worker 缓存内容
通过前面的章节已经了解过,Service Worker 使用 Cache API 进行缓存的读写,同样可以在 Chrome DevTools 上查看缓存的资源列表。
Cache Storage 选项卡提供了一个已使用(Service Worker 线程)Cache API 缓存的只读资源列表,如下图所示。

如果打开了两个或多个缓存,那在 Application 标签下的 Caches 面板将看到它们会陈列在 Cache Storage 下拉菜单下方,如下图所示。

当然,Cache Storage 提供清除 Cache 列表的功能,在选择 `Cache Storage` 选项卡后在 Cache Storge 缓存的 key 的 item 上右键点击出现 `delete` ,点击 `delete` 就可以清除该缓存了,如下图所示。

也可以选择 `Clear Storage` 选项卡进行清除缓存。
## 网络跟踪
此外经过 Service Worker 的 `fetch` 请求 Chrome 都会在 Chrome DevTools Network 标签页里标注出来,其中:
- 来自 Service Worker 的内容会在 Size 字段中标注为 `from ServiceWorker`
- Service Worker 发出的请求会在 Name 字段中添加 ⚙ 图标。
如下图所示,第一个名为 `300` 的请求是一张 jpeg 图片, 其 URL 为 `https://unsplash.it/200/300`,该请求是由 Service Worker 代理的, 因此被标注为 `from ServiceWorker`。
为了响应页面请求,Service Worker 也发出了名为 `300` 的请求(这是下图中第二个请求),但 Service Worker 把 URL 改成了 `https://unsplash.it/g/200/300`,因此返回给页面的图片是灰色的。

## 真机调试
由于 Service Worker 必须要在 HTTPS 环境下才能被注册成功,所以在真机调试的过程中还需要解决 HTTPS 调试问题,当然 `127.0.0.1` 和 `localhost` 是被允许的 host,但是在真机调试上无法指定为到 PC 上的本地服务器,所以真机 debug 必须要求是已经部署好的 https PWA 站点。
### Android inspect 远程调试
对于 Android 设备,可以借助于 Chrome 的 inspect 方法进行调试 PWA,其中有几个事项是需要提前准备的:
- PC 上已安装 Chrome 32 或更高版本。
- PC 上已安装 USB 驱动程序(如果使用 Windows),确保设备管理器报告正确的 USB 驱动程序。
- 一根可以将 Android 设备连接至开发计算机的 USB 线。
- 一台 Android 4.0 或更高版本的 Android 设备。
接下来可以通过以下步骤进行调试:
1. 将 Android 设备通过 USB 线与 PC 连接。
2. 在 Android 设备上进行一些设置,选择 `设置 > 开发者选项 > 开启 USB 调试`。
3. 在 PC 上打开 Chrome,使用一个 Google 帐户登录到 Chrome。(远程调试在隐身模式或访客模式下无法运行)。
4. 在 PC 的 Chrome 浏览器地址栏输入 `chrome://inspect`。
5. 在 `Remote Target` 下找到对应的 Android 设备。
6. 点击远程设备链接进入 Chrome Devtools。
这样的话就可以在 Chrome 的 Devtools 直接调试运行在 Android 手机端 Chrome 的 PWA 站点,体验完全和在本地 PC 电脑上 debug 一摸一样。
### iOS 远程真机调试
对于 iOS 设备运行的 PWA,真机 debug 有点麻烦,好在 Apple Safari 也提供了一套远程调试的方案,可以借助于 Web Inspector(web 检查器)机制来完成真机调试。在开始调试之前需要准备以下工具:
- 一台 Mac 电脑。
- 一个 icloud 账号。
- 一个 Apple 的移动设备(iPhone)。
- 用 iCloud 账号登陆 Mac 和 iPhone。
- 对 iPhone 进行设置:`设置 > Apple ID 用户中心入口 > iCloud > 打开 Safari`。
- 对 iPhone 进行设置:`设置 > Safari浏览器 > 高级 > 打开 Web Inspector`。
- 对 Mac 进行设置:` > 系统偏好设置 > iCloud > 勾上 Safari`。
- 对 Mac 进行设置:`打开 Safari > Safari 菜单 > 偏好设置 > 高级 > 勾选“在菜单栏中显示开发菜单”`(这时候 Safari 的系统菜单栏多了一个 `开发` 标签)。
当完成了准备工作后,下面可以开始调试了,调试步骤如下:
1. 用 USB 线连接 iPhone 和 Mac。
2. 在 iPhone 上打开 PWA 站点。
3. 打开 Mac 上 Safari 菜单栏的 `开发` 标签,就可以点击进 `我的 iPhone`。
4. 接下来会发现 `我的 iPhone` 子菜单里有在 iphone 上打开的 PWA 站点,这时候就可以用 Safari 的 Devtools 进行调试。
================================================
FILE: chapter04/code/applicationCacheDemo/index.html
================================================
<!DOCTYPE html>
<html manifest="./manifest.appcache">
<!--...-->
</html>
================================================
FILE: chapter04/code/applicationCacheDemo/manifest.appcache
================================================
CACHE MANIFEST
# version xx.xx.xx
CACHE:
cached.png
cached.js
NETWORK:
noCached.html
noCached.css
FALLBACK:
/ 404.html
=====
gitextract_nvjmu4af/ ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── SUMMARY.md ├── appendix01/ │ ├── 1-lighthouse.md │ ├── 2-lighthouse-score-guide.md │ └── 3-lighthouse-case.md ├── appendix01.md ├── book.json ├── chapter01/ │ ├── 1-how-was-pwa-born.md │ ├── 2-what-is-pwa.md │ ├── 3-what-are-key-techs.md │ ├── 4-how-is-pwa-going.md │ ├── 5-the-future-of-pwa.md │ └── 6-your-first-pwa.md ├── chapter01.md ├── chapter02/ │ ├── 1-what-is-good-ux.md │ ├── 2-design-and-tech.md │ ├── 3-app-shell.md │ ├── 4-app-skeleton.md │ └── 5-responsive-design.md ├── chapter02.md ├── chapter03/ │ ├── 1-promise.md │ ├── 2-async-function.md │ ├── 3-fetch-api.md │ ├── 4-cache-api.md │ └── 5-indexeddb.md ├── chapter03.md ├── chapter04/ │ ├── 1-service-worker-introduction.md │ ├── 2-service-worker-register.md │ ├── 3-service-worker-dive.md │ ├── 4-service-worker-debug.md │ └── code/ │ ├── applicationCacheDemo/ │ │ ├── index.html │ │ └── manifest.appcache │ ├── serviceWorkerCacheDemo/ │ │ ├── data.json │ │ ├── index.css │ │ ├── index.html │ │ ├── index.js │ │ └── sw.js │ ├── serviceWorkerDemo/ │ │ ├── index.html │ │ └── sw.js │ ├── serviceWorkerIndexeddbDemo/ │ │ ├── fruits.json │ │ ├── index.html │ │ └── sw.js │ ├── serviceWorkerLifecycleDemo/ │ │ ├── index.html │ │ └── sw.js │ ├── serviceWorkerScopeDemo/ │ │ ├── a/ │ │ │ └── b/ │ │ │ └── sw.js │ │ └── index.html │ ├── serviceWorkerScopeDemo1/ │ │ ├── a/ │ │ │ ├── a-sw.js │ │ │ └── index.html │ │ ├── b/ │ │ │ └── index.html │ │ └── root-sw.js │ └── serviceWorkerUnregisterDemo/ │ ├── index.html │ └── sw.js ├── chapter04.md ├── chapter05/ │ ├── 1-fetch-event-management.md │ ├── 2-local-storage-management.md │ ├── 3-respond-strategy.md │ ├── 4-precache.md │ └── 5-workbox.md ├── chapter05.md ├── chapter06/ │ ├── 1-manifest-json.md │ ├── 2-credentials-api.md │ ├── 3-notification-api.md │ ├── 4-web-push-api.md │ └── 5-payment-request-api.md ├── chapter06.md ├── chapter07/ │ ├── 1-https.md │ ├── 2-CSP.md │ ├── 3-policy.md │ └── 4-vulnerability.md ├── chapter07.md ├── chapter08/ │ ├── 1-loading-performance.md │ └── 2-rendering-performance.md ├── chapter08.md ├── chapter09/ │ ├── 1-search-engine-index.md │ ├── 2-pwa-and-amp-and-mip.md │ ├── 3-whole-site-amp-and-mip.md │ └── 4-preload-pwa.md ├── chapter09.md ├── ci.yml ├── docs/ │ ├── LICENSE │ ├── appendix01/ │ │ ├── 1-lighthouse.html │ │ ├── 2-lighthouse-score-guide.html │ │ └── 3-lighthouse-case.html │ ├── appendix01.html │ ├── chapter01/ │ │ ├── 1-how-was-pwa-born.html │ │ ├── 2-what-is-pwa.html │ │ ├── 3-what-are-key-techs.html │ │ ├── 4-how-is-pwa-going.html │ │ ├── 5-the-future-of-pwa.html │ │ └── 6-your-first-pwa.html │ ├── chapter01.html │ ├── chapter02/ │ │ ├── 1-what-is-good-ux.html │ │ ├── 2-design-and-tech.html │ │ ├── 3-app-shell.html │ │ ├── 4-app-skeleton.html │ │ └── 5-responsive-design.html │ ├── chapter02.html │ ├── chapter03/ │ │ ├── 1-promise.html │ │ ├── 2-async-function.html │ │ ├── 3-fetch-api.html │ │ ├── 4-cache-api.html │ │ └── 5-indexeddb.html │ ├── chapter03.html │ ├── chapter04/ │ │ ├── 1-service-worker-introduction.html │ │ ├── 2-service-worker-register.html │ │ ├── 3-service-worker-dive.html │ │ ├── 4-service-worker-debug.html │ │ └── code/ │ │ ├── applicationCacheDemo/ │ │ │ ├── index.html │ │ │ └── manifest.appcache │ │ ├── serviceWorkerCacheDemo/ │ │ │ ├── data.json │ │ │ ├── index.css │ │ │ ├── index.html │ │ │ ├── index.js │ │ │ └── sw.js │ │ ├── serviceWorkerDemo/ │ │ │ ├── index.html │ │ │ └── sw.js │ │ ├── serviceWorkerIndexeddbDemo/ │ │ │ ├── fruits.json │ │ │ ├── index.html │ │ │ └── sw.js │ │ ├── serviceWorkerLifecycleDemo/ │ │ │ ├── index.html │ │ │ └── sw.js │ │ ├── serviceWorkerScopeDemo/ │ │ │ ├── a/ │ │ │ │ └── b/ │ │ │ │ └── sw.js │ │ │ └── index.html │ │ ├── serviceWorkerScopeDemo1/ │ │ │ ├── a/ │ │ │ │ ├── a-sw.js │ │ │ │ └── index.html │ │ │ ├── b/ │ │ │ │ └── index.html │ │ │ └── root-sw.js │ │ └── serviceWorkerUnregisterDemo/ │ │ ├── index.html │ │ └── sw.js │ ├── chapter04.html │ ├── chapter05/ │ │ ├── 1-fetch-event-management.html │ │ ├── 2-local-storage-management.html │ │ ├── 3-respond-strategy.html │ │ ├── 4-precache.html │ │ └── 5-workbox.html │ ├── chapter05.html │ ├── chapter06/ │ │ ├── 1-manifest-json.html │ │ ├── 2-credentials-api.html │ │ ├── 3-notification-api.html │ │ ├── 4-web-push-api.html │ │ └── 5-payment-request-api.html │ ├── chapter06.html │ ├── chapter07/ │ │ ├── 1-https.html │ │ ├── 2-CSP.html │ │ ├── 3-policy.html │ │ └── 4-vulnerability.html │ ├── chapter07.html │ ├── chapter08/ │ │ ├── 1-loading-performance.html │ │ └── 2-rendering-performance.html │ ├── chapter08.html │ ├── chapter09/ │ │ ├── 1-search-engine-index.html │ │ ├── 2-pwa-and-amp-and-mip.html │ │ ├── 3-whole-site-amp-and-mip.html │ │ └── 4-preload-pwa.html │ ├── chapter09.html │ ├── ci.yml │ ├── gitbook/ │ │ ├── fonts/ │ │ │ └── fontawesome/ │ │ │ └── FontAwesome.otf │ │ ├── gitbook-plugin-3-ba/ │ │ │ └── plugin.js │ │ ├── gitbook-plugin-ace/ │ │ │ ├── ace/ │ │ │ │ ├── ace.js │ │ │ │ ├── ext-beautify.js │ │ │ │ ├── ext-chromevox.js │ │ │ │ ├── ext-elastic_tabstops_lite.js │ │ │ │ ├── ext-emmet.js │ │ │ │ ├── ext-error_marker.js │ │ │ │ ├── ext-keybinding_menu.js │ │ │ │ ├── ext-language_tools.js │ │ │ │ ├── ext-linking.js │ │ │ │ ├── ext-modelist.js │ │ │ │ ├── ext-old_ie.js │ │ │ │ ├── ext-searchbox.js │ │ │ │ ├── ext-settings_menu.js │ │ │ │ ├── ext-spellcheck.js │ │ │ │ ├── ext-split.js │ │ │ │ ├── ext-static_highlight.js │ │ │ │ ├── ext-statusbar.js │ │ │ │ ├── ext-textarea.js │ │ │ │ ├── ext-themelist.js │ │ │ │ ├── ext-whitespace.js │ │ │ │ ├── keybinding-emacs.js │ │ │ │ ├── keybinding-vim.js │ │ │ │ ├── mode-abap.js │ │ │ │ ├── mode-abc.js │ │ │ │ ├── mode-actionscript.js │ │ │ │ ├── mode-ada.js │ │ │ │ ├── mode-apache_conf.js │ │ │ │ ├── mode-applescript.js │ │ │ │ ├── mode-asciidoc.js │ │ │ │ ├── mode-assembly_x86.js │ │ │ │ ├── mode-autohotkey.js │ │ │ │ ├── mode-batchfile.js │ │ │ │ ├── mode-c9search.js │ │ │ │ ├── mode-c_cpp.js │ │ │ │ ├── mode-cirru.js │ │ │ │ ├── mode-clojure.js │ │ │ │ ├── mode-cobol.js │ │ │ │ ├── mode-coffee.js │ │ │ │ ├── mode-coldfusion.js │ │ │ │ ├── mode-csharp.js │ │ │ │ ├── mode-css.js │ │ │ │ ├── mode-curly.js │ │ │ │ ├── mode-d.js │ │ │ │ ├── mode-dart.js │ │ │ │ ├── mode-diff.js │ │ │ │ ├── mode-django.js │ │ │ │ ├── mode-dockerfile.js │ │ │ │ ├── mode-dot.js │ │ │ │ ├── mode-eiffel.js │ │ │ │ ├── mode-ejs.js │ │ │ │ ├── mode-elixir.js │ │ │ │ ├── mode-elm.js │ │ │ │ ├── mode-erlang.js │ │ │ │ ├── mode-forth.js │ │ │ │ ├── mode-ftl.js │ │ │ │ ├── mode-gcode.js │ │ │ │ ├── mode-gherkin.js │ │ │ │ ├── mode-gitignore.js │ │ │ │ ├── mode-glsl.js │ │ │ │ ├── mode-golang.js │ │ │ │ ├── mode-groovy.js │ │ │ │ ├── mode-haml.js │ │ │ │ ├── mode-handlebars.js │ │ │ │ ├── mode-haskell.js │ │ │ │ ├── mode-haxe.js │ │ │ │ ├── mode-html.js │ │ │ │ ├── mode-html_elixir.js │ │ │ │ ├── mode-html_ruby.js │ │ │ │ ├── mode-ini.js │ │ │ │ ├── mode-io.js │ │ │ │ ├── mode-jack.js │ │ │ │ ├── mode-jade.js │ │ │ │ ├── mode-java.js │ │ │ │ ├── mode-javascript.js │ │ │ │ ├── mode-json.js │ │ │ │ ├── mode-jsoniq.js │ │ │ │ ├── mode-jsp.js │ │ │ │ ├── mode-jsx.js │ │ │ │ ├── mode-julia.js │ │ │ │ ├── mode-latex.js │ │ │ │ ├── mode-lean.js │ │ │ │ ├── mode-less.js │ │ │ │ ├── mode-liquid.js │ │ │ │ ├── mode-lisp.js │ │ │ │ ├── mode-live_script.js │ │ │ │ ├── mode-livescript.js │ │ │ │ ├── mode-logiql.js │ │ │ │ ├── mode-lsl.js │ │ │ │ ├── mode-lua.js │ │ │ │ ├── mode-luapage.js │ │ │ │ ├── mode-lucene.js │ │ │ │ ├── mode-makefile.js │ │ │ │ ├── mode-markdown.js │ │ │ │ ├── mode-mask.js │ │ │ │ ├── mode-matlab.js │ │ │ │ ├── mode-maze.js │ │ │ │ ├── mode-mel.js │ │ │ │ ├── mode-mips_assembler.js │ │ │ │ ├── mode-mipsassembler.js │ │ │ │ ├── mode-mushcode.js │ │ │ │ ├── mode-mysql.js │ │ │ │ ├── mode-nix.js │ │ │ │ ├── mode-objectivec.js │ │ │ │ ├── mode-ocaml.js │ │ │ │ ├── mode-pascal.js │ │ │ │ ├── mode-perl.js │ │ │ │ ├── mode-pgsql.js │ │ │ │ ├── mode-php.js │ │ │ │ ├── mode-plain_text.js │ │ │ │ ├── mode-powershell.js │ │ │ │ ├── mode-praat.js │ │ │ │ ├── mode-prolog.js │ │ │ │ ├── mode-properties.js │ │ │ │ ├── mode-protobuf.js │ │ │ │ ├── mode-python.js │ │ │ │ ├── mode-r.js │ │ │ │ ├── mode-rdoc.js │ │ │ │ ├── mode-rhtml.js │ │ │ │ ├── mode-ruby.js │ │ │ │ ├── mode-rust.js │ │ │ │ ├── mode-sass.js │ │ │ │ ├── mode-scad.js │ │ │ │ ├── mode-scala.js │ │ │ │ ├── mode-scheme.js │ │ │ │ ├── mode-scss.js │ │ │ │ ├── mode-sh.js │ │ │ │ ├── mode-sjs.js │ │ │ │ ├── mode-smarty.js │ │ │ │ ├── mode-snippets.js │ │ │ │ ├── mode-soy_template.js │ │ │ │ ├── mode-space.js │ │ │ │ ├── mode-sql.js │ │ │ │ ├── mode-sqlserver.js │ │ │ │ ├── mode-stylus.js │ │ │ │ ├── mode-svg.js │ │ │ │ ├── mode-swift.js │ │ │ │ ├── mode-swig.js │ │ │ │ ├── mode-tcl.js │ │ │ │ ├── mode-tex.js │ │ │ │ ├── mode-text.js │ │ │ │ ├── mode-textile.js │ │ │ │ ├── mode-toml.js │ │ │ │ ├── mode-twig.js │ │ │ │ ├── mode-typescript.js │ │ │ │ ├── mode-vala.js │ │ │ │ ├── mode-vbscript.js │ │ │ │ ├── mode-velocity.js │ │ │ │ ├── mode-verilog.js │ │ │ │ ├── mode-vhdl.js │ │ │ │ ├── mode-xml.js │ │ │ │ ├── mode-xquery.js │ │ │ │ ├── mode-yaml.js │ │ │ │ ├── snippets/ │ │ │ │ │ ├── abap.js │ │ │ │ │ ├── abc.js │ │ │ │ │ ├── actionscript.js │ │ │ │ │ ├── ada.js │ │ │ │ │ ├── apache_conf.js │ │ │ │ │ ├── applescript.js │ │ │ │ │ ├── asciidoc.js │ │ │ │ │ ├── assembly_x86.js │ │ │ │ │ ├── autohotkey.js │ │ │ │ │ ├── batchfile.js │ │ │ │ │ ├── c9search.js │ │ │ │ │ ├── c_cpp.js │ │ │ │ │ ├── cirru.js │ │ │ │ │ ├── clojure.js │ │ │ │ │ ├── cobol.js │ │ │ │ │ ├── coffee.js │ │ │ │ │ ├── coldfusion.js │ │ │ │ │ ├── csharp.js │ │ │ │ │ ├── css.js │ │ │ │ │ ├── curly.js │ │ │ │ │ ├── d.js │ │ │ │ │ ├── dart.js │ │ │ │ │ ├── diff.js │ │ │ │ │ ├── django.js │ │ │ │ │ ├── dockerfile.js │ │ │ │ │ ├── dot.js │ │ │ │ │ ├── eiffel.js │ │ │ │ │ ├── ejs.js │ │ │ │ │ ├── elixir.js │ │ │ │ │ ├── elm.js │ │ │ │ │ ├── erlang.js │ │ │ │ │ ├── forth.js │ │ │ │ │ ├── ftl.js │ │ │ │ │ ├── gcode.js │ │ │ │ │ ├── gherkin.js │ │ │ │ │ ├── gitignore.js │ │ │ │ │ ├── glsl.js │ │ │ │ │ ├── golang.js │ │ │ │ │ ├── groovy.js │ │ │ │ │ ├── haml.js │ │ │ │ │ ├── handlebars.js │ │ │ │ │ ├── haskell.js │ │ │ │ │ ├── haxe.js │ │ │ │ │ ├── html.js │ │ │ │ │ ├── html_elixir.js │ │ │ │ │ ├── html_ruby.js │ │ │ │ │ ├── ini.js │ │ │ │ │ ├── io.js │ │ │ │ │ ├── jack.js │ │ │ │ │ ├── jade.js │ │ │ │ │ ├── java.js │ │ │ │ │ ├── javascript.js │ │ │ │ │ ├── json.js │ │ │ │ │ ├── jsoniq.js │ │ │ │ │ ├── jsp.js │ │ │ │ │ ├── jsx.js │ │ │ │ │ ├── julia.js │ │ │ │ │ ├── latex.js │ │ │ │ │ ├── lean.js │ │ │ │ │ ├── less.js │ │ │ │ │ ├── liquid.js │ │ │ │ │ ├── lisp.js │ │ │ │ │ ├── live_script.js │ │ │ │ │ ├── livescript.js │ │ │ │ │ ├── logiql.js │ │ │ │ │ ├── lsl.js │ │ │ │ │ ├── lua.js │ │ │ │ │ ├── luapage.js │ │ │ │ │ ├── lucene.js │ │ │ │ │ ├── makefile.js │ │ │ │ │ ├── markdown.js │ │ │ │ │ ├── mask.js │ │ │ │ │ ├── matlab.js │ │ │ │ │ ├── maze.js │ │ │ │ │ ├── mel.js │ │ │ │ │ ├── mips_assembler.js │ │ │ │ │ ├── mipsassembler.js │ │ │ │ │ ├── mushcode.js │ │ │ │ │ ├── mysql.js │ │ │ │ │ ├── nix.js │ │ │ │ │ ├── objectivec.js │ │ │ │ │ ├── ocaml.js │ │ │ │ │ ├── pascal.js │ │ │ │ │ ├── perl.js │ │ │ │ │ ├── pgsql.js │ │ │ │ │ ├── php.js │ │ │ │ │ ├── plain_text.js │ │ │ │ │ ├── powershell.js │ │ │ │ │ ├── praat.js │ │ │ │ │ ├── prolog.js │ │ │ │ │ ├── properties.js │ │ │ │ │ ├── protobuf.js │ │ │ │ │ ├── python.js │ │ │ │ │ ├── r.js │ │ │ │ │ ├── rdoc.js │ │ │ │ │ ├── rhtml.js │ │ │ │ │ ├── ruby.js │ │ │ │ │ ├── rust.js │ │ │ │ │ ├── sass.js │ │ │ │ │ ├── scad.js │ │ │ │ │ ├── scala.js │ │ │ │ │ ├── scheme.js │ │ │ │ │ ├── scss.js │ │ │ │ │ ├── sh.js │ │ │ │ │ ├── sjs.js │ │ │ │ │ ├── smarty.js │ │ │ │ │ ├── snippets.js │ │ │ │ │ ├── soy_template.js │ │ │ │ │ ├── space.js │ │ │ │ │ ├── sql.js │ │ │ │ │ ├── sqlserver.js │ │ │ │ │ ├── stylus.js │ │ │ │ │ ├── svg.js │ │ │ │ │ ├── swift.js │ │ │ │ │ ├── swig.js │ │ │ │ │ ├── tcl.js │ │ │ │ │ ├── tex.js │ │ │ │ │ ├── text.js │ │ │ │ │ ├── textile.js │ │ │ │ │ ├── toml.js │ │ │ │ │ ├── twig.js │ │ │ │ │ ├── typescript.js │ │ │ │ │ ├── vala.js │ │ │ │ │ ├── vbscript.js │ │ │ │ │ ├── velocity.js │ │ │ │ │ ├── verilog.js │ │ │ │ │ ├── vhdl.js │ │ │ │ │ ├── xml.js │ │ │ │ │ ├── xquery.js │ │ │ │ │ └── yaml.js │ │ │ │ ├── theme-ambiance.js │ │ │ │ ├── theme-chaos.js │ │ │ │ ├── theme-chrome.js │ │ │ │ ├── theme-clouds.js │ │ │ │ ├── theme-clouds_midnight.js │ │ │ │ ├── theme-cobalt.js │ │ │ │ ├── theme-crimson_editor.js │ │ │ │ ├── theme-dawn.js │ │ │ │ ├── theme-dreamweaver.js │ │ │ │ ├── theme-eclipse.js │ │ │ │ ├── theme-github.js │ │ │ │ ├── theme-idle_fingers.js │ │ │ │ ├── theme-iplastic.js │ │ │ │ ├── theme-katzenmilch.js │ │ │ │ ├── theme-kr_theme.js │ │ │ │ ├── theme-kuroir.js │ │ │ │ ├── theme-merbivore.js │ │ │ │ ├── theme-merbivore_soft.js │ │ │ │ ├── theme-mono_industrial.js │ │ │ │ ├── theme-monokai.js │ │ │ │ ├── theme-pastel_on_dark.js │ │ │ │ ├── theme-solarized_dark.js │ │ │ │ ├── theme-solarized_light.js │ │ │ │ ├── theme-sqlserver.js │ │ │ │ ├── theme-terminal.js │ │ │ │ ├── theme-textmate.js │ │ │ │ ├── theme-tomorrow.js │ │ │ │ ├── theme-tomorrow_night.js │ │ │ │ ├── theme-tomorrow_night_blue.js │ │ │ │ ├── theme-tomorrow_night_bright.js │ │ │ │ ├── theme-tomorrow_night_eighties.js │ │ │ │ ├── theme-twilight.js │ │ │ │ ├── theme-vibrant_ink.js │ │ │ │ ├── theme-xcode.js │ │ │ │ ├── worker-coffee.js │ │ │ │ ├── worker-css.js │ │ │ │ ├── worker-html.js │ │ │ │ ├── worker-javascript.js │ │ │ │ ├── worker-json.js │ │ │ │ ├── worker-lua.js │ │ │ │ ├── worker-php.js │ │ │ │ ├── worker-xml.js │ │ │ │ └── worker-xquery.js │ │ │ ├── ace.css │ │ │ ├── ace.js │ │ │ └── pdf.css │ │ ├── gitbook-plugin-advanced-emoji/ │ │ │ ├── LICENSE-IMAGES.md │ │ │ ├── LICENSE.md │ │ │ ├── emoji-book.css │ │ │ └── emoji-website.css │ │ ├── gitbook-plugin-anchor-navigation-ex/ │ │ │ ├── lib/ │ │ │ │ ├── config.js │ │ │ │ ├── log.js │ │ │ │ └── plugin.js │ │ │ └── style/ │ │ │ └── plugin.css │ │ ├── gitbook-plugin-anchors/ │ │ │ └── plugin.css │ │ ├── gitbook-plugin-chart/ │ │ │ └── highcharts/ │ │ │ └── highcharts.js │ │ ├── gitbook-plugin-disqus/ │ │ │ ├── plugin.css │ │ │ └── plugin.js │ │ ├── gitbook-plugin-edit-link/ │ │ │ └── plugin.js │ │ ├── gitbook-plugin-emphasize/ │ │ │ └── plugin.css │ │ ├── gitbook-plugin-expandable-chapters-small/ │ │ │ ├── expandable-chapters-small.css │ │ │ └── expandable-chapters-small.js │ │ ├── gitbook-plugin-fontsettings/ │ │ │ ├── fontsettings.js │ │ │ └── website.css │ │ ├── gitbook-plugin-github/ │ │ │ └── plugin.js │ │ ├── gitbook-plugin-page-footer-ex/ │ │ │ ├── lib/ │ │ │ │ └── plugin.js │ │ │ └── style/ │ │ │ └── plugin.css │ │ ├── gitbook-plugin-prism/ │ │ │ ├── prism-coy.css │ │ │ ├── prism-dark.css │ │ │ ├── prism-funky.css │ │ │ ├── prism-okaidia.css │ │ │ ├── prism-solarizedlight.css │ │ │ ├── prism-tomorrow.css │ │ │ ├── prism-twilight.css │ │ │ └── prism.css │ │ ├── gitbook-plugin-search-plus/ │ │ │ ├── search.css │ │ │ └── search.js │ │ ├── gitbook-plugin-sectionx/ │ │ │ ├── sectionx.css │ │ │ └── sectionx.js │ │ ├── gitbook-plugin-sharing-plus/ │ │ │ └── buttons.js │ │ ├── gitbook-plugin-splitter/ │ │ │ ├── splitter.css │ │ │ └── splitter.js │ │ ├── gitbook.js │ │ ├── style.css │ │ └── theme.js │ ├── index.html │ ├── package.json │ ├── scripts/ │ │ └── build.sh │ ├── search_plus_index.json │ └── thanks.html ├── package.json ├── scripts/ │ └── build.sh └── thanks.md
Showing preview only (248K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (2637 symbols across 80 files)
FILE: chapter04/code/serviceWorkerIndexeddbDemo/sw.js
function createDB (line 13) | function createDB () {
function readDB (line 58) | function readDB () {
FILE: docs/chapter04/code/serviceWorkerIndexeddbDemo/sw.js
function createDB (line 13) | function createDB () {
function readDB (line 58) | function readDB () {
FILE: docs/gitbook/gitbook-plugin-ace/ace/ace.js
function o (line 1) | function o(n){var i=e;n&&(e[n]||(e[n]={}),i=e[n]);if(!i.define||!i.defin...
function o (line 1) | function o(e){return(e.global?"g":"")+(e.ignoreCase?"i":"")+(e.multiline...
function u (line 1) | function u(e,t,n){if(Array.prototype.indexOf)return e.indexOf(t,n);for(v...
function r (line 1) | function r(){}
function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
function a (line 1) | function a(e,t,n){var a=u(t);if(!i.isMac&&s){s.OSKey&&(a|=8);if(s.altGr)...
function f (line 1) | function f(){s=Object.create(null),s.count=0,s.lastT=0}
function i (line 1) | function i(e){n&&n(e),r&&r(e),t.removeListener(document,"mousemove",n,!0...
function b (line 1) | function b(e){if(h)return;h=!0;if(k)t=0,r=e?0:n.value.length-1;else var ...
function w (line 1) | function w(){if(h)return;n.value=f,i.isWebKit&&y.schedule()}
function R (line 1) | function R(){clearTimeout(q),q=setTimeout(function(){p&&(n.style.cssText...
function u (line 1) | function u(e){e.$clickSelection=null;var t=e.editor;t.setDefaultHandler(...
function a (line 1) | function a(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}
function f (line 1) | function f(e,t){if(e.start.row==e.end.row)var n=2*t.column-e.start.colum...
function s (line 1) | function s(e){this.isOpen=!1,this.$element=null,this.$parentNode=e}
function u (line 1) | function u(e){function l(){var r=u.getDocumentPosition().row,s=n.$annota...
function a (line 1) | function a(e){o.call(this,e)}
function f (line 1) | function f(e){function T(e,n){var r=Date.now(),i=!n||e.row!=n.row,s=!n||...
function l (line 1) | function l(e,t,n,r){return Math.sqrt(Math.pow(n-e,2)+Math.pow(r-t,2))}
function o (line 1) | function o(e){typeof console!="undefined"&&console.warn&&console.warn.ap...
function u (line 1) | function u(e,t){var n=new Error(e);n.data=t,typeof console=="object"&&co...
function f (line 1) | function f(r){a.packaged=r||e.packaged||n.packaged||u.define&&define.pac...
function l (line 1) | function l(e){return e.replace(/-(.)/g,function(e,t){return t.toUpperCas...
function r (line 1) | function r(e){e.on("click",function(t){var n=t.getDocumentPosition(),r=e...
function i (line 1) | function i(s){var o=r[s];o.processed=!0;for(var u=0;u<o.length;u++){var ...
function r (line 1) | function r(e){var n=/\w{4}/g;for(var r in e)t.packages[r]=e[r].replace(n...
function w (line 1) | function w(e){for(var t=n;t<=r;t++)e(i.getLine(t),t)}
function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
function i (line 1) | function i(e,t){this.foldData=e,Array.isArray(t)?this.folds=t:t=this.fol...
function u (line 1) | function u(e,t){e.row-=t.row,e.row==0&&(e.column-=t.column)}
function a (line 1) | function a(e,t){u(e.start,t),u(e.end,t)}
function f (line 1) | function f(e,t){e.row==0&&(e.column+=t.column),e.row+=t.row}
function l (line 1) | function l(e,t){f(e.start,t),f(e.end,t)}
function u (line 1) | function u(){this.getFoldAt=function(e,t,n){var r=this.getFoldLine(e);if...
function s (line 1) | function s(){this.findMatchingBracket=function(e,t){if(e.column==0)retur...
function m (line 1) | function m(e){return e<4352?!1:e>=4352&&e<=4447||e>=4515&&e<=4519||e>=46...
function r (line 1) | function r(e){return t?e.action!=="insert":e.action==="insert"}
function g (line 1) | function g(){var t=0;if(m===0)return t;if(h)for(var n=0;n<e.length;n++){...
function y (line 1) | function y(t){var n=e.slice(a,t),r=n.length;n.join("").replace(/12/g,fun...
function o (line 1) | function o(e,t){this.platform=t||(i.isMac?"mac":"win"),this.commands={},...
function u (line 1) | function u(e,t){o.call(this,e,t),this.$singleCommand=!1}
function e (line 1) | function e(e){return typeof e=="object"&&e.bindKey&&e.bindKey.position||0}
function o (line 1) | function o(e,t){return{win:e,mac:t}}
function e (line 1) | function e(e){return e[e.length-1]}
function e (line 1) | function e(e){return{action:e.action,start:e.start,end:e.end,lines:e.lin...
function t (line 1) | function t(e){return{action:e.action,start:e.start,end:e.end,lines:e.lin...
function n (line 1) | function n(e,t){var n=new Array(e.length);for(var r=0;r<e.length;r++){va...
function e (line 1) | function e(e,t,n,r){return(e?1:0)|(t?2:0)|(n?4:0)|(r?8:0)}
function i (line 1) | function i(e,t,n){var i=0,s=0;while(s+e[i].value.length<t){s+=e[i].value...
function o (line 1) | function o(r){if(n.$themeId!=e)return t&&t();if(!r.cssClass)return;i.imp...
function s (line 1) | function s(e,t){return e.row==t.row&&e.column==t.column}
function o (line 1) | function o(e){var t=e.domEvent,n=t.altKey,o=t.shiftKey,u=t.ctrlKey,a=e.g...
function h (line 1) | function h(e,t,n){return c.$options.wrap=!0,c.$options.needle=t,c.$optio...
function v (line 1) | function v(e,t){return e.row==t.row&&e.column==t.column}
function m (line 1) | function m(e){if(e.$multiselectOnSessionChange)return;e.$onAddRange=e.$o...
function g (line 1) | function g(e){function r(t){n&&(e.renderer.setMouseCursor(""),n=!1)}var ...
function u (line 1) | function u(e){return a.stringRepeat(" ",e)}
function f (line 1) | function f(e){return e[2]?u(i)+e[2]+u(s-e[2].length+o)+e[4].replace(/^([...
function l (line 1) | function l(e){return e[2]?u(i+s-e[2].length)+e[2]+u(o," ")+e[4].replace(...
function c (line 1) | function c(e){return e[2]?u(i)+e[2]+u(o)+e[4].replace(/^([=:])\s+/,"$1 "...
function o (line 1) | function o(e){this.session=e,this.session.widgetManager=this,this.sessio...
function o (line 1) | function o(e,t,n){var r=0,i=e.length-1;while(r<=i){var s=r+i>>1,o=n(t,e[...
function u (line 1) | function u(e,t,n){var r=e.getAnnotations().sort(s.comparePoints);if(!r.l...
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-chromevox.js
function gt (line 1) | function gt(){return typeof cvox!="undefined"&&cvox&&cvox.Api}
function wt (line 1) | function wt(e){if(gt())mt(e);else{yt++;if(yt>=bt)return;window.setTimeou...
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-emmet.js
function e (line 1) | function e(e,t,n){return e=e.substr(1),/^\d+$/.test(e)&&!n.inFormatStrin...
function t (line 1) | function t(e){return"(?:[^\\\\"+e+"]|\\\\.)"}
function o (line 1) | function o(t){var n=e.indexOf(t,r+1);n!=-1&&(r=n)}
function f (line 1) | function f(e){var t=[];for(var n=0;n<e.length;n++){var r=e[n];if(typeof ...
function o (line 1) | function o(e){return e&&!/^\^?\(.*\)\$?$|^\\b$/.test(e)&&(e="(?:"+e+")")...
function u (line 1) | function u(e,t,n){return e=o(e),t=o(t),n?(e=t+e,e&&e[e.length-1]!="$"&&(...
function a (line 1) | function a(e){e.scope||(e.scope=t||"_"),t=e.scope,n[t]||(n[t]=[],r[t]={}...
function i (line 1) | function i(e){var i=r[e.scope||t];if(i&&i[e.name]){delete i[e.name];var ...
function f (line 1) | function f(){}
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-keybinding_menu.js
function l (line 1) | function l(e){e.keyCode===27&&a.click()}
function i (line 1) | function i(t){if(!document.getElementById("kbshortcutmenu")){var n=e("./...
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-language_tools.js
function e (line 1) | function e(e,t,n){return e=e.substr(1),/^\d+$/.test(e)&&!n.inFormatStrin...
function t (line 1) | function t(e){return"(?:[^\\\\"+e+"]|\\\\.)"}
function o (line 1) | function o(t){var n=e.indexOf(t,r+1);n!=-1&&(r=n)}
function f (line 1) | function f(e){var t=[];for(var n=0;n<e.length;n++){var r=e[n];if(typeof ...
function o (line 1) | function o(e){return e&&!/^\^?\(.*\)\$?$|^\\b$/.test(e)&&(e="(?:"+e+")")...
function u (line 1) | function u(e,t,n){return e=o(e),t=o(t),n?(e=t+e,e&&e[e.length-1]!="$"&&(...
function a (line 1) | function a(e){e.scope||(e.scope=t||"_"),t=e.scope,n[t]||(n[t]=[],r[t]={}...
function i (line 1) | function i(e){var i=r[e.scope||t];if(i&&i[e.name]){delete i[e.name];var ...
function s (line 1) | function s(e,t){var n=e.getTextRange(r.fromPoints({row:0,column:0},t));r...
function o (line 1) | function o(e,t){var n=s(e,t),r=e.getValue().split(i),o=Object.create(nul...
function m (line 1) | function m(e){var t=e.getCursorPosition(),n=e.session.getLine(t.row),r;r...
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-linking.js
function i (line 1) | function i(e){var t=e.editor,n=e.getAccelKey();if(n){var t=e.editor,r=e....
function s (line 1) | function s(e){var t=e.getAccelKey(),n=e.getButton();if(n==0&&t){var r=e....
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-modelist.js
function i (line 1) | function i(e){var t=a.text,n=e.split(/[\/\\]/).pop();for(var i=0;i<r.len...
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-old_ie.js
function patch (line 1) | function patch(obj,name,regexp,replacement){eval("obj['"+name+"']="+obj[...
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-settings_menu.js
function i (line 1) | function i(e){var t=a.text,n=e.split(/[\/\\]/).pop();for(var i=0;i<r.len...
function u (line 1) | function u(){o.sort(function(e,t){var n=e.getAttribute("contains"),r=t.g...
function a (line 1) | function a(){var t=document.createElement("div");t.setAttribute("id","ac...
function f (line 1) | function f(e,t,i,s){var o,u=document.createElement("div");return u.setAt...
function l (line 1) | function l(e,t,r,i){var s=n.menuOptions[e],o=t[i]();return typeof o=="ob...
function c (line 1) | function c(e){var t=e.functionName,r=e.parentObj,i=e.parentName,s,u=t.re...
function l (line 1) | function l(e){e.keyCode===27&&a.click()}
function s (line 1) | function s(e){var t=document.getElementById("ace_settingsmenu");t||i(e,r...
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-split.js
function l (line 1) | function l(e,t){this.$u=e,this.$doc=t}
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-static_highlight.js
function h (line 1) | function h(){var r=f.renderSync(e,t,n,i,s);return u?u(r):r}
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-statusbar.js
function n (line 1) | function n(e,n){e&&t.push(e,n||"|")}
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-textarea.js
function a (line 1) | function a(e,t){for(var n in t)e.style[n]=t[n]}
function f (line 1) | function f(e,t){if(e.type!="textarea")throw new Error("Textarea required...
function l (line 1) | function l(t,n,r){s.loadScript(t,function(){e([n],r)})}
function c (line 1) | function c(e,t,n,r,i,s){function a(e){return e==="true"||e==1}var o=e.ge...
function h (line 1) | function h(e,n,i){function f(e,t,n,r){if(!n){e.push("<input type='checkb...
FILE: docs/gitbook/gitbook-plugin-ace/ace/ext-whitespace.js
function c (line 1) | function c(e){var t=0;for(var r=e;r<n.length;r+=e)t+=n[r]||0;return t}
FILE: docs/gitbook/gitbook-plugin-ace/ace/keybinding-emacs.js
function a (line 1) | function a(){}
function f (line 1) | function f(){}
function u (line 1) | function u(e){this.$iSearch=e}
function f (line 1) | function f(){this.$options={wrap:!1,skipCurrent:!1},this.$keyboardHandle...
function l (line 1) | function l(e){return e instanceof RegExp}
function c (line 1) | function c(e){var t=String(e),n=t.indexOf("/"),r=t.lastIndexOf("/");retu...
function h (line 1) | function h(e,t){try{return new RegExp(e,t)}catch(n){return e}}
function p (line 1) | function p(e){return h(e.expression,e.flags)}
function u (line 1) | function u(){var t=e.popEmacsMark();t&&e.moveCursorToPosition(t)}
function t (line 1) | function t(){var t=e.selection,n=t.getRange(),i=t.isBackwards()?n.end:n....
FILE: docs/gitbook/gitbook-plugin-ace/ace/keybinding-vim.js
function r (line 1) | function r(){function t(e){return typeof e!="object"?e+"":"line"in e?e.l...
function m (line 1) | function m(e){return{row:e.line,column:e.ch}}
function g (line 1) | function g(e){return new E(e.row,e.column)}
function x (line 1) | function x(e){e.setOption("disableInput",!0),e.setOption("showCursorWhen...
function T (line 1) | function T(e){e.setOption("disableInput",!1),e.off("cursorActivity",Jn),...
function N (line 1) | function N(e,t){this==v.keyMap.vim&&v.rmClass(e.getWrapperElement(),"cm-...
function C (line 1) | function C(e,t){this==v.keyMap.vim&&v.addClass(e.getWrapperElement(),"cm...
function k (line 1) | function k(e,t){if(!t)return undefined;var n=O(e);if(!n)return!1;var r=v...
function O (line 1) | function O(e){if(e.charAt(0)=="'")return e.charAt(1);var t=e.split("-");...
function M (line 1) | function M(e){var t=e.state.vim;return t.onPasteFn||(t.onPasteFn=functio...
function H (line 1) | function H(e,t){var n=[];for(var r=e;r<e+t;r++)n.push(String.fromCharCod...
function R (line 1) | function R(e,t){return t>=e.firstLine()&&t<=e.lastLine()}
function U (line 1) | function U(e){return/^[a-z]$/.test(e)}
function z (line 1) | function z(e){return"()[]{}".indexOf(e)!=-1}
function W (line 1) | function W(e){return _.test(e)}
function X (line 1) | function X(e){return/^[A-Z]$/.test(e)}
function V (line 1) | function V(e){return/^\s*$/.test(e)}
function $ (line 1) | function $(e,t){for(var n=0;n<t.length;n++)if(t[n]==e)return!0;return!1}
function K (line 1) | function K(e,t,n,r,i){if(t===undefined&&!i)throw Error("defaultValue is ...
function Q (line 1) | function Q(e,t,n,r){var i=J[e];r=r||{};var s=r.scope;if(!i)throw Error("...
function G (line 1) | function G(e,t,n){var r=J[e];n=n||{};var i=n.scope;if(!r)throw Error("Un...
function et (line 1) | function et(){this.latestRegister=undefined,this.isPlaying=!1,this.isRec...
function tt (line 1) | function tt(e){return e.state.vim||(e.state.vim={inputState:new ot,lastE...
function rt (line 1) | function rt(){nt={searchQuery:null,searchIsReversed:!1,lastSubstituteRep...
function ot (line 1) | function ot(){this.prefixRepeat=[],this.motionRepeat=[],this.operator=nu...
function ut (line 1) | function ut(e,t){e.state.vim.inputState=new ot,v.signal(e,"vim-command-d...
function at (line 1) | function at(e,t,n){this.clear(),this.keyBuffer=[e||""],this.insertModeCh...
function ft (line 1) | function ft(e,t){var n=nt.registerController.registers[e];if(!e||e.lengt...
function lt (line 1) | function lt(e){this.registers=e,this.unnamedRegister=e['"']=new at,e["."...
function ct (line 1) | function ct(){this.historyBuffer=[],this.iterator,this.initialPrefix=null}
function dt (line 1) | function dt(e,t){pt[e]=t}
function vt (line 1) | function vt(e,t){var n=[];for(var r=0;r<t;r++)n.push(e);return n}
function gt (line 1) | function gt(e,t){mt[e]=t}
function bt (line 1) | function bt(e,t){yt[e]=t}
function wt (line 1) | function wt(e,t,n){var r=Math.min(Math.max(e.firstLine(),t.line),e.lastL...
function Et (line 1) | function Et(e){var t={};for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);...
function St (line 1) | function St(e,t,n){return typeof t=="object"&&(n=t.ch,t=t.line),E(e.line...
function xt (line 1) | function xt(e,t){return{line:t.line-e.line,ch:t.line-e.line}}
function Tt (line 1) | function Tt(e,t,n,r){var i,s=[],o=[];for(var u=0;u<t.length;u++){var a=t...
function Nt (line 1) | function Nt(e,t){if(t.slice(-11)=="<character>"){var n=t.length-11,r=e.s...
function Ct (line 1) | function Ct(e){var t=/^.*(<[\w\-]+>)$/.exec(e),n=t?t[1]:e.slice(-1);if(n...
function kt (line 1) | function kt(e,t,n){return function(){for(var r=0;r<n;r++)t(e)}}
function Lt (line 1) | function Lt(e){return E(e.line,e.ch)}
function At (line 1) | function At(e,t){return e.ch==t.ch&&e.line==t.line}
function Ot (line 1) | function Ot(e,t){return e.line<t.line?!0:e.line==t.line&&e.ch<t.ch?!0:!1}
function Mt (line 1) | function Mt(e,t){return arguments.length>2&&(t=Mt.apply(undefined,Array....
function _t (line 1) | function _t(e,t){return arguments.length>2&&(t=_t.apply(undefined,Array....
function Dt (line 1) | function Dt(e,t,n){var r=Ot(e,t),i=Ot(t,n);return r&&i}
function Pt (line 1) | function Pt(e,t){return e.getLine(t).length}
function Ht (line 1) | function Ht(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}
function Bt (line 1) | function Bt(e){return e.replace(/([.?*+$\[\]\/\\(){}|\-])/g,"\\$1")}
function jt (line 1) | function jt(e,t,n){var r=Pt(e,t),i=(new Array(n-r+1)).join(" ");e.setCur...
function Ft (line 1) | function Ft(e,t){var n=[],r=e.listSelections(),i=Lt(e.clipPos(t)),s=!At(...
function It (line 1) | function It(e,t,n){var r=[];for(var i=0;i<n;i++){var s=St(t,i,0);r.push(...
function qt (line 1) | function qt(e,t,n){for(var r=0;r<e.length;r++){var i=n!="head"&&At(e[r]....
function Rt (line 1) | function Rt(e,t){var n=t.lastSelection,r=function(){var t=e.listSelectio...
function Ut (line 1) | function Ut(e,t){var n=t.sel.anchor,r=t.sel.head;t.lastPastedText&&(r=e....
function zt (line 1) | function zt(e,t,n){var r=e.state.vim.sel,i=r.head,s=r.anchor,o;return Ot...
function Wt (line 1) | function Wt(e,t,n){var r=e.state.vim;t=t||r.sel;var n=n||r.visualLine?"l...
function Xt (line 1) | function Xt(e,t,n,r){var i=Lt(t.head),s=Lt(t.anchor);if(n=="char"){var o...
function Vt (line 1) | function Vt(e){var t=e.getCursor("head");return e.getSelection().length=...
function $t (line 1) | function $t(e,t){var n=e.state.vim;t!==!1&&e.setCursor(wt(e,n.sel.head))...
function Jt (line 1) | function Jt(e,t,n){var r=e.getRange(t,n);if(/\n\s*$/.test(r)){var i=r.sp...
function Kt (line 1) | function Kt(e,t,n){t.ch=0,n.ch=0,n.line++}
function Qt (line 1) | function Qt(e){if(!e)return 0;var t=e.search(/\S/);return t==-1?e.length:t}
function Gt (line 1) | function Gt(e,t,n,r,i){var s=Vt(e),o=e.getLine(s.line),u=s.ch,a=i?D[0]:P...
function Yt (line 1) | function Yt(e,t,n){At(t,n)||nt.jumpList.add(e,t,n)}
function Zt (line 1) | function Zt(e,t){nt.lastChararacterSearch.increment=e,nt.lastChararacter...
function nn (line 1) | function nn(e,t,n,r){var i=Lt(e.getCursor()),s=n?1:-1,o=n?e.lineCount():...
function rn (line 1) | function rn(e,t,n,r,i){var s=t.line,o=t.ch,u=e.getLine(s),a=n?1:-1,f=r?P...
function sn (line 1) | function sn(e,t,n,r,i,s){var o=Lt(t),u=[];(r&&!i||!r&&i)&&n++;var a=!r||...
function on (line 1) | function on(e,t,n,r){var i=e.getCursor(),s=i.ch,o;for(var u=0;u<t;u++){v...
function un (line 1) | function un(e,t){var n=e.getCursor().line;return wt(e,E(n,t-1))}
function an (line 1) | function an(e,t,n,r){if(!$(n,I))return;t.marks[n]&&t.marks[n].clear(),t....
function fn (line 1) | function fn(e,t,n,r,i){var s;return r?(s=t.indexOf(n,e+1),s!=-1&&!i&&(s-...
function ln (line 1) | function ln(e,t,n,r,i){function c(t){return!/\S/.test(e.getLine(t))}func...
function cn (line 1) | function cn(e,t,n,r){var i=t,s,o,u={"(":/[()]/,")":/[()]/,"[":/[[\]]/,"]...
function hn (line 1) | function hn(e,t,n,r){var i=Lt(t),s=e.getLine(i.line),o=s.split(""),u,a,f...
function pn (line 1) | function pn(){}
function dn (line 1) | function dn(e){var t=e.state.vim;return t.searchState_||(t.searchState_=...
function vn (line 1) | function vn(e,t,n,r,i){e.openDialog?e.openDialog(t,r,{bottom:!0,value:i....
function mn (line 1) | function mn(e){var t=gn(e)||[];if(!t.length)return[];var n=[];if(t[0]!==...
function gn (line 1) | function gn(e){var t=!1,n=[];for(var r=0;r<e.length;r++){var i=e.charAt(...
function yn (line 1) | function yn(e){var t="|(){",n="}",r=!1,i=[];for(var s=-1;s<e.length;s++)...
function wn (line 1) | function wn(e){var t=!1,n=[];for(var r=-1;r<e.length;r++){var i=e.charAt...
function Sn (line 1) | function Sn(e){var t=new v.StringStream(e),n=[];while(!t.eol()){while(t....
function xn (line 1) | function xn(e,t,n){var r=nt.registerController.getRegister("/");r.setTex...
function Tn (line 1) | function Tn(e,t){e.openNotification?e.openNotification('<span style="col...
function Nn (line 1) | function Nn(e,t){var n="";return e&&(n+='<span style="font-family: monos...
function kn (line 1) | function kn(e,t){var n=(t.prefix||"")+" "+(t.desc||""),r=Nn(t.prefix,t.d...
function Ln (line 1) | function Ln(e,t){if(e instanceof RegExp&&t instanceof RegExp){var n=["gl...
function An (line 1) | function An(e,t,n,r){if(!t)return;var i=dn(e),s=xn(t,!!n,!!r);if(!s)retu...
function On (line 1) | function On(e){if(e.source.charAt(0)=="^")var t=!0;return{token:function...
function Mn (line 1) | function Mn(e,t){var n=dn(e),r=n.getOverlay();if(!r||t!=r.query)r&&e.rem...
function _n (line 1) | function _n(e,t,n,r){return r===undefined&&(r=1),e.operation(function(){...
function Dn (line 1) | function Dn(e){var t=dn(e);e.removeOverlay(dn(e).getOverlay()),t.setOver...
function Pn (line 1) | function Pn(e,t,n){return typeof e!="number"&&(e=e.line),t instanceof Ar...
function Hn (line 1) | function Hn(e){var t=e.ace.renderer;return{top:t.getFirstFullyVisibleRow...
function In (line 1) | function In(e,t,n,r,i,s,o,u,a){function c(){e.operation(function(){while...
function qn (line 1) | function qn(e){var t=e.state.vim,n=nt.macroModeState,r=nt.registerContro...
function Rn (line 1) | function Rn(e){b.unshift(e)}
function Un (line 1) | function Un(e,t,n,r,i){var s={keys:e,type:t};s[t]=n,s[t+"Args"]=r;for(va...
function zn (line 1) | function zn(e,t,n,r){var i=nt.registerController.getRegister(r);if(r==":...
function Wn (line 1) | function Wn(e,t){if(e.isPlaying)return;var n=e.latestRegister,r=nt.regis...
function Xn (line 1) | function Xn(e){if(e.isPlaying)return;var t=e.latestRegister,n=nt.registe...
function Vn (line 1) | function Vn(e,t){if(e.isPlaying)return;var n=e.latestRegister,r=nt.regis...
function $n (line 1) | function $n(e,t){var n=nt.macroModeState,r=n.lastInsertModeChanges;if(!n...
function Jn (line 1) | function Jn(e){var t=e.state.vim;if(t.insertMode){var n=nt.macroModeStat...
function Kn (line 1) | function Kn(e){var t=e.state.vim,n=wt(e,Lt(t.sel.head)),r=St(n,0,1);t.fa...
function Qn (line 1) | function Qn(e,t){var n=e.getCursor("anchor"),r=e.getCursor("head");t.vis...
function Gn (line 1) | function Gn(e){this.keyName=e}
function Yn (line 1) | function Yn(e){function i(){return n.changes.push(new Gn(r)),!0}var t=nt...
function Zn (line 1) | function Zn(e,t,n,r){function u(){s?ht.processAction(e,t,t.lastEditActio...
function er (line 1) | function er(e,t,n){function r(t){return typeof t=="string"?v.commands[t]...
function nr (line 1) | function nr(e,t,n){t.length>1&&t[0]=="n"&&(t=t.replace("numpad","")),t=t...
function ir (line 1) | function ir(e){var t=new e.constructor;return Object.keys(e).forEach(fun...
function sr (line 1) | function sr(e,t,n){var r=!1,i=S.maybeInitVimState_(e),s=i.visualBlock||i...
function ar (line 1) | function ar(e,t){t.off("beforeEndOperation",ar);var n=t.state.cm.vimCmd;...
function e (line 1) | function e(e,t,n){var r=e.ace.container,i;return i=r.appendChild(documen...
function t (line 1) | function t(e,t){e.state.currentNotificationClose&&e.state.currentNotific...
function a (line 1) | function a(e){if(typeof e=="string")f.value=e;else{if(o)return;o=!0,s.pa...
function a (line 1) | function a(){if(s)return;s=!0,clearTimeout(o),i.parentNode.removeChild(i)}
function s (line 1) | function s(s,o,u){function l(n){var r=++t%e,o=i[r];o&&o.clear(),i[r]=s.s...
function o (line 1) | function o(s,o){t+=o,t>n?t=n:t<r&&(t=r);var u=i[(e+t)%e];if(u&&!u.find()...
function i (line 1) | function i(){var r=nt.macroModeState;if(r.isRecording){if(t=="q")return ...
function s (line 1) | function s(){if(t=="<Esc>")return ut(e),r.visualMode?$t(e):r.insertMode&...
function o (line 1) | function o(n){var r;while(n)r=/<\w+-.+?>|<\w+>|./.exec(n),t=r[0],n=n.sub...
function u (line 1) | function u(){if(s())return!0;var n=r.inputState.keyBuffer=r.inputState.k...
function a (line 1) | function a(){if(i()||s())return!0;var n=r.inputState.keyBuffer=r.inputSt...
function a (line 1) | function a(r,i,s){nt.searchHistoryController.pushInput(r),nt.searchHisto...
function f (line 1) | function f(t){e.scrollTo(u.left,u.top),a(t,!0,!0);var n=nt.macroModeStat...
function l (line 1) | function l(t,n,i){var s=v.keyName(t),o;s=="Up"||s=="Down"?(o=s=="Up"?!0:...
function c (line 1) | function c(t,n,r){var i=v.keyName(t);i=="Esc"||i=="Ctrl-C"||i=="Ctrl-["|...
function r (line 1) | function r(t){nt.exCommandHistoryController.pushInput(t),nt.exCommandHis...
function i (line 1) | function i(t,n,r){var i=v.keyName(t),s;if(i=="Esc"||i=="Ctrl-C"||i=="Ctr...
function o (line 1) | function o(){if(t.argString){var e=new v.StringStream(t.argString);e.eat...
function b (line 1) | function b(e,t){if(n){var i;i=e,e=t,t=i}r&&(e=e.toLowerCase(),t=t.toLowe...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-abap.js
function a (line 1) | function a(){this.HighlightRules=r,this.foldingRules=new i}
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-asciidoc.js
function t (line 1) | function t(e){var t=/\w/.test(e)?"\\b":"(?:\\B|^)";return t+e+"[^"+e+"]....
function l (line 1) | function l(t){return f=e.getTokens(t)[0],f&&f.type}
function d (line 1) | function d(){var t=f.value.match(p);if(t)return t[0].length;var r=c.inde...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-c9search.js
function o (line 1) | function o(e,t){try{return new RegExp(e,t)}catch(n){}}
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-coffee.js
function s (line 1) | function s(){var e="[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*",t="this...
function l (line 1) | function l(){this.HighlightRules=r,this.$outdent=new i,this.foldingRules...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-coldfusion.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-curly.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-django.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-ejs.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-ftl.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-groovy.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-handlebars.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function s (line 1) | function s(e,t){return t.splice(0,3),t.shift()||"start"}
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-html.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-html_elixir.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-html_ruby.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-jade.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function c (line 1) | function c(e,t){return{token:"support.function",regex:"^\\s*```"+e+"\\s*...
function s (line 1) | function s(){var e="[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*",t="this...
function l (line 1) | function l(e,t){return{token:"entity.name.function.jade",regex:"^\\s*\\:...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-java.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-javascript.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-jsoniq.js
function s (line 1) | function s(u,a){if(!n[u]){if(!t[u]){var f=typeof e=="function"&&e;if(!a&...
function r (line 1) | function r(e,t){E=t,S=e,x=e.length,s(0,0,0)}
function s (line 1) | function s(e,t,n){m=t,g=t,y=e,b=t,w=n,N=n,E.reset(S)}
function o (line 1) | function o(){E.startNonterminal("EQName",g);switch(y){case 80:f(80);brea...
function u (line 1) | function u(){E.startNonterminal("FunctionName",g);switch(y){case 17:f(17...
function a (line 1) | function a(){E.startNonterminal("NCName",g);switch(y){case 28:f(28);brea...
function f (line 1) | function f(e){y==e?(l(),E.terminal(i.TOKEN[y],b,w>x?x:w),m=b,g=w,y=0):d(...
function l (line 1) | function l(){g!=b&&(m=g,g=b,E.whitespace(m,g))}
function c (line 1) | function c(e){var t;for(;;){t=C(e);if(t!=30)break}return t}
function h (line 1) | function h(e){y==0&&(y=c(e),b=T,w=N)}
function p (line 1) | function p(e){y==0&&(y=C(e),b=T,w=N)}
function d (line 1) | function d(e,t,r,i,s){throw new n.ParseException(e,t,r,i,s)}
function C (line 1) | function C(e){var t=!1;T=N;var n=N,r=i.INITIAL[e],s=0;for(var o=r&4095;o...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function a (line 1) | function a(e,t){var n=!0,r=e.type.split("."),i=t.split(".");return i.for...
function u (line 1) | function u(i){r&&t.removeMarker(r),o.row=n.row;if(e.pos.sc!==undefined&&...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-jsp.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-jsx.js
function f (line 1) | function f(){this.HighlightRules=s,this.$outdent=new o,this.$behaviour=n...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-liquid.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-livescript.js
function u (line 1) | function u(e,t){function n(){}return n.prototype=(e.superclass=t).protot...
function a (line 1) | function a(e,t){var n={}.hasOwnProperty;for(var r in t)n.call(t,r)&&(e[r...
function o (line 1) | function o(){var t;this.$tokenizer=new(e("../tokenizer").Tokenizer)(o.Ru...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-lsl.js
function s (line 1) | function s(){var e=this.createKeywordMapper({"constant.language.float.ls...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-lua.js
function n (line 1) | function n(t){var n=0;for(var r=0;r<t.length;r++){var i=t[r];i.type=="ke...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-luapage.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function n (line 1) | function n(t){var n=0;for(var r=0;r<t.length;r++){var i=t[r];i.type=="ke...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-markdown.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){return{token:"support.function",regex:"^\\s*```"+e+"\\s*...
function l (line 1) | function l(t){return f=e.getTokens(t)[0],f&&f.type.lastIndexOf(c,0)===0}
function h (line 1) | function h(){var e=f.value[0];return e=="="?6:e=="-"?5:7-f.value.search(...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-mask.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function c (line 1) | function c(e,t){return{token:"support.function",regex:"^\\s*```"+e+"\\s*...
function N (line 1) | function N(){function t(e,t,n){var r="js-"+e+"-",i=e==="block"?["start"]...
function k (line 1) | function k(e,t,n){var r,i,s;return arguments.length===4?(r=n,i=arguments...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-mysql.js
function i (line 1) | function i(e){var t=e.start,n=e.escape;return{token:"string.start",regex...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-pgsql.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-php.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function s (line 1) | function s(e,t){return e.type.lastIndexOf(t)>-1}
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-rhtml.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-scala.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-sjs.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-smarty.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-soy_template.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-svg.js
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-swift.js
function t (line 1) | function t(e,t){var n=t.nestable||t.interpolation,r=t.interpolation&&t.i...
function n (line 1) | function n(){return[{token:"comment",regex:"\\/\\/(?=.)",next:[s.getTagR...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-swig.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-twig.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-typescript.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-velocity.js
function a (line 1) | function a(){var e=o.replace("\\d","\\d\\-"),t={onMatch:function(e,t,n){...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function f (line 1) | function f(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
function c (line 1) | function c(e,t){var n=new r(e,t.row,t.column),i=n.getCurrentToken();whil...
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-xml.js
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function l (line 1) | function l(e,t){return e.type.lastIndexOf(t+".xml")>-1}
FILE: docs/gitbook/gitbook-plugin-ace/ace/mode-xquery.js
function s (line 1) | function s(u,a){if(!n[u]){if(!t[u]){var f=typeof e=="function"&&e;if(!a&...
function r (line 1) | function r(e,t){E=t,S=e,x=e.length,s(0,0,0)}
function s (line 1) | function s(e,t,n){m=t,g=t,y=e,b=t,w=n,N=n,E.reset(S)}
function o (line 1) | function o(){E.startNonterminal("EQName",g);switch(y){case 77:f(77);brea...
function u (line 1) | function u(){E.startNonterminal("FunctionName",g);switch(y){case 14:f(14...
function a (line 1) | function a(){E.startNonterminal("NCName",g);switch(y){case 26:f(26);brea...
function f (line 1) | function f(e){y==e?(l(),E.terminal(i.TOKEN[y],b,w>x?x:w),m=b,g=w,y=0):d(...
function l (line 1) | function l(){g!=b&&(m=g,g=b,E.whitespace(m,g))}
function c (line 1) | function c(e){var t;for(;;){t=C(e);if(t!=28)break}return t}
function h (line 1) | function h(e){y==0&&(y=c(e),b=T,w=N)}
function p (line 1) | function p(e){y==0&&(y=C(e),b=T,w=N)}
function d (line 1) | function d(e,t,r,i,s){throw new n.ParseException(e,t,r,i,s)}
function C (line 1) | function C(e){var t=!1;T=N;var n=N,r=i.INITIAL[e],s=0;for(var o=r&4095;o...
function u (line 1) | function u(e,t){return e.type.lastIndexOf(t+".xml")>-1}
function a (line 1) | function a(e,t){var n=!0,r=e.type.split("."),i=t.split(".");return i.for...
function u (line 1) | function u(i){r&&t.removeMarker(r),o.row=n.row;if(e.pos.sc!==undefined&&...
FILE: docs/gitbook/gitbook-plugin-ace/ace/worker-coffee.js
function t (line 1) | function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")r...
function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
function define (line 1) | function define(e){module.exports=e()}
function _dereq_ (line 1) | function _dereq_(e){return _dereq_[e]}
function e (line 1) | function e(){}
function e (line 1) | function e(){}
function e (line 1) | function e(){this.yy={}}
function t (line 1) | function t(){var e;return e=d.lex()||h,"number"!=typeof e&&(e=n.symbols_...
function e (line 1) | function e(e,t,n,r){var i,s;this.parent=e,this.expressions=t,this.method...
function n (line 1) | function n(){this.constructor=e}
function e (line 1) | function e(e,t){var n;this.code=""+t,this.locationData=null!=e?e.locatio...
function e (line 1) | function e(){}
function t (line 1) | function t(e){this.expressions=et(it(e||[]))}
function t (line 1) | function t(e){this.value=e}
function t (line 1) | function t(){return t.__super__.constructor.apply(this,arguments)}
function t (line 1) | function t(){return t.__super__.constructor.apply(this,arguments)}
function t (line 1) | function t(e){this.val=e}
function t (line 1) | function t(e){this.expression=e}
function t (line 1) | function t(e,n,r){return!n&&e instanceof t?e:(this.base=e,this.propertie...
function t (line 1) | function t(e){this.comment=e}
function n (line 1) | function n(e,t,n){this.args=null!=t?t:[],this.soak=n,this.isNew=!1,this....
function t (line 1) | function t(e,t){this.child=e,this.parent=t}
function t (line 1) | function t(e,t){this.name=e,this.name.asKey=!0,this.soak="soak"===t}
function t (line 1) | function t(e){this.index=e}
function t (line 1) | function t(e,t,n){this.from=e,this.to=t,this.exclusive="exclusive"===n,t...
function t (line 1) | function t(e){this.range=e,t.__super__.constructor.call(this)}
function n (line 1) | function n(e,t){this.generated=null!=t?t:!1,this.objects=this.properties...
function t (line 1) | function t(e){this.objects=e||[]}
function n (line 1) | function n(e,t,n){this.variable=e,this.parent=t,this.body=null!=n?n:new ...
function n (line 1) | function n(e,t,n,r){var i,s,o;this.variable=e,this.value=t,this.context=...
function t (line 1) | function t(e,t,n){this.params=e||[],this.body=t||new s,this.bound="bound...
function t (line 1) | function t(e,t,n){var r,i;this.name=e,this.value=t,this.splat=n,i=r=this...
function t (line 1) | function t(e){this.name=e.compile?e:new L(e)}
function t (line 1) | function t(){return t.__super__.constructor.apply(this,arguments)}
function t (line 1) | function t(e,t){this.condition=(null!=t?t.invert:void 0)?e.invert():e,th...
function n (line 1) | function n(e,t,n,r){if("in"===e)return new w(t,n);if("do"===e)return thi...
function t (line 1) | function t(e,t){this.object=e,this.array=t}
function t (line 1) | function t(e,t,n,r){this.attempt=e,this.errorVariable=t,this.recovery=n,...
function t (line 1) | function t(e){this.expression=e}
function t (line 1) | function t(e){this.expression=e}
function t (line 1) | function t(e){this.body=e}
function t (line 1) | function t(e,t){var n;this.source=t.source,this.guard=t.guard,this.step=...
function t (line 1) | function t(e,t,n){this.subject=e,this.cases=t,this.otherwise=n}
function t (line 1) | function t(e,t,n){this.body=t,null==n&&(n={}),this.condition="unless"===...
function e (line 1) | function e(e){this.line=e,this.columns=[]}
function t (line 1) | function t(){this.lines=[]}
function r (line 1) | function r(){}
function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
FILE: docs/gitbook/gitbook-plugin-ace/ace/worker-css.js
function t (line 1) | function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")r...
function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
function objectToString (line 1) | function objectToString(e){return Object.prototype.toString.call(e)}
function clone (line 1) | function clone(e,t,n,r){function u(e,n){if(e===null)return null;if(n==0)...
function Reporter (line 1) | function Reporter(e,t){this.messages=[],this.stats=[],this.lines=e,this....
function e (line 1) | function e(){this._listeners={}}
function t (line 1) | function t(e){this._input=e.replace(/\n\r?/g,"\n"),this._line=1,this._co...
function n (line 1) | function n(e,t,n){this.col=n,this.line=t,this.message=e}
function r (line 1) | function r(e,t,n,r){this.col=n,this.line=t,this.text=e,this.type=r}
function i (line 1) | function i(e,n){this._reader=e?new t(e.toString()):null,this._token=null...
function Combinator (line 1) | function Combinator(e,t,n){SyntaxUnit.call(this,e,t,n,Parser.COMBINATOR_...
function MediaFeature (line 1) | function MediaFeature(e,t){SyntaxUnit.call(this,"("+e+(t!==null?":"+t:""...
function MediaQuery (line 1) | function MediaQuery(e,t,n,r,i){SyntaxUnit.call(this,(e?e+" ":"")+(t?t:""...
function Parser (line 1) | function Parser(e){EventTarget.call(this),this.options=e||{},this._token...
function PropertyName (line 1) | function PropertyName(e,t,n,r){SyntaxUnit.call(this,e,n,r,Parser.PROPERT...
function PropertyValue (line 1) | function PropertyValue(e,t,n){SyntaxUnit.call(this,e.join(" "),t,n,Parse...
function PropertyValueIterator (line 1) | function PropertyValueIterator(e){this._i=0,this._parts=e.parts,this._ma...
function PropertyValuePart (line 1) | function PropertyValuePart(text,line,col){SyntaxUnit.call(this,text,line...
function Selector (line 1) | function Selector(e,t,n){SyntaxUnit.call(this,e.join(" "),t,n,Parser.SEL...
function SelectorPart (line 1) | function SelectorPart(e,t,n,r,i){SyntaxUnit.call(this,n,r,i,Parser.SELEC...
function SelectorSubPart (line 1) | function SelectorSubPart(e,t,n,r){SyntaxUnit.call(this,e,n,r,Parser.SELE...
function Specificity (line 1) | function Specificity(e,t,n,r){this.a=e,this.b=t,this.c=n,this.d=r}
function isHexDigit (line 1) | function isHexDigit(e){return e!==null&&h.test(e)}
function isDigit (line 1) | function isDigit(e){return e!==null&&/\d/.test(e)}
function isWhitespace (line 1) | function isWhitespace(e){return e!==null&&/\s/.test(e)}
function isNewLine (line 1) | function isNewLine(e){return e!==null&&nl.test(e)}
function isNameStart (line 1) | function isNameStart(e){return e!==null&&/[a-z_\u0080-\uFFFF\\]/i.test(e)}
function isNameChar (line 1) | function isNameChar(e){return e!==null&&(isNameStart(e)||/[0-9\-\\]/.tes...
function isIdentStart (line 1) | function isIdentStart(e){return e!==null&&(isNameStart(e)||/\-\\/.test(e))}
function mix (line 1) | function mix(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}
function TokenStream (line 1) | function TokenStream(e){TokenStreamBase.call(this,e,Tokens)}
function ValidationError (line 1) | function ValidationError(e,t,n){this.col=n,this.line=t,this.message=e}
function u (line 1) | function u(e){var t,n,r,a,f=e.elementName?e.elementName.text:"",l;f&&f.c...
function i (line 1) | function i(e,t){var r,i=e&&e.match(n),s=i&&i[1];return s&&(r={"true":2,"...
function u (line 1) | function u(){s={},o=!1}
function a (line 1) | function a(){var e,u;if(!o){if(s.height)for(e in i)i.hasOwnProperty(e)&&...
function s (line 1) | function s(e,s,o){i[e]&&(typeof r[e]!="string"||i[e].value.toLowerCase()...
function o (line 1) | function o(){i={}}
function u (line 1) | function u(){var e=i.display?i.display.value:null;if(e)switch(e){case"in...
function s (line 1) | function s(){r={}}
function o (line 1) | function o(){s={},r=null}
function i (line 1) | function i(e){e.selectors?r={line:e.line,col:e.col,selectors:e.selectors...
function s (line 1) | function s(){r&&r.outline&&(r.selectors.toString().toLowerCase().indexOf...
function r (line 1) | function r(e){var r,i,s,o,u,a,f,l,c,h,p,d=e.selectors;for(r=0,i=d.length...
function f (line 1) | function f(){u={}}
function l (line 1) | function l(e){var r,i,s,o;for(r in a)if(a.hasOwnProperty(r)){o=0;for(i=0...
function s (line 1) | function s(){r=!1,i="inherit"}
function o (line 1) | function o(){r&&i!=="ltr"&&t.report("Negative text-indent doesn't work w...
function o (line 1) | function o(){r={},i=1}
function u (line 1) | function u(){var e,i,o,u,a,f=[];for(e in r)s[e]&&f.push({actual:e,needed...
function r (line 1) | function r(){}
function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
FILE: docs/gitbook/gitbook-plugin-ace/ace/worker-html.js
function t (line 1) | function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")r...
function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
function s (line 1) | function s(u,a){if(!n[u]){if(!t[u]){var f=typeof e=="function"&&e;if(!a&...
function r (line 1) | function r(e){if(e.namespaceURI==="http://www.w3.org/1999/xhtml")return ...
function i (line 1) | function i(e){return r(e)||e.namespaceURI==="http://www.w3.org/1999/xhtm...
function s (line 1) | function s(e){return e.namespaceURI==="http://www.w3.org/1999/xhtml"&&e....
function o (line 1) | function o(e){return e.namespaceURI==="http://www.w3.org/1999/xhtml"&&e....
function u (line 1) | function u(e){return e.namespaceURI==="http://www.w3.org/1999/xhtml"&&e....
function a (line 1) | function a(e){return r(e)||e.namespaceURI==="http://www.w3.org/1999/xhtm...
function f (line 1) | function f(e){return(e.namespaceURI!=="http://www.w3.org/1999/xhtml"||e....
function l (line 1) | function l(){this.elements=[],this.rootNode=null,this.headElement=null,t...
function o (line 1) | function o(e){return e>="0"&&e<="9"||e>="a"&&e<="z"||e>="A"&&e<="Z"}
function u (line 1) | function u(e){return e>="0"&&e<="9"||e>="a"&&e<="f"||e>="A"&&e<="F"}
function a (line 1) | function a(e){return e>="0"&&e<="9"}
function r (line 1) | function r(){this.data="",this.start=0,this.committed=0,this.eof=!1,this...
function i (line 1) | function i(e,t,n,r){this.localName=t,this.namespaceURI=e,this.attributes...
function s (line 1) | function s(e,t){for(var n=0;n<e.attributes.length;n++)if(e.attributes[n]...
function s (line 1) | function s(e){return e===" "||e==="\n"||e===" "||e==="\r"||e==="\f"}
function o (line 1) | function o(e){return e>="A"&&e<="Z"||e>="a"&&e<="z"}
function u (line 1) | function u(e){this._tokenHandler=e,this._state=u.DATA,this._inputStream=...
function n (line 1) | function n(e){var n=e.char();if(n===r.EOF)return t._emitToken({type:"EOF...
function a (line 1) | function a(e){var r=i.consumeEntity(e,t);return t.setState(n),t._emitTok...
function f (line 1) | function f(e){var n=e.char();if(n===r.EOF)return t._emitToken({type:"EOF...
function l (line 1) | function l(e){var n=i.consumeEntity(e,t);return t.setState(f),t._emitTok...
function c (line 1) | function c(e){var n=e.char();if(n===r.EOF)return t._emitToken({type:"EOF...
function h (line 1) | function h(e){var n=e.char();if(n===r.EOF)return t._emitToken({type:"EOF...
function p (line 1) | function p(e){var n=e.char();if(n===r.EOF)return t._emitToken({type:"EOF...
function d (line 1) | function d(e){var n=e.char();return n==="/"?(this._temporaryBuffer="",t....
function v (line 1) | function v(e){var n=e.char();return o(n)?(this._temporaryBuffer+=n,t.set...
function m (line 1) | function m(e){var r=t._currentToken&&t._currentToken.name===this._tempor...
function g (line 1) | function g(e){var n=e.char();return n==="/"?(this._temporaryBuffer="",t....
function y (line 1) | function y(e){var n=e.char();return o(n)?(this._temporaryBuffer+=n,t.set...
function b (line 1) | function b(e){var r=t._currentToken&&t._currentToken.name===this._tempor...
function w (line 1) | function w(e){var n=e.char();return n==="/"?(this._temporaryBuffer="",t....
function E (line 1) | function E(e){var n=e.char();return o(n)?(this._temporaryBuffer+=n,t.set...
function S (line 1) | function S(e){var n=t._currentToken&&t._currentToken.name===this._tempor...
function x (line 1) | function x(e){var n=e.char();return n==="-"?(t._emitToken({type:"Charact...
function T (line 1) | function T(e){var n=e.char();return n==="-"?(t._emitToken({type:"Charact...
function N (line 1) | function N(e){var i=e.char();if(i===r.EOF)e.unget(i),t.setState(n);else ...
function C (line 1) | function C(e){var i=e.char();return i===r.EOF?(e.unget(i),t.setState(n))...
function k (line 1) | function k(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-scr...
function L (line 1) | function L(e){var n=e.char();return n==="/"?(this._temporaryBuffer="",t....
function A (line 1) | function A(e){var n=e.char();return o(n)?(this._temporaryBuffer=n,t.setS...
function O (line 1) | function O(e){var r=t._currentToken&&t._currentToken.name===this._tempor...
function M (line 1) | function M(e){var n=e.char();return s(n)||n==="/"||n===">"?(t._emitToken...
function _ (line 1) | function _(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-scr...
function D (line 1) | function D(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-scr...
function P (line 1) | function P(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-scr...
function H (line 1) | function H(e){var n=e.char();return n==="/"?(t._emitToken({type:"Charact...
function B (line 1) | function B(e){var n=e.char();return s(n)||n==="/"||n===">"?(t._emitToken...
function j (line 1) | function j(e){var i=e.char();return i===r.EOF?(t._parseError("bare-less-...
function F (line 1) | function F(e){var i=e.char();return i===r.EOF?(t._parseError("expected-c...
function I (line 1) | function I(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-tag...
function q (line 1) | function q(e){var i=e.char();if(i===r.EOF)t._parseError("expected-attrib...
function R (line 1) | function R(e){var i=e.char(),u=!0,a=!1;i===r.EOF?(t._parseError("eof-in-...
function U (line 1) | function U(e){var i=e.char();if(i===r.EOF)t._parseError("expected-end-of...
function z (line 1) | function z(e){var i=e.char();if(i===r.EOF)t._parseError("expected-attrib...
function W (line 1) | function W(e){var i=e.char();if(i===r.EOF)t._parseError("eof-in-attribut...
function X (line 1) | function X(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-att...
function V (line 1) | function V(e){var i=e.char();if(i===r.EOF)t._parseError("eof-after-attri...
function $ (line 1) | function $(e){var n=i.consumeEntity(e,t,this._additionalAllowedCharacter...
function J (line 1) | function J(e){var i=e.char();return i===r.EOF?(t._parseError("eof-after-...
function K (line 1) | function K(e){var i=e.char();return i===r.EOF?(t._parseError("unexpected...
function Q (line 1) | function Q(e){var r=e.matchUntil(">");return r=r.replace(/\u0000/g,"\uff...
function G (line 1) | function G(e){var n=e.shift(2);if(n==="--")t._currentToken={type:"Commen...
function Y (line 1) | function Y(e){var r=e.matchUntil("]]>");return e.shift(3),r&&t._emitToke...
function Z (line 1) | function Z(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-com...
function et (line 1) | function et(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-co...
function tt (line 1) | function tt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-co...
function nt (line 1) | function nt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-co...
function rt (line 1) | function rt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-co...
function it (line 1) | function it(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-co...
function st (line 1) | function st(e){var i=e.char();return i===r.EOF?(t._parseError("expected-...
function ot (line 1) | function ot(e){var i=e.char();return i===r.EOF?(t._parseError("expected-...
function ut (line 1) | function ut(e){var i=e.char();return i===r.EOF?(t._currentToken.forceQui...
function at (line 1) | function at(e){var i=e.char();if(i===r.EOF)t._currentToken.forceQuirks=!...
function ft (line 1) | function ft(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function lt (line 1) | function lt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function ct (line 1) | function ct(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function ht (line 1) | function ht(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function pt (line 1) | function pt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function dt (line 1) | function dt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function vt (line 1) | function vt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function mt (line 1) | function mt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function gt (line 1) | function gt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function yt (line 1) | function yt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function bt (line 1) | function bt(e){var i=e.char();return i===r.EOF?(t._parseError("eof-in-do...
function wt (line 1) | function wt(e){var i=e.char();return i===r.EOF?(e.unget(i),t._emitCurren...
function c (line 1) | function c(e){return e===" "||e==="\n"||e===" "||e==="\r"||e==="\f"}
function h (line 1) | function h(e){return c(e)||e==="\ufffd"}
function p (line 1) | function p(e){for(var t=0;t<e.length;t++){var n=e[t];if(!c(n))return!1}r...
function d (line 1) | function d(e){for(var t=0;t<e.length;t++){var n=e[t];if(!h(n))return!1}r...
function v (line 1) | function v(e,t){for(var n=0;n<e.attributes.length;n++){var r=e.attribute...
function m (line 1) | function m(e){this.characters=e,this.current=0,this.end=this.characters....
function g (line 1) | function g(){this.tokenizer=null,this.errorHandler=null,this.scriptingEn...
function y (line 1) | function y(e,t){return e.replace(new RegExp("{[0-9a-z-]+}","gi"),functio...
function i (line 1) | function i(e){return e===r}
function o (line 1) | function o(){this.contentHandler=null,this._errorHandler=null,this._tree...
function s (line 1) | function s(){i.call(this)}
function o (line 1) | function o(e,t){for(var n=0;n<e.attributes.length;n++){var r=e.attribute...
function a (line 1) | function a(e){e?(this.columnNumber=e.columnNumber,this.lineNumber=e.line...
function f (line 1) | function f(e){a.call(this,e),this.lastChild=null,this._endLocator=null}
function l (line 1) | function l(e){f.call(this,e),this.nodeType=u.DOCUMENT}
function c (line 1) | function c(){f.call(this,new Locator),this.nodeType=u.DOCUMENT_FRAGMENT}
function h (line 1) | function h(e,t,n,r,i,s){f.call(this,e),this.uri=t,this.localName=n,this....
function p (line 1) | function p(e,t){a.call(this,e),this.data=t,this.nodeType=u.CHARACTERS}
function d (line 1) | function d(e,t){a.call(this,e),this.data=t,this.nodeType=u.IGNORABLE_WHI...
function v (line 1) | function v(e,t){a.call(this,e),this.data=t,this.nodeType=u.COMMENT}
function m (line 1) | function m(e){f.call(this,e),this.nodeType=u.CDATA}
function g (line 1) | function g(e){f.call(this),this.name=e,this.nodeType=u.ENTITY}
function y (line 1) | function y(e){a.call(this),this.name=e,this.nodeType=u.SKIPPED_ENTITY}
function b (line 1) | function b(e,t){a.call(this),this.target=e,this.data=t}
function w (line 1) | function w(e,t,n){f.call(this),this.name=e,this.publicIdentifier=t,this....
function r (line 1) | function r(e,t){this.contentHandler,this.lexicalHandler,this.locatorDele...
function i (line 1) | function i(){}
function u (line 1) | function u(e,t){return r.isUndefined(t)?""+t:r.isNumber(t)&&(isNaN(t)||!...
function a (line 1) | function a(e,t){return r.isString(e)?e.length<t?e:e.slice(0,t):e}
function f (line 1) | function f(e){return a(JSON.stringify(e.actual,u),128)+" "+e.operator+" ...
function l (line 1) | function l(e,t,n,r,i){throw new o.AssertionError({message:n,actual:e,exp...
function c (line 1) | function c(e,t){e||l(e,!0,t,"==",o.ok)}
function h (line 1) | function h(e,t){if(e===t)return!0;if(r.isBuffer(e)&&r.isBuffer(t)){if(e....
function p (line 1) | function p(e){return Object.prototype.toString.call(e)=="[object Argumen...
function d (line 1) | function d(e,t){if(r.isNullOrUndefined(e)||r.isNullOrUndefined(t))return...
function v (line 1) | function v(e,t){return!e||!t?!1:Object.prototype.toString.call(t)=="[obj...
function m (line 1) | function m(e,t,n,i){var s;r.isString(n)&&(i=n,n=null);try{t()}catch(o){s...
function u (line 1) | function u(e,t){var r={seen:[],stylize:f};return arguments.length>=3&&(r...
function a (line 1) | function a(e,t){var n=u.styles[t];return n?"["+u.colors[n][0]+"m"+e+"[...
function f (line 1) | function f(e,t){return e}
function l (line 1) | function l(e){var t={};return e.forEach(function(e,n){t[e]=!0}),t}
function c (line 1) | function c(e,t,r){if(e.customInspect&&t&&A(t.inspect)&&t.inspect!==n.ins...
function h (line 1) | function h(e,t){if(T(t))return e.stylize("undefined","undefined");if(S(t...
function p (line 1) | function p(e){return"["+Error.prototype.toString.call(e)+"]"}
function d (line 1) | function d(e,t,n,r,i){var s=[];for(var o=0,u=t.length;o<u;++o)H(t,String...
function v (line 1) | function v(e,t,n,r,i,s){var o,u,a;a=Object.getOwnPropertyDescriptor(t,i)...
function m (line 1) | function m(e,t,n){var r=0,i=e.reduce(function(e,t){return r++,t.indexOf(...
function g (line 1) | function g(e){return Array.isArray(e)}
function y (line 1) | function y(e){return typeof e=="boolean"}
function b (line 1) | function b(e){return e===null}
function w (line 1) | function w(e){return e==null}
function E (line 1) | function E(e){return typeof e=="number"}
function S (line 1) | function S(e){return typeof e=="string"}
function x (line 1) | function x(e){return typeof e=="symbol"}
function T (line 1) | function T(e){return e===void 0}
function N (line 1) | function N(e){return C(e)&&M(e)==="[object RegExp]"}
function C (line 1) | function C(e){return typeof e=="object"&&e!==null}
function k (line 1) | function k(e){return C(e)&&M(e)==="[object Date]"}
function L (line 1) | function L(e){return C(e)&&(M(e)==="[object Error]"||e instanceof Error)}
function A (line 1) | function A(e){return typeof e=="function"}
function O (line 1) | function O(e){return e===null||typeof e=="boolean"||typeof e=="number"||...
function M (line 1) | function M(e){return Object.prototype.toString.call(e)}
function _ (line 1) | function _(e){return e<10?"0"+e.toString(10):e.toString(10)}
function P (line 1) | function P(){var e=new Date,t=[_(e.getHours()),_(e.getMinutes()),_(e.get...
function H (line 1) | function H(e,t){return Object.prototype.hasOwnProperty.call(e,t)}
function o (line 1) | function o(){if(!s){if(t.throwDeprecation)throw new Error(i);t.traceDepr...
function r (line 1) | function r(){this._events=this._events||{},this._maxListeners=this._maxL...
function i (line 1) | function i(e){return typeof e=="function"}
function s (line 1) | function s(e){return typeof e=="number"}
function o (line 1) | function o(e){return typeof e=="object"&&e!==null}
function u (line 1) | function u(e){return e===void 0}
function r (line 1) | function r(){this.removeListener(e,r),n||(n=!0,t.apply(this,arguments))}
function i (line 1) | function i(){}
function r (line 1) | function r(){}
function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
FILE: docs/gitbook/gitbook-plugin-ace/ace/worker-javascript.js
function t (line 1) | function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")r...
function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
function o (line 1) | function o(u,a){if(!n[u]){if(!t[u]){var f=typeof e=="function"&&e;if(!a&...
function r (line 1) | function r(){this._events=this._events||{},this._maxListeners=this._maxL...
function i (line 1) | function i(e){return typeof e=="function"}
function s (line 1) | function s(e){return typeof e=="number"}
function o (line 1) | function o(e){return typeof e=="object"&&e!==null}
function u (line 1) | function u(e){return e===void 0}
function r (line 1) | function r(){this.removeListener(e,r),n||(n=!0,t.apply(this,arguments))}
function $ (line 1) | function $(e,t,n){var r=e.length,i=n?r:-1;while(n?i--:++i<r)if(t(e[i],i,...
function J (line 1) | function J(e,t,n){if(t!==t)return G(e,n);var r=n-1,i=e.length;while(++r<...
function K (line 1) | function K(e){return typeof e=="function"||!1}
function Q (line 1) | function Q(e){return typeof e=="string"?e:e==null?"":e+""}
function G (line 1) | function G(e,t,n){var r=e.length,i=t+(n?0:-1);while(n?i--:++i<r){var s=e...
function Y (line 1) | function Y(e){return!!e&&typeof e=="object"}
function Ct (line 1) | function Ct(){}
function Lt (line 1) | function Lt(e,t){var n=-1,r=e.length;t||(t=Array(r));while(++n<r)t[n]=e[...
function At (line 1) | function At(e,t){var n=-1,r=e.length;while(++n<r)if(t(e[n],n,e)===!1)bre...
function Ot (line 1) | function Ot(e,t){var n=-1,r=e.length,i=-1,s=[];while(++n<r){var o=e[n];t...
function Mt (line 1) | function Mt(e,t){var n=-1,r=e.length,i=Array(r);while(++n<r)i[n]=t(e[n],...
function _t (line 1) | function _t(e){var t=-1,n=e.length,r=wt;while(++t<n){var i=e[t];i>r&&(r=...
function Dt (line 1) | function Dt(e,t){var n=-1,r=e.length;while(++n<r)if(t(e[n],n,e))return!0...
function Pt (line 1) | function Pt(e,t,n){var i=rr(t);lt.apply(i,bn(t));var s=-1,o=i.length;whi...
function Bt (line 1) | function Bt(e,t,n){n||(n={});var r=-1,i=t.length;while(++r<i){var s=t[r]...
function jt (line 1) | function jt(e,t,n){var i=typeof e;return i=="function"?t===r?e:on(e,t,n)...
function Ft (line 1) | function Ft(e,t,n,i,s,u,a){var f;n&&(f=s?n(e,i,s):n(e));if(f!==r)return ...
function qt (line 1) | function qt(e,t){var n=[];return It(e,function(e,r,i){t(e,r,i)&&n.push(e...
function Ut (line 1) | function Ut(e,t){return Rt(e,t,ir)}
function zt (line 1) | function zt(e,t){return Rt(e,t,rr)}
function Wt (line 1) | function Wt(e,t,n){if(e==null)return;n!==r&&n in On(e)&&(t=[n]);var i=-1...
function Xt (line 1) | function Xt(e,t,n,r,i,s){if(e===t)return e!==0||1/e==1/t;var o=typeof e,...
function Vt (line 1) | function Vt(e,t,n,r,i,s,a){var f=Xn(e),l=Xn(t),c=u,h=u;f||(c=rt.call(e),...
function $t (line 1) | function $t(e,t,n,i,s){var o=-1,u=t.length,a=!s;while(++o<u)if(a&&i[o]?n...
function Jt (line 1) | function Jt(e){var t=rr(e),n=t.length;if(!n)return fr(!0);if(n==1){var i...
function Kt (line 1) | function Kt(e,t){var n=Xn(e),i=Nn(e)&&kn(t),s=e+"";return e=Mn(e),functi...
function Qt (line 1) | function Qt(e,t,n,i,s){if(!Jn(e))return e;var o=Cn(t.length)&&(Xn(t)||Zn...
function Gt (line 1) | function Gt(e,t,n,i,s,o,u){var a=o.length,f=t[n];while(a--)if(o[a]==f){e...
function Yt (line 1) | function Yt(e){return function(t){return t==null?r:t[e]}}
function Zt (line 1) | function Zt(e){var t=e+"";return e=Mn(e),function(n){return Wt(n,e,t)}}
function en (line 1) | function en(e,t,n){var i=-1,s=e.length;t=t==null?0:+t||0,t<0&&(t=-t>s?0:...
function tn (line 1) | function tn(e,t){var n;return It(e,function(e,r,i){return n=t(e,r,i),!n}...
function nn (line 1) | function nn(e,t){var n=-1,r=t.length,i=Array(r);while(++n<r)i[n]=e[t[n]]...
function rn (line 1) | function rn(e,t,n){var r=0,i=e?e.length:r;if(typeof t=="number"&&t===t&&...
function sn (line 1) | function sn(e,t,n,i){t=n(t);var s=0,o=e?e.length:0,u=t!==t,a=t===r;while...
function on (line 1) | function on(e,t,n){if(typeof e!="function")return lr;if(t===r)return e;s...
function un (line 1) | function un(e){return ot.call(e,0)}
function an (line 1) | function an(e){return Un(function(t,n){var r=-1,i=t==null?0:n.length,s=i...
function fn (line 1) | function fn(e,t){return function(n,r){var i=n?yn(n):0;if(!Cn(i))return e...
function ln (line 1) | function ln(e){return function(t,n,r){var i=On(t),s=r(t),o=s.length,u=e?...
function cn (line 1) | function cn(e){return function(t,n,r){return!t||!t.length?-1:(n=mn(n,r,3...
function hn (line 1) | function hn(e,t){return function(n,i,s){return typeof i=="function"&&s==...
function pn (line 1) | function pn(e,t,n,i,s,o,u){var a=-1,f=e.length,l=t.length,c=!0;if(f!=l&&...
function dn (line 1) | function dn(e,t,n){switch(n){case a:case f:return+e==+t;case l:return e....
function vn (line 1) | function vn(e,t,n,i,s,o,u){var a=rr(e),f=a.length,l=rr(t),c=l.length;if(...
function mn (line 1) | function mn(e,t,n){var r=Ct.callback||ar;return r=r===ar?jt:r,n?r(e,t,n):r}
function gn (line 1) | function gn(e,t,n){var r=Ct.indexOf||Dn;return r=r===Dn?J:r,e?r(e,t,n):r}
function wn (line 1) | function wn(e){var t=e.length,n=new e.constructor(t);return t&&typeof e[...
function En (line 1) | function En(e){var t=e.constructor;return typeof t=="function"&&t instan...
function Sn (line 1) | function Sn(e,t,n){var r=e.constructor;switch(t){case b:return un(e);cas...
function xn (line 1) | function xn(e,t){return e=+e,t=t==null?Nt:t,e>-1&&e%1==0&&e<t}
function Tn (line 1) | function Tn(e,t,n){if(!Jn(n))return!1;var r=typeof t;if(r=="number")var ...
function Nn (line 1) | function Nn(e,t){var n=typeof e;if(n=="string"&&O.test(e)||n=="number")r...
function Cn (line 1) | function Cn(e){return typeof e=="number"&&e>-1&&e%1==0&&e<=Nt}
function kn (line 1) | function kn(e){return e===e&&(e===0?1/e>0:!Jn(e))}
function Ln (line 1) | function Ln(e){var t,n=Ct.support;if(!Y(e)||rt.call(e)!=d||!nt.call(e,"c...
function An (line 1) | function An(e){var t=ir(e),n=t.length,r=n&&e.length,i=Ct.support,s=r&&Cn...
function On (line 1) | function On(e){return Jn(e)?e:Object(e)}
function Mn (line 1) | function Mn(e){if(Xn(e))return e;var t=[];return Q(e).replace(M,function...
function Dn (line 1) | function Dn(e,t,n){var r=e?e.length:0;if(!r)return-1;if(typeof n=="numbe...
function Pn (line 1) | function Pn(e){var t=e?e.length:0;return t?e[t-1]:r}
function Hn (line 1) | function Hn(e,t,n){var r=e?e.length:0;return r?(n&&typeof n!="number"&&T...
function Bn (line 1) | function Bn(e){var t=-1,n=(e&&e.length&&_t(Mt(e,yn)))>>>0,r=Array(n);whi...
function In (line 1) | function In(e,t,n,r){var i=e?yn(e):0;return Cn(i)||(e=or(e),i=e.length),...
function qn (line 1) | function qn(e,t,n){var r=Xn(e)?Ot:qt;return t=mn(t,n,3),r(e,function(e,n...
function Rn (line 1) | function Rn(e,t,n){var i=Xn(e)?Dt:tn;n&&Tn(e,t,n)&&(t=null);if(typeof t!...
function Un (line 1) | function Un(e,t){if(typeof e!="function")throw new TypeError(s);return t...
function zn (line 1) | function zn(e,t,n,r){return t&&typeof t!="boolean"&&Tn(e,t,n)?t=!1:typeo...
function Wn (line 1) | function Wn(e){var t=Y(e)?e.length:r;return Cn(t)&&rt.call(e)==o}
function Vn (line 1) | function Vn(e){if(e==null)return!0;var t=yn(e);return Cn(t)&&(Xn(e)||Yn(...
function Jn (line 1) | function Jn(e){var t=typeof e;return t=="function"||!!e&&t=="object"}
function Kn (line 1) | function Kn(e){return e==null?!1:rt.call(e)==c?it.test(tt.call(e)):Y(e)&...
function Qn (line 1) | function Qn(e){return typeof e=="number"||Y(e)&&rt.call(e)==p}
function Yn (line 1) | function Yn(e){return typeof e=="string"||Y(e)&&rt.call(e)==g}
function Zn (line 1) | function Zn(e){return Y(e)&&Cn(e.length)&&!!j[rt.call(e)]}
function er (line 1) | function er(e){return Bt(e,ir(e))}
function nr (line 1) | function nr(e,t){if(e==null)return!1;var n=nt.call(e,t);return!n&&!Nn(t)...
function ir (line 1) | function ir(e){if(e==null)return[];Jn(e)||(e=Object(e));var t=e.length;t...
function or (line 1) | function or(e){return nn(e,rr(e))}
function ur (line 1) | function ur(e){return e=Q(e),e&&D.test(e)?e.replace(_,"\\$&"):e}
function ar (line 1) | function ar(e,t,n){return n&&Tn(e,t,n)&&(t=null),jt(e,t)}
function fr (line 1) | function fr(e){return function(){return e}}
function lr (line 1) | function lr(e){return e}
function cr (line 1) | function cr(e){return Nn(e)?Yt(e):Zt(e)}
function k (line 1) | function k(e,t){return e=e.trim(),/^[+-]W\d{3}$/g.test(e)?!0:c.validName...
function L (line 1) | function L(e){return Object.prototype.toString.call(e)==="[object String]"}
function A (line 1) | function A(e,t){return e?!e.identifier||e.value!==t?!1:!0:!1}
function O (line 1) | function O(e){if(!e.reserved)return!1;var t=e.meta;if(t&&t.isFutureReser...
function M (line 1) | function M(e,t){return e.replace(/\{([^{}]*)\}/g,function(e,n){var r=t[n...
function D (line 1) | function D(e,t){Object.keys(t).forEach(function(n){if(r.has(p.blacklist,...
function P (line 1) | function P(){if(f.option.enforceall){for(var e in c.bool.enforcing)f.opt...
function H (line 1) | function H(){P(),!f.option.esversion&&!f.option.moz&&(f.option.es3?f.opt...
function B (line 1) | function B(e,t,n){var r=Math.floor(t/f.lines.length*100),i=o.errors[e].d...
function j (line 1) | function j(){var e=f.ignoredLines;if(r.isEmpty(e))return;p.errors=r.reje...
function F (line 1) | function F(e,t,n,r,i,s){var u,a,l,c;if(/^W\d{3}$/.test(e)){if(f.ignored[...
function I (line 1) | function I(e,t,n,r,i,s,o){return F(e,{line:t,from:n},r,i,s,o)}
function q (line 1) | function q(e,t,n,r,i,s){F(e,t,n,r,i,s)}
function R (line 1) | function R(e,t,n,r,i,s,o){return q(e,{line:t,from:n},r,i,s,o)}
function U (line 1) | function U(e,t){var n;return n={id:"(internal)",elem:e,value:t},p.intern...
function z (line 1) | function z(){var e=f.tokens.next,t=e.body.match(/(-\s+)?[^\s,:]+(?:\s*:\...
function W (line 1) | function W(e){var t=e||0,n=y.length,r;if(t<n)return y[t];while(n<=t)r=y[...
function X (line 1) | function X(){var e=0,t;do t=W(e++);while(t.id==="(endline)");return t}
function V (line 1) | function V(e,t){switch(f.tokens.curr.id){case"(number)":f.tokens.next.id...
function $ (line 1) | function $(e){return e.infix||!e.identifier&&!e.template&&!!e.led}
function J (line 1) | function J(){var e=f.tokens.curr,t=f.tokens.next;return t.id===";"||t.id...
function K (line 1) | function K(e){return!e.left&&e.arity!=="unary"}
function Q (line 1) | function Q(e,t){var n,i=!1,s=!1,o=!1;f.nameStack.push(),!t&&f.tokens.nex...
function G (line 1) | function G(e){return e.startLine||e.line}
function Y (line 1) | function Y(e,t){e=e||f.tokens.curr,t=t||f.tokens.next,!f.option.laxbreak...
function Z (line 1) | function Z(e){e=e||f.tokens.curr,e.line!==G(f.tokens.next)&&F("E022",e,e...
function et (line 1) | function et(e,t){e.line!==G(t)&&(f.option.laxcomma||(tt.first&&(F("I001"...
function tt (line 1) | function tt(e){e=e||{},e.peek?et(f.tokens.prev,f.tokens.curr):(et(f.toke...
function nt (line 1) | function nt(e,t){var n=f.syntax[e];if(!n||typeof n!="object")f.syntax[e]...
function rt (line 1) | function rt(e){var t=nt(e,0);return t.delim=!0,t}
function it (line 1) | function it(e,t){var n=rt(e);return n.identifier=n.reserved=!0,n.fud=t,n}
function st (line 1) | function st(e,t){var n=it(e,t);return n.block=!0,n}
function ot (line 1) | function ot(e){var t=e.id.charAt(0);if(t>="a"&&t<="z"||t>="A"&&t<="Z")e....
function ut (line 1) | function ut(e,t){var n=nt(e,150);return ot(n),n.nud=typeof t=="function"...
function at (line 1) | function at(e,t){var n=rt(e);return n.type=e,n.nud=t,n}
function ft (line 1) | function ft(e,t){var n=at(e,t);return n.identifier=!0,n.reserved=!0,n}
function lt (line 1) | function lt(e,t){var n=at(e,t&&t.nud||function(){return this});return t=...
function ct (line 1) | function ct(e,t){return ft(e,function(){return typeof t=="function"&&t(t...
function ht (line 1) | function ht(e,t,n,r){var i=nt(e,n);return ot(i),i.infix=!0,i.led=functio...
function pt (line 1) | function pt(e){var t=nt(e,42);return t.led=function(e){return Y(f.tokens...
function dt (line 1) | function dt(e,t){var n=nt(e,100);return n.led=function(e){Y(f.tokens.pre...
function vt (line 1) | function vt(e){return e&&(e.type==="(number)"&&+e.value===0||e.type==="(...
function gt (line 1) | function gt(e,t,n){var i;return n.option.notypeof?!1:!e||!t?!1:(i=n.inES...
function yt (line 1) | function yt(e,t){var n=!1;return e.type==="this"&&t.funct["(context)"]==...
function bt (line 1) | function bt(e){function n(e){if(typeof e!="object")return;return e.right...
function wt (line 1) | function wt(e,t,n){var r=n&&n.allowDestructuring;t=t||e;if(f.option.free...
function Et (line 1) | function Et(e,t,n){var r=ht(e,typeof t=="function"?t:function(e,t){t.lef...
function St (line 1) | function St(e,t,n){var r=nt(e,n);return ot(r),r.led=typeof t=="function"...
function xt (line 1) | function xt(e){return Et(e,function(e,t){f.option.bitwise&&F("W016",t,t....
function Tt (line 1) | function Tt(e){var t=nt(e,150);return t.led=function(e){return f.option....
function Nt (line 1) | function Nt(e,t,n){if(!f.tokens.next.identifier)return;n||V();var r=f.to...
function Ct (line 1) | function Ct(e,t){var n=Nt(e,t,!1);if(n)return n;if(f.tokens.next.value==...
function kt (line 1) | function kt(e){var t=0,n;if(f.tokens.next.id!==";"||e.inBracelessBlock)r...
function Lt (line 1) | function Lt(){if(f.tokens.next.id!==";"){if(f.tokens.next.isUnclosed)ret...
function At (line 1) | function At(){var e=g,t,n=f.tokens.next,r=!1;if(n.id===";"){V(";");retur...
function Ot (line 1) | function Ot(){var e=[],t;while(!f.tokens.next.reach&&f.tokens.next.id!==...
function Mt (line 1) | function Mt(){var e,t,n;while(f.tokens.next.id==="(string)"){t=W(0);if(t...
function _t (line 1) | function _t(e,t,n,i,s){var o,u=m,a=g,l,c,h,p;m=e,c=f.tokens.next;var d=f...
function Dt (line 1) | function Dt(e){E&&typeof E[e]!="boolean"&&F("W036",f.tokens.curr,e),type...
function Bt (line 1) | function Bt(){var e={};e.exps=!0,f.funct["(comparray)"].stack();var t=!1...
function jt (line 1) | function jt(){return f.funct["(statement)"]&&f.funct["(statement)"].type...
function Ft (line 1) | function Ft(e){return e.identifier||e.id==="(string)"||e.id==="(number)"}
function It (line 1) | function It(e){var t,n=!0;return typeof e=="object"?t=e:(n=e,t=Nt(!1,!0,...
function qt (line 1) | function qt(e){function h(e){f.funct["(scope)"].addParam.apply(f.funct["...
function Rt (line 1) | function Rt(e,t,n){var i={"(name)":e,"(breakage)":0,"(loopage)":0,"(toke...
function Ut (line 1) | function Ut(e){return"(scope)"in e}
function zt (line 1) | function zt(e){return e["(global)"]&&!e["(verb)"]}
function Wt (line 1) | function Wt(e){function i(){if(f.tokens.curr.template&&f.tokens.curr.tai...
function Xt (line 1) | function Xt(e){var t,n,r,i,s,o,u,a,l=f.option,c=f.ignored;e&&(r=e.name,i...
function Vt (line 1) | function Vt(e){return{statementCount:0,nestedBlockDepth:-1,ComplexityCou...
function $t (line 1) | function $t(){f.funct["(metrics)"].ComplexityCount+=1}
function Jt (line 1) | function Jt(e){var t,n;e&&(t=e.id,n=e.paren,t===","&&(e=e.exprs[e.exprs....
function Kt (line 1) | function Kt(e){if(f.inES5())for(var t in e)e[t]&&e[t].setterToken&&!e[t]...
function Qt (line 1) | function Qt(e,t){if(pn(f.tokens.next,".")){var n=f.tokens.curr.id;V(".")...
function Gt (line 1) | function Gt(e){var t=e&&e.assignment;return f.inES6()||F("W104",f.tokens...
function Yt (line 1) | function Yt(e){var t,n=[],r=e&&e.openingParsed,i=e&&e.assignment,s=i?{as...
function Zt (line 1) | function Zt(e,t){var n=t.first;if(!n)return;r.zip(e,Array.isArray(n)?n:[...
function en (line 1) | function en(e,t,n){var i=n&&n.prefix,s=n&&n.inexport,o=e==="let",u=e==="...
function sn (line 1) | function sn(e){return f.inES6()||F("W104",f.tokens.curr,"class","6"),e?(...
function on (line 1) | function on(e){var t=f.inClassBody;f.tokens.next.value==="extends"&&(V("...
function un (line 1) | function un(e){var t,n,r,i,s=Object.create(null),o=Object.create(null),u...
function fn (line 1) | function fn(e,t,n,r,i){var s=["key","class method","static class method"...
function ln (line 1) | function ln(e,t,n,r,i,s){var o=e==="get"?"getterToken":"setterToken",u="...
function cn (line 1) | function cn(){V("["),f.inES6()||F("W119",f.tokens.curr,"computed propert...
function hn (line 1) | function hn(e,t){return e.type==="(punctuator)"?r.contains(t,e.value):!1}
function pn (line 1) | function pn(e,t){return e.type==="(punctuator)"&&e.value===t}
function dn (line 1) | function dn(){var e=an();e.notJson?(!f.inES6()&&e.isDestAssign&&F("W104"...
function mn (line 1) | function mn(){function e(){var e={},t=f.tokens.next;V("{");if(f.tokens.n...
function t (line 1) | function t(){V("catch"),V("("),f.funct["(scope)"].stack("catchparams");i...
function i (line 1) | function i(e){var t=n.variables.filter(function(t){if(t.value===e)return...
function s (line 1) | function s(e){var t=n.variables.filter(function(t){if(t.value===e&&!t.un...
function U (line 1) | function U(e,t){if(!e)return;!Array.isArray(e)&&typeof e=="object"&&(e=O...
method isJSON (line 1) | get isJSON(){return f.jsonMode}
function h (line 1) | function h(){var e=[];return{push:function(t){e.push(t)},check:function(...
function p (line 1) | function p(e){var t=e;typeof t=="string"&&(t=t.replace(/\r\n/g,"\n").rep...
function u (line 1) | function u(e,t,n){var r=["jshint","jslint","members","member","globals",...
function i (line 1) | function i(e){return e>256}
function s (line 1) | function s(e){return e>256}
function o (line 1) | function o(e){return/^[0-9a-fA-F]$/.test(e)}
function p (line 1) | function p(e){return e.replace(/\\u([0-9a-fA-F]{4})/g,function(e,t){retu...
function f (line 1) | function f(e){return/^[0-9]$/.test(e)}
function c (line 1) | function c(e){return/^[0-7]$/.test(e)}
function h (line 1) | function h(e){return/^[01]$/.test(e)}
function p (line 1) | function p(e){return/^[0-9a-fA-F]$/.test(e)}
function d (line 1) | function d(e){return e==="$"||e==="_"||e==="\\"||e>="a"&&e<="z"||e>="A"&...
function n (line 1) | function n(e,t){if(!e.reserved)return!1;var n=e.meta;if(n&&n.isFutureRes...
function r (line 1) | function r(){this._stack=[]}
function f (line 1) | function f(e){u={"(labels)":Object.create(null),"(usages)":Object.create...
function v (line 1) | function v(e,t){d.emit("warning",{code:e,token:t,data:r.slice(arguments,...
function m (line 1) | function m(e,t){d.emit("warning",{code:e,token:t,data:r.slice(arguments,...
function g (line 1) | function g(e){u["(usages)"][e]||(u["(usages)"][e]={"(modified)":[],"(rea...
function w (line 1) | function w(){if(u["(type)"]==="functionparams"){E();return}var e=u["(lab...
function E (line 1) | function E(){var t=u["(params)"];if(!t)return;var n=t.pop(),r;while(n){v...
function S (line 1) | function S(e){for(var t=a.length-1;t>=0;--t){var n=a[t]["(labels)"];if(n...
function x (line 1) | function x(e){for(var t=a.length-1;t>=0;t--){var n=a[t];if(n["(usages)"]...
function T (line 1) | function T(t,n){if(e.option.shadow!=="outer")return;var r=l["(type)"]===...
function N (line 1) | function N(t,n,r){e.option.latedef&&(e.option.latedef===!0&&t==="functio...
function startRegex (line 1) | function startRegex(e){return RegExp("^("+e.join("|")+")")}
function r (line 1) | function r(){}
function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
FILE: docs/gitbook/gitbook-plugin-ace/ace/worker-json.js
function t (line 1) | function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")r...
function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
function r (line 1) | function r(){}
function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
FILE: docs/gitbook/gitbook-plugin-ace/ace/worker-lua.js
function t (line 1) | function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")r...
function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
function m (line 1) | function m(e){if(mt){var t=vt.pop();t.complete(),n.locations&&(e.loc=t.l...
function w (line 1) | function w(e,t,n){for(var r=0,i=e.length;r<i;r++)if(e[r][t]===n)return r...
function E (line 1) | function E(e){var t=g.call(arguments,1);return e=e.replace(/%(\d)/g,func...
function S (line 1) | function S(){var e=g.call(arguments),t={},n,r;for(var i=0,s=e.length;i<s...
function x (line 1) | function x(e){var t=E.apply(null,g.call(arguments,1)),n,r;throw"undefine...
function T (line 1) | function T(e,t){x(t,d.expectedToken,e,t.value)}
function N (line 1) | function N(e,t){"undefined"==typeof t&&(t=A.value);if("undefined"!=typeo...
function P (line 1) | function P(){H();while(45===t.charCodeAt(C)&&45===t.charCodeAt(C+1))X(),...
function H (line 1) | function H(){while(C<r){var e=t.charCodeAt(C);if(Q(e))C++;else{if(!G(e))...
function B (line 1) | function B(){var e,n;while(tt(t.charCodeAt(++C)));return e=t.slice(M,C),...
function j (line 1) | function j(e){return C+=e.length,{type:l,value:e,line:_,lineStart:D,rang...
function F (line 1) | function F(){return C+=3,{type:p,value:"...",line:_,lineStart:D,range:[M...
function I (line 1) | function I(){var e=t.charCodeAt(C++),n=C,i="",s;while(C<r){s=t.charCodeA...
function q (line 1) | function q(){var e=V();return!1===e&&x(k,d.expected,"[",k.value),{type:o...
function R (line 1) | function R(){var e=t.charAt(C),n=t.charAt(C+1),r="0"===e&&"xX".indexOf(n...
function U (line 1) | function U(){var e=0,n=1,r=1,i,s,o,u;u=C+=2,Z(t.charCodeAt(C))||x({},d.m...
function z (line 1) | function z(){while(Y(t.charCodeAt(C)))C++;if("."===t.charAt(C)){C++;whil...
function W (line 1) | function W(){var e=C;switch(t.charAt(C)){case"n":return C++,"\n";case"r"...
function X (line 1) | function X(){M=C,C+=2;var e=t.charAt(C),i="",s=!1,o=C,u=D,a=_;"["===e&&(...
function V (line 1) | function V(){var e=0,n="",i=!1,s,o;C++;while("="===t.charAt(C+e))e++;if(...
function $ (line 1) | function $(){L=k,k=A,A=P()}
function J (line 1) | function J(e){return e===k.value?($(),!0):!1}
function K (line 1) | function K(e){e===k.value?$():x(k,d.expected,e,k.value)}
function Q (line 1) | function Q(e){return 9===e||32===e||11===e||12===e}
function G (line 1) | function G(e){return 10===e||13===e}
function Y (line 1) | function Y(e){return e>=48&&e<=57}
function Z (line 1) | function Z(e){return e>=48&&e<=57||e>=97&&e<=102||e>=65&&e<=70}
function et (line 1) | function et(e){return e>=65&&e<=90||e>=97&&e<=122||95===e}
function tt (line 1) | function tt(e){return e>=65&&e<=90||e>=97&&e<=122||95===e||e>=48&&e<=57}
function nt (line 1) | function nt(e){switch(e.length){case 2:return"do"===e||"if"===e||"in"===...
function rt (line 1) | function rt(e){return l===e.type?"#-".indexOf(e.value)>=0:u===e.type?"no...
function it (line 1) | function it(e){switch(e.type){case"CallExpression":case"TableCallExpress...
function st (line 1) | function st(e){if(s===e.type)return!0;if(u!==e.type)return!1;switch(e.va...
function ft (line 1) | function ft(){ot.push(Array.apply(null,ot[ut++]))}
function lt (line 1) | function lt(){ot.pop(),ut--}
function ct (line 1) | function ct(e){if(-1!==b(ot[ut],e))return;ot[ut].push(e)}
function ht (line 1) | function ht(e){ct(e.name),pt(e,!0)}
function pt (line 1) | function pt(e,t){!t&&-1===w(at,"name",e.name)&&at.push(e),e.isLocal=t}
function dt (line 1) | function dt(e){return-1!==b(ot[ut],e)}
function gt (line 1) | function gt(){return new yt(k)}
function yt (line 1) | function yt(e){n.locations&&(this.loc={start:{line:e.line,column:e.range...
function bt (line 1) | function bt(){mt&&vt.push(gt())}
function wt (line 1) | function wt(e){mt&&vt.push(e)}
function Et (line 1) | function Et(){$(),bt();var e=St();return s!==k.type&&N(k),mt&&!e.length&...
function St (line 1) | function St(e){var t=[],r;n.scope&&ft();while(!st(k)){if("return"===k.va...
function xt (line 1) | function xt(){bt();if(u===k.type)switch(k.value){case"local":return $(),...
function Tt (line 1) | function Tt(){var e=k.value,t=Ht();return n.scope&&(ct("::"+e+"::"),pt(t...
function Nt (line 1) | function Nt(){return m(v.breakStatement())}
function Ct (line 1) | function Ct(){var e=k.value,t=Ht();return n.scope&&(t.isLabel=dt("::"+e+...
function kt (line 1) | function kt(){var e=St();return K("end"),m(v.doStatement(e))}
function Lt (line 1) | function Lt(){var e=qt();K("do");var t=St();return K("end"),m(v.whileSta...
function At (line 1) | function At(){var e=St();K("until");var t=qt();return m(v.repeatStatemen...
function Ot (line 1) | function Ot(){var e=[];if("end"!==k.value){var t=It();null!=t&&e.push(t)...
function Mt (line 1) | function Mt(){var e=[],t,n,r;mt&&(r=vt[vt.length-1],vt.push(r)),t=qt(),K...
function _t (line 1) | function _t(){var e=Ht(),t;n.scope&&ht(e);if(J("=")){var r=qt();K(",");v...
function Dt (line 1) | function Dt(){var e;if(a===k.type){var t=[],r=[];do e=Ht(),t.push(e);whi...
function Pt (line 1) | function Pt(){var e=k,t,n;mt&&(n=gt()),t=zt();if(null==t)return N(k);if(...
function Ht (line 1) | function Ht(){bt();var e=k.value;return a!==k.type&&T("<name>",k),$(),m(...
function Bt (line 1) | function Bt(e,t){var r=[];K("(");if(!J(")"))for(;;)if(a===k.type){var i=...
function jt (line 1) | function jt(){var e,t,r;mt&&(r=gt()),e=Ht(),n.scope&&pt(e,!1);while(J("....
function Ft (line 1) | function Ft(){var e=[],t,n;for(;;){bt();if(l===k.type&&J("["))t=qt(),K("...
function It (line 1) | function It(){var e=Ut(0);return e}
function qt (line 1) | function qt(){var e=It();if(null!=e)return e;T("<expression>",k)}
function Rt (line 1) | function Rt(e){var t=e.charCodeAt(0),n=e.length;if(1===n)switch(t){case ...
function Ut (line 1) | function Ut(e){var t=k.value,n,r;mt&&(r=gt());if(rt(k)){bt(),$();var i=U...
function zt (line 1) | function zt(){var e,t,r,i;mt&&(r=gt());if(a===k.type)t=k.value,e=Ht(),n....
function Wt (line 1) | function Wt(e){if(l===k.type)switch(k.value){case"(":$();var t=[],n=It()...
function Xt (line 1) | function Xt(){var e=o|f|c|h|p,n=k.value,r=k.type,i;mt&&(i=gt());if(r&e){...
function Vt (line 1) | function Vt(s,o){return"undefined"==typeof o&&"object"==typeof s&&(o=s,s...
function $t (line 1) | function $t(n){return t+=String(n),r=t.length,e}
function Jt (line 1) | function Jt(e){"undefined"!=typeof e&&$t(e),r=t.length,mt=n.locations||n...
function r (line 1) | function r(){}
function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
FILE: docs/gitbook/gitbook-plugin-ace/ace/worker-php.js
function t (line 1) | function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")r...
function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
function r (line 1) | function r(){}
function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
FILE: docs/gitbook/gitbook-plugin-ace/ace/worker-xml.js
function t (line 1) | function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")r...
function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
function d (line 1) | function d(){}
function v (line 1) | function v(e,t,n,r,i){function s(e){if(e>65535){e-=65536;var t=55296+(e>...
function m (line 1) | function m(e,t){return t.lineNumber=e.lineNumber,t.columnNumber=e.column...
function g (line 1) | function g(e,t,n,r,i){var s,d,v=++t,m=o;for(;;){var g=e.charAt(v);switch...
function y (line 1) | function y(e,t,n){var r=e.tagName,i=null,s=n[n.length-1].currentNSMap,o=...
function b (line 1) | function b(e,t,n,r,i){if(/^(?:script|textarea)$/i.test(n)){var s=e.index...
function w (line 1) | function w(e,t,n,r){var i=r[n];return i==null&&(i=r[n]=e.lastIndexOf("</...
function E (line 1) | function E(e,t){for(var n in e)t[n]=e[n]}
function S (line 1) | function S(e,t,n,r){var i=e.charAt(t+2);switch(i){case"-":if(e.charAt(t+...
function x (line 1) | function x(e,t,n){var r=e.indexOf("?>",t);if(r){var i=e.substring(t,r).m...
function T (line 1) | function T(e){}
function N (line 1) | function N(e,t){return e.__proto__=t,e}
function C (line 1) | function C(e,t){var n,r=[],i=/'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/...
function n (line 1) | function n(){}
function r (line 1) | function r(e,t){for(var n in e)t[n]=e[n]}
function i (line 1) | function i(e,t){var n=e.prototype;if(Object.create){var i=Object.create(...
function B (line 1) | function B(e,t){if(t instanceof Error)var n=t;else n=this,Error.call(thi...
function j (line 1) | function j(){}
function F (line 1) | function F(e,t){this._node=e,this._refresh=t,I(this)}
function I (line 1) | function I(e){var t=e._node._inc||e._node.ownerDocument._inc;if(e._inc!=...
function q (line 1) | function q(){}
function R (line 1) | function R(e,t){var n=e.length;while(n--)if(e[n]===t)return n}
function U (line 1) | function U(e,t,n,r){r?t[R(t,r)]=n:t[t.length++]=n;if(e){n.ownerElement=e...
function z (line 1) | function z(e,t,n){var r=R(t,n);if(!(r>=0))throw B(L,new Error);var i=t.l...
function W (line 1) | function W(e){this._features={};if(e)for(var t in e)this._features=e[t]}
function X (line 1) | function X(){}
function V (line 1) | function V(e){return e=="<"&&"<"||e==">"&&">"||e=="&"&&"&"||e=...
function $ (line 1) | function $(e,t){if(t(e))return!0;if(e=e.firstChild)do if($(e,t))return!0...
function J (line 1) | function J(){}
function K (line 1) | function K(e,t,n){e&&e._inc++;var r=n.namespaceURI;r=="http://www.w3.org...
function Q (line 1) | function Q(e,t,n,r){e&&e._inc++;var i=n.namespaceURI;i=="http://www.w3.o...
function G (line 1) | function G(e,t,n){if(e&&e._inc){e._inc++;var r=t.childNodes;if(n)r[r.len...
function Y (line 1) | function Y(e,t){var n=t.previousSibling,r=t.nextSibling;return n?n.nextS...
function Z (line 1) | function Z(e,t,n){var r=t.parentNode;r&&r.removeChild(t);if(t.nodeType==...
function et (line 1) | function et(e,t){var n=t.parentNode;if(n){var r=e.lastChild;n.removeChil...
function tt (line 1) | function tt(){this._nsMap={}}
function nt (line 1) | function nt(){}
function rt (line 1) | function rt(){}
function it (line 1) | function it(){}
function st (line 1) | function st(){}
function ot (line 1) | function ot(){}
function ut (line 1) | function ut(){}
function at (line 1) | function at(){}
function ft (line 1) | function ft(){}
function lt (line 1) | function lt(){}
function ct (line 1) | function ct(){}
function ht (line 1) | function ht(){}
function pt (line 1) | function pt(){}
function dt (line 1) | function dt(e,t){switch(e.nodeType){case u:var n=e.attributes,r=n.length...
function vt (line 1) | function vt(e,t,n){var r;switch(t.nodeType){case u:r=t.cloneNode(!1),r.o...
function mt (line 1) | function mt(e,t,n){var r=new t.constructor;for(var i in t){var s=t[i];ty...
function gt (line 1) | function gt(e,t,n){e[t]=n}
function yt (line 1) | function yt(e){switch(e.nodeType){case 1:case 11:var t=[];e=e.firstChild...
function s (line 1) | function s(e){this.options=e||{locator:{}}}
function o (line 1) | function o(e,t,n){function s(t){var s=e[t];if(!s)if(i)s=e.length==2?func...
function u (line 1) | function u(){this.cdata=!1}
function a (line 1) | function a(e,t){t.lineNumber=e.lineNumber,t.columnNumber=e.columnNumber}
function f (line 1) | function f(e){if(e)return"\n@"+(e.systemId||"")+"#[line:"+e.lineNumber+"...
function l (line 1) | function l(e,t,n){return typeof e=="string"?e.substr(t,n):e.length>=t+n|...
function c (line 1) | function c(e,t){e.currentElement?e.currentElement.appendChild(t):e.docum...
function r (line 1) | function r(){}
function w (line 1) | function w(e){try{return Object.defineProperty(e,"sentinel",{}),"sentine...
function H (line 1) | function H(e){return e=+e,e!==e?e=0:e!==0&&e!==1/0&&e!==-1/0&&(e=(e>0||-...
function B (line 1) | function B(e){var t=typeof e;return e===null||t==="undefined"||t==="bool...
function j (line 1) | function j(e){var t,n,r;if(B(e))return e;n=e.valueOf;if(typeof n=="funct...
function e (line 1) | function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}
FILE: docs/gitbook/gitbook-plugin-ace/ace/worker-xquery.js
function t (line 1) | function t(e,t){var n=e,r="";while(n){var i=t[n];if(typeof i=="string")r...
function r (line 1) | function r(e,t){throw console.log("Invalid Delta:",e),"Invalid Delta: "+t}
function i (line 1) | function i(e,t){return t.row>=0&&t.row<e.length&&t.column>=0&&t.column<=...
function s (line 1) | function s(e,t){t.action!="insert"&&t.action!="remove"&&r(t,"delta.actio...
function e (line 1) | function e(e,t,n){var r=n?e.column<=t.column:e.column<t.column;return e....
function t (line 1) | function t(t,n,r){var i=t.action=="insert",s=(i?1:-1)*(t.end.row-t.start...
function s (line 1) | function s(u,a){if(!n[u]){if(!t[u]){var f=typeof e=="function"&&e;if(!a&...
function s (line 1) | function s(e,t,n){n=n||i;var r=[];for(var s=t-1;s>=0;s--){if(!n.test(e[s...
function o (line 1) | function o(e,t){var n=0,r=e.length-1,i=Math.floor((r+n)/2);while(r>n&&i>...
function f (line 1) | function f(e){return{name:e,children:[],getParent:null,pos:{sl:0,sc:0,el...
function l (line 1) | function l(e){var t=f(e);r===null?(r=t,r.index=[],i=t):(t.getParent=i,i....
function c (line 1) | function c(){if(i.children.length>0){var e=i.children[0],s=null;for(var ...
function h (line 1) | function h(e,t,n){var r=n-o;i.value=s.substring(0,r),s=s.substring(r),o=...
function r (line 1) | function r(e,t){ic=t,ac=e,fc=e.length,s(0,0,0)}
function s (line 1) | function s(e,t,n){Xl=t,Vl=t,$l=e,Jl=t,Kl=n,Ql=0,cc=n,ec=-1,sc={},ic.rese...
function o (line 1) | function o(){ic.startNonterminal("Module",Vl);switch($l){case 170:ql(168...
function u (line 1) | function u(){ic.startNonterminal("VersionDecl",Vl),Pl(170),Il(120);switc...
function a (line 1) | function a(){ic.startNonterminal("LibraryModule",Vl),f(),Il(142),jl(),l(...
function f (line 1) | function f(){ic.startNonterminal("ModuleDecl",Vl),Pl(185),Il(64),Pl(187)...
function l (line 1) | function l(){ic.startNonterminal("Prolog",Vl);for(;;){Il(278);switch($l)...
function c (line 1) | function c(){ic.startNonterminal("Separator",Vl),Pl(54),ic.endNontermina...
function h (line 1) | function h(){ic.startNonterminal("Setter",Vl);switch($l){case 109:ql(194...
function p (line 1) | function p(){ic.startNonterminal("BoundarySpaceDecl",Vl),Pl(109),Il(36),...
function d (line 1) | function d(){ic.startNonterminal("DefaultCollationDecl",Vl),Pl(109),Il(4...
function v (line 1) | function v(){Hl(109),Il(49),Hl(110),Il(41),Hl(95),Il(15),Hl(7)}
function m (line 1) | function m(){ic.startNonterminal("BaseURIDecl",Vl),Pl(109),Il(35),Pl(84)...
function g (line 1) | function g(){ic.startNonterminal("ConstructionDecl",Vl),Pl(109),Il(44),P...
function y (line 1) | function y(){ic.startNonterminal("OrderingModeDecl",Vl),Pl(109),Il(71),P...
function b (line 1) | function b(){ic.startNonterminal("EmptyOrderDecl",Vl),Pl(109),Il(49),Pl(...
function w (line 1) | function w(){Hl(109),Il(49),Hl(110),Il(70),Hl(205),Il(52),Hl(124),Il(125...
function E (line 1) | function E(){ic.startNonterminal("CopyNamespacesDecl",Vl),Pl(109),Il(47)...
function S (line 1) | function S(){ic.startNonterminal("PreserveMode",Vl);switch($l){case 218:...
function x (line 1) | function x(){ic.startNonterminal("InheritMode",Vl);switch($l){case 159:P...
function T (line 1) | function T(){ic.startNonterminal("DecimalFormatDecl",Vl),Pl(109),Il(118)...
function N (line 1) | function N(){ic.startNonterminal("DFPropertyName",Vl);switch($l){case 10...
function C (line 1) | function C(){ic.startNonterminal("Import",Vl);switch($l){case 155:ql(130...
function k (line 1) | function k(){ic.startNonterminal("SchemaImport",Vl),Pl(155),Il(76),Pl(22...
function L (line 1) | function L(){ic.startNonterminal("SchemaPrefix",Vl);switch($l){case 187:...
function A (line 1) | function A(){ic.startNonterminal("ModuleImport",Vl),Pl(155),Il(63),Pl(18...
function O (line 1) | function O(){ic.startNonterminal("NamespaceDecl",Vl),Pl(109),Il(64),Pl(1...
function M (line 1) | function M(){ic.startNonterminal("DefaultNamespaceDecl",Vl),Pl(109),Il(4...
function _ (line 1) | function _(){Hl(109),Il(49),Hl(110),Il(119);switch($l){case 122:Hl(122);...
function D (line 1) | function D(){ic.startNonterminal("FTOptionDecl",Vl),Pl(109),Il(55),Pl(14...
function P (line 1) | function P(){ic.startNonterminal("AnnotatedDecl",Vl),Pl(109);for(;;){Il(...
function H (line 1) | function H(){ic.startNonterminal("CompatibilityAnnotation",Vl),Pl(263),i...
function B (line 1) | function B(){ic.startNonterminal("Annotation",Vl),Pl(33),Il(246),jl(),$a...
function j (line 1) | function j(){Hl(33),Il(246),Ja(),Il(193);if($l==35){Hl(35),Il(190),vi();...
function F (line 1) | function F(){ic.startNonterminal("VarDecl",Vl),Pl(268),Il(21),Pl(31),Il(...
function I (line 1) | function I(){ic.startNonterminal("VarValue",Vl),Wf(),ic.endNonterminal("...
function q (line 1) | function q(){ic.startNonterminal("VarDefaultValue",Vl),Wf(),ic.endNonter...
function R (line 1) | function R(){ic.startNonterminal("ContextItemDecl",Vl),Pl(109),Il(46),Pl...
function U (line 1) | function U(){ic.startNonterminal("ParamList",Vl),W();for(;;){Il(105);if(...
function z (line 1) | function z(){X();for(;;){Il(105);if($l!=42)break;Hl(42),Il(21),X()}}
function W (line 1) | function W(){ic.startNonterminal("Param",Vl),Pl(31),Il(246),jl(),$a(),Il...
function X (line 1) | function X(){Hl(31),Il(246),Ja(),Il(153),$l==80&&ks()}
function V (line 1) | function V(){ic.startNonterminal("FunctionBody",Vl),J(),ic.endNontermina...
function $ (line 1) | function $(){K()}
function J (line 1) | function J(){ic.startNonterminal("EnclosedExpr",Vl),Pl(281),Il(267),jl()...
function K (line 1) | function K(){Hl(281),Il(267),Y(),Hl(287)}
function Q (line 1) | function Q(){ic.startNonterminal("OptionDecl",Vl),Pl(109),Il(69),Pl(203)...
function G (line 1) | function G(){ic.startNonterminal("Expr",Vl),Wf();for(;;){if($l!=42)break...
function Y (line 1) | function Y(){Xf();for(;;){if($l!=42)break;Hl(42),Il(267),Xf()}}
function Z (line 1) | function Z(){ic.startNonterminal("FLWORExpr",Vl),tt();for(;;){Il(195);if...
function et (line 1) | function et(){nt();for(;;){Il(195);if($l==224)break;it()}sn()}
function tt (line 1) | function tt(){ic.startNonterminal("InitialClause",Vl);switch($l){case 13...
function nt (line 1) | function nt(){switch($l){case 139:ql(151);break;default:Wl=$l}switch(Wl)...
function rt (line 1) | function rt(){ic.startNonterminal("IntermediateClause",Vl);switch($l){ca...
function it (line 1) | function it(){switch($l){case 139:case 177:nt();break;case 272:qt();brea...
function st (line 1) | function st(){ic.startNonterminal("ForClause",Vl),Pl(139),Il(21),jl(),ut...
function ot (line 1) | function ot(){Hl(139),Il(21),at();for(;;){if($l!=42)break;Hl(42),Il(21),...
function ut (line 1) | function ut(){ic.startNonterminal("ForBinding",Vl),Pl(31),Il(246),jl(),T...
function at (line 1) | function at(){Hl(31),Il(246),Ni(),Il(182),$l==80&&ks(),Il(173),$l==73&&l...
function ft (line 1) | function ft(){ic.startNonterminal("AllowingEmpty",Vl),Pl(73),Il(52),Pl(1...
function lt (line 1) | function lt(){Hl(73),Il(52),Hl(124)}
function ct (line 1) | function ct(){ic.startNonterminal("PositionalVar",Vl),Pl(82),Il(21),Pl(3...
function ht (line 1) | function ht(){Hl(82),Il(21),Hl(31),Il(246),Ni()}
function pt (line 1) | function pt(){ic.startNonterminal("FTScoreVar",Vl),Pl(232),Il(21),Pl(31)...
function dt (line 1) | function dt(){Hl(232),Il(21),Hl(31),Il(246),Ni()}
function vt (line 1) | function vt(){ic.startNonterminal("LetClause",Vl),Pl(177),Il(100),jl(),g...
function mt (line 1) | function mt(){Hl(177),Il(100),yt();for(;;){if($l!=42)break;Hl(42),Il(100...
function gt (line 1) | function gt(){ic.startNonterminal("LetBinding",Vl);switch($l){case 31:Pl...
function yt (line 1) | function yt(){switch($l){case 31:Hl(31),Il(246),Ni(),Il(109),$l==80&&ks(...
function bt (line 1) | function bt(){ic.startNonterminal("WindowClause",Vl),Pl(139),Il(139);swi...
function wt (line 1) | function wt(){Hl(139),Il(139);switch($l){case 257:St();break;default:Tt()}}
function Et (line 1) | function Et(){ic.startNonterminal("TumblingWindowClause",Vl),Pl(257),Il(...
function St (line 1) | function St(){Hl(257),Il(88),Hl(275),Il(21),Hl(31),Il(246),Ni(),Il(114),...
function xt (line 1) | function xt(){ic.startNonterminal("SlidingWindowClause",Vl),Pl(239),Il(8...
function Tt (line 1) | function Tt(){Hl(239),Il(88),Hl(275),Il(21),Hl(31),Il(246),Ni(),Il(114),...
function Nt (line 1) | function Nt(){ic.startNonterminal("WindowStartCondition",Vl),Pl(242),Il(...
function Ct (line 1) | function Ct(){Hl(242),Il(181),Ot(),Il(86),Hl(271),Il(267),Xf()}
function kt (line 1) | function kt(){ic.startNonterminal("WindowEndCondition",Vl),$l==202&&Pl(2...
function Lt (line 1) | function Lt(){$l==202&&Hl(202),Il(53),Hl(127),Il(181),Ot(),Il(86),Hl(271...
function At (line 1) | function At(){ic.startNonterminal("WindowVars",Vl),$l==31&&(Pl(31),Il(24...
function Ot (line 1) | function Ot(){$l==31&&(Hl(31),Il(246),_t()),Il(174),$l==82&&ht(),Il(163)...
function Mt (line 1) | function Mt(){ic.startNonterminal("CurrentItem",Vl),$a(),ic.endNontermin...
function _t (line 1) | function _t(){Ja()}
function Dt (line 1) | function Dt(){ic.startNonterminal("PreviousItem",Vl),$a(),ic.endNontermi...
function Pt (line 1) | function Pt(){Ja()}
function Ht (line 1) | function Ht(){ic.startNonterminal("NextItem",Vl),$a(),ic.endNonterminal(...
function Bt (line 1) | function Bt(){Ja()}
function jt (line 1) | function jt(){ic.startNonterminal("CountClause",Vl),Pl(106),Il(21),Pl(31...
function Ft (line 1) | function Ft(){Hl(106),Il(21),Hl(31),Il(246),Ni()}
function It (line 1) | function It(){ic.startNonterminal("WhereClause",Vl),Pl(272),Il(267),jl()...
function qt (line 1) | function qt(){Hl(272),Il(267),Xf()}
function Rt (line 1) | function Rt(){ic.startNonterminal("GroupByClause",Vl),Pl(150),Il(37),Pl(...
function Ut (line 1) | function Ut(){Hl(150),Il(37),Hl(88),Il(267),Wt()}
function zt (line 1) | function zt(){ic.startNonterminal("GroupingSpecList",Vl),Xt();for(;;){Il...
function Wt (line 1) | function Wt(){Vt();for(;;){Il(198);if($l!=42)break;Hl(42),Il(267),Vt()}}
function Xt (line 1) | function Xt(){ic.startNonterminal("GroupingSpec",Vl);switch($l){case 31:...
function Vt (line 1) | function Vt(){switch($l){case 31:ql(246);break;default:Wl=$l}if(Wl==3103...
function $t (line 1) | function $t(){ic.startNonterminal("GroupingVariable",Vl),Pl(31),Il(246),...
function Jt (line 1) | function Jt(){Hl(31),Il(246),Ni()}
function Kt (line 1) | function Kt(){ic.startNonterminal("OrderByClause",Vl);switch($l){case 20...
function Qt (line 1) | function Qt(){switch($l){case 205:Hl(205),Il(37),Hl(88);break;default:Hl...
function Gt (line 1) | function Gt(){ic.startNonterminal("OrderSpecList",Vl),Zt();for(;;){Il(19...
function Yt (line 1) | function Yt(){en();for(;;){Il(198);if($l!=42)break;Hl(42),Il(267),en()}}
function Zt (line 1) | function Zt(){ic.startNonterminal("OrderSpec",Vl),Wf(),jl(),tn(),ic.endN...
function en (line 1) | function en(){Xf(),nn()}
function tn (line 1) | function tn(){ic.startNonterminal("OrderModifier",Vl);if($l==81||$l==114...
function nn (line 1) | function nn(){if($l==81||$l==114)switch($l){case 81:Hl(81);break;default...
function rn (line 1) | function rn(){ic.startNonterminal("ReturnClause",Vl),Pl(224),Il(267),jl(...
function sn (line 1) | function sn(){Hl(224),Il(267),Xf()}
function on (line 1) | function on(){ic.startNonterminal("QuantifiedExpr",Vl);switch($l){case 2...
function un (line 1) | function un(){switch($l){case 240:Hl(240);break;default:Hl(130)}Il(21),f...
function an (line 1) | function an(){ic.startNonterminal("QuantifiedVarDecl",Vl),Pl(31),Il(246)...
function fn (line 1) | function fn(){Hl(31),Il(246),Ni(),Il(114),$l==80&&ks(),Il(56),Hl(156),Il...
function ln (line 1) | function ln(){ic.startNonterminal("SwitchExpr",Vl),Pl(248),Il(22),Pl(35)...
function cn (line 1) | function cn(){Hl(248),Il(22),Hl(35),Il(267),Y(),Hl(38);for(;;){Il(38),pn...
function hn (line 1) | function hn(){ic.startNonterminal("SwitchCaseClause",Vl);for(;;){Pl(89),...
function pn (line 1) | function pn(){for(;;){Hl(89),Il(267),vn();if($l!=89)break}Hl(224),Il(267...
function dn (line 1) | function dn(){ic.startNonterminal("SwitchCaseOperand",Vl),Wf(),ic.endNon...
function vn (line 1) | function vn(){Xf()}
function mn (line 1) | function mn(){ic.startNonterminal("TypeswitchExpr",Vl),Pl(259),Il(22),Pl...
function gn (line 1) | function gn(){Hl(259),Il(22),Hl(35),Il(267),Y(),Hl(38);for(;;){Il(38),bn...
function yn (line 1) | function yn(){ic.startNonterminal("CaseClause",Vl),Pl(89),Il(258),$l==31...
function bn (line 1) | function bn(){Hl(89),Il(258),$l==31&&(Hl(31),Il(246),Ni(),Il(33),Hl(80))...
function wn (line 1) | function wn(){ic.startNonterminal("SequenceTypeUnion",Vl),Ls();for(;;){I...
function En (line 1) | function En(){As();for(;;){Il(138);if($l!=284)break;Hl(284),Il(254),As()}}
function Sn (line 1) | function Sn(){ic.startNonterminal("IfExpr",Vl),Pl(154),Il(22),Pl(35),Il(...
function xn (line 1) | function xn(){Hl(154),Il(22),Hl(35),Il(267),Y(),Hl(38),Il(80),Hl(250),Il...
function Tn (line 1) | function Tn(){ic.startNonterminal("TryCatchExpr",Vl),Cn();for(;;){Il(39)...
function Nn (line 1) | function Nn(){kn();for(;;){Il(39),Mn(),Il(208);if($l!=92)break}}
function Cn (line 1) | function Cn(){ic.startNonterminal("TryClause",Vl),Pl(256),Il(90),Pl(281)...
function kn (line 1) | function kn(){Hl(256),Il(90),Hl(281),Il(267),An(),Hl(287)}
function Ln (line 1) | function Ln(){ic.startNonterminal("TryTargetExpr",Vl),G(),ic.endNontermi...
function An (line 1) | function An(){Y()}
function On (line 1) | function On(){ic.startNonterminal("CatchClause",Vl),Pl(92),Il(249),jl(),...
function Mn (line 1) | function Mn(){Hl(92),Il(249),Dn(),Hl(281),Il(267),Y(),Hl(287)}
function _n (line 1) | function _n(){ic.startNonterminal("CatchErrorList",Vl),Yr();for(;;){Il(1...
function Dn (line 1) | function Dn(){Zr();for(;;){Il(140);if($l!=284)break;Hl(284),Il(249),Zr()}}
function Pn (line 1) | function Pn(){ic.startNonterminal("OrExpr",Vl),Bn();for(;;){if($l!=204)b...
function Hn (line 1) | function Hn(){jn();for(;;){if($l!=204)break;Hl(204),Il(267),jn()}}
function Bn (line 1) | function Bn(){ic.startNonterminal("AndExpr",Vl),Fn();for(;;){if($l!=76)b...
function jn (line 1) | function jn(){In();for(;;){if($l!=76)break;Hl(76),Il(267),In()}}
function Fn (line 1) | function Fn(){ic.startNonterminal("NotExpr",Vl),$l==196&&Pl(196),Il(266)...
function In (line 1) | function In(){$l==196&&Hl(196),Il(266),Rn()}
function qn (line 1) | function qn(){ic.startNonterminal("ComparisonExpr",Vl),Un();if($l==27||$...
function Rn (line 1) | function Rn(){zn();if($l==27||$l==55||$l==58||$l==59||$l==61||$l==62||$l...
function Un (line 1) | function Un(){ic.startNonterminal("FTContainsExpr",Vl),Wn(),$l==100&&(Pl...
function zn (line 1) | function zn(){Xn(),$l==100&&(Hl(100),Il(79),Hl(249),Il(177),uu(),$l==277...
function Wn (line 1) | function Wn(){ic.startNonterminal("StringConcatExpr",Vl),Vn();for(;;){if...
function Xn (line 1) | function Xn(){$n();for(;;){if($l!=285)break;Hl(285),Il(266),$n()}}
function Vn (line 1) | function Vn(){ic.startNonterminal("RangeExpr",Vl),Jn(),$l==253&&(Pl(253)...
function $n (line 1) | function $n(){Kn(),$l==253&&(Hl(253),Il(266),Kn())}
function Jn (line 1) | function Jn(){ic.startNonterminal("AdditiveExpr",Vl),Qn();for(;;){if($l!...
function Kn (line 1) | function Kn(){Gn();for(;;){if($l!=41&&$l!=43)break;switch($l){case 41:Hl...
function Qn (line 1) | function Qn(){ic.startNonterminal("MultiplicativeExpr",Vl),Yn();for(;;){...
function Gn (line 1) | function Gn(){Zn();for(;;){if($l!=39&&$l!=119&&$l!=153&&$l!=183)break;sw...
function Yn (line 1) | function Yn(){ic.startNonterminal("UnionExpr",Vl),er();for(;;){if($l!=26...
function Zn (line 1) | function Zn(){tr();for(;;){if($l!=260&&$l!=284)break;switch($l){case 260...
function er (line 1) | function er(){ic.startNonterminal("IntersectExceptExpr",Vl),nr();for(;;)...
function tr (line 1) | function tr(){rr();for(;;){Il(222);if($l!=132&&$l!=164)break;switch($l){...
function nr (line 1) | function nr(){ic.startNonterminal("InstanceofExpr",Vl),ir(),Il(223),$l==...
function rr (line 1) | function rr(){sr(),Il(223),$l==162&&(Hl(162),Il(67),Hl(200),Il(254),As())}
function ir (line 1) | function ir(){ic.startNonterminal("TreatExpr",Vl),or(),Il(224),$l==254&&...
function sr (line 1) | function sr(){ur(),Il(224),$l==254&&(Hl(254),Il(33),Hl(80),Il(254),As())}
function or (line 1) | function or(){ic.startNonterminal("CastableExpr",Vl),ar(),Il(225),$l==91...
function ur (line 1) | function ur(){fr(),Il(225),$l==91&&(Hl(91),Il(33),Hl(80),Il(246),Ns())}
function ar (line 1) | function ar(){ic.startNonterminal("CastExpr",Vl),lr(),Il(227),$l==90&&(P...
function fr (line 1) | function fr(){cr(),Il(227),$l==90&&(Hl(90),Il(33),Hl(80),Il(246),Ns())}
function lr (line 1) | function lr(){ic.startNonterminal("UnaryExpr",Vl);for(;;){Il(266);if($l!...
function cr (line 1) | function cr(){for(;;){Il(266);if($l!=41&&$l!=43)break;switch($l){case 43...
function hr (line 1) | function hr(){ic.startNonterminal("ValueExpr",Vl);switch($l){case 266:ql...
function pr (line 1) | function pr(){switch($l){case 266:ql(188);break;default:Wl=$l}switch(Wl)...
function dr (line 1) | function dr(){ic.startNonterminal("SimpleMapExpr",Vl),Or();for(;;){if($l...
function vr (line 1) | function vr(){Mr();for(;;){if($l!=26)break;Hl(26),Il(263),Mr()}}
function mr (line 1) | function mr(){ic.startNonterminal("GeneralComp",Vl);switch($l){case 61:P...
function gr (line 1) | function gr(){switch($l){case 61:Hl(61);break;case 27:Hl(27);break;case ...
function yr (line 1) | function yr(){ic.startNonterminal("ValueComp",Vl);switch($l){case 129:Pl...
function br (line 1) | function br(){switch($l){case 129:Hl(129);break;case 189:Hl(189);break;c...
function wr (line 1) | function wr(){ic.startNonterminal("NodeComp",Vl);switch($l){case 166:Pl(...
function Er (line 1) | function Er(){switch($l){case 166:Hl(166);break;case 58:Hl(58);break;def...
function Sr (line 1) | function Sr(){ic.startNonterminal("ValidateExpr",Vl),Pl(266),Il(175);if(...
function xr (line 1) | function xr(){Hl(266),Il(175);if($l!=281)switch($l){case 258:Hl(258),Il(...
function Tr (line 1) | function Tr(){ic.startNonterminal("ValidationMode",Vl);switch($l){case 1...
function Nr (line 1) | function Nr(){switch($l){case 174:Hl(174);break;default:Hl(245)}}
function Cr (line 1) | function Cr(){ic.startNonterminal("ExtensionExpr",Vl);for(;;){jl(),Lr(),...
function kr (line 1) | function kr(){for(;;){Ar(),Il(104);if($l!=36)break}Hl(281),Il(275),$l!=2...
function Lr (line 1) | function Lr(){ic.startNonterminal("Pragma",Vl),Pl(36),Rl(243),$l==21&&Pl...
function Ar (line 1) | function Ar(){Hl(36),Rl(243),$l==21&&Hl(21),Ja(),Rl(10),$l==21&&(Hl(21),...
function Or (line 1) | function Or(){ic.startNonterminal("PathExpr",Vl);switch($l){case 47:Pl(4...
function Mr (line 1) | function Mr(){switch($l){case 47:Hl(47),Il(289);switch($l){case 25:case ...
function _r (line 1) | function _r(){ic.startNonterminal("RelativePathExpr",Vl),ei();for(;;){sw...
function Dr (line 1) | function Dr(){ti();for(;;){switch($l){case 26:ql(265);break;default:Wl=$...
function Pr (line 1) | function Pr(){ic.startNonterminal("StepExpr",Vl);switch($l){case 83:ql(2...
function Hr (line 1) | function Hr(){switch($l){case 83:ql(288);break;case 122:ql(287);break;ca...
function Br (line 1) | function Br(){ic.startNonterminal("AxisStep",Vl);switch($l){case 74:case...
function jr (line 1) | function jr(){switch($l){case 74:case 75:case 210:case 216:case 217:ql(2...
function Fr (line 1) | function Fr(){ic.startNonterminal("ForwardStep",Vl);switch($l){case 83:q...
function Ir (line 1) | function Ir(){switch($l){case 83:ql(236);break;case 94:case 112:case 113...
function qr (line 1) | function qr(){ic.startNonterminal("ForwardAxis",Vl);switch($l){case 94:P...
function Rr (line 1) | function Rr(){switch($l){case 94:Hl(94),Il(27),Hl(52);break;case 112:Hl(...
function Ur (line 1) | function Ur(){ic.startNonterminal("AbbrevForwardStep",Vl),$l==67&&Pl(67)...
function zr (line 1) | function zr(){$l==67&&Hl(67),Il(249),Gr()}
function Wr (line 1) | function Wr(){ic.startNonterminal("ReverseStep",Vl);switch($l){case 46:J...
function Xr (line 1) | function Xr(){switch($l){case 46:Kr();break;default:$r(),Il(249),Gr()}}
function Vr (line 1) | function Vr(){ic.startNonterminal("ReverseAxis",Vl);switch($l){case 210:...
function $r (line 1) | function $r(){switch($l){case 210:Hl(210),Il(27),Hl(52);break;case 74:Hl...
function Jr (line 1) | function Jr(){ic.startNonterminal("AbbrevReverseStep",Vl),Pl(46),ic.endN...
function Kr (line 1) | function Kr(){Hl(46)}
function Qr (line 1) | function Qr(){ic.startNonterminal("NodeTest",Vl);switch($l){case 83:case...
function Gr (line 1) | function Gr(){switch($l){case 83:case 97:case 121:case 122:case 188:case...
function Yr (line 1) | function Yr(){ic.startNonterminal("NameTest",Vl);switch($l){case 5:Pl(5)...
function Zr (line 1) | function Zr(){switch($l){case 5:Hl(5);break;default:Ja()}}
function ei (line 1) | function ei(){ic.startNonterminal("PostfixExpr",Vl),yl();for(;;){Il(235)...
function ti (line 1) | function ti(){bl();for(;;){Il(235);if($l!=35&&$l!=45&&$l!=69)break;switc...
function ni (line 1) | function ni(){ic.startNonterminal("ObjectLookup",Vl),Pl(45),Il(251);swit...
function ri (line 1) | function ri(){Hl(45),Il(251);switch($l){case 11:Hl(11);break;case 35:ki(...
function ii (line 1) | function ii(){ic.startNonterminal("ArrayLookup",Vl),Pl(69),Il(31),Pl(69)...
function si (line 1) | function si(){Hl(69),Il(31),Hl(69),Il(267),Y(),Hl(70),Il(32),Hl(70)}
function oi (line 1) | function oi(){ic.startNonterminal("ArrayUnboxing",Vl),Pl(69),Il(32),Pl(7...
function ui (line 1) | function ui(){Hl(69),Il(32),Hl(70)}
function ai (line 1) | function ai(){ic.startNonterminal("ArgumentList",Vl),Pl(35),Il(280);if($...
function fi (line 1) | function fi(){Hl(35),Il(280);if($l!=38){ji();for(;;){Il(105);if($l!=42)b...
function li (line 1) | function li(){ic.startNonterminal("PredicateList",Vl);for(;;){Il(228);if...
function ci (line 1) | function ci(){for(;;){Il(228);if($l!=69)break;pi()}}
function hi (line 1) | function hi(){ic.startNonterminal("Predicate",Vl),Pl(69),Il(267),jl(),G(...
function pi (line 1) | function pi(){Hl(69),Il(267),Y(),Hl(70)}
function di (line 1) | function di(){ic.startNonterminal("Literal",Vl);switch($l){case 11:Pl(11...
function vi (line 1) | function vi(){switch($l){case 11:Hl(11);break;case 135:case 255:gi();bre...
function mi (line 1) | function mi(){ic.startNonterminal("BooleanLiteral",Vl);switch($l){case 2...
function gi (line 1) | function gi(){switch($l){case 255:Hl(255);break;default:Hl(135)}}
function yi (line 1) | function yi(){ic.startNonterminal("NullLiteral",Vl),Pl(197),ic.endNonter...
function bi (line 1) | function bi(){Hl(197)}
function wi (line 1) | function wi(){ic.startNonterminal("NumericLiteral",Vl);switch($l){case 8...
function Ei (line 1) | function Ei(){switch($l){case 8:Hl(8);break;case 9:Hl(9);break;default:H...
function Si (line 1) | function Si(){ic.startNonterminal("VarRef",Vl),Pl(31),Il(246),jl(),Ti(),...
function xi (line 1) | function xi(){Hl(31),Il(246),Ni()}
function Ti (line 1) | function Ti(){ic.startNonterminal("VarName",Vl),$a(),ic.endNonterminal("...
function Ni (line 1) | function Ni(){Ja()}
function Ci (line 1) | function Ci(){ic.startNonterminal("ParenthesizedExpr",Vl),Pl(35),Il(270)...
function ki (line 1) | function ki(){Hl(35),Il(270),$l!=38&&Y(),Hl(38)}
function Li (line 1) | function Li(){ic.startNonterminal("ContextItemExpr",Vl),Pl(32),ic.endNon...
function Ai (line 1) | function Ai(){Hl(32)}
function Oi (line 1) | function Oi(){ic.startNonterminal("OrderedExpr",Vl),Pl(206),Il(90),Pl(28...
function Mi (line 1) | function Mi(){Hl(206),Il(90),Hl(281),Il(267),Y(),Hl(287)}
function _i (line 1) | function _i(){ic.startNonterminal("UnorderedExpr",Vl),Pl(262),Il(90),Pl(...
function Di (line 1) | function Di(){Hl(262),Il(90),Hl(281),Il(267),Y(),Hl(287)}
function Pi (line 1) | function Pi(){ic.startNonterminal("FunctionCall",Vl),Ka(),Il(22),jl(),ai...
function Hi (line 1) | function Hi(){Qa(),Il(22),fi()}
function Bi (line 1) | function Bi(){ic.startNonterminal("Argument",Vl);switch($l){case 65:Fi()...
function ji (line 1) | function ji(){switch($l){case 65:Ii();break;default:Xf()}}
function Fi (line 1) | function Fi(){ic.startNonterminal("ArgumentPlaceholder",Vl),Pl(65),ic.en...
function Ii (line 1) | function Ii(){Hl(65)}
function qi (line 1) | function qi(){ic.startNonterminal("Constructor",Vl);switch($l){case 55:c...
function Ri (line 1) | function Ri(){switch($l){case 55:case 56:case 60:zi();break;default:us()}}
function Ui (line 1) | function Ui(){ic.startNonterminal("DirectConstructor",Vl);switch($l){cas...
function zi (line 1) | function zi(){switch($l){case 55:Xi();break;case 56:rs();break;default:s...
function Wi (line 1) | function Wi(){ic.startNonterminal("DirElemConstructor",Vl),Pl(55),Rl(4),...
function Xi (line 1) | function Xi(){Hl(55),Rl(4),Hl(20),$i();switch($l){case 49:Hl(49);break;d...
function Vi (line 1) | function Vi(){ic.startNonterminal("DirAttributeList",Vl);for(;;){Rl(19);...
function $i (line 1) | function $i(){for(;;){Rl(19);if($l!=21)break;Hl(21),Rl(94),$l==20&&(Hl(2...
function Ji (line 1) | function Ji(){ic.startNonterminal("DirAttributeValue",Vl),Rl(14);switch(...
function Ki (line 1) | function Ki(){Rl(14);switch($l){case 28:Hl(28);for(;;){Rl(185);if($l==28...
function Qi (line 1) | function Qi(){ic.startNonterminal("QuotAttrValueContent",Vl);switch($l){...
function Gi (line 1) | function Gi(){switch($l){case 16:Hl(16);break;default:sl()}}
function Yi (line 1) | function Yi(){ic.startNonterminal("AposAttrValueContent",Vl);switch($l){...
function Zi (line 1) | function Zi(){switch($l){case 17:Hl(17);break;default:sl()}}
function es (line 1) | function es(){ic.startNonterminal("DirElemContent",Vl);switch($l){case 5...
function ts (line 1) | function ts(){switch($l){case 55:case 56:case 60:zi();break;case 4:Hl(4)...
function ns (line 1) | function ns(){ic.startNonterminal("DirCommentConstructor",Vl),Pl(56),Rl(...
function rs (line 1) | function rs(){Hl(56),Rl(1),Hl(2),Rl(6),Hl(44)}
function is (line 1) | function is(){ic.startNonterminal("DirPIConstructor",Vl),Pl(60),Rl(3),Pl...
function ss (line 1) | function ss(){Hl(60),Rl(3),Hl(18),Rl(13),$l==21&&(Hl(21),Rl(2),Hl(3)),Rl...
function os (line 1) | function os(){ic.startNonterminal("ComputedConstructor",Vl);switch($l){c...
function us (line 1) | function us(){switch($l){case 120:fl();break;case 122:fs();break;case 83...
function as (line 1) | function as(){ic.startNonterminal("CompElemConstructor",Vl),Pl(122),Il(2...
function fs (line 1) | function fs(){Hl(122),Il(250);switch($l){case 281:Hl(281),Il(267),Y(),Hl...
function ls (line 1) | function ls(){ic.startNonterminal("CompNamespaceConstructor",Vl),Pl(187)...
function cs (line 1) | function cs(){Hl(187),Il(242);switch($l){case 281:Hl(281),Il(267),vs(),H...
function hs (line 1) | function hs(){ic.startNonterminal("Prefix",Vl),Ga(),ic.endNonterminal("P...
function ps (line 1) | function ps(){Ya()}
function ds (line 1) | function ds(){ic.startNonterminal("PrefixExpr",Vl),G(),ic.endNonterminal...
function vs (line 1) | function vs(){Y()}
function ms (line 1) | function ms(){ic.startNonterminal("URIExpr",Vl),G(),ic.endNonterminal("U...
function gs (line 1) | function gs(){Y()}
function ys (line 1) | function ys(){ic.startNonterminal("FunctionItemExpr",Vl);switch($l){case...
function bs (line 1) | function bs(){switch($l){case 147:ql(95);break;default:Wl=$l}switch(Wl){...
function ws (line 1) | function ws(){ic.startNonterminal("NamedFunctionRef",Vl),$a(),Il(20),Pl(...
function Es (line 1) | function Es(){Ja(),Il(20),Hl(29),Il(16),Hl(8)}
function Ss (line 1) | function Ss(){ic.startNonterminal("InlineFunctionExpr",Vl);for(;;){Il(10...
function xs (line 1) | function xs(){for(;;){Il(101);if($l!=33)break;j()}Hl(147),Il(22),Hl(35),...
function Ts (line 1) | function Ts(){ic.startNonterminal("SingleType",Vl),ko(),Il(226),$l==65&&...
function Ns (line 1) | function Ns(){Lo(),Il(226),$l==65&&Hl(65)}
function Cs (line 1) | function Cs(){ic.startNonterminal("TypeDeclaration",Vl),Pl(80),Il(254),j...
function ks (line 1) | function ks(){Hl(80),Il(254),As()}
function Ls (line 1) | function Ls(){ic.startNonterminal("SequenceType",Vl);switch($l){case 35:...
function As (line 1) | function As(){switch($l){case 35:ql(259);break;case 125:ql(233);break;de...
function Os (line 1) | function Os(){ic.startNonterminal("OccurrenceIndicator",Vl);switch($l){c...
function Ms (line 1) | function Ms(){switch($l){case 65:Hl(65);break;case 40:Hl(40);break;defau...
function _s (line 1) | function _s(){ic.startNonterminal("ItemType",Vl);switch($l){case 79:case...
function Ds (line 1) | function Ds(){switch($l){case 79:case 83:case 97:case 121:case 122:case ...
function Ps (line 1) | function Ps(){ic.startNonterminal("JSONTest",Vl);switch($l){case 169:Fs(...
function Hs (line 1) | function Hs(){switch($l){case 169:Is();break;case 198:Rs();break;default...
function Bs (line 1) | function Bs(){ic.startNonterminal("StructuredItemTest",Vl),Pl(247),Il(23...
function js (line 1) | function js(){Hl(247),Il(233),$l==35&&(Hl(35),Il(23),Hl(38))}
function Fs (line 1) | function Fs(){ic.startNonterminal("JSONItemTest",Vl),Pl(169),Il(233),$l=...
function Is (line 1) | function Is(){Hl(169),Il(233),$l==35&&(Hl(35),Il(23),Hl(38))}
function qs (line 1) | function qs(){ic.startNonterminal("JSONObjectTest",Vl),Pl(198),Il(233),$...
function Rs (line 1) | function Rs(){Hl(198),Il(233),$l==35&&(Hl(35),Il(23),Hl(38))}
function Us (line 1) | function Us(){ic.startNonterminal("JSONArrayTest",Vl),Pl(79),Il(233),$l=...
function zs (line 1) | function zs(){Hl(79),Il(233),$l==35&&(Hl(35),Il(23),Hl(38))}
function Ws (line 1) | function Ws(){ic.startNonterminal("AtomicOrUnionType",Vl),$a(),ic.endNon...
function Xs (line 1) | function Xs(){Ja()}
function Vs (line 1) | function Vs(){ic.startNonterminal("KindTest",Vl);switch($l){case 121:Qs(...
function $s (line 1) | function $s(){switch($l){case 121:Gs();break;case 122:mo();break;case 83...
function Js (line 1) | function Js(){ic.startNonterminal("AnyKindTest",Vl),Pl(194),Il(22),Pl(35...
function Ks (line 1) | function Ks(){Hl(194),Il(22),Hl(35),Il(23),Hl(38)}
function Qs (line 1) | function Qs(){ic.startNonterminal("DocumentTest",Vl),Pl(121),Il(22),Pl(3...
function Gs (line 1) | function Gs(){Hl(121),Il(22),Hl(35),Il(154);if($l!=38)switch($l){case 12...
function Ys (line 1) | function Ys(){ic.startNonterminal("TextTest",Vl),Pl(249),Il(22),Pl(35),I...
function Zs (line 1) | function Zs(){Hl(249),Il(22),Hl(35),Il(23),Hl(38)}
function eo (line 1) | function eo(){ic.startNonterminal("CommentTest",Vl),Pl(97),Il(22),Pl(35)...
function to (line 1) | function to(){Hl(97),Il(22),Hl(35),Il(23),Hl(38)}
function no (line 1) | function no(){ic.startNonterminal("NamespaceNodeTest",Vl),Pl(188),Il(22)...
function ro (line 1) | function ro(){Hl(188),Il(22),Hl(35),Il(23),Hl(38)}
function io (line 1) | function io(){ic.startNonterminal("PITest",Vl),Pl(220),Il(22),Pl(35),Il(...
function so (line 1) | function so(){Hl(220),Il(22),Hl(35),Il(244);if($l!=38)switch($l){case 11...
function oo (line 1) | function oo(){ic.startNonterminal("AttributeTest",Vl),Pl(83),Il(22),Pl(3...
function uo (line 1) | function uo(){Hl(83),Il(22),Hl(35),Il(255),$l!=38&&(fo(),Il(105),$l==42&...
function ao (line 1) | function ao(){ic.startNonterminal("AttribNameOrWildcard",Vl);switch($l){...
function fo (line 1) | function fo(){switch($l){case 39:Hl(39);break;default:To()}}
function lo (line 1) | function lo(){ic.startNonterminal("SchemaAttributeTest",Vl),Pl(230),Il(2...
function co (line 1) | function co(){Hl(230),Il(22),Hl(35),Il(246),po(),Il(23),Hl(38)}
function ho (line 1) | function ho(){ic.startNonterminal("AttributeDeclaration",Vl),xo(),ic.end...
function po (line 1) | function po(){To()}
function vo (line 1) | function vo(){ic.startNonterminal("ElementTest",Vl),Pl(122),Il(22),Pl(35...
function mo (line 1) | function mo(){Hl(122),Il(22),Hl(35),Il(255),$l!=38&&(yo(),Il(105),$l==42...
function go (line 1) | function go(){ic.startNonterminal("ElementNameOrWildcard",Vl);switch($l)...
function yo (line 1) | function yo(){switch($l){case 39:Hl(39);break;default:Co()}}
function bo (line 1) | function bo(){ic.startNonterminal("SchemaElementTest",Vl),Pl(231),Il(22)...
function wo (line 1) | function wo(){Hl(231),Il(22),Hl(35),Il(246),So(),Il(23),Hl(38)}
function Eo (line 1) | function Eo(){ic.startNonterminal("ElementDeclaration",Vl),No(),ic.endNo...
function So (line 1) | function So(){Co()}
function xo (line 1) | function xo(){ic.startNonterminal("AttributeName",Vl),$a(),ic.endNonterm...
function To (line 1) | function To(){Ja()}
function No (line 1) | function No(){ic.startNonterminal("ElementName",Vl),$a(),ic.endNontermin...
function Co (line 1) | function Co(){Ja()}
function ko (line 1) | function ko(){ic.startNonterminal("SimpleTypeName",Vl),Ao(),ic.endNonter...
function Lo (line 1) | function Lo(){Oo()}
function Ao (line 1) | function Ao(){ic.startNonterminal("TypeName",Vl),$a(),ic.endNonterminal(...
function Oo (line 1) | function Oo(){Ja()}
function Mo (line 1) | function Mo(){ic.startNonterminal("FunctionTest",Vl);for(;;){Il(101);if(...
function _o (line 1) | function _o(){for(;;){Il(101);if($l!=33)break;j()}switch($l){case 147:ql...
function Do (line 1) | function Do(){ic.startNonterminal("AnyFunctionTest",Vl),Pl(147),Il(22),P...
function Po (line 1) | function Po(){Hl(147),Il(22),Hl(35),Il(24),Hl(39),Il(23),Hl(38)}
function Ho (line 1) | function Ho(){ic.startNonterminal("TypedFunctionTest",Vl),Pl(147),Il(22)...
function Bo (line 1) | function Bo(){Hl(147),Il(22),Hl(35),Il(259);if($l!=38){As();for(;;){Il(1...
function jo (line 1) | function jo(){ic.startNonterminal("ParenthesizedItemType",Vl),Pl(35),Il(...
function Fo (line 1) | function Fo(){Hl(35),Il(254),Ds(),Il(23),Hl(38)}
function Io (line 1) | function Io(){ic.startNonterminal("RevalidationDecl",Vl),Pl(109),Il(75),...
function qo (line 1) | function qo(){ic.startNonterminal("InsertExprTargetChoice",Vl);switch($l...
function Ro (line 1) | function Ro(){switch($l){case 71:Hl(71);break;case 85:Hl(85);break;defau...
function Uo (line 1) | function Uo(){ic.startNonterminal("InsertExpr",Vl),Pl(161),Il(133);switc...
function zo (line 1) | function zo(){Hl(161),Il(133);switch($l){case 194:Hl(194);break;default:...
function Wo (line 1) | function Wo(){ic.startNonterminal("DeleteExpr",Vl),Pl(111),Il(133);switc...
function Xo (line 1) | function Xo(){Hl(111),Il(133);switch($l){case 194:Hl(194);break;default:...
function Vo (line 1) | function Vo(){ic.startNonterminal("ReplaceExpr",Vl),Pl(223),Il(134),$l==...
function $o (line 1) | function $o(){Hl(223),Il(134),$l==267&&(Hl(267),Il(67),Hl(200)),Il(65),H...
function Jo (line 1) | function Jo(){ic.startNonterminal("RenameExpr",Vl),Pl(222),Il(65),Pl(194...
function Ko (line 1) | function Ko(){Hl(222),Il(65),Hl(194),Il(267),Zo(),Hl(80),Il(267),tu()}
function Qo (line 1) | function Qo(){ic.startNonterminal("SourceExpr",Vl),Wf(),ic.endNontermina...
function Go (line 1) | function Go(){Xf()}
function Yo (line 1) | function Yo(){ic.startNonterminal("TargetExpr",Vl),Wf(),ic.endNontermina...
function Zo (line 1) | function Zo(){Xf()}
function eu (line 1) | function eu(){ic.startNonterminal("NewNameExpr",Vl),Wf(),ic.endNontermin...
function tu (line 1) | function tu(){Xf()}
function nu (line 1) | function nu(){ic.startNonterminal("TransformExpr",Vl),Pl(104),Il(21),jl(...
function ru (line 1) | function ru(){Hl(104),Il(21),su();for(;;){if($l!=42)break;Hl(42),Il(21),...
function iu (line 1) | function iu(){ic.startNonterminal("TransformSpec",Vl),Pl(31),Il(246),jl(...
function su (line 1) | function su(){Hl(31),Il(246),Ni(),Il(28),Hl(53),Il(267),Xf()}
function ou (line 1) | function ou(){ic.startNonterminal("FTSelection",Vl),lu();for(;;){Il(212)...
function uu (line 1) | function uu(){cu();for(;;){Il(212);switch($l){case 82:ql(161);break;defa...
function au (line 1) | function au(){ic.startNonterminal("FTWeight",Vl),Pl(270),Il(90),Pl(281),...
function fu (line 1) | function fu(){Hl(270),Il(90),Hl(281),Il(267),Y(),Hl(287)}
function lu (line 1) | function lu(){ic.startNonterminal("FTOr",Vl),hu();for(;;){if($l!=146)bre...
function cu (line 1) | function cu(){pu();for(;;){if($l!=146)break;Hl(146),Il(177),pu()}}
function hu (line 1) | function hu(){ic.startNonterminal("FTAnd",Vl),du();for(;;){if($l!=144)br...
function pu (line 1) | function pu(){vu();for(;;){if($l!=144)break;Hl(144),Il(177),vu()}}
function du (line 1) | function du(){ic.startNonterminal("FTMildNot",Vl),mu();for(;;){Il(213);i...
function vu (line 1) | function vu(){gu();for(;;){Il(213);if($l!=196)break;Hl(196),Il(56),Hl(15...
function mu (line 1) | function mu(){ic.startNonterminal("FTUnaryNot",Vl),$l==145&&Pl(145),Il(1...
function gu (line 1) | function gu(){$l==145&&Hl(145),Il(164),bu()}
function yu (line 1) | function yu(){ic.startNonterminal("FTPrimaryWithOptions",Vl),wu(),Il(214...
function bu (line 1) | function bu(){Eu(),Il(214),$l==265&&Gu(),$l==270&&fu()}
function wu (line 1) | function wu(){ic.startNonterminal("FTPrimary",Vl);switch($l){case 35:Pl(...
function Eu (line 1) | function Eu(){switch($l){case 35:Hl(35),Il(177),uu(),Hl(38);break;case 3...
function Su (line 1) | function Su(){ic.startNonterminal("FTWords",Vl),Tu(),Il(221);if($l==72||...
function xu (line 1) | function xu(){Nu(),Il(221),($l==72||$l==77||$l==214)&&Au()}
function Tu (line 1) | function Tu(){ic.startNonterminal("FTWordsValue",Vl);switch($l){case 11:...
function Nu (line 1) | function Nu(){switch($l){case 11:Hl(11);break;default:Hl(281),Il(267),Y(...
function Cu (line 1) | function Cu(){ic.startNonterminal("FTExtensionSelection",Vl);for(;;){jl(...
function ku (line 1) | function ku(){for(;;){Ar(),Il(104);if($l!=36)break}Hl(281),Il(184),$l!=2...
function Lu (line 1) | function Lu(){ic.startNonterminal("FTAnyallOption",Vl);switch($l){case 7...
function Au (line 1) | function Au(){switch($l){case 77:Hl(77),Il(218),$l==278&&Hl(278);break;c...
function Ou (line 1) | function Ou(){ic.startNonterminal("FTTimes",Vl),Pl(199),Il(159),jl(),_u(...
function Mu (line 1) | function Mu(){Hl(199),Il(159),Du(),Hl(252)}
function _u (line 1) | function _u(){ic.startNonterminal("FTRange",Vl);switch($l){case 131:Pl(1...
function Du (line 1) | function Du(){switch($l){case 131:Hl(131),Il(266),Kn();break;case 82:Hl(...
function Pu (line 1) | function Pu(){ic.startNonterminal("FTPosFilter",Vl);switch($l){case 206:...
function Hu (line 1) | function Hu(){switch($l){case 206:ju();break;case 275:Iu();break;case 11...
function Bu (line 1) | function Bu(){ic.startNonterminal("FTOrder",Vl),Pl(206),ic.endNontermina...
function ju (line 1) | function ju(){Hl(206)}
function Fu (line 1) | function Fu(){ic.startNonterminal("FTWindow",Vl),Pl(275),Il(266),jl(),Jn...
function Iu (line 1) | function Iu(){Hl(275),Il(266),Kn(),zu()}
function qu (line 1) | function qu(){ic.startNonterminal("FTDistance",Vl),Pl(118),Il(159),jl(),...
function Ru (line 1) | function Ru(){Hl(118),Il(159),Du(),zu()}
function Uu (line 1) | function Uu(){ic.startNonterminal("FTUnit",Vl);switch($l){case 279:Pl(27...
function zu (line 1) | function zu(){switch($l){case 279:Hl(279);break;case 237:Hl(237);break;d...
function Wu (line 1) | function Wu(){ic.startNonterminal("FTScope",Vl);switch($l){case 227:Pl(2...
function Xu (line 1) | function Xu(){switch($l){case 227:Hl(227);break;default:Hl(116)}Il(136),...
function Vu (line 1) | function Vu(){ic.startNonterminal("FTBigUnit",Vl);switch($l){case 236:Pl...
function $u (line 1) | function $u(){switch($l){case 236:Hl(236);break;default:Hl(208)}}
function Ju (line 1) | function Ju(){ic.startNonterminal("FTContent",Vl);switch($l){case 82:Pl(...
function Ku (line 1) | function Ku(){switch($l){case 82:Hl(82),Il(121);switch($l){case 242:Hl(2...
function Qu (line 1) | function Qu(){ic.startNonterminal("FTMatchOptions",Vl);for(;;){Pl(265),I...
function Gu (line 1) | function Gu(){for(;;){Hl(265),Il(205),Zu(),Il(214);if($l!=265)break}}
function Yu (line 1) | function Yu(){ic.startNonterminal("FTMatchOption",Vl);switch($l){case 19...
function Zu (line 1) | function Zu(){switch($l){case 191:ql(176);break;default:Wl=$l}switch(Wl)...
function ea (line 1) | function ea(){ic.startNonterminal("FTCaseOption",Vl);switch($l){case 89:...
function ta (line 1) | function ta(){switch($l){case 89:Hl(89),Il(128);switch($l){case 160:Hl(1...
function na (line 1) | function na(){ic.startNonterminal("FTDiacriticsOption",Vl),Pl(115),Il(12...
function ra (line 1) | function ra(){Hl(115),Il(128);switch($l){case 160:Hl(160);break;default:...
function ia (line 1) | function ia(){ic.startNonterminal("FTStemOption",Vl);switch($l){case 243...
function sa (line 1) | function sa(){switch($l){case 243:Hl(243);break;default:Hl(191),Il(77),H...
function oa (line 1) | function oa(){ic.startNonterminal("FTThesaurusOption",Vl);switch($l){cas...
function ua (line 1) | function ua(){switch($l){case 251:Hl(251),Il(152);switch($l){case 82:fa(...
function aa (line 1) | function aa(){ic.startNonterminal("FTThesaurusID",Vl),Pl(82),Il(15),Pl(7...
function fa (line 1) | function fa(){Hl(82),Il(15),Hl(7),Il(220),$l==221&&(Hl(221),Il(17),Hl(11...
function la (line 1) | function la(){ic.startNonterminal("FTLiteralRange",Vl);switch($l){case 1...
function ca (line 1) | function ca(){switch($l){case 131:Hl(131),Il(16),Hl(8);break;case 82:Hl(...
function ha (line 1) | function ha(){ic.startNonterminal("FTStopWordOption",Vl);switch($l){case...
function pa (line 1) | function pa(){switch($l){case 244:Hl(244),Il(89),Hl(279),Il(152);switch(...
function da (line 1) | function da(){ic.startNonterminal("FTStopWords",Vl);switch($l){case 82:P...
function va (line 1) | function va(){switch($l){case 82:Hl(82),Il(15),Hl(7);break;default:Hl(35...
function ma (line 1) | function ma(){ic.startNonterminal("FTStopWordsInclExcl",Vl);switch($l){c...
function ga (line 1) | function ga(){switch($l){case 260:Hl(260);break;default:Hl(132)}Il(103),...
function ya (line 1) | function ya(){ic.startNonterminal("FTLanguageOption",Vl),Pl(172),Il(17),...
function ba (line 1) | function ba(){Hl(172),Il(17),Hl(11)}
function wa (line 1) | function wa(){ic.startNonterminal("FTWildCardOption",Vl);switch($l){case...
function Ea (line 1) | function Ea(){switch($l){case 274:Hl(274);break;default:Hl(191),Il(87),H...
function Sa (line 1) | function Sa(){ic.startNonterminal("FTExtensionOption",Vl),Pl(203),Il(246...
function xa (line 1) | function xa(){Hl(203),Il(246),Ja(),Il(17),Hl(11)}
function Ta (line 1) | function Ta(){ic.startNonterminal("FTIgnoreOption",Vl),Pl(277),Il(45),Pl...
function Na (line 1) | function Na(){Hl(277),Il(45),Hl(101),Il(266),Zn()}
function Ca (line 1) | function Ca(){ic.startNonterminal("CollectionDecl",Vl),Pl(96),Il(246),jl...
function ka (line 1) | function ka(){ic.startNonterminal("CollectionTypeDecl",Vl),Pl(80),Il(200...
function La (line 1) | function La(){ic.startNonterminal("IndexName",Vl),$a(),ic.endNonterminal...
function Aa (line 1) | function Aa(){ic.startNonterminal("IndexDomainExpr",Vl),Or(),ic.endNonte...
function Oa (line 1) | function Oa(){ic.startNonterminal("IndexKeySpec",Vl),Ma(),$l==80&&(jl(),...
function Ma (line 1) | function Ma(){ic.startNonterminal("IndexKeyExpr",Vl),Or(),ic.endNontermi...
function _a (line 1) | function _a(){ic.startNonterminal("IndexKeyTypeDecl",Vl),Pl(80),Il(246),...
function Da (line 1) | function Da(){ic.startNonterminal("AtomicType",Vl),$a(),ic.endNontermina...
function Pa (line 1) | function Pa(){ic.startNonterminal("IndexKeyCollation",Vl),Pl(95),Il(15),...
function Ha (line 1) | function Ha(){ic.startNonterminal("IndexDecl",Vl),Pl(157),Il(246),jl(),L...
function Ba (line 1) | function Ba(){ic.startNonterminal("ICDecl",Vl),Pl(163),Il(43),Pl(98),Il(...
function ja (line 1) | function ja(){ic.startNonterminal("ICCollection",Vl),Pl(201),Il(42),Pl(9...
function Fa (line 1) | function Fa(){ic.startNonterminal("ICCollSequence",Vl),Si(),Il(40),Pl(93...
function Ia (line 1) | function Ia(){ic.startNonterminal("ICCollSequenceUnique",Vl),Pl(194),Il(...
function qa (line 1) | function qa(){ic.startNonterminal("ICCollNode",Vl),Pl(140),Il(65),Pl(194...
function Ra (line 1) | function Ra(){ic.startNonterminal("ICForeignKey",Vl),Pl(141),Il(60),Pl(1...
function Ua (line 1) | function Ua(){ic.startNonterminal("ICForeignKeySource",Vl),Pl(142),Il(42...
function za (line 1) | function za(){ic.startNonterminal("ICForeignKeyTarget",Vl),Pl(253),Il(42...
function Wa (line 1) | function Wa(){ic.startNonterminal("ICForeignKeyValues",Vl),Pl(96),Il(246...
function Xa (line 1) | function Xa(){Hl(37);for(;;){Rl(92);if($l==51)break;switch($l){case 24:H...
function Va (line 1) | function Va(){switch($l){case 22:Hl(22);break;default:Xa()}}
function $a (line 1) | function $a(){ic.startNonterminal("EQName",Vl),Rl(241);switch($l){case 8...
function Ja (line 1) | function Ja(){Rl(241);switch($l){case 83:Hl(83);break;case 97:Hl(97);bre...
function Ka (line 1) | function Ka(){ic.startNonterminal("FunctionName",Vl);switch($l){case 6:P...
function Qa (line 1) | function Qa(){switch($l){case 6:Hl(6);break;case 71:Hl(71);break;case 74...
function Ga (line 1) | function Ga(){ic.startNonterminal("NCName",Vl);switch($l){case 19:Pl(19)...
function Ya (line 1) | function Ya(){switch($l){case 19:Hl(19);break;case 71:Hl(71);break;case ...
function Za (line 1) | function Za(){ic.startNonterminal("MainModule",Vl),l(),jl(),ef(),ic.endN...
function ef (line 1) | function ef(){ic.startNonterminal("Program",Vl),of(),ic.endNonterminal("...
function tf (line 1) | function tf(){ic.startNonterminal("Statements",Vl);for(;;){Il(284);switc...
function nf (line 1) | function nf(){for(;;){Il(284);switch($l){case 35:ql(270);break;case 36:U...
function rf (line 1) | function rf(){ic.startNonterminal("StatementsAndExpr",Vl),tf(),jl(),G(),...
function sf (line 1) | function sf(){nf(),Y()}
function of (line 1) | function of(){ic.startNonterminal("StatementsAndOptionalExpr",Vl),tf(),$...
function uf (line 1) | function uf(){nf(),$l!=25&&$l!=287&&Y()}
function af (line 1) | function af(){ic.startNonterminal("Statement",Vl);switch($l){case 133:ql...
function ff (line 1) | function ff(){switch($l){case 133:ql(147);break;case 139:ql(179);break;c...
function lf (line 1) | function lf(){ic.startNonterminal("ApplyStatement",Vl),Vf(),Pl(54),ic.en...
function cf (line 1) | function cf(){$f(),Hl(54)}
function hf (line 1) | function hf(){ic.startNonterminal("AssignStatement",Vl),Pl(31),Il(246),j...
function pf (line 1) | function pf(){Hl(31),Il(246),Ni(),Il(28),Hl(53),Il(267),Xf(),Hl(54)}
function df (line 1) | function df(){ic.startNonterminal("BlockStatement",Vl),Pl(281),Il(271),j...
function vf (line 1) | function vf(){Hl(281),Il(271),ff(),Il(281),nf(),Hl(287)}
function mf (line 1) | function mf(){ic.startNonterminal("BreakStatement",Vl),Pl(87),Il(62),Pl(...
function gf (line 1) | function gf(){Hl(87),Il(62),Hl(179),Il(29),Hl(54)}
function yf (line 1) | function yf(){ic.startNonterminal("ContinueStatement",Vl),Pl(103),Il(62)...
function bf (line 1) | function bf(){Hl(103),Il(62),Hl(179),Il(29),Hl(54)}
function wf (line 1) | function wf(){ic.startNonterminal("ExitStatement",Vl),Pl(133),Il(74),Pl(...
function Ef (line 1) | function Ef(){Hl(133),Il(74),Hl(225),Il(267),Xf(),Hl(54)}
function Sf (line 1) | function Sf(){ic.startNonterminal("FLWORStatement",Vl),tt();for(;;){Il(1...
function xf (line 1) | function xf(){nt();for(;;){Il(195);if($l==224)break;it()}Nf()}
function Tf (line 1) | function Tf(){ic.startNonterminal("ReturnStatement",Vl),Pl(224),Il(271),...
function Nf (line 1) | function Nf(){Hl(224),Il(271),ff()}
function Cf (line 1) | function Cf(){ic.startNonterminal("IfStatement",Vl),Pl(154),Il(22),Pl(35...
function kf (line 1) | function kf(){Hl(154),Il(22),Hl(35),Il(267),Y(),Hl(38),Il(80),Hl(250),Il...
function Lf (line 1) | function Lf(){ic.startNonterminal("SwitchStatement",Vl),Pl(248),Il(22),P...
function Af (line 1) | function Af(){Hl(248),Il(22),Hl(35),Il(267),Y(),Hl(38);for(;;){Il(38),Mf...
function Of (line 1) | function Of(){ic.startNonterminal("SwitchCaseStatement",Vl);for(;;){Pl(8...
function Mf (line 1) | function Mf(){for(;;){Hl(89),Il(267),vn();if($l!=89)break}Hl(224),Il(271...
function _f (line 1) | function _f(){ic.startNonterminal("TryCatchStatement",Vl),Pl(256),Il(90)...
function Df (line 1) | function Df(){Hl(256),Il(90),vf();for(;;){Il(39),Hl(92),Il(249),Dn(),vf(...
function Pf (line 1) | function Pf(){ic.startNonterminal("TypeswitchStatement",Vl),Pl(259),Il(2...
function Hf (line 1) | function Hf(){Hl(259),Il(22),Hl(35),Il(267),Y(),Hl(38);for(;;){Il(38),jf...
function Bf (line 1) | function Bf(){ic.startNonterminal("CaseStatement",Vl),Pl(89),Il(258),$l=...
function jf (line 1) | function jf(){Hl(89),Il(258),$l==31&&(Hl(31),Il(246),Ni(),Il(33),Hl(80))...
function Ff (line 1) | function Ff(){ic.startNonterminal("VarDeclStatement",Vl);for(;;){Il(102)...
function If (line 1) | function If(){for(;;){Il(102);if($l!=33)break;j()}Hl(268),Il(21),Hl(31),...
function qf (line 1) | function qf(){ic.startNonterminal("WhileStatement",Vl),Pl(273),Il(22),Pl...
function Rf (line 1) | function Rf(){Hl(273),Il(22),Hl(35),Il(267),Y(),Hl(38),Il(271),ff()}
function Uf (line 1) | function Uf(){ic.startNonterminal("VoidStatement",Vl),Pl(54),ic.endNonte...
function zf (line 1) | function zf(){Hl(54)}
function Wf (line 1) | function Wf(){ic.startNonterminal("ExprSingle",Vl);switch($l){case 139:q...
function Xf (line 1) | function Xf(){switch($l){case 139:ql(179);break;case 177:ql(166);break;c...
function Vf (line 1) | function Vf(){ic.startNonterminal("ExprSimple",Vl);switch($l){case 78:ql...
function $f (line 1) | function $f(){switch($l){case 78:ql(269);break;case 161:ql(276);break;ca...
function Jf (line 1) | function Jf(){ic.startNonterminal("JSONDeleteExpr",Vl),Pl(111),Il(260);s...
function Kf (line 1) | function Kf(){Hl(111),Il(260);switch($l){case 168:ql(261);break;default:...
function Qf (line 1) | function Qf(){ic.startNonterminal("JSONInsertExpr",Vl);switch($l){case 1...
function Gf (line 1) | function Gf(){switch($l){case 161:ql(268);break;default:Wl=$l}if(Wl!=988...
function Yf (line 1) | function Yf(){ic.startNonterminal("JSONRenameExpr",Vl),Pl(222),Il(260);s...
function Zf (line 1) | function Zf(){Hl(222),Il(260);switch($l){case 168:ql(261);break;default:...
function el (line 1) | function el(){ic.startNonterminal("JSONReplaceExpr",Vl),Pl(223),Il(85),P...
function tl (line 1) | function tl(){Hl(223),Il(85),Hl(267),Il(67),Hl(200),Il(59),Hl(168),Il(26...
function nl (line 1) | function nl(){ic.startNonterminal("JSONAppendExpr",Vl),Pl(78),Il(267);sw...
function rl (line 1) | function rl(){Hl(78),Il(267);switch($l){case 168:ql(269);break;default:W...
function il (line 1) | function il(){ic.startNonterminal("CommonContent",Vl);switch($l){case 12...
function sl (line 1) | function sl(){switch($l){case 12:Hl(12);break;case 23:Hl(23);break;case ...
function ol (line 1) | function ol(){ic.startNonterminal("ContentExpr",Vl),rf(),ic.endNontermin...
function ul (line 1) | function ul(){sf()}
function al (line 1) | function al(){ic.startNonterminal("CompDocConstructor",Vl),Pl(120),Il(90...
function fl (line 1) | function fl(){Hl(120),Il(90),Ml()}
function ll (line 1) | function ll(){ic.startNonterminal("CompAttrConstructor",Vl),Pl(83),Il(25...
function cl (line 1) | function cl(){Hl(83),Il(250);switch($l){case 281:Hl(281),Il(267),Y(),Hl(...
function hl (line 1) | function hl(){ic.startNonterminal("CompPIConstructor",Vl),Pl(220),Il(242...
function pl (line 1) | function pl(){Hl(220),Il(242);switch($l){case 281:Hl(281),Il(267),Y(),Hl...
function dl (line 1) | function dl(){ic.startNonterminal("CompCommentConstructor",Vl),Pl(97),Il...
function vl (line 1) | function vl(){Hl(97),Il(90),Ml()}
function ml (line 1) | function ml(){ic.startNonterminal("CompTextConstructor",Vl),Pl(249),Il(9...
function gl (line 1) | function gl(){Hl(249),Il(90),Ml()}
function yl (line 1) | function yl(){ic.startNonterminal("PrimaryExpr",Vl);switch($l){case 187:...
function bl (line 1) | function bl(){switch($l){case 187:ql(247);break;case 220:ql(245);break;c...
function wl (line 1) | function wl(){ic.startNonterminal("JSONSimpleObjectUnion",Vl),Pl(283),Il...
function El (line 1) | function El(){Hl(283),Il(274),$l!=286&&Y(),Hl(286)}
function Sl (line 1) | function Sl(){ic.startNonterminal("ObjectConstructor",Vl),Pl(281),Il(277...
function xl (line 1) | function xl(){Hl(281),Il(277),$l!=287&&Nl(),Hl(287)}
function Tl (line 1) | function Tl(){ic.startNonterminal("PairConstructorList",Vl),Cl();for(;;)...
function Nl (line 1) | function Nl(){kl();for(;;){if($l!=42)break;Hl(42),Il(268),kl()}}
function Cl (line 1) | function Cl(){ic.startNonterminal("PairConstructor",Vl);switch($l){case ...
function kl (line 1) | function kl(){switch($l){case 78:ql(279);break;case 139:ql(187);break;ca...
function Ll (line 1) | function Ll(){ic.startNonterminal("ArrayConstructor",Vl),Pl(69),Il(273),...
function Al (line 1) | function Al(){Hl(69),Il(273),$l!=70&&Y(),Hl(70)}
function Ol (line 1) | function Ol(){ic.startNonterminal("BlockExpr",Vl),Pl(281),Il(281),jl(),o...
function Ml (line 1) | function Ml(){Hl(281),Il(281),uf(),Hl(287)}
function _l (line 1) | function _l(){ic.startNonterminal("FunctionDecl",Vl),Pl(147),Il(246),jl(...
function Dl (line 1) | function Dl(){ic.startNonterminal("ReturnType",Vl),Pl(80),Il(254),jl(),L...
function Pl (line 1) | function Pl(e){$l==e?(jl(),ic.terminal(i.TOKEN[$l],Jl,Kl>fc?fc:Kl),Xl=Jl...
function Hl (line 1) | function Hl(e){$l==e?(Xl=Jl,Vl=Kl,$l=Ql,$l!=0&&(Jl=Gl,Kl=Yl,Ql=0)):zl(Jl...
function Bl (line 1) | function Bl(e){var t=Xl,n=Vl,r=$l,i=Jl,s=Kl;$l=e,Jl=lc,Kl=cc,Ql=0,Va(),X...
function jl (line 1) | function jl(){Vl!=Jl&&(Xl=Vl,Vl=Jl,ic.whitespace(Xl,Vl))}
function Fl (line 1) | function Fl(e){var t;for(;;){t=hc(e);if(t!=22){if(t!=37)break;Bl(t)}}ret...
function Il (line 1) | function Il(e){$l==0&&($l=Fl(e),Jl=lc,Kl=cc)}
function ql (line 1) | function ql(e){Ql==0&&(Ql=Fl(e),Gl=lc,Yl=cc),Wl=Ql<<9|$l}
function Rl (line 1) | function Rl(e){$l==0&&($l=hc(e),Jl=lc,Kl=cc)}
function Ul (line 1) | function Ul(e){Ql==0&&(Ql=hc(e),Gl=lc,Yl=cc),Wl=Ql<<9|$l}
function zl (line 1) | function zl(e,t,r,i,s){throw t>ec&&(Zl=e,ec=t,tc=r,nc=i,rc=s),new n.Pars...
function oc (line 1) | function oc(e,t,n){sc[(t<<5)+e]=n}
function uc (line 1) | function uc(e,t){var n=sc[(t<<5)+e];return typeof n!="undefined"?n:0}
function hc (line 1) | function hc(e){var t=!1;lc=cc;var n=cc,r=i.INITIAL[e],s=0;for(var o=r&81...
function r (line 1) | function r(e,t){Vl=t,Ql=e,Gl=e.length,s(0,0,0)}
function s (line 1) | function s(e,t,n){Dl=t,Pl=t,Hl=e,Bl=t,jl=n,Fl=0,Zl=n,Ul=-1,$l={},Vl.rese...
function o (line 1) | function o(){Vl.startNonterminal("Module",Pl);switch(Hl){case 274:Ll(199...
function u (line 1) | function u(){Vl.startNonterminal("VersionDecl",Pl),Sl(274),kl(116);switc...
function a (line 1) | function a(){Vl.startNonterminal("LibraryModule",Pl),f(),kl(138),Nl(),l(...
function f (line 1) | function f(){Vl.startNonterminal("ModuleDecl",Pl),Sl(182),kl(61),Sl(184)...
function l (line 1) | function l(){Vl.startNonterminal("Prolog",Pl);for(;;){kl(275);switch(Hl)...
function c (line 1) | function c(){Vl.startNonterminal("Separator",Pl),Sl(53),Vl.endNontermina...
function h (line 1) | function h(){Vl.startNonterminal("Setter",Pl);switch(Hl){case 108:Ll(172...
function p (line 1) | function p(){Vl.startNonterminal("BoundarySpaceDecl",Pl),Sl(108),kl(33),...
function d (line 1) | function d(){Vl.startNonterminal("DefaultCollationDecl",Pl),Sl(108),kl(4...
function v (line 1) | function v(){xl(108),kl(46),xl(109),kl(38),xl(94),kl(15),xl(7)}
function m (line 1) | function m(){Vl.startNonterminal("BaseURIDecl",Pl),Sl(108),kl(32),Sl(83)...
function g (line 1) | function g(){Vl.startNonterminal("ConstructionDecl",Pl),Sl(108),kl(41),S...
function y (line 1) | function y(){Vl.startNonterminal("OrderingModeDecl",Pl),Sl(108),kl(68),S...
function b (line 1) | function b(){Vl.startNonterminal("EmptyOrderDecl",Pl),Sl(108),kl(46),Sl(...
function w (line 1) | function w(){xl(108),kl(46),xl(109),kl(67),xl(201),kl(49),xl(123),kl(121...
function E (line 1) | function E(){Vl.startNonterminal("CopyNamespacesDecl",Pl),Sl(108),kl(44)...
function S (line 1) | function S(){Vl.startNonterminal("PreserveMode",Pl);switch(Hl){case 214:...
function x (line 1) | function x(){Vl.startNonterminal("InheritMode",Pl);switch(Hl){case 157:S...
function T (line 1) | function T(){Vl.startNonterminal("DecimalFormatDecl",Pl),Sl(108),kl(114)...
function N (line 1) | function N(){Vl.startNonterminal("DFPropertyName",Pl);switch(Hl){case 10...
function C (line 1) | function C(){Vl.startNonterminal("Import",Pl);switch(Hl){case 153:Ll(126...
function k (line 1) | function k(){Vl.startNonterminal("SchemaImport",Pl),Sl(153),kl(73),Sl(22...
function L (line 1) | function L(){Vl.startNonterminal("SchemaPrefix",Pl);switch(Hl){case 184:...
function A (line 1) | function A(){Vl.startNonterminal("ModuleImport",Pl),Sl(153),kl(60),Sl(18...
function O (line 1) | function O(){Vl.startNonterminal("NamespaceDecl",Pl),Sl(108),kl(61),Sl(1...
function M (line 1) | function M(){Vl.startNonterminal("DefaultNamespaceDecl",Pl),Sl(108),kl(4...
function _ (line 1) | function _(){xl(108),kl(46),xl(109),kl(115);switch(Hl){case 121:xl(121);...
function D (line 1) | function D(){Vl.startNonterminal("FTOptionDecl",Pl),Sl(108),kl(52),Sl(14...
function P (line 1) | function P(){Vl.startNonterminal("AnnotatedDecl",Pl),Sl(108);for(;;){kl(...
function H (line 1) | function H(){Vl.startNonterminal("CompatibilityAnnotation",Pl),Sl(257),V...
function B (line 1) | function B(){Vl.startNonterminal("Annotation",Pl),Sl(32),kl(255),Nl(),Ha...
function j (line 1) | function j(){xl(32),kl(255),Ba(),kl(171);if(Hl==34){xl(34),kl(154),ui();...
function F (line 1) | function F(){Vl.startNonterminal("VarDecl",Pl),Sl(262),kl(21),Sl(31),kl(...
function I (line 1) | function I(){Vl.startNonterminal("VarValue",Pl),_f(),Vl.endNonterminal("...
function q (line 1) | function q(){Vl.startNonterminal("VarDefaultValue",Pl),_f(),Vl.endNonter...
function R (line 1) | function R(){Vl.startNonterminal("ContextItemDecl",Pl),Sl(108),kl(43),Sl...
function U (line 1) | function U(){Vl.startNonterminal("ParamList",Pl),W();for(;;){kl(101);if(...
function z (line 1) | function z(){X();for(;;){kl(101);if(Hl!=41)break;xl(41),kl(21),X()}}
function W (line 1) | function W(){Vl.startNonterminal("Param",Pl),Sl(31),kl(255),Nl(),Ha(),kl...
function X (line 1) | function X(){xl(31),kl(255),Ba(),kl(143),Hl==79&&vs()}
function V (line 1) | function V(){Vl.startNonterminal("FunctionBody",Pl),J(),Vl.endNontermina...
function $ (line 1) | function $(){K()}
function J (line 1) | function J(){Vl.startNonterminal("EnclosedExpr",Pl),Sl(276),kl(267),Nl()...
function K (line 1) | function K(){xl(276),kl(267),Y(),xl(282)}
function Q (line 1) | function Q(){Vl.startNonterminal("OptionDecl",Pl),Sl(108),kl(66),Sl(199)...
function G (line 1) | function G(){Vl.startNonterminal("Expr",Pl),_f();for(;;){if(Hl!=41)break...
function Y (line 1) | function Y(){Df();for(;;){if(Hl!=41)break;xl(41),kl(267),Df()}}
function Z (line 1) | function Z(){Vl.startNonterminal("FLWORExpr",Pl),tt();for(;;){kl(173);if...
function et (line 1) | function et(){nt();for(;;){kl(173);if(Hl==220)break;it()}sn()}
function tt (line 1) | function tt(){Vl.startNonterminal("InitialClause",Pl);switch(Hl){case 13...
function nt (line 1) | function nt(){switch(Hl){case 137:Ll(141);break;default:_l=Hl}switch(_l)...
function rt (line 1) | function rt(){Vl.startNonterminal("IntermediateClause",Pl);switch(Hl){ca...
function it (line 1) | function it(){switch(Hl){case 137:case 174:nt();break;case 266:qt();brea...
function st (line 1) | function st(){Vl.startNonterminal("ForClause",Pl),Sl(137),kl(21),Nl(),ut...
function ot (line 1) | function ot(){xl(137),kl(21),at();for(;;){if(Hl!=41)break;xl(41),kl(21),...
function ut (line 1) | function ut(){Vl.startNonterminal("ForBinding",Pl),Sl(31),kl(255),Nl(),h...
function at (line 1) | function at(){xl(31),kl(255),pi(),kl(164),Hl==79&&vs(),kl(158),Hl==72&&l...
function ft (line 1) | function ft(){Vl.startNonterminal("AllowingEmpty",Pl),Sl(72),kl(49),Sl(1...
function lt (line 1) | function lt(){xl(72),kl(49),xl(123)}
function ct (line 1) | function ct(){Vl.startNonterminal("PositionalVar",Pl),Sl(81),kl(21),Sl(3...
function ht (line 1) | function ht(){xl(81),kl(21),xl(31),kl(255),pi()}
function pt (line 1) | function pt(){Vl.startNonterminal("FTScoreVar",Pl),Sl(228),kl(21),Sl(31)...
function dt (line 1) | function dt(){xl(228),kl(21),xl(31),kl(255),pi()}
function vt (line 1) | function vt(){Vl.startNonterminal("LetClause",Pl),Sl(174),kl(96),Nl(),gt...
function mt (line 1) | function mt(){xl(174),kl(96),yt();for(;;){if(Hl!=41)break;xl(41),kl(96),...
function gt (line 1) | function gt(){Vl.startNonterminal("LetBinding",Pl);switch(Hl){case 31:Sl...
function yt (line 1) | function yt(){switch(Hl){case 31:xl(31),kl(255),pi(),kl(105),Hl==79&&vs(...
function bt (line 1) | function bt(){Vl.startNonterminal("WindowClause",Pl),Sl(137),kl(135);swi...
function wt (line 1) | function wt(){xl(137),kl(135);switch(Hl){case 251:St();break;default:Tt()}}
function Et (line 1) | function Et(){Vl.startNonterminal("TumblingWindowClause",Pl),Sl(251),kl(...
function St (line 1) | function St(){xl(251),kl(85),xl(269),kl(21),xl(31),kl(255),pi(),kl(110),...
function xt (line 1) | function xt(){Vl.startNonterminal("SlidingWindowClause",Pl),Sl(234),kl(8...
function Tt (line 1) | function Tt(){xl(234),kl(85),xl(269),kl(21),xl(31),kl(255),pi(),kl(110),...
function Nt (line 1) | function Nt(){Vl.startNonterminal("WindowStartCondition",Pl),Sl(237),kl(...
function Ct (line 1) | function Ct(){xl(237),kl(163),Ot(),kl(83),xl(265),kl(267),Df()}
function kt (line 1) | function kt(){Vl.startNonterminal("WindowEndCondition",Pl),Hl==198&&Sl(1...
function Lt (line 1) | function Lt(){Hl==198&&xl(198),kl(50),xl(126),kl(163),Ot(),kl(83),xl(265...
function At (line 1) | function At(){Vl.startNonterminal("WindowVars",Pl),Hl==31&&(Sl(31),kl(25...
function Ot (line 1) | function Ot(){Hl==31&&(xl(31),kl(255),_t()),kl(159),Hl==81&&ht(),kl(153)...
function Mt (line 1) | function Mt(){Vl.startNonterminal("CurrentItem",Pl),Ha(),Vl.endNontermin...
function _t (line 1) | function _t(){Ba()}
function Dt (line 1) | function Dt(){Vl.startNonterminal("PreviousItem",Pl),Ha(),Vl.endNontermi...
function Pt (line 1) | function Pt(){Ba()}
function Ht (line 1) | function Ht(){Vl.startNonterminal("NextItem",Pl),Ha(),Vl.endNonterminal(...
function Bt (line 1) | function Bt(){Ba()}
function jt (line 1) | function jt(){Vl.startNonterminal("CountClause",Pl),Sl(105),kl(21),Sl(31...
function Ft (line 1) | function Ft(){xl(105),kl(21),xl(31),kl(255),pi()}
function It (line 1) | function It(){Vl.startNonterminal("WhereClause",Pl),Sl(266),kl(267),Nl()...
function qt (line 1) | function qt(){xl(266),kl(267),Df()}
function Rt (line 1) | function Rt(){Vl.startNonterminal("GroupByClause",Pl),Sl(148),kl(34),Sl(...
function Ut (line 1) | function Ut(){xl(148),kl(34),xl(87),kl(267),Wt()}
function zt (line 1) | function zt(){Vl.startNonterminal("GroupingSpecList",Pl),Xt();for(;;){kl...
function Wt (line 1) | function Wt(){Vt();for(;;){kl(176);if(Hl!=41)break;xl(41),kl(267),Vt()}}
function Xt (line 1) | function Xt(){Vl.startNonterminal("GroupingSpec",Pl);switch(Hl){case 31:...
function Vt (line 1) | function Vt(){switch(Hl){case 31:Ll(255);break;default:_l=Hl}if(_l==3103...
function $t (line 1) | function $t(){Vl.startNonterminal("GroupingVariable",Pl),Sl(31),kl(255),...
function Jt (line 1) | function Jt(){xl(31),kl(255),pi()}
function Kt (line 1) | function Kt(){Vl.startNonterminal("OrderByClause",Pl);switch(Hl){case 20...
function Qt (line 1) | function Qt(){switch(Hl){case 201:xl(201),kl(34),xl(87);break;default:xl...
function Gt (line 1) | function Gt(){Vl.startNonterminal("OrderSpecList",Pl),Zt();for(;;){kl(17...
function Yt (line 1) | function Yt(){en();for(;;){kl(176);if(Hl!=41)break;xl(41),kl(267),en()}}
function Zt (line 1) | function Zt(){Vl.startNonterminal("OrderSpec",Pl),_f(),Nl(),tn(),Vl.endN...
function en (line 1) | function en(){Df(),nn()}
function tn (line 1) | function tn(){Vl.startNonterminal("OrderModifier",Pl);if(Hl==80||Hl==113...
function nn (line 1) | function nn(){if(Hl==80||Hl==113)switch(Hl){case 80:xl(80);break;default...
function rn (line 1) | function rn(){Vl.startNonterminal("ReturnClause",Pl),Sl(220),kl(267),Nl(...
function sn (line 1) | function sn(){xl(220),kl(267),Df()}
function on (line 1) | function on(){Vl.startNonterminal("QuantifiedExpr",Pl);switch(Hl){case 2...
function un (line 1) | function un(){switch(Hl){case 235:xl(235);break;default:xl(129)}kl(21),f...
function an (line 1) | function an(){Vl.startNonterminal("QuantifiedVarDecl",Pl),Sl(31),kl(255)...
function fn (line 1) | function fn(){xl(31),kl(255),pi(),kl(110),Hl==79&&vs(),kl(53),xl(154),kl...
function ln (line 1) | function ln(){Vl.startNonterminal("SwitchExpr",Pl),Sl(243),kl(22),Sl(34)...
function cn (line 1) | function cn(){xl(243),kl(22),xl(34),kl(267),Y(),xl(37);for(;;){kl(35),pn...
function hn (line 1) | function hn(){Vl.startNonterminal("SwitchCaseClause",Pl);for(;;){Sl(88),...
function pn (line 1) | function pn(){for(;;){xl(88),kl(267),vn();if(Hl!=88)break}xl(220),kl(267...
function dn (line 1) | function dn(){Vl.startNonterminal("SwitchCaseOperand",Pl),_f(),Vl.endNon...
function vn (line 1) | function vn(){Df()}
function mn (line 1) | function mn(){Vl.startNonterminal("TypeswitchExpr",Pl),Sl(253),kl(22),Sl...
function gn (line 1) | function gn(){xl(253),kl(22),xl(34),kl(267),Y(),xl(37);for(;;){kl(35),bn...
function yn (line 1) | function yn(){Vl.startNonterminal("CaseClause",Pl),Sl(88),kl(262),Hl==31...
function bn (line 1) | function bn(){xl(88),kl(262),Hl==31&&(xl(31),kl(255),pi(),kl(30),xl(79))...
function wn (line 1) | function wn(){Vl.startNonterminal("SequenceTypeUnion",Pl),ms();for(;;){k...
function En (line 1) | function En(){gs();for(;;){kl(134);if(Hl!=279)break;xl(279),kl(260),gs()}}
function Sn (line 1) | function Sn(){Vl.startNonterminal("IfExpr",Pl),Sl(152),kl(22),Sl(34),kl(...
function xn (line 1) | function xn(){xl(152),kl(22),xl(34),kl(267),Y(),xl(37),kl(77),xl(245),kl...
function Tn (line 1) | function Tn(){Vl.startNonterminal("TryCatchExpr",Pl),Cn();for(;;){kl(36)...
function Nn (line 1) | function Nn(){kn();for(;;){kl(36),Mn(),kl(184);if(Hl!=91)break}}
function Cn (line 1) | function Cn(){Vl.startNonterminal("TryClause",Pl),Sl(250),kl(87),Sl(276)...
function kn (line 1) | function kn(){xl(250),kl(87),xl(276),kl(267),An(),xl(282)}
function Ln (line 1) | function Ln(){Vl.startNonterminal("TryTargetExpr",Pl),G(),Vl.endNontermi...
function An (line 1) | function An(){Y()}
function On (line 1) | function On(){Vl.startNonterminal("CatchClause",Pl),Sl(91),kl(257),Nl(),...
function Mn (line 1) | function Mn(){xl(91),kl(257),Dn(),xl(276),kl(267),Y(),xl(282)}
function _n (line 1) | function _n(){Vl.startNonterminal("CatchErrorList",Pl),Qr();for(;;){kl(1...
function Dn (line 1) | function Dn(){Gr();for(;;){kl(136);if(Hl!=279)break;xl(279),kl(257),Gr()}}
function Pn (line 1) | function Pn(){Vl.startNonterminal("OrExpr",Pl),Bn();for(;;){if(Hl!=200)b...
function Hn (line 1) | function Hn(){jn();for(;;){if(Hl!=200)break;xl(200),kl(267),jn()}}
function Bn (line 1) | function Bn(){Vl.startNonterminal("AndExpr",Pl),Fn();for(;;){if(Hl!=75)b...
function jn (line 1) | function jn(){In();for(;;){if(Hl!=75)break;xl(75),kl(267),In()}}
function Fn (line 1) | function Fn(){Vl.startNonterminal("ComparisonExpr",Pl),qn();if(Hl==27||H...
function In (line 1) | function In(){Rn();if(Hl==27||Hl==54||Hl==57||Hl==58||Hl==60||Hl==61||Hl...
function qn (line 1) | function qn(){Vl.startNonterminal("FTContainsExpr",Pl),Un(),Hl==99&&(Sl(...
function Rn (line 1) | function Rn(){zn(),Hl==99&&(xl(99),kl(76),xl(244),kl(162),Ko(),Hl==271&&...
function Un (line 1) | function Un(){Vl.startNonterminal("StringConcatExpr",Pl),Wn();for(;;){if...
function zn (line 1) | function zn(){Xn();for(;;){if(Hl!=280)break;xl(280),kl(267),Xn()}}
function Wn (line 1) | function Wn(){Vl.startNonterminal("RangeExpr",Pl),Vn(),Hl==248&&(Sl(248)...
function Xn (line 1) | function Xn(){$n(),Hl==248&&(xl(248),kl(267),$n())}
function Vn (line 1) | function Vn(){Vl.startNonterminal("AdditiveExpr",Pl),Jn();for(;;){if(Hl!...
function $n (line 1) | function $n(){Kn();for(;;){if(Hl!=40&&Hl!=42)break;switch(Hl){case 40:xl...
function Jn (line 1) | function Jn(){Vl.startNonterminal("MultiplicativeExpr",Pl),Qn();for(;;){...
function Kn (line 1) | function Kn(){Gn();for(;;){if(Hl!=38&&Hl!=118&&Hl!=151&&Hl!=180)break;sw...
function Qn (line 1) | function Qn(){Vl.startNonterminal("UnionExpr",Pl),Yn();for(;;){if(Hl!=25...
function Gn (line 1) | function Gn(){Zn();for(;;){if(Hl!=254&&Hl!=279)break;switch(Hl){case 254...
function Yn (line 1) | function Yn(){Vl.startNonterminal("IntersectExceptExpr",Pl),er();for(;;)...
function Zn (line 1) | function Zn(){tr();for(;;){kl(223);if(Hl!=131&&Hl!=162)break;switch(Hl){...
function er (line 1) | function er(){Vl.startNonterminal("InstanceofExpr",Pl),nr(),kl(224),Hl==...
function tr (line 1) | function tr(){rr(),kl(224),Hl==160&&(xl(160),kl(64),xl(196),kl(260),gs())}
function nr (line 1) | function nr(){Vl.startNonterminal("TreatExpr",Pl),ir(),kl(225),Hl==249&&...
function rr (line 1) | function rr(){sr(),kl(225),Hl==249&&(xl(249),kl(30),xl(79),kl(260),gs())}
function ir (line 1) | function ir(){Vl.startNonterminal("CastableExpr",Pl),or(),kl(226),Hl==90...
function sr (line 1) | function sr(){ur(),kl(226),Hl==90&&(xl(90),kl(30),xl(79),kl(255),ps())}
function or (line 1) | function or(){Vl.startNonterminal("CastExpr",Pl),ar(),kl(228),Hl==89&&(S...
function ur (line 1) | function ur(){fr(),kl(228),Hl==89&&(xl(89),kl(30),xl(79),kl(255),ps())}
function ar (line 1) | function ar(){Vl.startNonterminal("UnaryExpr",Pl);for(;;){kl(267);if(Hl!...
function fr (line 1) | function fr(){for(;;){kl(267);if(Hl!=40&&Hl!=42)break;switch(Hl){case 42...
function lr (line 1) | function lr(){Vl.startNonterminal("ValueExpr",Pl);switch(Hl){case 260:Ll...
function cr (line 1) | function cr(){switch(Hl){case 260:Ll(248);break;default:_l=Hl}switch(_l)...
function hr (line 1) | function hr(){Vl.startNonterminal("SimpleMapExpr",Pl),Lr();for(;;){if(Hl...
function pr (line 1) | function pr(){Ar();for(;;){if(Hl!=26)break;xl(26),kl(266),Ar()}}
function dr (line 1) | function dr(){Vl.startNonterminal("GeneralComp",Pl);switch(Hl){case 60:S...
function vr (line 1) | function vr(){switch(Hl){case 60:xl(60);break;case 27:xl(27);break;case ...
function mr (line 1) | function mr(){Vl.startNonterminal("ValueComp",Pl);switch(Hl){case 128:Sl...
function gr (line 1) | function gr(){switch(Hl){case 128:xl(128);break;case 186:xl(186);break;c...
function yr (line 1) | function yr(){Vl.startNonterminal("NodeComp",Pl);switch(Hl){case 164:Sl(...
function br (line 1) | function br(){switch(Hl){case 164:xl(164);break;case 57:xl(57);break;def...
function wr (line 1) | function wr(){Vl.startNonterminal("ValidateExpr",Pl),Sl(260),kl(160);if(...
function Er (line 1) | function Er(){xl(260),kl(160);if(Hl!=276)switch(Hl){case 252:xl(252),kl(...
function Sr (line 1) | function Sr(){Vl.startNonterminal("ValidationMode",Pl);switch(Hl){case 1...
function xr (line 1) | function xr(){switch(Hl){case 171:xl(171);break;default:xl(240)}}
function Tr (line 1) | function Tr(){Vl.startNonterminal("ExtensionExpr",Pl);for(;;){Nl(),Cr(),...
function Nr (line 1) | function Nr(){for(;;){kr(),kl(100);if(Hl!=35)break}xl(276),kl(274),Hl!=2...
function Cr (line 1) | function Cr(){Vl.startNonterminal("Pragma",Pl),Sl(35),Al(252),Hl==21&&Sl...
function kr (line 1) | function kr(){xl(35),Al(252),Hl==21&&xl(21),Ba(),Al(10),Hl==21&&(xl(21),...
function Lr (line 1) | function Lr(){Vl.startNonterminal("PathExpr",Pl);switch(Hl){case 46:Sl(4...
function Ar (line 1) | function Ar(){switch(Hl){case 46:xl(46),kl(286);switch(Hl){case 25:case ...
function Or (line 1) | function Or(){Vl.startNonterminal("RelativePathExpr",Pl),_r();for(;;){sw...
function Mr (line 1) | function Mr(){Dr();for(;;){switch(Hl){case 26:Ll(266);break;default:_l=H...
function _r (line 1) | function _r(){Vl.startNonterminal("StepExpr",Pl);switch(Hl){case 82:Ll(2...
function Dr (line 1) | function Dr(){switch(Hl){case 82:Ll(285);break;case 121:Ll(283);break;ca...
function Pr (line 1) | function Pr(){Vl.startNonterminal("AxisStep",Pl);switch(Hl){case 73:case...
function Hr (line 1) | function Hr(){switch(Hl){case 73:case 74:case 206:case 212:case 213:Ll(2...
function Br (line 1) | function Br(){Vl.startNonterminal("ForwardStep",Pl);switch(Hl){case 82:L...
function jr (line 1) | function jr(){switch(Hl){case 82:Ll(245);break;case 93:case 111:case 112...
function Fr (line 1) | function Fr(){Vl.startNonterminal("ForwardAxis",Pl);switch(Hl){case 93:S...
function Ir (line 1) | function Ir(){switch(Hl){case 93:xl(93),kl(26),xl(51);break;case 111:xl(...
function qr (line 1) | function qr(){Vl.startNonterminal("AbbrevForwardStep",Pl),Hl==66&&Sl(66)...
function Rr (line 1) | function Rr(){Hl==66&&xl(66),kl(257),Kr()}
function Ur (line 1) | function Ur(){Vl.startNonterminal("ReverseStep",Pl);switch(Hl){case 45:V...
function zr (line 1) | function zr(){switch(Hl){case 45:$r();break;default:Xr(),kl(257),Kr()}}
function Wr (line 1) | function Wr(){Vl.startNonterminal("ReverseAxis",Pl);switch(Hl){case 206:...
function Xr (line 1) | function Xr(){switch(Hl){case 206:xl(206),kl(26),xl(51);break;case 73:xl...
function Vr (line 1) | function Vr(){Vl.startNonterminal("AbbrevReverseStep",Pl),Sl(45),Vl.endN...
function $r (line 1) | function $r(){xl(45)}
function Jr (line 1) | function Jr(){Vl.startNonterminal("NodeTest",Pl);switch(Hl){case 82:case...
function Kr (line 1) | function Kr(){switch(Hl){case 82:case 96:case 120:case 121:case 185:case...
function Qr (line 1) | function Qr(){Vl.startNonterminal("NameTest",Pl);switch(Hl){case 5:Sl(5)...
function Gr (line 1) | function Gr(){switch(Hl){case 5:xl(5);break;default:Ba()}}
function Yr (line 1) | function Yr(){Vl.startNonterminal("PostfixExpr",Pl),ol();for(;;){kl(241)...
function Zr (line 1) | function Zr(){ul();for(;;){kl(241);if(Hl!=34&&Hl!=68)break;switch(Hl){ca...
function ei (line 1) | function ei(){Vl.startNonterminal("ArgumentList",Pl),Sl(34),kl(276);if(H...
function ti (line 1) | function ti(){xl(34),kl(276);if(Hl!=37){Ni();for(;;){kl(101);if(Hl!=41)b...
function ni (line 1) | function ni(){Vl.startNonterminal("PredicateList",Pl);for(;;){kl(238);if...
function ri (line 1) | function ri(){for(;;){kl(238);if(Hl!=68)break;si()}}
function ii (line 1) | function ii(){Vl.startNonterminal("Predicate",Pl),Sl(68),kl(267),Nl(),G(...
function si (line 1) | function si(){xl(68),kl(267),Y(),xl(69)}
function oi (line 1) | function oi(){Vl.startNonterminal("Literal",Pl);switch(Hl){case 11:Sl(11...
function ui (line 1) | function ui(){switch(Hl){case 11:xl(11);break;default:fi()}}
function ai (line 1) | function ai(){Vl.startNonterminal("NumericLiteral",Pl);switch(Hl){case 8...
function fi (line 1) | function fi(){switch(Hl){case 8:xl(8);break;case 9:xl(9);break;default:x...
function li (line 1) | function li(){Vl.startNonterminal("VarRef",Pl),Sl(31),kl(255),Nl(),hi(),...
function ci (line 1) | function ci(){xl(31),kl(255),pi()}
function hi (line 1) | function hi(){Vl.startNonterminal("VarName",Pl),Ha(),Vl.endNonterminal("...
function pi (line 1) | function pi(){Ba()}
function di (line 1) | function di(){Vl.startNonterminal("ParenthesizedExpr",Pl),Sl(34),kl(269)...
function vi (line 1) | function vi(){xl(34),kl(269),Hl!=37&&Y(),xl(37)}
function mi (line 1) | function mi(){Vl.startNonterminal("ContextItemExpr",Pl),Sl(44),Vl.endNon...
function gi (line 1) | function gi(){xl(44)}
function yi (line 1) | function yi(){Vl.startNonterminal("OrderedExpr",Pl),Sl(202),kl(87),Sl(27...
function bi (line 1) | function bi(){xl(202),kl(87),xl(276),kl(267),Y(),xl(282)}
function wi (line 1) | function wi(){Vl.startNonterminal("UnorderedExpr",Pl),Sl(256),kl(87),Sl(...
function Ei (line 1) | function Ei(){xl(256),kl(87),xl(276),kl(267),Y(),xl(282)}
function Si (line 1) | function Si(){Vl.startNonterminal("FunctionCall",Pl),ja(),kl(22),Nl(),ei...
function xi (line 1) | function xi(){Fa(),kl(22),ti()}
function Ti (line 1) | function Ti(){Vl.startNonterminal("Argument",Pl);switch(Hl){case 64:Ci()...
function Ni (line 1) | function Ni(){switch(Hl){case 64:ki();break;default:Df()}}
function Ci (line 1) | function Ci(){Vl.startNonterminal("ArgumentPlaceholder",Pl),Sl(64),Vl.en...
function ki (line 1) | function ki(){xl(64)}
function Li (line 1) | function Li(){Vl.startNonterminal("Constructor",Pl);switch(Hl){case 54:c...
function Ai (line 1) | function Ai(){switch(Hl){case 54:case 55:case 59:Mi();break;default:Ki()}}
function Oi (line 1) | function Oi(){Vl.startNonterminal("DirectConstructor",Pl);switch(Hl){cas...
function Mi (line 1) | function Mi(){switch(Hl){case 54:Di();break;case 55:Xi();break;default:$...
function _i (line 1) | function _i(){Vl.startNonterminal("DirElemConstructor",Pl),Sl(54),Al(4),...
function Di (line 1) | function Di(){xl(54),Al(4),xl(20),Hi();switch(Hl){case 48:xl(48);break;d...
function Pi (line 1) | function Pi(){Vl.startNonterminal("DirAttributeList",Pl);for(;;){Al(19);...
function Hi (line 1) | function Hi(){for(;;){Al(19);if(Hl!=21)break;xl(21),Al(91),Hl==20&&(xl(2...
function Bi (line 1) | function Bi(){Vl.startNonterminal("DirAttributeValue",Pl),Al(14);switch(...
function ji (line 1) | function ji(){Al(14);switch(Hl){case 28:xl(28);for(;;){Al(167);if(Hl==28...
function Fi (line 1) | function Fi(){Vl.startNonterminal("QuotAttrValueContent",Pl);switch(Hl){...
function Ii (line 1) | function Ii(){switch(Hl){case 16:xl(16);break;default:$f()}}
function qi (line 1) | function qi(){Vl.startNonterminal("AposAttrValueContent",Pl);switch(Hl){...
function Ri (line 1) | function Ri(){switch(Hl){case 17:xl(17);break;default:$f()}}
function Ui (line 1) | function Ui(){Vl.startNonterminal("DirElemContent",Pl);switch(Hl){case 5...
function zi (line 1) | function zi(){switch(Hl){case 54:case 55:case 59:Mi();break;case 4:xl(4)...
function Wi (line 1) | function Wi(){Vl.startNonterminal("DirCommentConstructor",Pl),Sl(55),Al(...
function Xi (line 1) | function Xi(){xl(55),Al(1),xl(2),Al(6),xl(43)}
function Vi (line 1) | function Vi(){Vl.startNonterminal("DirPIConstructor",Pl),Sl(59),Al(3),Sl...
function $i (line 1) | function $i(){xl(59),Al(3),xl(18),Al(13),Hl==21&&(xl(21),Al(2),xl(3)),Al...
function Ji (line 1) | function Ji(){Vl.startNonterminal("ComputedConstructor",Pl);switch(Hl){c...
function Ki (line 1) | function Ki(){switch(Hl){case 119:Gf();break;case 121:Gi();break;case 82...
function Qi (line 1) | function Qi(){Vl.startNonterminal("CompElemConstructor",Pl),Sl(121),kl(2...
function Gi (line 1) | function Gi(){xl(121),kl(258);switch(Hl){case 276:xl(276),kl(267),Y(),xl...
function Yi (line 1) | function Yi(){Vl.startNonterminal("CompNamespaceConstructor",Pl),Sl(184)...
function Zi (line 1) | function Zi(){xl(184),kl(251);switch(Hl){case 276:xl(276),kl(267),rs(),x...
function es (line 1) | function es(){Vl.startNonterminal("Prefix",Pl),Ia(),Vl.endNonterminal("P...
function ts (line 1) | function ts(){qa()}
function ns (line 1) | function ns(){Vl.startNonterminal("PrefixExpr",Pl),G(),Vl.endNonterminal...
function rs (line 1) | function rs(){Y()}
function is (line 1) | function is(){Vl.startNonterminal("URIExpr",Pl),G(),Vl.endNonterminal("U...
function ss (line 1) | function ss(){Y()}
function os (line 1) | function os(){Vl.startNonterminal("FunctionItemExpr",Pl);switch(Hl){case...
function us (line 1) | function us(){switch(Hl){case 145:Ll(92);break;default:_l=Hl}switch(_l){...
function as (line 1) | function as(){Vl.startNonterminal("NamedFunctionRef",Pl),Ha(),kl(20),Sl(...
function fs (line 1) | function fs(){Ba(),kl(20),xl(29),kl(16),xl(8)}
function ls (line 1) | function ls(){Vl.startNonterminal("InlineFunctionExpr",Pl);for(;;){kl(97...
function cs (line 1) | function cs(){for(;;){kl(97);if(Hl!=32)break;j()}xl(145),kl(22),xl(34),k...
function hs (line 1) | function hs(){Vl.startNonterminal("SingleType",Pl),vo(),kl(227),Hl==64&&...
function ps (line 1) | function ps(){mo(),kl(227),Hl==64&&xl(64)}
function ds (line 1) | function ds(){Vl.startNonterminal("TypeDeclaration",Pl),Sl(79),kl(260),N...
function vs (line 1) | function vs(){xl(79),kl(260),gs()}
function ms (line 1) | function ms(){Vl.startNonterminal("SequenceType",Pl);switch(Hl){case 124...
function gs (line 1) | function gs(){switch(Hl){case 124:Ll(243);break;default:_l=Hl}switch(_l)...
function ys (line 1) | function ys(){Vl.startNonterminal("OccurrenceIndicator",Pl);switch(Hl){c...
function bs (line 1) | function bs(){switch(Hl){case 64:xl(64);break;case 39:xl(39);break;defau...
function ws (line 1) | function ws(){Vl.startNonterminal("ItemType",Pl);switch(Hl){case 78:case...
function Es (line 1) | function Es(){switch(Hl){case 78:case 82:case 96:case 120:case 121:case ...
function Ss (line 1) | function Ss(){Vl.startNonterminal("JSONTest",Pl);switch(Hl){case 167:Cs(...
function xs (line 1) | function xs(){switch(Hl){case 167:ks();break;case 194:As();break;default...
function Ts (line 1) | function Ts(){Vl.startNonterminal("StructuredItemTest",Pl),Sl(242),kl(22...
function Ns (line 1) | function Ns(){xl(242),kl(22),xl(34),kl(23),xl(37)}
function Cs (line 1) | function Cs(){Vl.startNonterminal("JSONItemTest",Pl),Sl(167),kl(22),Sl(3...
function ks (line 1) | function ks(){xl(167),kl(22),xl(34),kl(23),xl(37)}
function Ls (line 1) | function Ls(){Vl.startNonterminal("JSONObjectTest",Pl),Sl(194),kl(22),Sl...
function As (line 1) | function As(){xl(194),kl(22),xl(34),kl(23),xl(37)}
function Os (line 1) | function Os(){Vl.startNonterminal("JSONArrayTest",Pl),Sl(78),kl(22),Sl(3...
function Ms (line 1) | function Ms(){xl(78),kl(22),xl(34),kl(23),xl(37)}
function _s (line 1) | function _s(){Vl.startNonterminal("AtomicOrUnionType",Pl),Ha(),Vl.endNon...
function Ds (line 1) | function Ds(){Ba()}
function Ps (line 1) | function Ps(){Vl.startNonterminal("KindTest",Pl);switch(Hl){case 120:Fs(...
function Hs (line 1) | function Hs(){switch(Hl){case 120:Is();break;case 121:ro();break;case 82...
function Bs (line 1) | function Bs(){Vl.startNonterminal("AnyKindTest",Pl),Sl(191),kl(22),Sl(34...
function js (line 1) | function js(){xl(191),kl(22),xl(34),kl(23),xl(37)}
function Fs (line 1) | function Fs(){Vl.startNonterminal("DocumentTest",Pl),Sl(120),kl(22),Sl(3...
function Is (line 1) | function Is(){xl(120),kl(22),xl(34),kl(144);if(Hl!=37)switch(Hl){case 12...
function qs (line 1) | function qs(){Vl.startNonterminal("TextTest",Pl),Sl(244),kl(22),Sl(34),k...
function Rs (line 1) | function Rs(){xl(244),kl(22),xl(34),kl(23),xl(37)}
function Us (line 1) | function Us(){Vl.startNonterminal("CommentTest",Pl),Sl(96),kl(22),Sl(34)...
function zs (line 1) | function zs(){xl(96),kl(22),xl(34),kl(23),xl(37)}
function Ws (line 1) | function Ws(){Vl.startNonterminal("NamespaceNodeTest",Pl),Sl(185),kl(22)...
function Xs (line 1) | function Xs(){xl(185),kl(22),xl(34),kl(23),xl(37)}
function Vs (line 1) | function Vs(){Vl.startNonterminal("PITest",Pl),Sl(216),kl(22),Sl(34),kl(...
function $s (line 1) | function $s(){xl(216),kl(22),xl(34),kl(253);if(Hl!=37)switch(Hl){case 11...
function Js (line 1) | function Js(){Vl.startNonterminal("AttributeTest",Pl),Sl(82),kl(22),Sl(3...
function Ks (line 1) | function Ks(){xl(82),kl(22),xl(34),kl(261),Hl!=37&&(Gs(),kl(101),Hl==41&...
function Qs (line 1) | function Qs(){Vl.startNonterminal("AttribNameOrWildcard",Pl);switch(Hl){...
function Gs (line 1) | function Gs(){switch(Hl){case 38:xl(38);break;default:co()}}
function Ys (line 1) | function Ys(){Vl.startNonterminal("SchemaAttributeTest",Pl),Sl(226),kl(2...
function Zs (line 1) | function Zs(){xl(226),kl(22),xl(34),kl(255),to(),kl(23),xl(37)}
function eo (line 1) | function eo(){Vl.startNonterminal("AttributeDeclaration",Pl),lo(),Vl.end...
function to (line 1) | function to(){co()}
function no (line 1) | function no(){Vl.startNonterminal("ElementTest",Pl),Sl(121),kl(22),Sl(34...
function ro (line 1) | function ro(){xl(121),kl(22),xl(34),kl(261),Hl!=37&&(so(),kl(101),Hl==41...
function io (line 1) | function io(){Vl.startNonterminal("ElementNameOrWildcard",Pl);switch(Hl)...
function so (line 1) | function so(){switch(Hl){case 38:xl(38);break;default:po()}}
function oo (line 1) | function oo(){Vl.startNonterminal("SchemaElementTest",Pl),Sl(227),kl(22)...
function uo (line 1) | function uo(){xl(227),kl(22),xl(34),kl(255),fo(),kl(23),xl(37)}
function ao (line 1) | function ao(){Vl.startNonterminal("ElementDeclaration",Pl),ho(),Vl.endNo...
function fo (line 1) | function fo(){po()}
function lo (line 1) | function lo(){Vl.startNonterminal("AttributeName",Pl),Ha(),Vl.endNonterm...
function co (line 1) | function co(){Ba()}
function ho (line 1) | function ho(){Vl.startNonterminal("ElementName",Pl),Ha(),Vl.endNontermin...
function po (line 1) | function po(){Ba()}
function vo (line 1) | function vo(){Vl.startNonterminal("SimpleTypeName",Pl),go(),Vl.endNonter...
function mo (line 1) | function mo(){yo()}
function go (line 1) | function go(){Vl.startNonterminal("TypeName",Pl),Ha(),Vl.endNonterminal(...
function yo (line 1) | function yo(){Ba()}
function bo (line 1) | function bo(){Vl.startNonterminal("FunctionTest",Pl);for(;;){kl(97);if(H...
function wo (line 1) | function wo(){for(;;){kl(97);if(Hl!=32)break;j()}switch(Hl){case 145:Ll(...
function Eo (line 1) | function Eo(){Vl.startNonterminal("AnyFunctionTest",Pl),Sl(145),kl(22),S...
function So (line 1) | function So(){xl(145),kl(22),xl(34),kl(24),xl(38),kl(23),xl(37)}
function xo (line 1) | function xo(){Vl.startNonterminal("TypedFunctionTest",Pl),Sl(145),kl(22)...
function To (line 1) | function To(){xl(145),kl(22),xl(34),kl(263);if(Hl!=37){gs();for(;;){kl(1...
function No (line 1) | function No(){Vl.startNonterminal("ParenthesizedItemType",Pl),Sl(34),kl(...
function Co (line 1) | function Co(){xl(34),kl(260),Es(),kl(23),xl(37)}
function ko (line 1) | function ko(){Vl.startNonterminal("RevalidationDecl",Pl),Sl(108),kl(72),...
function Lo (line 1) | function Lo(){Vl.startNonterminal("InsertExprTargetChoice",Pl);switch(Hl...
function Ao (line 1) | function Ao(){switch(Hl){case 70:xl(70);break;case 84:xl(84);break;defau...
function Oo (line 1) | function Oo(){Vl.startNonterminal("InsertExpr",Pl),Sl(159),kl(129);switc...
function Mo (line 1) | function Mo(){xl(159),kl(129);switch(Hl){case 191:xl(191);break;default:...
function _o (line 1) | function _o(){Vl.startNonterminal("DeleteExpr",Pl),Sl(110),kl(129);switc...
function Do (line 1) | function Do(){xl(110),kl(129);switch(Hl){case 191:xl(191);break;default:...
function Po (line 1) | function Po(){Vl.startNonterminal("ReplaceExpr",Pl),Sl(219),kl(130),Hl==...
function Ho (line 1) | function Ho(){xl(219),kl(130),Hl==261&&(xl(261),kl(64),xl(196)),kl(62),x...
function Bo (line 1) | function Bo(){Vl.startNonterminal("RenameExpr",Pl),Sl(218),kl(62),Sl(191...
function jo (line 1) | function jo(){xl(218),kl(62),xl(191),kl(267),Ro(),xl(79),kl(267),zo()}
function Fo (line 1) | function Fo(){Vl.startNonterminal("SourceExpr",Pl),_f(),Vl.endNontermina...
function Io (line 1) | function Io(){Df()}
function qo (line 1) | function qo(){Vl.startNonterminal("TargetExpr",Pl),_f(),Vl.endNontermina...
function Ro (line 1) | function Ro(){Df()}
function Uo (line 1) | function Uo(){Vl.startNonterminal("NewNameExpr",Pl),_f(),Vl.endNontermin...
function zo (line 1) | function zo(){Df()}
function Wo (line 1) | function Wo(){Vl.startNonterminal("TransformExpr",Pl),Sl(103),kl(21),Nl(...
function Xo (line 1) | function Xo(){xl(103),kl(21),$o();for(;;){if(Hl!=41)break;xl(41),kl(21),...
function Vo (line 1) | function Vo(){Vl.startNonterminal("TransformSpec",Pl),Sl(31),kl(255),Nl(...
function $o (line 1) | function $o(){xl(31),kl(255),pi(),kl(27),xl(52),kl(267),Df()}
function Jo (line 1) | function Jo(){Vl.startNonterminal("FTSelection",Pl),Yo();for(;;){kl(212)...
function Ko (line 1) | function Ko(){Zo();for(;;){kl(212);switch(Hl){case 81:Ll(151);break;defa...
function Qo (line 1) | function Qo(){Vl.startNonterminal("FTWeight",Pl),Sl(264),kl(87),Sl(276),...
function Go (line 1) | function Go(){xl(264),kl(87),xl(276),kl(267),Y(),xl(282)}
function Yo (line 1) | function Yo(){Vl.startNonterminal("FTOr",Pl),eu();for(;;){if(Hl!=144)bre...
function Zo (line 1) | function Zo(){tu();for(;;){if(Hl!=144)break;xl(144),kl(162),tu()}}
function eu (line 1) | function eu(){Vl.startNonterminal("FTAnd",Pl),nu();for(;;){if(Hl!=142)br...
function tu (line 1) | function tu(){ru();for(;;){if(Hl!=142)break;xl(142),kl(162),ru()}}
function nu (line 1) | function nu(){Vl.startNonterminal("FTMildNot",Pl),iu();for(;;){kl(213);i...
function ru (line 1) | function ru(){su();for(;;){kl(213);if(Hl!=193)break;xl(193),kl(53),xl(15...
function iu (line 1) | function iu(){Vl.startNonterminal("FTUnaryNot",Pl),Hl==143&&Sl(143),kl(1...
function su (line 1) | function su(){Hl==143&&xl(143),kl(155),uu()}
function ou (line 1) | function ou(){Vl.startNonterminal("FTPrimaryWithOptions",Pl),au(),kl(215...
function uu (line 1) | function uu(){fu(),kl(215),Hl==259&&Iu(),Hl==264&&Go()}
function au (line 1) | function au(){Vl.startNonterminal("FTPrimary",Pl);switch(Hl){case 34:Sl(...
function fu (line 1) | function fu(){switch(Hl){case 34:xl(34),kl(162),Ko(),xl(37);break;case 3...
function lu (line 1) | function lu(){Vl.startNonterminal("FTWords",Pl),hu(),kl(222);if(Hl==71||...
function cu (line 1) | function cu(){pu(),kl(222),(Hl==71||Hl==76||Hl==210)&&gu()}
function hu (line 1) | function hu(){Vl.startNonterminal("FTWordsValue",Pl);switch(Hl){case 11:...
function pu (line 1) | function pu(){switch(Hl){case 11:xl(11);break;default:xl(276),kl(267),Y(...
function du (line 1) | function du(){Vl.startNonterminal("FTExtensionSelection",Pl);for(;;){Nl(...
function vu (line 1) | function vu(){for(;;){kr(),kl(100);if(Hl!=35)break}xl(276),kl(166),Hl!=2...
function mu (line 1) | function mu(){Vl.startNonterminal("FTAnyallOption",Pl);switch(Hl){case 7...
function gu (line 1) | function gu(){switch(Hl){case 76:xl(76),kl(219),Hl==272&&xl(272);break;c...
function yu (line 1) | function yu(){Vl.startNonterminal("FTTimes",Pl),Sl(195),kl(149),Nl(),wu(...
function bu (line 1) | function bu(){xl(195),kl(149),Eu(),xl(247)}
function wu (line 1) | function wu(){Vl.startNonterminal("FTRange",Pl);switch(Hl){case 130:Sl(1...
function Eu (line 1) | function Eu(){switch(Hl){case 130:xl(130),kl(267),$n();break;case 81:xl(...
function Su (line 1) | function Su(){Vl.startNonterminal("FTPosFilter",Pl);switch(Hl){case 202:...
function xu (line 1) | function xu(){switch(Hl){case 202:Nu();break;case 269:ku();break;case 11...
function Tu (line 1) | function Tu(){Vl.startNonterminal("FTOrder",Pl),Sl(202),Vl.endNontermina...
function Nu (line 1) | function Nu(){xl(202)}
function Cu (line 1) | function Cu(){Vl
Copy disabled (too large)
Download .json
Condensed preview — 536 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (12,645K chars).
[
{
"path": ".editorconfig",
"chars": 147,
"preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
},
{
"path": ".gitignore",
"chars": 1108,
"preview": "# Referenced from https://github.com/github/gitignore/blob/master/Node.gitignore\n\n_book\n# Logs\nlogs\n*.log\nnpm-debug.log*"
},
{
"path": "LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "README.md",
"chars": 1278,
"preview": "\n<img src=\"./img/pwa.png\" width=\"50%\">\n\n# 《PWA 应用实战》\n\n欢迎走进 PWA 世界!!\n\n## 简介\n\n本书围绕着 PWA 以及周边技术,从概念入手,以实战的方式给读者讲述如何编写 PWA,以"
},
{
"path": "SUMMARY.md",
"chars": 2655,
"preview": "# Summary\n\n<!-- * [书写规范](standard.md)\n* [前言](chapter01/11-why.md) -->\n* [HOME](README.md)\n* [第1章 PWA 介绍](chapter01.md)\n "
},
{
"path": "appendix01/1-lighthouse.md",
"chars": 6806,
"preview": "## 使用 Lighthouse 测评 PWA\n\nLighthouse 是 Google Chrome 推出的一个开源自动化工具,能够对 PWA 多方面的效果指标进行评测,并给出最佳实践的建议以帮助开发者改进 PWA 的质量。它的使用方法也"
},
{
"path": "appendix01/2-lighthouse-score-guide.md",
"chars": 4969,
"preview": "## Lighthouse 评分指南\n\n使用 Lighthouse 对网站进行测评后,我们会得到一份评分报告,它包含了性能(Performance),PWA(Progressive Web App),访问无障碍(Accessibility)"
},
{
"path": "appendix01/3-lighthouse-case.md",
"chars": 1929,
"preview": "## Lighthouse 使用案例\n\n前面两节我们对 Lighthouse 的使用方法和性能评分计算的原理有了一定的了解,下面我们使用 Lighthouse 对一个实际站点 [https://lavas.baidu.com/](https"
},
{
"path": "appendix01.md",
"chars": 166,
"preview": "# 使用 Lighthouse 测评 PWA\n\n完成了 PWA 站点的开发之后,我们需要对站点进行评估和测试,了解其性能是否达标、是否符合 PWA 规范等。本章中,我们将介绍一款 Web App 的测评工具 —— Lighthouse 的使"
},
{
"path": "book.json",
"chars": 2900,
"preview": "{\n \"title\": \"PWA 应用实战\",\n \"description\": \"PWA 开发实战\",\n \"author\": \"lavas <lavas@baidu.com>\",\n \"language\": \"zh-hans\",\n "
},
{
"path": "chapter01/1-how-was-pwa-born.md",
"chars": 1278,
"preview": "# 为什么会出现 PWA\n\nPWA 是 Progressive Web Apps 的缩写,翻译为渐进式网络应用。早在 2014 年, W3C 就公布过 Service Worker 的相关草案,但是其在生产环境被 Chrome 支持是在 2"
},
{
"path": "chapter01/2-what-is-pwa.md",
"chars": 2090,
"preview": "# 什么是 PWA\n\nGoogle 提出 PWA 的时候,并没有给它一个准确的定义,经过我们的实践和总结,\nPWA 它不是特指某一项技术,而是应用多项技术来改善用户体验的 Web App,其核心技术包括 Web App Manifest,S"
},
{
"path": "chapter01/3-what-are-key-techs.md",
"chars": 3058,
"preview": "# PWA 的核心技术\n\n前文提到,PWA 的核心是用户体验,能让 PWA 达到原生应用的体验并不仅仅依赖于某一项技术,而是多管齐下,进行改进,从而在安全、性能和体验上都获得很大的提升。下面将简单介绍几个 PWA 应用中经常使用到的技术,后"
},
{
"path": "chapter01/4-how-is-pwa-going.md",
"chars": 1925,
"preview": "# PWA 的发展\n\n从 PWA 被提出到现在,已经过去了 4 年,PWA 取得的成绩有目共睹,特别是在国外,在网络速度不够快或者相对贫困的地区,PWA 非常受欢迎,因为它不需要很高的硬件配置,也很省流量,比如在印度,就有一个很成功的案例,"
},
{
"path": "chapter01/5-the-future-of-pwa.md",
"chars": 1690,
"preview": "# PWA 的未来\n\n从 Google 最初提出 PWA 到现在,PWA 已经有不小的改变了,这就是 Web 的魅力,遵循标准且完全开放的魅力,来自世界各地的开发者参与标准的制定,它还在不断进化,Web 即使已经 30 岁了,它还依旧是被广"
},
{
"path": "chapter01/6-your-first-pwa.md",
"chars": 4128,
"preview": "# 你的第一个 PWA\n\n本书中大部分示例均基于下面的这个模板展开,开发者可以跟随书中示例逐步操作,加深理解。在这个示例里,我们一起来实现一个能添加到桌面并且离线可用的 PWA。\n\n## 准备工作\n\n在准备编写第一个 PWA 前,有一些准备"
},
{
"path": "chapter01.md",
"chars": 373,
"preview": "# 什么是 PWA\n\n随着移动互联网的发展,Native App 开始兴起,那么 Web App 落伍淘汰了吗?很显然答案是没有,它依然生机勃勃,和 Native App 互相依存,还衍生出很多 Native App 和 Web App 相"
},
{
"path": "chapter02/1-what-is-good-ux.md",
"chars": 1071,
"preview": "# 什么是好的用户体验\n\nPWA 的核心是用户体验,它的核心技术(如 Service Worker,Web App Manifest 等)都是为了提升 Web App 用户体验,但“体验”其实是个很主观的感受,我们很难用一个或几个量化指标来"
},
{
"path": "chapter02/2-design-and-tech.md",
"chars": 1003,
"preview": "# 设计与技术\n\n好的设计没有好的技术来实现同样无法达到最佳的用户体验。\n\n设计与技术并不只是实现的关系,而是需要互相配合,再完美的设计如果不能实现,那也只是白费,而一些技术上的优化也需要设计给予配合才能更加完美。\n\n上一节中,列出了很多设"
},
{
"path": "chapter02/3-app-shell.md",
"chars": 7247,
"preview": "# App Shell\n\n在第一章中已经简单介绍过 App Shell,这个小节我们会更深入学习如何构建和使用 App Shell。它并不是一种新的技术或者 API,而是设计与技术相结合产生的一种整站设计方案,减少用户进入页面的等待时间,用"
},
{
"path": "chapter02/4-app-skeleton.md",
"chars": 7744,
"preview": "# 骨架屏\n\n在前面的章节,我们说过,首屏速度对于用户留存率至关重要。\n\n很多站点都会在完成基本功能后(或者同时)进行性能优化,常见的性能优化手段包括静态资源合并,压缩文件,使用 CDN,包括上一小节介绍的 App Shell 等,这些的确"
},
{
"path": "chapter02/5-responsive-design.md",
"chars": 17282,
"preview": "# 响应式布局\n\n自从进入移动互联网时代,响应式布局这个词经常出现在 Web 设计和开发领域,它让 Web 页面在不同尺寸的设备上都具有良好的浏览体验。\n\n## 开始之前\n\n在讲解响应式布局之前,需要先了解一下基础知识,只有对它们都有一定的"
},
{
"path": "chapter02.md",
"chars": 260,
"preview": "# 设计与体验\n\nPWA 的提出包含了很多新的技术,如 Service Worker 等,但**用户体验**才是它的核心,用户体验包括很多方面,速度、顺滑度、阅读体验等,这不是单靠设计师能做到的,需要设计与技术互相配合,设计配合技术,技术实"
},
{
"path": "chapter03/1-promise.md",
"chars": 13169,
"preview": "# Promise\n\n在深入介绍 Service Worker 之前,先来了解一下 Promise API。因为 Service Worker 的所有的异步接口内部都采用 Promise 来实现,因此学习了 Promise 讲能够有助于对 "
},
{
"path": "chapter03/2-async-function.md",
"chars": 7379,
"preview": "# Async 函数\n\n上一节介绍了 Promise 对象,我们可以很方便地利用 Promise 将过去基于回调函数的异步过程改造成基于链式调用实现,这样更符合我们线性的思维习惯。但实践过程中发现,这种链式调用的异步方案仍然不够直观,我们更"
},
{
"path": "chapter03/3-fetch-api.md",
"chars": 9123,
"preview": "# Fetch API\n\nFetch API 是目前最新的异步请求解决方案,它在功能上与 XMLHttpRequest(XHR)类似,都是从服务端异步获取数据或资源的方法。对于有过 AJAX 开发经验的读者应该深有体会,基于 XHR 的异步"
},
{
"path": "chapter03/4-cache-api.md",
"chars": 3926,
"preview": "# Cache API\n\n在上一节 Fetch API 的介绍当中提到,Fetch API 提供了 Request、Response 等偏底层的类对象,这样就能够以统一的形式将资源的请求与响应过程应用到更多的场景当中。本节所介绍的 Cach"
},
{
"path": "chapter03/5-indexeddb.md",
"chars": 17519,
"preview": "# IndexedDB\n\nCache Storage 是一种缓存管理的缓存空间,前面了解到了 Cache Storage 是基于键值对的方式缓存数据,是适用于存储和检索网络请求及响应的存储系统,不能提供搜索功能,不能建立自定义的索引。Ind"
},
{
"path": "chapter03.md",
"chars": 416,
"preview": "# 基础技术简介\n\nPWA 是建立在现代前端技术和标准之上的,因此在介绍 PWA 特别是 Service Worker 相关的内容时,会频繁地使用一些方法和对象。它们有的不属于 PWA 的范畴,有的不一定要配合 Service Worker"
},
{
"path": "chapter04/1-service-worker-introduction.md",
"chars": 6465,
"preview": "# Service Worker 简介\n\n丢失网络连接是一个困扰 Web 用户多年的难题,即使是世界上最好的 Web App,如果因为网络原因访问不了它,那体验也是非常糟糕的。本小节要介绍的 Service Worker 能提供一种良好的统"
},
{
"path": "chapter04/2-service-worker-register.md",
"chars": 12692,
"preview": "# Service Worker 注册\n\n通过前面对 Service Worker 概念的介绍,我们对 Service Worker 的一些概念和原理有了一定的了解,在本节将会重点介绍 Service Worker 注册的相关内容。主要会介"
},
{
"path": "chapter04/3-service-worker-dive.md",
"chars": 12961,
"preview": "# Service Worker 工作原理\n\n前面已经介绍了 Service Worker 是一个工作线程的本质,也了解了 Service Worker 可以离线工作,还介绍了 Service Worker 在主线程中是如何被注册的。但是到"
},
{
"path": "chapter04/4-service-worker-debug.md",
"chars": 5252,
"preview": "# Service Worker 调试\n\n在开发 Service Worker 文件的过程中,如何调试呢?怎么才能确保线下开发的 Service Worker 文件在经过注册后到线上去运行是符合预期的呢?在这小节中将详细介绍如何调试 Ser"
},
{
"path": "chapter04/code/applicationCacheDemo/index.html",
"chars": 73,
"preview": "<!DOCTYPE html>\n<html manifest=\"./manifest.appcache\">\n<!--...-->\n</html>\n"
},
{
"path": "chapter04/code/applicationCacheDemo/manifest.appcache",
"chars": 121,
"preview": "CACHE MANIFEST\n# version xx.xx.xx\nCACHE:\ncached.png\ncached.js\n\nNETWORK:\nnoCached.html\nnoCached.css\n\nFALLBACK:\n/ 404.html"
},
{
"path": "chapter04/code/serviceWorkerCacheDemo/data.json",
"chars": 132,
"preview": "[\n {\n \"name\": \"Tom\",\n \"age\": 34,\n \"lang\": \"english\"\n },\n {\n \"name\": \"Lee\",\n \"age\": 23,\n \"lang\": \"ch"
},
{
"path": "chapter04/code/serviceWorkerCacheDemo/index.css",
"chars": 47,
"preview": "html {\n background: #000;\n color: #fff;\n}"
},
{
"path": "chapter04/code/serviceWorkerCacheDemo/index.html",
"chars": 699,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Cache Demo</title>\n <link href=\"./index.css\" rel=\"stylesheet\" />\n "
},
{
"path": "chapter04/code/serviceWorkerCacheDemo/index.js",
"chars": 771,
"preview": "/**\n * @file index.js\n * @author pwa\n */\n\n/* global fetch, alert */\n\nfetch('./data.json')\n .then(response => response.j"
},
{
"path": "chapter04/code/serviceWorkerCacheDemo/sw.js",
"chars": 1637,
"preview": "/**\n * @file sw.js\n * @author pwa\n */\n\n/* global self, caches, fetch */\n\nself.addEventListener('install', event => {\n s"
},
{
"path": "chapter04/code/serviceWorkerDemo/index.html",
"chars": 230,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Demo</title>\n </head>\n <body>\n <script>\n if ('serviceWorker"
},
{
"path": "chapter04/code/serviceWorkerDemo/sw.js",
"chars": 0,
"preview": ""
},
{
"path": "chapter04/code/serviceWorkerIndexeddbDemo/fruits.json",
"chars": 20,
"preview": "{\n \"data\": \"demo\"\n}"
},
{
"path": "chapter04/code/serviceWorkerIndexeddbDemo/index.html",
"chars": 723,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Lifecycle Demo</title>\n </head>\n <body>\n <script>\n /* globa"
},
{
"path": "chapter04/code/serviceWorkerIndexeddbDemo/sw.js",
"chars": 1992,
"preview": "/**\n * @file sw.js\n * @author pwa\n */\n\n/* global self, Response */\n\n// 为了保证每次新建的 indexedDB 都会触发更新\n// 用时间戳来维护 db 的版本号\n// "
},
{
"path": "chapter04/code/serviceWorkerLifecycleDemo/index.html",
"chars": 578,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Lifecycle Demo</title>\n </head>\n <body>\n <img src=\"./imgs/dog.jp"
},
{
"path": "chapter04/code/serviceWorkerLifecycleDemo/sw.js",
"chars": 572,
"preview": "/**\n * @file sw.js\n * @author pws\n */\n\n/* global self */\n\nconsole.log('service worker 注册成功')\n\nself.addEventListener('ins"
},
{
"path": "chapter04/code/serviceWorkerScopeDemo/a/b/sw.js",
"chars": 0,
"preview": ""
},
{
"path": "chapter04/code/serviceWorkerScopeDemo/index.html",
"chars": 355,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Scope Demo</title>\n </head>\n <body>\n <script>\n if ('service"
},
{
"path": "chapter04/code/serviceWorkerScopeDemo1/a/a-sw.js",
"chars": 102,
"preview": "/**\n * @file service worker 文件\n * @author pwa\n */\n\n/* global self */\n\nself.version = '20190402235959'\n"
},
{
"path": "chapter04/code/serviceWorkerScopeDemo1/a/index.html",
"chars": 566,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Scope Demo1 PageA</title>\n </head>\n <body>\n <script>\n if ('"
},
{
"path": "chapter04/code/serviceWorkerScopeDemo1/b/index.html",
"chars": 264,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Scope Demo1 PageB</title>\n </head>\n <body>\n <script>\n if ('"
},
{
"path": "chapter04/code/serviceWorkerScopeDemo1/root-sw.js",
"chars": 0,
"preview": ""
},
{
"path": "chapter04/code/serviceWorkerUnregisterDemo/index.html",
"chars": 849,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Unregister Demo</title>\n </head>\n <body>\n <script>\n if ('se"
},
{
"path": "chapter04/code/serviceWorkerUnregisterDemo/sw.js",
"chars": 0,
"preview": ""
},
{
"path": "chapter04.md",
"chars": 375,
"preview": "# Service Worker\n\nService Worker 是 PWA 技术基础之一,脱离浏览器主线程的特性,使得 Web App 离线缓存成为可能,更为后台同步、通知推送等功能提供了思路。Service Worker 和缓存之间的关"
},
{
"path": "chapter05/1-fetch-event-management.md",
"chars": 6356,
"preview": "# 资源请求的拦截代理\n\n对资源请求的拦截代理是 Service Worker 的重要功能之一。Service Worker 在完成注册并激活之后,对 `fetch` 事件的监听就会开始生效,我们可以在事件回调里完成对请求的拦截与改写。下面"
},
{
"path": "chapter05/2-local-storage-management.md",
"chars": 5854,
"preview": "# 本地存储管理\n\n在上一节解决了如何对资源请求进行拦截代理之后,要实现网页的离线缓存还需要解决本地存储的选择与管理问题。\n\n从前面学习中我们知道,处于同一作用域下的网页会共用一个 Service Worker 线程,这个 Service "
},
{
"path": "chapter05/3-respond-strategy.md",
"chars": 7198,
"preview": "# 资源请求响应策略\n\n在 Service Worker 环境下,可以通过 Fetch API 发送网络请求获取资源,也可以通过 Cache API、IndexedDB 等本地缓存中获取缓存资源,甚至可以在 Service Worker 直"
},
{
"path": "chapter05/4-precache.md",
"chars": 10992,
"preview": "# 预缓存方案\n\n一个网页的展现所依赖的资源可以大致分为两类:一、静态资源,比如 JS,CSS,前端渲染的 HTML 文件,样式文件中可能使用到的字体、背景图片等等。它们的作用是保证页面的前端功能和样式正常工作。这些资源只有在网站更新上线的"
},
{
"path": "chapter05/5-workbox.md",
"chars": 6515,
"preview": "# 使用 Workbox\n\nWorkbox 是 Google Chrome 团队推出的一套 PWA 的解决方案,这套解决方案当中包含了核心库和构建工具,因此我们可以利用 Workbox 实现 Service Worker 的快速开发。本节内"
},
{
"path": "chapter05.md",
"chars": 411,
"preview": "# 离线缓存\n\n通过前面章节对 PWA 概念的了解,我们知道,离线缓存是 PWA 应用在体验提升上的一个重要特性,离线缓存特性能够使得 PWA 应用在离线环境下可以正常使用,在弱网环境下能够使站点快速响应。通常 PWA 的离线缓存特性主要是"
},
{
"path": "chapter06/1-manifest-json.md",
"chars": 9485,
"preview": "# Web 应用清单\n\nWeb 应用清单(Web App Manifest)是一份 JSON 格式的文件,它定义了网站应用的相关信息,包括应用名称、图标、启动方式等等。当网站提供了这么一份应用程序清单,并且满足一定的生效条件之后,将具有添加"
},
{
"path": "chapter06/2-credentials-api.md",
"chars": 10525,
"preview": "# 凭证管理 API\n\n一直以来,登录网站总是一件非常麻烦的事情,尤其是在移动端,如果过早要求用户进行登录,转化率会大大降低。用户输入账号密码并提交给服务器进行校验,服务器校验通过之后将创建 session 保持会话。基于安全角度的考虑,用"
},
{
"path": "chapter06/3-notification-api.md",
"chars": 10706,
"preview": "# 桌面通知\n\n在 iOS 和 Android 移动设备中,Native App 向用户推送通知是很常见的行为,这是重新吸引用户访问应用最有效方法之一。然而推送通知一直被认为是 Web App 缺少的能力,在接下来的两节我们将介绍 Web "
},
{
"path": "chapter06/4-web-push-api.md",
"chars": 15341,
"preview": "# 网络推送\n\n在上一节我们已经介绍了使用 Notification API 如何创建推送并展示给用户,但是当浏览器没有打开,Service Worker 处于休眠状态时,如何将通知推送给用户呢?Native App 很早就实现了离线通知,"
},
{
"path": "chapter06/5-payment-request-api.md",
"chars": 5318,
"preview": "# Payment Request API\n\n对于用户留存来说,浏览器的支付功能就是培养用户习惯的利器,一旦用户习惯了在 Web 站点中可以直接使用支付 API 进行购物等消费活动,那么 Web 站点的用户留存率自然就提高了。\n\n虽然目前各"
},
{
"path": "chapter06.md",
"chars": 991,
"preview": "# 用户存留\n\n用户存留一直是网站应用保持活跃度的一项重大课题。通过自身过硬的品质和适当的营销手段吸引到用户之后,PWA 提供了一系列方法来提高网站吸引力,增加网站与用户的互动性等等,达到提高用户留存率的目的。\n\n## 添加到主屏幕\n\n网站"
},
{
"path": "chapter07/1-https.md",
"chars": 7986,
"preview": "## 使用 HTTPS 保护站点安全\n\n构建 PWA 应用时,HTTPS 是必不可少的条件之一。使用 HTTP 协议的应用存在着一定的安全隐患,这是因为 HTTP 本身不具备加密的功能,通信中使用明文传输请求和响应的内容,内容可能会被窃听,"
},
{
"path": "chapter07/2-CSP.md",
"chars": 3662,
"preview": "## 内容安全策略\n\nCSP(Content Security Policy)即内容安全策略,主要目标是减少、并有效报告 XSS 攻击,其实质就是让开发者定制一份白名单,告诉浏览器允许加载、执行的外部资源。即使攻击者能够发现可从中注入脚本的"
},
{
"path": "chapter07/3-policy.md",
"chars": 3799,
"preview": "## 同源策略\n\n上一节介绍 CSP 时,我们提到了浏览器的同源策略,同源策略是 Web 安全的基础,它对从一个源加载的资源如何与来自另一个源的资源进行交互做出了限制。这是一个用于隔离潜在恶意文件的关键安全机制,每个源均与其他网络保持隔离,"
},
{
"path": "chapter07/4-vulnerability.md",
"chars": 4088,
"preview": "## 常见的安全漏洞\n\n在构建 PWA 站点的过程中,我们会面临很多的安全风险和漏洞,如 `XSS`,`CSRF`,`SQL 注入漏洞`,`ClickJacking`,`文件上传漏洞` 等等。在本小节中,我们列举几种客户端常见的安全漏洞,了"
},
{
"path": "chapter07.md",
"chars": 233,
"preview": "# 安全\n\n上一章,我们介绍了 PWA 中与用户留存相关的内容。在构建 PWA 应用时,如何保证用户的信息安全,也是一个十分重要的话题。Web 应用程序的使用范围非常广泛,其安全问题也日益突显,代码中的细小漏洞随时可能被攻击者利用,导致用户"
},
{
"path": "chapter08/1-loading-performance.md",
"chars": 11561,
"preview": "# 加载性能\n\nWeb 页面由 HTML、CSS、JavaScript 和其他多媒体资源组成。页面加载时,必须从服务器获取这些资源。在这一节中,我们会围绕这些资源和网络请求,讨论如何优化页面加载性能。\n\n## 减小资源体积\n\n为了让页面更快"
},
{
"path": "chapter08/2-rendering-performance.md",
"chars": 14331,
"preview": "# 渲染性能\n\n在上文中,我们从多个角度讨论了如何优化页面加载性能。但一个用户体验良好的页面,不仅要快速加载,还需要有一系列流畅的交互。从而,这一节我们把目光投向页面渲染性能。\n\n## 渲染流程\n\n浏览器在渲染页面前,首先会将 HTML 文"
},
{
"path": "chapter08.md",
"chars": 201,
"preview": "# 性能\n\n我们在 [第一章 什么是 PWA](./chapter01/2-what-is-pwa.md) 中提到,PWA 应该快速加载、及时响应用户反馈、提供流畅的动画、以及拥有类似 Native App 一般沉浸的用户体验。这每一点都表"
},
{
"path": "chapter09/1-search-engine-index.md",
"chars": 1678,
"preview": "# 搜索引擎收录\n\n搜索引擎作为 Web 的重要的流量入口,是每个 Web App 开发者或运营者需要重点关注的一个方向,而 PWA 作为 Web 的一种形式,自然是需要关注搜索引擎对它的抓取和收录情况。通常 PWA 多数是 SPA,有以下"
},
{
"path": "chapter09/2-pwa-and-amp-and-mip.md",
"chars": 5299,
"preview": "# PWA 与 AMP/MIP\n\n本节将会介绍 PWA 如何和 AMP/MIP 进行结合应用,以至于让 PWA 能够在搜索环境下体验变得更好。在看 PWA 如何和 AMP/MIP 结合之前,还是先了解一下什么是 AMP/MIP。\n\n## 什"
},
{
"path": "chapter09/3-whole-site-amp-and-mip.md",
"chars": 6311,
"preview": "# 全站 AMP/MIP\n\n全站 AMP/MIP,顾名思义就是指整个站点每个页面都用 AMP/MIP 来编写。由于全站 AMP 和全站 MIP 都会依赖于 AMP/MIP 运行时提供的交互机制,表 9-1 列出了能够通过 AMP/MIP 实"
},
{
"path": "chapter09/4-preload-pwa.md",
"chars": 1766,
"preview": "# 在 AMP/MIP 页面中预加载 PWA\n\n通过本章第二节,我们了解到 AMP/MIP 不适合用实现复杂的 Web App,按照 AMP 官网的介绍,AMP 是“叶子页面”(有具体内容,不是导航类型的页面)理想的解决方案,因为它加载快,"
},
{
"path": "chapter09.md",
"chars": 269,
"preview": "# PWA 与搜索\n\nPWA 是 Web 的一种形式,搜索仍是它的一大入口。\n\n熟知搜索引擎的开发者都知道,早期的搜索引擎是不支持抓取 SPA(Single Page Application) 的,而 PWA 多是 SPA,在这种情况下,如"
},
{
"path": "ci.yml",
"chars": 370,
"preview": "Global:\n tool: build_submitter\n\nDefault:\n profile: [buildProduction]\n\nProfiles:\n\n - profile:\n name: buildProductio"
},
{
"path": "docs/LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "docs/appendix01/1-lighthouse.html",
"chars": 70732,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/appendix01/2-lighthouse-score-guide.html",
"chars": 68738,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/appendix01/3-lighthouse-case.html",
"chars": 46822,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/appendix01.html",
"chars": 33370,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter01/1-how-was-pwa-born.html",
"chars": 40024,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter01/2-what-is-pwa.html",
"chars": 44297,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter01/3-what-are-key-techs.html",
"chars": 51781,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter01/4-how-is-pwa-going.html",
"chars": 43572,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter01/5-the-future-of-pwa.html",
"chars": 41611,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter01/6-your-first-pwa.html",
"chars": 54558,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter01.html",
"chars": 33948,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter02/1-what-is-good-ux.html",
"chars": 39985,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter02/2-design-and-tech.html",
"chars": 41217,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter02/3-app-shell.html",
"chars": 71218,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter02/4-app-skeleton.html",
"chars": 91894,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter02/5-responsive-design.html",
"chars": 161167,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter02.html",
"chars": 34144,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter03/1-promise.html",
"chars": 148305,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter03/2-async-function.html",
"chars": 97349,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter03/3-fetch-api.html",
"chars": 94773,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter03/4-cache-api.html",
"chars": 62001,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter03/5-indexeddb.html",
"chars": 150781,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter03.html",
"chars": 34665,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter04/1-service-worker-introduction.html",
"chars": 67684,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter04/2-service-worker-register.html",
"chars": 92372,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter04/3-service-worker-dive.html",
"chars": 102302,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter04/4-service-worker-debug.html",
"chars": 60056,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter04/code/applicationCacheDemo/index.html",
"chars": 73,
"preview": "<!DOCTYPE html>\n<html manifest=\"./manifest.appcache\">\n<!--...-->\n</html>\n"
},
{
"path": "docs/chapter04/code/applicationCacheDemo/manifest.appcache",
"chars": 121,
"preview": "CACHE MANIFEST\n# version xx.xx.xx\nCACHE:\ncached.png\ncached.js\n\nNETWORK:\nnoCached.html\nnoCached.css\n\nFALLBACK:\n/ 404.html"
},
{
"path": "docs/chapter04/code/serviceWorkerCacheDemo/data.json",
"chars": 132,
"preview": "[\n {\n \"name\": \"Tom\",\n \"age\": 34,\n \"lang\": \"english\"\n },\n {\n \"name\": \"Lee\",\n \"age\": 23,\n \"lang\": \"ch"
},
{
"path": "docs/chapter04/code/serviceWorkerCacheDemo/index.css",
"chars": 47,
"preview": "html {\n background: #000;\n color: #fff;\n}"
},
{
"path": "docs/chapter04/code/serviceWorkerCacheDemo/index.html",
"chars": 699,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Cache Demo</title>\n <link href=\"./index.css\" rel=\"stylesheet\" />\n "
},
{
"path": "docs/chapter04/code/serviceWorkerCacheDemo/index.js",
"chars": 771,
"preview": "/**\n * @file index.js\n * @author pwa\n */\n\n/* global fetch, alert */\n\nfetch('./data.json')\n .then(response => response.j"
},
{
"path": "docs/chapter04/code/serviceWorkerCacheDemo/sw.js",
"chars": 1637,
"preview": "/**\n * @file sw.js\n * @author pwa\n */\n\n/* global self, caches, fetch */\n\nself.addEventListener('install', event => {\n s"
},
{
"path": "docs/chapter04/code/serviceWorkerDemo/index.html",
"chars": 230,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Demo</title>\n </head>\n <body>\n <script>\n if ('serviceWorker"
},
{
"path": "docs/chapter04/code/serviceWorkerDemo/sw.js",
"chars": 0,
"preview": ""
},
{
"path": "docs/chapter04/code/serviceWorkerIndexeddbDemo/fruits.json",
"chars": 20,
"preview": "{\n \"data\": \"demo\"\n}"
},
{
"path": "docs/chapter04/code/serviceWorkerIndexeddbDemo/index.html",
"chars": 723,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Lifecycle Demo</title>\n </head>\n <body>\n <script>\n /* globa"
},
{
"path": "docs/chapter04/code/serviceWorkerIndexeddbDemo/sw.js",
"chars": 1992,
"preview": "/**\n * @file sw.js\n * @author pwa\n */\n\n/* global self, Response */\n\n// 为了保证每次新建的 indexedDB 都会触发更新\n// 用时间戳来维护 db 的版本号\n// "
},
{
"path": "docs/chapter04/code/serviceWorkerLifecycleDemo/index.html",
"chars": 578,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Lifecycle Demo</title>\n </head>\n <body>\n <img src=\"./imgs/dog.jp"
},
{
"path": "docs/chapter04/code/serviceWorkerLifecycleDemo/sw.js",
"chars": 572,
"preview": "/**\n * @file sw.js\n * @author pws\n */\n\n/* global self */\n\nconsole.log('service worker 注册成功')\n\nself.addEventListener('ins"
},
{
"path": "docs/chapter04/code/serviceWorkerScopeDemo/a/b/sw.js",
"chars": 0,
"preview": ""
},
{
"path": "docs/chapter04/code/serviceWorkerScopeDemo/index.html",
"chars": 355,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Scope Demo</title>\n </head>\n <body>\n <script>\n if ('service"
},
{
"path": "docs/chapter04/code/serviceWorkerScopeDemo1/a/a-sw.js",
"chars": 102,
"preview": "/**\n * @file service worker 文件\n * @author pwa\n */\n\n/* global self */\n\nself.version = '20190402235959'\n"
},
{
"path": "docs/chapter04/code/serviceWorkerScopeDemo1/a/index.html",
"chars": 566,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Scope Demo1 PageA</title>\n </head>\n <body>\n <script>\n if ('"
},
{
"path": "docs/chapter04/code/serviceWorkerScopeDemo1/b/index.html",
"chars": 264,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Scope Demo1 PageB</title>\n </head>\n <body>\n <script>\n if ('"
},
{
"path": "docs/chapter04/code/serviceWorkerScopeDemo1/root-sw.js",
"chars": 0,
"preview": ""
},
{
"path": "docs/chapter04/code/serviceWorkerUnregisterDemo/index.html",
"chars": 849,
"preview": "<!DOCTYPE html>\n <head>\n <title>Service Worker Unregister Demo</title>\n </head>\n <body>\n <script>\n if ('se"
},
{
"path": "docs/chapter04/code/serviceWorkerUnregisterDemo/sw.js",
"chars": 0,
"preview": ""
},
{
"path": "docs/chapter04.html",
"chars": 34143,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter05/1-fetch-event-management.html",
"chars": 81930,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter05/2-local-storage-management.html",
"chars": 81738,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter05/3-respond-strategy.html",
"chars": 79729,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter05/4-precache.html",
"chars": 106185,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter05/5-workbox.html",
"chars": 75161,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter05.html",
"chars": 34607,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter06/1-manifest-json.html",
"chars": 96331,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter06/2-credentials-api.html",
"chars": 102861,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter06/3-notification-api.html",
"chars": 100434,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter06/4-web-push-api.html",
"chars": 128681,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter06/5-payment-request-api.html",
"chars": 69332,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter06.html",
"chars": 41575,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter07/1-https.html",
"chars": 79057,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter07/2-CSP.html",
"chars": 49677,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter07/3-policy.html",
"chars": 57712,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter07/4-vulnerability.html",
"chars": 60031,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter07.html",
"chars": 33627,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter08/1-loading-performance.html",
"chars": 117367,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter08/2-rendering-performance.html",
"chars": 139027,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter08.html",
"chars": 33202,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter09/1-search-engine-index.html",
"chars": 42222,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter09/2-pwa-and-amp-and-mip.html",
"chars": 66979,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter09/3-whole-site-amp-and-mip.html",
"chars": 70368,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter09/4-preload-pwa.html",
"chars": 44638,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/chapter09.html",
"chars": 33787,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/ci.yml",
"chars": 370,
"preview": "Global:\n tool: build_submitter\n\nDefault:\n profile: [buildProduction]\n\nProfiles:\n\n - profile:\n name: buildProductio"
},
{
"path": "docs/gitbook/gitbook-plugin-3-ba/plugin.js",
"chars": 659,
"preview": "require([\"gitbook\"], function(gitbook) {\n // Load analytics.js\n gitbook.events.bind(\"start\", function(e, config) {"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ace.js",
"chars": 347010,
"preview": "(function(){function o(n){var i=e;n&&(e[n]||(e[n]={}),i=e[n]);if(!i.define||!i.define.packaged)t.original=i.define,i.def"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-beautify.js",
"chars": 3813,
"preview": "define(\"ace/ext/beautify/php_rules\",[\"require\",\"exports\",\"module\",\"ace/token_iterator\"],function(e,t,n){\"use strict\";var"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-chromevox.js",
"chars": 6404,
"preview": "define(\"ace/ext/chromevox\",[\"require\",\"exports\",\"module\",\"ace/editor\",\"ace/config\"],function(e,t,n){function gt(){return"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-elastic_tabstops_lite.js",
"chars": 3839,
"preview": "define(\"ace/ext/elastic_tabstops_lite\",[\"require\",\"exports\",\"module\",\"ace/editor\",\"ace/config\"],function(e,t,n){\"use str"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-emmet.js",
"chars": 21309,
"preview": "define(\"ace/snippets\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/lib/event_emitter\",\"ace/lib/lang\",\"ace/range\",\"ac"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-error_marker.js",
"chars": 143,
"preview": ";\n (function() {\n window.require([\"ace/ext/error_marker\"], function() {});\n "
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-keybinding_menu.js",
"chars": 3631,
"preview": "define(\"ace/ext/menu_tools/overlay_page\",[\"require\",\"exports\",\"module\",\"ace/lib/dom\"],function(e,t,n){\"use strict\";var r"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-language_tools.js",
"chars": 34153,
"preview": "define(\"ace/snippets\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/lib/event_emitter\",\"ace/lib/lang\",\"ace/range\",\"ac"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-linking.js",
"chars": 824,
"preview": "define(\"ace/ext/linking\",[\"require\",\"exports\",\"module\",\"ace/editor\",\"ace/config\"],function(e,t,n){function i(e){var t=e."
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-modelist.js",
"chars": 3530,
"preview": "define(\"ace/ext/modelist\",[\"require\",\"exports\",\"module\"],function(e,t,n){\"use strict\";function i(e){var t=a.text,n=e.spl"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-old_ie.js",
"chars": 11896,
"preview": "define(\"ace/ext/searchbox\",[\"require\",\"exports\",\"module\",\"ace/lib/dom\",\"ace/lib/lang\",\"ace/lib/event\",\"ace/keyboard/hash"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-searchbox.js",
"chars": 9991,
"preview": "define(\"ace/ext/searchbox\",[\"require\",\"exports\",\"module\",\"ace/lib/dom\",\"ace/lib/lang\",\"ace/lib/event\",\"ace/keyboard/hash"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-settings_menu.js",
"chars": 12037,
"preview": "define(\"ace/ext/menu_tools/element_generator\",[\"require\",\"exports\",\"module\"],function(e,t,n){\"use strict\";n.exports.crea"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-spellcheck.js",
"chars": 1439,
"preview": "define(\"ace/ext/spellcheck\",[\"require\",\"exports\",\"module\",\"ace/lib/event\",\"ace/editor\",\"ace/config\"],function(e,t,n){\"us"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-split.js",
"chars": 4188,
"preview": "define(\"ace/split\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/lib/lang\",\"ace/lib/event_emitter\",\"ace/editor\",\"ace/"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-static_highlight.js",
"chars": 2818,
"preview": "define(\"ace/ext/static_highlight\",[\"require\",\"exports\",\"module\",\"ace/edit_session\",\"ace/layer/text\",\"ace/config\",\"ace/li"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-statusbar.js",
"chars": 1087,
"preview": "define(\"ace/ext/statusbar\",[\"require\",\"exports\",\"module\",\"ace/lib/dom\",\"ace/lib/lang\"],function(e,t,n){\"use strict\";var "
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-textarea.js",
"chars": 9107,
"preview": "define(\"ace/theme/textmate\",[\"require\",\"exports\",\"module\",\"ace/lib/dom\"],function(e,t,n){\"use strict\";t.isDark=!1,t.cssC"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-themelist.js",
"chars": 1440,
"preview": "define(\"ace/ext/themelist\",[\"require\",\"exports\",\"module\",\"ace/lib/fixoldbrowsers\"],function(e,t,n){\"use strict\";e(\"ace/l"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/ext-whitespace.js",
"chars": 2571,
"preview": "define(\"ace/ext/whitespace\",[\"require\",\"exports\",\"module\",\"ace/lib/lang\"],function(e,t,n){\"use strict\";var r=e(\"../lib/l"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/keybinding-emacs.js",
"chars": 24424,
"preview": "define(\"ace/occur\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/range\",\"ace/search\",\"ace/edit_session\",\"ace/search_h"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/keybinding-vim.js",
"chars": 96571,
"preview": "define(\"ace/keyboard/vim\",[\"require\",\"exports\",\"module\",\"ace/range\",\"ace/lib/event_emitter\",\"ace/lib/dom\",\"ace/lib/oop\","
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-abap.js",
"chars": 6030,
"preview": "define(\"ace/mode/abap_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\"],func"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-abc.js",
"chars": 4658,
"preview": "define(\"ace/mode/abc_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\"],funct"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-actionscript.js",
"chars": 20494,
"preview": "define(\"ace/mode/actionscript_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rule"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-ada.js",
"chars": 1749,
"preview": "define(\"ace/mode/ada_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\"],funct"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-apache_conf.js",
"chars": 13858,
"preview": "define(\"ace/mode/apache_conf_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-applescript.js",
"chars": 5394,
"preview": "define(\"ace/mode/applescript_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-asciidoc.js",
"chars": 8148,
"preview": "define(\"ace/mode/asciidoc_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\"],"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-assembly_x86.js",
"chars": 8847,
"preview": "define(\"ace/mode/assembly_x86_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rule"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-autohotkey.js",
"chars": 63589,
"preview": "define(\"ace/mode/autohotkey_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\""
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-batchfile.js",
"chars": 4805,
"preview": "define(\"ace/mode/batchfile_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\"]"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-c9search.js",
"chars": 3848,
"preview": "define(\"ace/mode/c9search_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/lib/lang\",\"ace/mode/text_hig"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-c_cpp.js",
"chars": 17285,
"preview": "define(\"ace/mode/doc_comment_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-cirru.js",
"chars": 2958,
"preview": "define(\"ace/mode/cirru_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\"],fun"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-clojure.js",
"chars": 7977,
"preview": "define(\"ace/mode/clojure_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\"],f"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-cobol.js",
"chars": 2300,
"preview": "define(\"ace/mode/cobol_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\"],fun"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-coffee.js",
"chars": 7664,
"preview": "define(\"ace/mode/coffee_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules\"],fu"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-coldfusion.js",
"chars": 68744,
"preview": "define(\"ace/mode/doc_comment_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules"
},
{
"path": "docs/gitbook/gitbook-plugin-ace/ace/mode-csharp.js",
"chars": 15106,
"preview": "define(\"ace/mode/doc_comment_highlight_rules\",[\"require\",\"exports\",\"module\",\"ace/lib/oop\",\"ace/mode/text_highlight_rules"
}
]
// ... and 336 more files (download for full content)
About this extraction
This page contains the full source code of the lavas-project/pwa-book GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 536 files (11.1 MB), approximately 2.9M tokens, and a symbol index with 2637 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.