Compare commits

..

117 Commits

Author SHA1 Message Date
Conduitry
3dd947089d site: mention host in preload and in page store 2019-06-11 11:33:18 -04:00
Conduitry
a5872477ad add --host option to sapper export 2019-06-11 11:32:27 -04:00
Conduitry
c4f92a597d add tests 2019-06-11 11:32:00 -04:00
Conduitry
57b11b4b67 expose host in preload and page store 2019-06-11 11:13:44 -04:00
Richard Harris
e2f13aad7a -> v0.27.4 2019-06-10 08:53:30 -04:00
Richard Harris
fdacbb93bb -> v0.27.3 2019-06-09 20:20:02 -04:00
Rich Harris
fb8bf5c9b0 Merge pull request #732 from sveltejs/fix-resolution
Fix resolution
2019-06-09 20:16:30 -04:00
Richard Harris
8baf694e6d revert previous unpleasantness 2019-06-09 20:10:56 -04:00
Richard Harris
b6c693934d update lockfile 2019-06-09 20:00:57 -04:00
Richard Harris
c42de4cc44 Merge branch 'master' into fix-resolution 2019-06-09 18:58:25 -04:00
Richard Harris
ff6db7d8af merge 2019-06-09 18:57:59 -04:00
Richard Harris
6c9a90e87d rewrite source code to point at correct files 2019-06-09 18:55:00 -04:00
Richard Harris
a1d3927958 update deps 2019-06-09 18:16:40 -04:00
Conduitry
d0caad5303 -> v0.27.2 2019-06-08 18:50:52 -04:00
cudr
b1e84687c0 repair regexp routes 2019-06-08 18:34:09 -04:00
Conduitry
993bd6cc5b site: make readme more relevant 2019-06-06 17:17:14 -04:00
Conduitry
d4ee7ab040 site: quote degit repo argument (#726) 2019-06-06 17:13:11 -04:00
Poluektov Dmitriy
43e7f0d30d fix: cli ignores --output option (#723) 2019-06-06 06:57:17 -04:00
Conduitry
d7e2662298 site: bump sapper 2019-05-25 18:51:53 -04:00
Richard Harris
a86b613e12 -> v0.27.1 2019-05-21 00:17:22 -04:00
Rich Harris
fcd66fb736 Merge pull request #704 from sveltejs/gh-701
always re-run preload functions when query string changes
2019-05-21 00:12:09 -04:00
Richard Harris
b6e2736eb0 always re-run preload functions when query string changes - fixes #701 2019-05-20 23:45:39 -04:00
Richard Harris
aa31e3ca6a refactor slightly 2019-05-20 23:11:50 -04:00
Richard Harris
8b310dd458 Merge branch 'issues/688' of https://github.com/btakita/sapper into btakita-issues/688 2019-05-20 23:08:58 -04:00
Brian Takita
5460896228 Checking the current_branch[i] route match against the current request's route match.
current_branch[i].match.slice(1, i+2) compared to match.slice(1, i+2)

Fixes https://github.com/sveltejs/sapper/issues/688
2019-05-20 23:03:08 -04:00
Rich Harris
bcf6ac3a6f Merge pull request #692 from sveltejs/gh-331
leave anchor scrolling on initial load to browser
2019-05-20 22:37:26 -04:00
Rich Harris
b8d99aaa90 Merge pull request #632 from pngwn/master
Add support for custom route file extensions.
2019-05-20 22:33:56 -04:00
Rich Harris
2bb173f140 Merge pull request #683 from cristianl/pr-make-live-optional
Allow disabling live reload
2019-05-20 22:24:06 -04:00
Rich Harris
594cb0a356 Merge pull request #694 from jarrodldavis/fix/restart-server
Fix restarting server in dev
2019-05-20 22:23:29 -04:00
Rich Harris
9ff87af23b Merge pull request #698 from mrkishi/gh-689
Fix export queue
2019-05-20 22:20:59 -04:00
Conduitry
75afc691f4 site: fix doc hash link destinations (#696) 2019-05-18 15:41:59 -04:00
mrkishi
9dd63ab760 fix export queue 2019-05-17 16:50:08 -03:00
pngwn
e7b1aa373a Add support for custom route file extensions. 2019-05-17 19:19:04 +01:00
Antony Jones
ce50c2ff98 License is MIT, not LIL 2019-05-16 11:42:20 -04:00
Jarrod Davis
6add2518aa Fix restarting server in dev
Wait for the server process to exit before starting a new one, so that
the server debugger port is free when the new process begins.
2019-05-15 02:28:41 -06:00
Conduitry
ab939b8cb5 site: update links to Discord chat 2019-05-15 00:34:48 -04:00
Conduitry
b59d9003f9 leave anchor scrolling on initial load to browser (#331) 2019-05-13 08:56:21 -04:00
Cristian Lorsson
1fc169e7b8 Reload only when live reload is enabled 2019-05-10 15:08:32 -03:00
Rich Harris
7aa3e90f87 Merge pull request #677 from mrkishi/infinite-loop
Abort infinite loop for preload errors
2019-05-10 09:45:11 -04:00
Rich Harris
b27c9ecd59 Merge pull request #681 from Troush/patch-1
Typo fix for proper routing link
2019-05-10 09:13:56 -04:00
Alex
8653a799eb Typo fix for proper routing link 2019-05-10 14:50:42 +03:00
mrkishi
62969d59f6 Abort infinite loop for preload errors 2019-05-09 10:40:23 -03:00
Richard Harris
5c07080207 -> v0.27.0 2019-05-09 09:38:21 -04:00
Richard Harris
c2ed73f103 -> v0.27.0 2019-05-09 09:30:45 -04:00
Richard Harris
133ac07ed2 remove some unused imports 2019-05-09 09:26:49 -04:00
Richard Harris
7417de101e Merge branch 'master' of github.com:sveltejs/sapper 2019-05-09 09:26:13 -04:00
Rich Harris
2792b7c5d1 Merge pull request #676 from mrkishi/server-indexes
Consolidate index handling for page and server routes
2019-05-09 09:18:26 -04:00
Rich Harris
cf8d5ee717 Merge pull request #670 from cristianl/pr-rollup-output-naming
Build rollup config: Don't add hashes to files in dist
2019-05-09 09:15:04 -04:00
Rich Harris
8e4517a1ad Merge pull request #673 from mrkishi/testing-issue
Harden tests (...a bit)
2019-05-09 08:57:18 -04:00
Rich Harris
91894722ee Merge pull request #652 from sveltejs/relicense
switch license to MIT
2019-05-09 08:44:32 -04:00
Rich Harris
933b3b76a6 Merge pull request #665 from mrkishi/node-stable
Switch to node stable
2019-05-09 08:42:07 -04:00
Richard Harris
3ed4d1d887 update deps 2019-05-09 08:39:58 -04:00
Richard Harris
dda936e53b fix theme color 2019-05-09 08:34:41 -04:00
Rich Harris
ac4eb84f3d Merge pull request #662 from thollander/feat/fix-mobile-viewport
Fix mobile viewport KO
2019-05-09 08:32:57 -04:00
Rich Harris
9e70e68c0c Merge pull request #664 from mrkishi/refactor
Tidy a few things up
2019-05-09 08:28:57 -04:00
Rich Harris
22389eab99 Merge pull request #678 from mrkishi/generated-app-quoted-attributes
Add attribute quotes to generated `App.svelte`
2019-05-09 08:24:02 -04:00
mrkishi
fe6b7976ef Add attribute quotes to generated App.svelte 2019-05-08 22:44:07 -03:00
mrkishi
54e92c3b99 Regularize page and server routes
Treats both page and server routes similarly in regards to indexes.

It's a (somewhat) breaking change: server routes will accept trailing
slashes to match page routes behavior, but only if there's no extension.

We still need a stronger support for dealing with different clean urls
rules.
2019-05-08 12:24:45 -03:00
mrkishi
e6c1a54164 Harden tests (...a bit) 2019-05-08 00:57:59 -03:00
Cristian Lorsson
629b5601c8 Don't add hashes to dist output
Default is '[name].[hash].js'
2019-05-06 20:46:32 -03:00
Conduitry
8bdd363a19 site: remove chokidar 2019-05-06 12:50:31 -04:00
mrkishi
8fcc27d44f Update svelte to released v3 2019-05-05 23:19:00 -03:00
mrkishi
f6e72a0432 Update dependencies 2019-05-05 21:25:00 -03:00
mrkishi
f886a12bd7 Group route filters 2019-05-05 14:51:23 -03:00
mrkishi
6c03cfd46a Extract layout file search 2019-05-05 14:51:22 -03:00
mrkishi
78480fe5e8 Fix typo 2019-05-05 14:51:22 -03:00
mrkishi
5cba40b7e0 Refactor compose_handlers and ignore 2019-05-05 14:51:22 -03:00
Richard Harris
c99b787632 -> v0.26.1 2019-05-05 11:44:53 -04:00
Rich Harris
99a25308fc Merge pull request #663 from sveltejs/skipped-segments
Handle skipped segments
2019-05-05 11:44:08 -04:00
Richard Harris
c0ada5c52f handle skipped segments 2019-05-05 11:36:02 -04:00
Richard Harris
d51e1a0af8 failing test for skipped segments 2019-05-05 10:49:38 -04:00
thollander
5e5a8c4c69 Fix mobile viewport KO
- Changed `;` to `,` which causes the bug
2019-05-05 12:17:41 +02:00
Rich Harris
49f8b2c4bd Update 10-exporting.md 2019-05-04 10:58:26 -04:00
Rich Harris
2754ba0ee4 Merge pull request #655 from zigomir/patch-1
Fix template
2019-05-04 10:34:10 -04:00
ziga
1ad27573c6 Need to use branch
- fix port
2019-05-02 20:41:11 -04:00
ziga
f10b941c4e Fix template 2019-05-02 20:37:11 -04:00
Conduitry
6ca869a3b1 update links in readme 2019-05-02 10:11:24 -04:00
Richard Harris
f44e900d61 -> v0.26.0 2019-05-02 09:10:48 -04:00
Rich Harris
69baa99a79 Merge pull request #638 from sveltejs/site
start adding site to main repo
2019-05-02 08:49:09 -04:00
Rich Harris
ff6eb3d915 Merge pull request #651 from sveltejs/shimport
always use latest shimport
2019-05-02 08:36:21 -04:00
Richard Harris
a2204a9d2e switch license to MIT 2019-05-02 08:32:40 -04:00
Richard Harris
011e20519f use shimport ^1.0.0 2019-05-02 08:30:51 -04:00
Richard Harris
4589ffe759 add deploy script 2019-05-02 08:25:57 -04:00
Richard Harris
e2502edc08 fix broken links 2019-05-02 08:23:04 -04:00
Richard Harris
c55fe8fcf8 always use latest shimport 2019-05-01 09:22:26 -04:00
Richard Harris
5d6e4c2287 add netlify config 2019-05-01 09:17:05 -04:00
Richard Harris
b0a8534de5 Merge branch 'master' into site 2019-05-01 09:01:39 -04:00
Rich Harris
1a36fb53cd Merge pull request #642 from sveltejs/context-stores
give each app its own page, preloading and session stores, using context
2019-05-01 09:01:25 -04:00
Richard Harris
f84318b21d clarify a couple of points 2019-05-01 08:56:59 -04:00
Rich Harris
316c9d7baa lol 2019-04-30 13:28:52 -04:00
Rich Harris
52ed1cb2c6 update site-kit version 2019-04-30 13:25:17 -04:00
Rich Harris
dc73973d44 update docs 2019-04-30 12:10:53 -04:00
Richard Harris
bca88831da add 0.26 migration guide 2019-04-30 09:25:40 -04:00
Rich Harris
afeedf6bb2 give each app its own page, preloading and session stores, using context 2019-04-29 14:52:19 -04:00
Rich Harris
9c48e32a45 Merge pull request #641 from sveltejs/gh-604-redux
prevent hanging with large numbers of links
2019-04-29 13:43:55 -04:00
Rich Harris
bc8e5501cd prevent hanging with large numbers of links (#604) 2019-04-29 13:10:15 -04:00
Rich Harris
1707fe8e9d wait inside handle function when exporting 2019-04-29 11:55:03 -04:00
Rich Harris
ff60a7b389 Merge branch 'gh-604-alpha' of https://github.com/bwbroersma/sapper into bwbroersma-gh-604-alpha 2019-04-29 11:50:08 -04:00
Rich Harris
1bc7096a9e Merge pull request #591 from thgh/redirect-basepath
Fix redirect with basepath
2019-04-29 11:43:51 -04:00
Rich Harris
497ae89279 add a test for #618 2019-04-29 11:23:42 -04:00
Rich Harris
02e0aeb8a2 merge master -> artemjackson-patch-3 2019-04-29 11:22:21 -04:00
Rich Harris
15924d9768 shuffle things around 2019-04-29 11:04:40 -04:00
Rich Harris
e07c27adba Merge pull request #607 from cudr/spread_routes
[...spread] routes
2019-04-29 10:39:21 -04:00
Richard Harris
54abed7d6e hack 2019-04-29 09:09:45 -04:00
Richard Harris
d3782dac28 reverse migration order 2019-04-29 09:04:18 -04:00
Richard Harris
26bdd54484 move more stuff into site-kit 2019-04-28 22:52:56 -04:00
Richard Harris
63251c6733 fix some styles 2019-04-28 22:06:02 -04:00
Richard Harris
381af86f04 move old docs over 2019-04-28 21:44:34 -04:00
Richard Harris
36f91d4e9a start adding site to main repo 2019-04-28 14:37:02 -04:00
cudr
54efe7235a fix comparator sort && add more tests 2019-04-28 00:14:27 +03:00
Rich Harris
8c7ce2c6bb Merge branch 'master' into spread_routes 2019-04-27 13:09:54 -04:00
Rich Harris
83e427178e Merge branch 'master' into spread_routes 2019-04-27 11:16:18 -04:00
Artyom Stepanishchev
06eee32ee5 Fix for #618
`query-string` is used as example

see https://github.com/sindresorhus/query-string/blob/master/index.js#L186
2019-04-08 22:56:35 +03:00
Benjamin W. Broersma
bf50392df5 Fixes #604 - using single queue for export. 2019-03-22 00:43:08 +01:00
cudr
81f80e6215 spread routes 2019-03-13 15:14:06 +03:00
Thomas Ghysels
13b64cd1bb Fix redirect with basepath
Fix #589
2019-03-03 10:40:49 +01:00
Thomas Ghysels
9522cb4539 Add failing test for #589 2019-03-02 18:35:16 +01:00
202 changed files with 8765 additions and 1501 deletions

4
.gitignore vendored
View File

@@ -3,11 +3,7 @@ yarn.lock
yarn-error.log yarn-error.log
node_modules node_modules
cypress/screenshots cypress/screenshots
test/app/.sapper
test/app/src/manifest
__sapper__ __sapper__
test/app/export
test/app/build
sapper sapper
runtime.js runtime.js
dist dist

3
.netlify/state.json Normal file
View File

@@ -0,0 +1,3 @@
{
"siteId": "a6a3cfed-c34e-42f7-9616-07ad2eedb15f"
}

View File

@@ -1,5 +1,59 @@
# sapper changelog # sapper changelog
## 0.27.4
* Update devalue
## 0.27.3
* Accommodate Svelte 3.5.0
## 0.27.2
* Fix routes with regular expressions ([#707](https://github.com/sveltejs/sapper/issues/707))
* Fix `sapper build --output` option ([#723](https://github.com/sveltejs/sapper/pull/723))
## 0.27.1
* Prevent infinite loop if `preload` errors ([#677](https://github.com/sveltejs/sapper/pull/677))
* Allow disabling of live reload ([#683](https://github.com/sveltejs/sapper/pull/683))
* Let browser handle initial scroll ([#331](https://github.com/sveltejs/sapper/issues/331))
* Allow custom route file extensions via `--ext` ([#632](https://github.com/sveltejs/sapper/pull/632))
* Wait for server to restart before attaching debugger ([#694](https://github.com/sveltejs/sapper/pull/694))
* Fix export queue ([#698](https://github.com/sveltejs/sapper/pull/698))
* Rerun `preload` functions when query changes ([#701](https://github.com/sveltejs/sapper/issues/701))
* Navigate when spread route changes ([#688](https://github.com/sveltejs/sapper/issues/688))
## 0.27.0
* Change license from LIL to MIT ([#652](https://github.com/sveltejs/sapper/pull/652))
* Fix index server route mapping ([#624](https://github.com/sveltejs/sapper/issues/624))
## 0.26.1
* Handle skipped segments ([#663](https://github.com/sveltejs/sapper/pull/663))
## 0.26.0
* Update to Svelte 3
* Slot-based nested routes ([#573](https://github.com/sveltejs/sapper/issues/573))
* Make `page`, `preloading` and `session` stores available to components ([#642](https://github.com/sveltejs/sapper/pull/642))
* Handle missing/empty refs when exporting ([#602](https://github.com/sveltejs/sapper/issues/602))
* Prevent race condition when exporting ([#585](https://github.com/sveltejs/sapper/pull/585))
* Fix redirects with base path ([#589](https://github.com/sveltejs/sapper/issues/589))
* Add `<link rel="preload">` to exported HTML ([#568](https://github.com/sveltejs/sapper/pull/568))
* Handle deep links that are invalid selectors on initial load ([#516](https://github.com/sveltejs/sapper/issues/516))
* Use shared queue for exporting ([#604](https://github.com/sveltejs/sapper/issues/604))
* Handle `+` character in query string ([#618](https://github.com/sveltejs/sapper/issues/618))
* Spread routes ([#545](https://github.com/sveltejs/sapper/issues/545))
* Fix navigation from `/a/[id]` to `/b/[id]` ([#610](https://github.com/sveltejs/sapper/pull/610))
* Allow `preload` functions to return falsy values ([#587](https://github.com/sveltejs/sapper/issues/587))
* Mount error pages correctly ([#620](https://github.com/sveltejs/sapper/pull/620))
## 0.25.0 ## 0.25.0
* Force refresh on `goto(current_url)` ([#484](https://github.com/sveltejs/sapper/pull/484)) * Force refresh on `goto(current_url)` ([#484](https://github.com/sveltejs/sapper/pull/484))

10
LICENSE
View File

@@ -1,9 +1,7 @@
Copyright (c) 2017 [these people](https://github.com/sveltejs/sapper/graphs/contributors). Copyright (c) 2016-19 [these people](https://github.com/sveltejs/sapper/graphs/contributors)
Permission is hereby granted by the authors of this software, to any person, to use the software for any purpose, free of charge, including the rights to run, read, copy, change, distribute and sell it, and including usage rights to any patents the authors may hold on it, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
This license, or a link to its text, must be included with all copies of the software and any derivative works. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Any modification to the software submitted to the authors may be incorporated into the software under the terms of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The software is provided "as is", without warranty of any kind, including but not limited to the warranties of title, fitness, merchantability and non-infringement. The authors have no obligation to provide support or updates for the software, and may not be held liable for any damages, claims or other liability arising from its use.

View File

@@ -1,11 +1,11 @@
# sapper # sapper
[Military-grade progressive web apps, powered by Svelte.](https://sapper.svelte.technology) [Military-grade progressive web apps, powered by Svelte.](https://sapper.svelte.dev)
## What is Sapper? ## What is Sapper?
Sapper is a framework for building high-performance universal web apps. [Read the guide](https://sapper.svelte.technology/guide) or the [introductory blog post](https://svelte.technology/blog/sapper-towards-the-ideal-web-app-framework) to learn more. Sapper is a framework for building high-performance universal web apps. [Read the guide](https://sapper.svelte.dev/docs) or the [introductory blog post](https://svelte.dev/blog/sapper-towards-the-ideal-web-app-framework) to learn more.
## Get started ## Get started
@@ -74,4 +74,4 @@ npm run test
## License ## License
[LIL](LICENSE) [MIT](LICENSE)

View File

@@ -1,4 +1,5 @@
--require source-map-support/register --require source-map-support/register
--require sucrase/register --require sucrase/register
--recursive --recursive
test/unit/*/test.ts
test/apps/*/test.ts test/apps/*/test.ts

1581
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "sapper", "name": "sapper",
"version": "0.26.0-alpha.12", "version": "0.27.4",
"description": "Military-grade apps, engineered by Svelte", "description": "Military-grade apps, engineered by Svelte",
"bin": { "bin": {
"sapper": "./sapper" "sapper": "./sapper"
@@ -18,50 +18,50 @@
"test": "test" "test": "test"
}, },
"dependencies": { "dependencies": {
"html-minifier": "^3.5.21", "html-minifier": "^4.0.0",
"http-link-header": "^1.0.2", "http-link-header": "^1.0.2",
"shimport": "0.0.14", "shimport": "^1.0.0",
"sourcemap-codec": "^1.4.4", "sourcemap-codec": "^1.4.4",
"string-hash": "^1.1.3" "string-hash": "^1.1.3"
}, },
"devDependencies": { "devDependencies": {
"@types/mocha": "^5.2.5", "@types/mocha": "^5.2.7",
"@types/node": "^10.12.21", "@types/node": "^12.0.7",
"@types/puppeteer": "^1.11.3", "@types/puppeteer": "^1.12.4",
"agadoo": "^1.0.1", "agadoo": "^1.0.1",
"cheap-watch": "^1.0.2", "cheap-watch": "^1.0.2",
"cookie": "^0.3.1", "cookie": "^0.4.0",
"devalue": "^1.1.0", "devalue": "^1.1.1",
"eslint": "^5.12.1", "eslint": "^5.16.0",
"eslint-plugin-import": "^2.16.0", "eslint-plugin-import": "^2.17.3",
"kleur": "^3.0.1", "kleur": "^3.0.3",
"mocha": "^5.2.0", "mocha": "^6.1.4",
"node-fetch": "^2.3.0", "node-fetch": "^2.6.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"polka": "^0.5.1", "polka": "^0.5.2",
"port-authority": "^1.0.5", "port-authority": "^1.0.5",
"pretty-bytes": "^5.1.0", "pretty-bytes": "^5.2.0",
"puppeteer": "^1.12.0", "puppeteer": "^1.17.0",
"require-relative": "^0.8.7", "require-relative": "^0.8.7",
"rollup": "^1.1.2", "rollup": "^1.14.5",
"rollup-plugin-commonjs": "^9.2.0", "rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-json": "^3.1.0", "rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^4.0.0", "rollup-plugin-node-resolve": "^5.0.1",
"rollup-plugin-replace": "^2.1.0", "rollup-plugin-replace": "^2.2.0",
"rollup-plugin-string": "^2.0.2", "rollup-plugin-string": "^3.0.0",
"rollup-plugin-sucrase": "^2.1.0", "rollup-plugin-sucrase": "^2.1.0",
"rollup-plugin-svelte": "^5.0.3", "rollup-plugin-svelte": "^5.0.3",
"sade": "^1.4.2", "sade": "^1.5.0",
"sirv": "^0.2.2", "sirv": "^0.4.2",
"sucrase": "^3.9.5", "sucrase": "^3.10.1",
"svelte": "^3.0.0-beta.11", "svelte": "^3.5.0",
"svelte-loader": "^2.13.3", "svelte-loader": "^2.13.4",
"webpack": "^4.29.0", "webpack": "^4.33.0",
"webpack-format-messages": "^2.0.5", "webpack-format-messages": "^2.0.5",
"yootils": "0.0.14" "yootils": "0.0.16"
}, },
"peerDependencies": { "peerDependencies": {
"svelte": "^3.0.0" "svelte": "^3.5.0"
}, },
"scripts": { "scripts": {
"test": "mocha --opts mocha.opts", "test": "mocha --opts mocha.opts",
@@ -81,7 +81,7 @@
"express" "express"
], ],
"author": "Rich Harris", "author": "Rich Harris",
"license": "LIL", "license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/sveltejs/sapper/issues" "url": "https://github.com/sveltejs/sapper/issues"
}, },

View File

@@ -1,5 +1,5 @@
import sucrase from 'rollup-plugin-sucrase'; import sucrase from 'rollup-plugin-sucrase';
import string from 'rollup-plugin-string'; import { string } from 'rollup-plugin-string';
import json from 'rollup-plugin-json'; import json from 'rollup-plugin-json';
import resolve from 'rollup-plugin-node-resolve'; import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs'; import commonjs from 'rollup-plugin-commonjs';
@@ -52,7 +52,8 @@ export default [
output: { output: {
dir: 'dist', dir: 'dist',
format: 'cjs', format: 'cjs',
sourcemap: true sourcemap: true,
chunkFileNames: '[name].js'
}, },
external, external,
plugins: [ plugins: [

View File

@@ -1,12 +0,0 @@
<script>
import { setContext } from 'svelte';
import { CONTEXT_KEY } from './shared';
export let Root;
export let props;
export let session;
setContext(CONTEXT_KEY, session);
</script>
<Root {...props}/>

View File

@@ -1,10 +1,5 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
export const stores = {
preloading: writable(false),
page: writable(null)
};
export const CONTEXT_KEY = {}; export const CONTEXT_KEY = {};
export const preload = () => ({}); export const preload = () => ({});

View File

@@ -1,7 +1,6 @@
import { writable } from 'svelte/store.mjs'; import { writable } from 'svelte/store';
import App from '@sapper/internal/App.svelte'; import App from '@sapper/internal/App.svelte';
import { stores } from '@sapper/internal/shared'; import { root_preload, ErrorComponent, ignore, components, routes } from '@sapper/internal/manifest-client';
import { Root, root_preload, ErrorComponent, ignore, components, routes } from '@sapper/internal/manifest-client';
import { import {
Target, Target,
ScrollPosition, ScrollPosition,
@@ -23,13 +22,18 @@ let root_component: Component;
let current_token: {}; let current_token: {};
let root_preloaded: Promise<any>; let root_preloaded: Promise<any>;
let current_branch = []; let current_branch = [];
let current_query = '{}';
const session = writable(initial_data && initial_data.session); const stores = {
page: writable({}),
preloading: writable(null),
session: writable(initial_data && initial_data.session)
};
let $session; let $session;
let session_dirty: boolean; let session_dirty: boolean;
session.subscribe(async value => { stores.session.subscribe(async value => {
$session = value; $session = value;
if (!ready) return; if (!ready) return;
@@ -85,8 +89,7 @@ export function extract_query(search: string) {
const query = Object.create(null); const query = Object.create(null);
if (search.length > 0) { if (search.length > 0) {
search.slice(1).split('&').forEach(searchParam => { search.slice(1).split('&').forEach(searchParam => {
let [, key, value] = /([^=]*)(?:=(.*))?/.exec(decodeURIComponent(searchParam)); let [, key, value = ''] = /([^=]*)(?:=(.*))?/.exec(decodeURIComponent(searchParam.replace(/\+/g, ' ')));
value = (value || '').replace(/\+/g, ' ');
if (typeof query[key] === 'string') query[key] = [<string>query[key]]; if (typeof query[key] === 'string') query[key] = [<string>query[key]];
if (typeof query[key] === 'object') (query[key] as string[]).push(value); if (typeof query[key] === 'object') (query[key] as string[]).push(value);
else query[key] = value; else query[key] = value;
@@ -99,7 +102,11 @@ export function select_target(url: URL): Target {
if (url.origin !== location.origin) return null; if (url.origin !== location.origin) return null;
if (!url.pathname.startsWith(initial_data.baseUrl)) return null; if (!url.pathname.startsWith(initial_data.baseUrl)) return null;
const path = url.pathname.slice(initial_data.baseUrl.length); let path = url.pathname.slice(initial_data.baseUrl.length);
if (path === '') {
path = '/';
}
// avoid accidental clashes between server routes and page routes // avoid accidental clashes between server routes and page routes
if (ignore.some(pattern => pattern.test(path))) return; if (ignore.some(pattern => pattern.test(path))) return;
@@ -114,7 +121,7 @@ export function select_target(url: URL): Target {
const part = route.parts[route.parts.length - 1]; const part = route.parts[route.parts.length - 1];
const params = part.params ? part.params(match) : {}; const params = part.params ? part.params(match) : {};
const page = { path, query, params }; const page = { host: location.host, path, query, params };
return { href: url.href, route, match, page }; return { href: url.href, route, match, page };
} }
@@ -122,7 +129,7 @@ export function select_target(url: URL): Target {
} }
export function handle_error(url: URL) { export function handle_error(url: URL) {
const { pathname, search } = location; const { host, pathname, search } = location;
const { session, preloaded, status, error } = initial_data; const { session, preloaded, status, error } = initial_data;
if (!root_preloaded) { if (!root_preloaded) {
@@ -147,7 +154,7 @@ export function handle_error(url: URL) {
} }
const query = extract_query(search); const query = extract_query(search);
render(null, [], props, { path: pathname, query, params: {} }); render(null, [], props, { host, path: pathname, query, params: {} });
} }
export function scroll_state() { export function scroll_state() {
@@ -217,7 +224,11 @@ async function render(redirect: Redirect, branch: any[], props: any, page: Page)
if (root_component) { if (root_component) {
root_component.$set(props); root_component.$set(props);
} else { } else {
props.session = session; props.stores = {
page: { subscribe: stores.page.subscribe },
preloading: { subscribe: stores.preloading.subscribe },
session: stores.session
};
props.level0 = { props.level0 = {
props: await root_preloaded props: await root_preloaded
}; };
@@ -240,10 +251,28 @@ async function render(redirect: Redirect, branch: any[], props: any, page: Page)
} }
current_branch = branch; current_branch = branch;
current_query = JSON.stringify(page.query);
ready = true; ready = true;
session_dirty = false; session_dirty = false;
} }
function part_changed(i, segment, match, stringified_query) {
// TODO only check query string changes for preload functions
// that do in fact depend on it (using static analysis or
// runtime instrumentation)
if (stringified_query !== current_query) return true;
const previous = current_branch[i];
if (!previous) return false;
if (segment !== previous.segment) return true;
if (previous.match) {
if (JSON.stringify(previous.match.slice(1, i + 2)) !== JSON.stringify(match.slice(1, i + 2))) {
return true;
}
}
}
export async function hydrate_target(target: Target): Promise<{ export async function hydrate_target(target: Target): Promise<{
redirect?: Redirect; redirect?: Redirect;
props?: any; props?: any;
@@ -272,6 +301,7 @@ export async function hydrate_target(target: Target): Promise<{
if (!root_preloaded) { if (!root_preloaded) {
root_preloaded = initial_data.preloaded[0] || root_preload.call(preload_context, { root_preloaded = initial_data.preloaded[0] || root_preload.call(preload_context, {
host: page.host,
path: page.path, path: page.path,
query: page.query, query: page.query,
params: {} params: {}
@@ -282,14 +312,26 @@ export async function hydrate_target(target: Target): Promise<{
let l = 1; let l = 1;
try { try {
const stringified_query = JSON.stringify(page.query);
const match = route.pattern.exec(page.path);
let segment_dirty = false;
branch = await Promise.all(route.parts.map(async (part, i) => { branch = await Promise.all(route.parts.map(async (part, i) => {
const segment = segments[i];
if (part_changed(i, segment, match, stringified_query)) segment_dirty = true;
props.segments[l] = segments[i + 1]; // TODO make this less confusing props.segments[l] = segments[i + 1]; // TODO make this less confusing
if (!part) return null; if (!part) return { segment };
const j = l++; const j = l++;
const segment = segments[i]; if (!session_dirty && !segment_dirty && current_branch[i] && current_branch[i].part === part.i) {
if (!session_dirty && current_branch[i] && current_branch[i].segment === segment && current_branch[i].part === part.i) return current_branch[i]; return current_branch[i];
}
segment_dirty = false;
const { default: component, preload } = await load_component(components[part.i]); const { default: component, preload } = await load_component(components[part.i]);
@@ -297,6 +339,7 @@ export async function hydrate_target(target: Target): Promise<{
if (ready || !initial_data.preloaded[i + 1]) { if (ready || !initial_data.preloaded[i + 1]) {
preloaded = preload preloaded = preload
? await preload.call(preload_context, { ? await preload.call(preload_context, {
host: page.host,
path: page.path, path: page.path,
query: page.query, query: page.query,
params: part.params ? part.params(target.match) : {} params: part.params ? part.params(target.match) : {}
@@ -306,7 +349,7 @@ export async function hydrate_target(target: Target): Promise<{
preloaded = initial_data.preloaded[i + 1]; preloaded = initial_data.preloaded[i + 1];
} }
return (props[`level${j}`] = { component, props: preloaded, segment, part: part.i }); return (props[`level${j}`] = { component, props: preloaded, segment, match, part: part.i });
})); }));
} catch (error) { } catch (error) {
props.error = error; props.error = error;

View File

@@ -1,10 +1,7 @@
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { CONTEXT_KEY, stores } from '@sapper/internal/shared'; import { CONTEXT_KEY } from '@sapper/internal/shared';
export const preloading = { subscribe: stores.preloading.subscribe }; export const stores = () => getContext(CONTEXT_KEY);
export const page = { subscribe: stores.page.subscribe };
export const getSession = () => getContext(CONTEXT_KEY);
export { default as start } from './start/index'; export { default as start } from './start/index';
export { default as goto } from './goto/index'; export { default as goto } from './goto/index';

View File

@@ -40,7 +40,7 @@ export default function start(opts: {
if (initial_data.error) return handle_error(url); if (initial_data.error) return handle_error(url);
const target = select_target(url); const target = select_target(url);
if (target) return navigate(target, uid, false, hash); if (target) return navigate(target, uid, true, hash);
}); });
} }

View File

@@ -56,6 +56,7 @@ export type Redirect = {
}; };
export type Page = { export type Page = {
host: string;
path: string; path: string;
params: Record<string, string>; params: Record<string, string>;
query: Record<string, string | string[]>; query: Record<string, string | string[]>;

View File

@@ -1 +0,0 @@
export const IGNORE = '__SAPPER__IGNORE__';

View File

@@ -1,14 +1,12 @@
import { writable } from 'svelte/store.mjs'; import { writable } from 'svelte/store';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import cookie from 'cookie'; import cookie from 'cookie';
import devalue from 'devalue'; import devalue from 'devalue';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import URL from 'url'; import URL from 'url';
import { IGNORE } from '../constants'; import { Manifest, Page, Req, Res } from './types';
import { Manifest, Page, Props, Req, Res } from './types';
import { build_dir, dev, src_dir } from '@sapper/internal/manifest-server'; import { build_dir, dev, src_dir } from '@sapper/internal/manifest-server';
import { stores } from '@sapper/internal/shared';
import App from '@sapper/internal/App.svelte'; import App from '@sapper/internal/App.svelte';
export function get_page_handler( export function get_page_handler(
@@ -28,6 +26,15 @@ export function get_page_handler(
const { server_routes, pages } = manifest; const { server_routes, pages } = manifest;
const error_route = manifest.error; const error_route = manifest.error;
function bail(req: Req, res: Res, err: Error) {
console.error(err);
const message = dev ? escape_html(err.message) : 'Internal server error';
res.statusCode = 500;
res.end(`<pre>${message}</pre>`);
}
function handle_error(req: Req, res: Res, statusCode: number, error: Error | string) { function handle_error(req: Req, res: Res, statusCode: number, error: Error | string) {
handle_page({ handle_page({
pattern: null, pattern: null,
@@ -142,6 +149,7 @@ export function get_page_handler(
try { try {
const root_preloaded = manifest.root_preload const root_preloaded = manifest.root_preload
? manifest.root_preload.call(preload_context, { ? manifest.root_preload.call(preload_context, {
host: req.headers.host,
path: req.path, path: req.path,
query: req.query, query: req.query,
params: {} params: {}
@@ -161,6 +169,7 @@ export function get_page_handler(
return part.preload return part.preload
? part.preload.call(preload_context, { ? part.preload.call(preload_context, {
host: req.headers.host,
path: req.path, path: req.path,
query: req.query, query: req.query,
params params
@@ -171,13 +180,17 @@ export function get_page_handler(
preloaded = await Promise.all(toPreload); preloaded = await Promise.all(toPreload);
} catch (err) { } catch (err) {
if (error) {
return bail(req, res, err)
}
preload_error = { statusCode: 500, message: err }; preload_error = { statusCode: 500, message: err };
preloaded = []; // appease TypeScript preloaded = []; // appease TypeScript
} }
try { try {
if (redirect) { if (redirect) {
const location = URL.resolve(req.baseUrl || '/', redirect.location); const location = URL.resolve((req.baseUrl || '') + '/', redirect.location);
res.statusCode = redirect.statusCode; res.statusCode = redirect.statusCode;
res.setHeader('Location', location); res.setHeader('Location', location);
@@ -204,10 +217,23 @@ export function get_page_handler(
}); });
const props = { const props = {
stores: {
page: {
subscribe: writable({
host: req.headers.host,
path: req.path,
query: req.query,
params
}).subscribe
},
preloading: {
subscribe: writable(null).subscribe
},
session: writable(session)
},
segments: layout_segments, segments: layout_segments,
status: error ? status : 200, status: error ? status : 200,
error: error ? error instanceof Error ? error : { message: error } : null, error: error ? error instanceof Error ? error : { message: error } : null,
session: writable(session),
level0: { level0: {
props: preloaded[0] props: preloaded[0]
}, },
@@ -231,12 +257,6 @@ export function get_page_handler(
} }
} }
stores.page.set({
path: req.path,
query: req.query,
params: params
});
const { html, head, css } = App.render(props); const { html, head, css } = App.render(props);
const serialized = { const serialized = {
@@ -310,11 +330,8 @@ export function get_page_handler(
res.statusCode = status; res.statusCode = status;
res.end(body); res.end(body);
} catch(err) { } catch(err) {
console.log(err);
if (error) { if (error) {
// we encountered an error while rendering the error page — oops bail(req, res, err)
res.statusCode = 500;
res.end(`<pre>${escape_html(err.message)}</pre>`);
} else { } else {
handle_error(req, res, 500, err); handle_error(req, res, 500, err);
} }
@@ -322,8 +339,6 @@ export function get_page_handler(
} }
return function find_route(req: Req, res: Res, next: () => void) { return function find_route(req: Req, res: Res, next: () => void) {
if (req[IGNORE]) return next();
if (req.path === '/service-worker-index.html') { if (req.path === '/service-worker-index.html') {
const homePage = pages.find(page => page.pattern.test('/')); const homePage = pages.find(page => page.pattern.test('/'));
handle_page(homePage, req, res); handle_page(homePage, req, res);

View File

@@ -1,4 +1,3 @@
import { IGNORE } from '../constants';
import { Req, Res, ServerRoute } from './types'; import { Req, Res, ServerRoute } from './types';
export function get_server_route_handler(routes: ServerRoute[]) { export function get_server_route_handler(routes: ServerRoute[]) {
@@ -64,8 +63,6 @@ export function get_server_route_handler(routes: ServerRoute[]) {
} }
return function find_route(req: Req, res: Res, next: () => void) { return function find_route(req: Req, res: Res, next: () => void) {
if (req[IGNORE]) return next();
for (const route of routes) { for (const route of routes) {
if (route.pattern.test(req.path)) { if (route.pattern.test(req.path)) {
handle_route(route, req, res, next); handle_route(route, req, res, next);

View File

@@ -5,7 +5,6 @@ import { Handler, Req, Res } from './types';
import { get_server_route_handler } from './get_server_route_handler'; import { get_server_route_handler } from './get_server_route_handler';
import { get_page_handler } from './get_page_handler'; import { get_page_handler } from './get_page_handler';
import { lookup } from './mime'; import { lookup } from './mime';
import { IGNORE } from '../constants';
export default function middleware(opts: { export default function middleware(opts: {
session?: (req: Req, res: Res) => any, session?: (req: Req, res: Res) => any,
@@ -15,15 +14,8 @@ export default function middleware(opts: {
let emitted_basepath = false; let emitted_basepath = false;
return compose_handlers([ return compose_handlers(ignore, [
ignore && ((req: Req, res: Res, next: () => void) => {
req[IGNORE] = should_ignore(req.path, ignore);
next();
}),
(req: Req, res: Res, next: () => void) => { (req: Req, res: Res, next: () => void) => {
if (req[IGNORE]) return next();
if (req.baseUrl === undefined) { if (req.baseUrl === undefined) {
let { originalUrl } = req; let { originalUrl } = req;
if (req.url === '/' && originalUrl[originalUrl.length - 1] !== '/') { if (req.url === '/' && originalUrl[originalUrl.length - 1] !== '/') {
@@ -73,24 +65,26 @@ export default function middleware(opts: {
].filter(Boolean)); ].filter(Boolean));
} }
export function compose_handlers(handlers: Handler[]) { export function compose_handlers(ignore: any, handlers: Handler[]): Handler {
return (req: Req, res: Res, next: () => void) => { const total = handlers.length;
let i = 0;
function go() {
const handler = handlers[i];
if (handler) { function nth_handler(n: number, req: Req, res: Res, next: () => void) {
handler(req, res, () => { if (n >= total) {
i += 1; return next();
go();
});
} else {
next();
}
} }
go(); handlers[n](req, res, () => nth_handler(n+1, req, res, next));
}; }
return !ignore
? (req, res, next) => nth_handler(0, req, res, next)
: (req, res, next) => {
if (should_ignore(req.path, ignore)) {
next();
} else {
nth_handler(0, req, res, next);
}
};
} }
export function should_ignore(uri: string, val: any) { export function should_ignore(uri: string, val: any) {
@@ -116,8 +110,6 @@ export function serve({ prefix, pathname, cache_control }: {
: (file: string) => (cache.has(file) ? cache : cache.set(file, fs.readFileSync(path.resolve(build_dir, file)))).get(file) : (file: string) => (cache.has(file) ? cache : cache.set(file, fs.readFileSync(path.resolve(build_dir, file)))).get(file)
return (req: Req, res: Res, next: () => void) => { return (req: Req, res: Res, next: () => void) => {
if (req[IGNORE]) return next();
if (filter(req)) { if (filter(req)) {
const type = lookup(req.path); const type = lookup(req.path);

5
site/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
node_modules
yarn-error.log
/cypress/screenshots/
/__sapper__/

17
site/README.md Normal file
View File

@@ -0,0 +1,17 @@
## Running locally
Set up the project:
```bash
git clone https://github.com/sveltejs/sapper.git
cd sapper/site
npm ci
```
Start the server with `npm run dev`, and navigate to [localhost:3000](http://localhost:3000).
## Translating the API docs
Anchors are automatically generated using headings in the documentation and by default (for the english language) they are latinised to make sure the URL is always conforming to RFC3986.
If we need to translate the API documentation to a language using unicode chars, we can setup this app to export the correct anchors by setting up `SLUG_PRESERVE_UNICODE` to `true` in `config.js`.

18
site/appveyor.yml Normal file
View File

@@ -0,0 +1,18 @@
version: "{build}"
shallow_clone: true
init:
- git config --global core.autocrlf false
build: off
environment:
matrix:
# node.js
- nodejs_version: stable
install:
- ps: Install-Product node $env:nodejs_version
- npm install cypress
- npm install

3
site/config.js Normal file
View File

@@ -0,0 +1,3 @@
export const SLUG_PRESERVE_UNICODE = false;
export const SLUG_SEPARATOR = '_';
export const SLUG_LANG = 'en';

View File

@@ -0,0 +1,52 @@
---
title: Introduction
---
### Before we begin
> Sapper is in early development, and some things may change before we hit version 1.0. This document is a work-in-progress. If you get stuck, reach out for help in the [Discord chatroom](https://svelte.dev/chat).
>
> See the [migration guides](migrating) for help upgrading from older versions.
### What is Sapper?
Sapper is a framework for building extremely high-performance web apps. You're looking at one right now! There are two basic concepts:
* Each page of your app is a [Svelte](https://svelte.dev) component
* You create pages by adding files to the `src/routes` directory of your project. These will be server-rendered so that a user's first visit to your app is as fast as possible, then a client-side app takes over
Building an app with all the modern best practices — code-splitting, offline support, server-rendered views with client-side hydration — is fiendishly complicated. Sapper does all the boring stuff for you so that you can get on with the creative part.
You don't need to know Svelte to understand the rest of this guide, but it will help. In short, it's a UI framework that compiles your components to highly optimized vanilla JavaScript. Read the [introductory blog post](https://svelte.dev/blog/svelte-3-rethinking-reactivity) and the [tutorial](https://svelte.dev/tutorial) to learn more.
### Why the name?
In war, the soldiers who build bridges, repair roads, clear minefields and conduct demolitions — all under combat conditions — are known as *sappers*.
For web developers, the stakes are generally lower than for combat engineers. But we face our own hostile environment: underpowered devices, poor network connections, and the complexity inherent in front-end engineering. Sapper, which is short for <b>S</b>velte <b>app</b> mak<b>er</b>, is your courageous and dutiful ally.
### Comparison with Next.js
[Next.js](https://github.com/zeit/next.js) is a React framework from [Zeit](https://zeit.co), and is the inspiration for Sapper. There are a few notable differences, however:
* Sapper is powered by Svelte instead of React, so it's faster and your apps are smaller
* Instead of route masking, we encode route parameters in filenames (see the [routing](docs#Routing) section below)
* As well as *pages*, you can create *server routes* in your `src/routes` directory. This makes it very easy to, for example, add a JSON API such as the one powering this very page (try visiting [/docs.json](/docs.json))
* Links are just `<a>` elements, rather than framework-specific `<Link>` components. That means, for example, that [this link right here](/), despite being inside a blob of markdown, works with the router as you'd expect
### Getting started
The easiest way to start building a Sapper app is to clone the [sapper-template](https://github.com/sveltejs/sapper-template) repo with [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit "sveltejs/sapper-template#rollup" my-app
# or: npx degit "sveltejs/sapper-template#webpack" my-app
cd my-app
npm install
npm run dev
```
This will scaffold a new project in the `my-app` directory, install its dependencies, and start a server on [localhost:3000](http://localhost:3000). Try editing the files to get a feel for how everything works you may not need to bother reading the rest of this guide!

View File

@@ -0,0 +1,112 @@
---
title: Sapper app structure
---
This section is a reference for the curious. We recommend you play around with the project template first, and come back here when you've got a feel for how things fit together.
If you take a look inside the [sapper-template](https://github.com/sveltejs/sapper-template) repo, you'll see some files that Sapper expects to find:
```bash
├ package.json
├ src
│ ├ routes
│ │ ├ # your routes here
│ │ ├ _error.svelte
│ │ └ index.svelte
│ ├ client.js
│ ├ server.js
│ ├ service-worker.js
│ └ template.html
├ static
│ ├ # your files here
└ rollup.config.js / webpack.config.js
```
When you first run Sapper, it will create an additional `__sapper__` directory containing generated files.
You'll notice a few extra files and a `cypress` directory which relates to [testing](docs#Testing) — we don't need to worry about those right now.
> You *can* create these files from scratch, but it's much better to use the template. See [getting started](docs#Getting_started) for instructions on how to easily clone it
### package.json
Your package.json contains your app's dependencies and defines a number of scripts:
* `npm run dev` — start the app in development mode, and watch source files for changes
* `npm run build` — build the app in production mode
* `npm run export` — bake out a static version, if applicable (see [exporting](docs#Exporting))
* `npm start` — start the app in production mode after you've built it
* `npm test` — run the tests (see [testing](docs#Testing))
### src
This contains the three *entry points* for your app — `src/client.js`, `src/server.js` and (optionally) `src/service-worker.js` — along with a `src/template.html` file.
#### src/client.js
This *must* import, and call, the `start` function from the generated `@sapper/app` module:
```js
import * as sapper from '@sapper/app';
sapper.start({
target: document.querySelector('#sapper')
});
```
In many cases, that's the entirety of your entry module, though you can do as much or as little here as you wish. See the [client API](docs#Client_API) section for more information on functions you can import.
#### src/server.js
This is a normal Express (or [Polka](https://github.com/lukeed/polka), etc) app, with three requirements:
* it should serve the contents of the `static` folder, using for example [sirv](https://github.com/lukeed/sirv)
* it should call `app.use(sapper.middleware())` at the end, where `sapper` is imported from `@sapper/server`
* it must listen on `process.env.PORT`
Beyond that, you can write the server however you like.
#### src/service-worker.js
Service workers act as proxy servers that give you fine-grained control over how to respond to network requests. For example, when the browser requests `/goats.jpg`, the service worker can respond with a file it previously cached, or it can pass the request on to the server, or it could even respond with something completely different, such as a picture of llamas.
Among other things, this makes it possible to build applications that work offline.
Because every app needs a slightly different service worker (sometimes it's appropriate to always serve from the cache, sometimes that should only be a last resort in case of no connectivity), Sapper doesn't attempt to control the service worker. Instead, you write the logic in `service-worker.js`. You can import any of the following from `@sapper/service-worker`:
* `files` — an array of files found in the `static` directory
* `shell` — the client-side JavaScript generated by the bundler (Rollup or webpack)
* `routes` — an array of `{ pattern: RegExp }` objects you can use to determine whether a Sapper-controlled page is being requested
* `timestamp` — the time the service worker was generated (useful for generating unique cache names)
#### src/template.html
This file is a template for responses from the server. Sapper will inject content that replaces the following tags:
* `%sapper.base%` — a `<base>` element (see [base URLs](docs#Base_URLs))
* `%sapper.styles%` — critical CSS for the page being requested
* `%sapper.head%` — HTML representing page-specific `<head>` contents, like `<title>`
* `%sapper.html%` — HTML representing the body of the page being rendered
* `%sapper.scripts%` — script tags for the client-side app
### src/routes
This is the meat of your app — the pages and server routes. See the section on [routing](docs#Routing) for the juicy details.
### static
This is a place to put any files that your app uses — fonts, images and so on. For example `static/favicon.png` will be served as `/favicon.png`.
Sapper doesn't serve these files — you'd typically use [sirv](https://github.com/lukeed/sirv) or [serve-static](https://github.com/expressjs/serve-static) for that — but it will read the contents of the `static` folder so that you can easily generate a cache manifest for offline support (see [service-worker.js](docs#templates-service-worker-js)).
### rollup.config.js / webpack.config.js
Sapper can use [Rollup](https://rollupjs.org/) or [webpack](https://webpack.js.org/) to bundle your app. You probably won't need to change the config, but if you want to (for example to add new loaders or plugins), you can.

View File

@@ -0,0 +1,127 @@
---
title: Routing
---
As we've seen, there are two types of route in Sapper — pages, and server routes.
### Pages
Pages are Svelte components written in `.svelte` files. When a user first visits the application, they will be served a server-rendered version of the route in question, plus some JavaScript that 'hydrates' the page and initialises a client-side router. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel.
The filename determines the route. For example, `src/routes/index.svelte` is the root of your site:
```html
<!-- src/routes/index.svelte -->
<svelte:head>
<title>Welcome</title>
</svelte:head>
<h1>Hello and welcome to my site!</h1>
```
A file called either `src/routes/about.svelte` or `src/routes/about/index.svelte` would correspond to the `/about` route:
```html
<!-- src/routes/about.svelte -->
<svelte:head>
<title>About</title>
</svelte:head>
<h1>About this site</h1>
<p>TODO...</p>
```
Dynamic parameters are encoded using `[brackets]`. For example, here's how you could create a page that renders a blog post:
```html
<!-- src/routes/blog/[slug].svelte -->
<script context="module">
// the (optional) preload function takes a
// `{ path, params, query }` object and turns it into
// the data we need to render the page
export async function preload(page, session) {
// the `slug` parameter is available because this file
// is called [slug].svelte
const { slug } = page.params;
// `this.fetch` is a wrapper around `fetch` that allows
// you to make credentialled requests on both
// server and client
const res = await this.fetch(`blog/${slug}.json`);
const article = await res.json();
return { article };
}
</script>
<script>
export let article;
</script>
<svelte:head>
<title>{article.title}</title>
</svelte:head>
<h1>{article.title}</h1>
<div class='content'>
{@html article.html}
</div>
```
> See the section on [preloading](docs#Preloading) for more info about `preload` and `this.fetch`
### Server routes
Server routes are modules written in `.js` files that export functions corresponding to HTTP methods. Each function receives HTTP `request` and `response` objects as arguments, plus a `next` function. This is useful for creating a JSON API. For example, here's how you could create an endpoint that served the blog page above:
```js
// routes/blog/[slug].json.js
import db from './_database.js'; // the underscore tells Sapper this isn't a route
export async function get(req, res, next) {
// the `slug` parameter is available because this file
// is called [slug].json.js
const { slug } = req.params;
const article = await db.get(slug);
if (article !== null) {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(article));
} else {
next();
}
}
```
> `delete` is a reserved word in JavaScript. To handle DELETE requests, export a function called `del` instead.
### File naming rules
There are three simple rules for naming the files that define your routes:
* A file called `src/routes/about.svelte` corresponds to the `/about` route. A file called `src/routes/blog/[slug].svelte` corresponds to the `/blog/:slug` route, in which case `params.slug` is available to `preload`
* The file `src/routes/index.svelte` corresponds to the root of your app. `src/routes/about/index.svelte` is treated the same as `src/routes/about.svelte`.
* Files and directories with a leading underscore do *not* create routes. This allows you to colocate helper modules and components with the routes that depend on them — for example you could have a file called `src/routes/_helpers/datetime.js` and it would *not* create a `/_helpers/datetime` route
### Error page
In addition to regular pages, there is a 'special' page that Sapper expects to find — `src/routes/_error.svelte`. This will be shown when an error occurs while rendering a page.
The `error` object is made available to the template along with the HTTP `status` code.
### Regexes in routes
You can use a subset of regular expressions to qualify route parameters, by placing them in parentheses after the parameter name.
For example, `src/routes/items/[id([0-9]+)].svelte` would only match numeric IDs — `/items/123` would match, but `/items/xyz` would not.
Because of technical limitations, the following characters cannot be used: `/`, `\`, `?`, `:`, `(` and `)`.

View File

@@ -0,0 +1,47 @@
---
title: Client API
---
The `@sapper/app` module, which is generated by Sapper based on the shape of your app, contains functions for controlling Sapper programmatically and responding to events.
### start({ target })
* `target` — an element to render pages to
This configures the router and starts the application — listens for clicks on `<a>` elements, interacts with the `history` API, and renders and updates your Svelte components.
Returns a `Promise` that resolves when the initial page has been hydrated.
```js
import * as sapper from '@sapper/app';
sapper.start({
target: document.querySelector('#sapper')
}).then(() => {
console.log('client-side app has started');
});
```
### goto(href, options?)
* `href` — the page to go to
* `options` — can include a `replaceState` property, which determines whether to use `history.pushState` (the default) or `history.replaceState`). Not required
Programmatically navigates to the given `href`. If the destination is a Sapper route, Sapper will handle the navigation, otherwise the page will be reloaded with the new `href`. (In other words, the behaviour is as though the user clicked on a link with this `href`.)
### prefetch(href)
* `href` — the page to prefetch
Programmatically prefetches the given page, which means a) ensuring that the code for the page is loaded, and b) calling the page's `preload` method with the appropriate options. This is the same behaviour that Sapper triggers when the user taps or mouses over an `<a>` element with [rel=prefetch](docs#Prefetching).
### prefetchRoutes(routes?)
* `routes` — an optional array of strings representing routes to prefetch
Programmatically prefetches the code for routes that haven't yet been fetched. Typically, you might call this after `sapper.start()` is complete, to speed up subsequent navigation (this is the 'L' of the [PRPL pattern](https://developers.google.com/web/fundamentals/performance/prpl-pattern/)). Omitting arguments will cause all routes to be fetched, or you can specify routes by any matching pathname such as `/about` (to match `src/routes/about.svelte`) or `/blog/*` (to match `src/routes/blog/[slug].svelte`). Unlike `prefetch`, this won't call `preload` for individual pages.

View File

@@ -0,0 +1,114 @@
---
title: Preloading
---
As seen in the [routing](docs#Routing) section, page components can have an optional `preload` function that will load some data that the page depends on. This is similar to `getInitialProps` in Next.js or `asyncData` in Nuxt.js.
```html
<script context="module">
export async function preload(page, session) {
const { slug } = page.params;
const res = await this.fetch(`blog/${slug}.json`);
const article = await res.json();
return { article };
}
</script>
```
It lives in a `context="module"` script — see the [tutorial](https://svelte.dev/tutorial/module-exports) — because it's not part of the component instance itself; instead, it runs *before* the component is created, allowing you to avoid flashes while data is fetched.
### Argument
The `preload` function receives two arguments — `page` and `session`.
`page` is a `{ host, path, params, query }` object where `host` is the URL's host, `path` is its pathname, `params` is derived from `path` and the route filename, and `query` is an object of values in the query string.
So if the example above was `src/routes/blog/[slug].svelte` and the URL was `/blog/some-post?foo=bar&baz`, the following would be true:
* `page.path === '/blog/some-post'`
* `page.params.slug === 'some-post'`
* `page.query.foo === 'bar'`
* `page.query.baz === true`
`session` is generated on the server by the `session` option passed to `sapper.middleware` (TODO this needs further documentation. Perhaps a server API section?)
### Return value
If you return a Promise from `preload`, the page will delay rendering until the promise resolves. You can also return a plain object.
When Sapper renders a page on the server, it will attempt to serialize the resolved value (using [devalue](https://github.com/Rich-Harris/devalue)) and include it on the page, so that the client doesn't also need to call `preload` upon initialization. Serialization will fail if the value includes functions or custom classes (cyclical and repeated references are fine, as are built-ins like `Date`, `Map`, `Set` and `RegExp`).
### Context
Inside `preload`, you have access to three methods:
* `this.fetch(url, options)`
* `this.error(statusCode, error)`
* `this.redirect(statusCode, location)`
#### this.fetch
In browsers, you can use `fetch` to make AJAX requests, for getting data from your server routes (among other things). On the server it's a little trickier — you can make HTTP requests, but you must specify an origin, and you don't have access to cookies. This means that it's impossible to request data based on the user's session, such as data that requires you to be logged in.
To fix this, Sapper provides `this.fetch`, which works on the server as well as in the client:
```html
<script context="module">
export async function preload() {
const res = await this.fetch(`secret-data.json`, {
credentials: 'include'
});
// ...
}
</script>
```
Note that you will need to use session middleware such as [express-session](https://github.com/expressjs/session) in your `app/server.js` in order to maintain user sessions or do anything involving authentication.
#### this.error
If the user navigated to `/blog/some-invalid-slug`, we would want to render a 404 Not Found page. We can do that with `this.error`:
```html
<script context="module">
export async function preload({ params, query }) {
const { slug } = params;
const res = await this.fetch(`blog/${slug}.json`);
if (res.status === 200) {
const article = await res.json();
return { article };
}
this.error(404, 'Not found');
}
</script>
```
The same applies to other error codes you might encounter.
#### this.redirect
You can abort rendering and redirect to a different location with `this.redirect`:
```html
<script context="module">
export async function preload(page, session) {
const { user } = session;
if (!user) {
return this.redirect(302, 'login');
}
return { user };
}
</script>
```

View File

@@ -0,0 +1,86 @@
---
title: Layouts
---
So far, we've treated pages as entirely standalone components — upon navigation, the existing component will be destroyed, and a new one will take its place.
But in many apps, there are elements that should be visible on *every* page, such as top-level navigation or a footer. Instead of repeating them in every page, we can use *layout* components.
To create a layout component that applies to every page, make a file called `src/routes/_layout.svelte`. The default layout component (the one that Sapper uses if you don't bring your own) looks like this...
```html
<slot></slot>
```
...but we can add whatever markup, styles and behaviour we want. For example, let's add a nav bar:
```html
<!-- src/routes/_layout.svelte -->
<nav>
<a href=".">Home</a>
<a href="about">About</a>
<a href="settings">Settings</a>
</nav>
<slot></slot>
```
If we create pages for `/`, `/about` and `/settings`...
```html
<!-- src/routes/index.svelte -->
<h1>Home</h1>
```
```html
<!-- src/routes/about.svelte -->
<h1>About</h1>
```
```html
<!-- src/routes/settings.svelte -->
<h1>Settings</h1>
```
...the nav will always be visible, and clicking between the three pages will only result in the `<h1>` being replaced.
### Nested routes
Suppose we don't just have a single `/settings` page, but instead have nested pages like `/settings/profile` and `/settings/notifications` with a shared submenu (for an real-life example, see [github.com/settings](https://github.com/settings)).
We can create a layout that only applies to pages below `/settings` (while inheriting the root layout with the top-level nav):
```html
<!-- src/routes/settings/_layout.svelte -->
<h1>Settings</h1>
<div class="submenu">
<a href="settings/profile">Profile</a>
<a href="settings/notifications">Notifications</a>
</div>
<slot></slot>
```
Layout components receive a `segment` property which is useful for things like styling:
```diff
+<script>
+ export let segment;
+</script>
+
<div class="submenu">
- <a href="settings/profile">Profile</a>
- <a href="settings/notifications">Notifications</a>
+ <a
+ class:selected={segment === "profile"}
+ href="settings/profile"
+ >Profile</a>
+
+ <a
+ class:selected={segment === "notifications"}
+ href="settings/notifications"
+ >Notifications</a>
</div>
```

View File

@@ -0,0 +1,32 @@
---
title: Server-side rendering
---
Sapper, by default, renders server-side first (SSR), and then re-mounts any dynamic elements on the client. Svelte provides [excellent support for this](https://svelte.dev/docs#server-side-rendering). This has benefits in performance and search engine indexing, among others, but comes with its own set of complexities.
### Making a component SSR compatible
Sapper works well with most third-party libraries you are likely to come across. However, sometimes, a third-party library comes bundled in a way which allows it to work with multiple different module loaders. Sometimes, this code creates a dependency on `window`, such as checking for the existence of `window.global` might do.
Since there is no `window` in a server-side environment like Sapper's, the action of simply importing such a module can cause the import to fail, and terminate the Sapper's server with an error such as:
```bash
ReferenceError: window is not defined
```
The way to get around this is to use a dynamic import for your component, from within the `onMount` function (which is only called on the client), so that your import code is never called on the server.
```html
<script>
import { onMount } from 'svelte';
let MyComponent;
onMount(async () => {
const module = await import('my-non-ssr-component');
MyComponent = module.default;
});
</script>
<svelte:component this={MyComponent} foo="bar"/>
```

View File

@@ -0,0 +1,40 @@
---
title: Stores
---
The `page` and `session` values passed to `preload` functions are available to components as [stores](https://svelte.dev/tutorial/writable-stores), along with `preloading`.
Inside a component, get references to the stores like so:
```html
<script>
import { stores } from '@sapper/app';
const { preloading, page, session } = stores();
</script>
```
* `preloading` contains a readonly boolean value, indicating whether or not a navigation is pending
* `page` contains a readonly `{ host, path, params, query }` object, identical to that passed to `preload` functions
* `session` contains whatever data was seeded on the server. It is a [writable store](https://svelte.dev/tutorial/writable-stores), meaning you can update it with new data (for example, after the user logs in) and your app will be refreshed
### Seeding session data
On the server, you can populate `session` by passing an option to `sapper.middleware`:
```js
// src/server.js
express() // or Polka, or a similar framework
.use(
serve('assets'),
authenticationMiddleware(),
sapper.middleware({
session: (req, res) => ({
user: req.user
})
})
)
.listen(process.env.PORT);
```
> Session data must be serializable (using [devalue](https://github.com/Rich-Harris/devalue)) — no functions or custom classes, just built-in JavaScript data types

View File

@@ -0,0 +1,22 @@
---
title: Prefetching
---
Sapper uses code splitting to break your app into small chunks (one per route), ensuring fast startup times.
For *dynamic* routes, such as our `src/routes/blog/[slug].svelte` example, that's not enough. In order to render the blog post, we need to fetch the data for it, and we can't do that until we know what `slug` is. In the worst case, that could cause lag as the browser waits for the data to come back from the server.
### rel=prefetch
We can mitigate that by *prefetching* the data. Adding a `rel=prefetch` attribute to a link...
```html
<a rel=prefetch href='blog/what-is-sapper'>What is Sapper?</a>
```
...will cause Sapper to run the page's `preload` function as soon as the user hovers over the link (on a desktop) or touches it (on mobile), rather than waiting for the `click` event to trigger navigation. Typically, this buys us an extra couple of hundred milliseconds, which is the difference between a user interface that feels laggy, and one that feels snappy.
> `rel=prefetch` is a Sapper idiom, not a standard attribute for `<a>` elements
<!-- TODO add a function to prefetch programmatically -->

View File

@@ -0,0 +1,15 @@
---
title: Building
---
Up until now we've been using `sapper dev` to build our application and run a development server. But when it comes to production, we want to create a self-contained optimized build.
### sapper build
This command packages up your application into the `__sapper__/build` directory. (You can change this to a custom directory, as well as controlling various other options — do `sapper build --help` for more information.)
The output is a Node app that you can run from the project root:
```bash
node __sapper__/build
```

View File

@@ -0,0 +1,63 @@
---
title: Exporting
---
Many sites are effectively *static*, which is to say they don't actually need an Express server backing them. Instead, they can be hosted and served as static files, which allows them to be deployed to more hosting environments (such as [Netlify](https://www.netlify.com/) or [GitHub Pages](https://pages.github.com/)). Static sites are generally cheaper to operate and have better performance characteristics.
Sapper allows you to *export* a static site with a single zero-config `sapper export` command. In fact, you're looking at an exported site right now!
Static doesn't mean non-interactive — your Svelte components work exactly as they do normally, and you still get all the benefits of client-side routing and prefetching.
### sapper export
Inside your Sapper project, try this:
```bash
# npx allows you to use locally-installed dependencies
npx sapper export
```
This will create a `__sapper__/export` folder with a production-ready build of your site. You can launch it like so:
```bash
npx serve __sapper__/export
```
Navigate to [localhost:5000](http://localhost:5000) (or whatever port `serve` picked), and verify that your site works as expected.
You can also add a script to your package.json...
```js
{
"scripts": {
...
"export": "sapper export"
}
}
```
...allowing you to `npm run export` your app.
### How it works
When you run `sapper export`, Sapper first builds a production version of your app, as though you had run `sapper build`, and copies the contents of your `assets` folder to the destination. It then starts the server, and navigates to the root of your app. From there, it follows any `<a>` elements it finds, and captures any data served by the app.
Because of this, any pages you want to be included in the exported site must be reachable by `<a>` elements. Additionally, any non-page routes should be requested in `preload`, *not* in `onMount` or elsewhere.
### When not to export
The basic rule is this: for an app to be exportable, any two users hitting the same page of your app must get the same content from the server. In other words, any app that involves user sessions or authentication is *not* a candidate for `sapper export`.
Note that you can still export apps with dynamic routes, like our `src/routes/blog/[slug].svelte` example from earlier. `sapper export` will intercept `fetch` requests made inside `preload`, so the data served from `src/routes/blog/[slug].json.js` will also be captured.
### Route conflicts
Because `sapper export` writes to the filesystem, it isn't possible to have two server routes that would cause a directory and a file to have the same name. For example, `src/routes/foo/index.js` and `src/routes/foo/bar.js` would try to create `export/foo` and `export/foo/bar`, which is impossible.
The solution is to rename one of the routes to avoid conflict — for example, `src/routes/foo-bar.js`. (Note that you would also need to update any code that fetches data from `/foo/bar` to reference `/foo-bar` instead.)
For *pages*, we skirt around this problem by writing `export/foo/index.html` instead of `export/foo`.

View File

@@ -0,0 +1,64 @@
---
title: Deployment
---
Sapper apps run anywhere that supports Node 8 or higher.
### Deploying to Now
> This section relates to Now 1, not Now 2
We can very easily deploy our apps to [Now][]:
```bash
npm install -g now
now
```
This will upload the source code to Now, whereupon it will do `npm run build` and `npm start` and give you a URL for the deployed app.
For other hosting environments, you may need to do `npm run build` yourself.
### Deploying service workers
Sapper makes the Service Worker file (`service-worker.js`) unique by including a timestamp in the source code
(calculated using `Date.now()`).
In environments where the app is deployed to multiple servers (such as [Now][]), it is advisable to use a
consistent timestamp for all deployments. Otherwise, users may run into issues where the Service Worker
updates unexpectedly because the app hits server 1, then server 2, and they have slightly different timestamps.
To override Sapper's timestamp, you can use an environment variable (e.g. `SAPPER_TIMESTAMP`) and then modify
the `service-worker.js`:
```js
const timestamp = process.env.SAPPER_TIMESTAMP; // instead of `import { timestamp }`
const ASSETS = `cache${timestamp}`;
export default {
/* ... */
plugins: [
/* ... */
replace({
/* ... */
'process.env.SAPPER_TIMESTAMP': process.env.SAPPER_TIMESTAMP || Date.now()
})
]
}
```
Then you can set it using the environment variable, e.g.:
```bash
SAPPER_TIMESTAMP=$(date +%s%3N) npm run build
```
When deploying to [Now][], you can pass the environment variable into Now itself:
```bash
now -e SAPPER_TIMESTAMP=$(date +%s%3N)
```
[Now]: https://zeit.co/now

View File

@@ -0,0 +1,39 @@
---
title: Security
---
By default, Sapper does not add security headers to your app, but you may add them yourself using middleware such as [Helmet][].
### Content Security Policy (CSP)
Sapper generates inline `<script>`s, which can fail to execute if [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) headers disallow arbitrary script execution (`unsafe-inline`).
To work around this, Sapper can inject a [nonce](https://www.troyhunt.com/locking-down-your-website-scripts-with-csp-hashes-nonces-and-report-uri/) which can be configured with middleware to emit the proper CSP headers. Here is an example using [Express][] and [Helmet][]:
```js
// server.js
import uuidv4 from 'uuid/v4';
import helmet from 'helmet';
app.use((req, res, next) => {
res.locals.nonce = uuidv4();
next();
});
app.use(helmet({
contentSecurityPolicy: {
directives: {
scriptSrc: [
"'self'",
(req, res) => `'nonce-${res.locals.nonce}'`
]
}
}
}));
app.use(sapper.middleware());
```
Using `res.locals.nonce` in this way follows the convention set by
[Helmet's CSP docs](https://helmetjs.github.io/docs/csp/#generating-nonces).
[Express]: https://expressjs.com/
[Helmet]: https://helmetjs.github.io/

View File

@@ -0,0 +1,28 @@
---
title: Base URLs
---
Ordinarily, the root of your Sapper app is served at `/`. But in some cases, your app may need to be served from a different base path — for example, if Sapper only controls part of your domain, or if you have multiple Sapper apps living side-by-side.
This can be done like so:
```js
// app/server.js
express() // or Polka, or a similar framework
.use(
'/my-base-path', // <!-- add this line
compression({ threshold: 0 }),
serve('assets'),
sapper.middleware()
)
.listen(process.env.PORT);
```
Sapper will detect the base path and configure both the server-side and client-side routers accordingly.
If you're [exporting](docs#Exporting) your app, you will need to tell the exporter where to begin crawling:
```bash
sapper export --basepath my-base-path
```

View File

@@ -0,0 +1,14 @@
---
title: Testing
---
You can use whatever testing frameworks and libraries you'd like. The default in [sapper-template](https://github.com/sveltejs/sapper-template) is [Cypress](https://cypress.io).
### Running the tests
```bash
npm test
```
This will start the server and open Cypress. You can (and should!) add your own tests in `cypress/integration/spec.js` — consult the [docs](https://docs.cypress.io/guides/overview/why-cypress.html) for more information.

View File

@@ -0,0 +1,19 @@
---
title: Debugging
---
Debugging your server code is particularly easy with [ndb](https://github.com/GoogleChromeLabs/ndb). Install it globally...
```bash
npm install -g ndb
```
...then run Sapper:
```bash
ndb npm run dev
```
> This assumes that `npm run dev` runs `sapper dev`. You can also run Sapper via [npx](https://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner), as in `ndb npx sapper dev`.
Note that you may not see any terminal output for a few seconds while ndb starts up.

View File

@@ -0,0 +1,367 @@
---
title: Migrating
---
Until we reach version 1.0, there may be occasional changes to the project structure Sapper expects.
### 0.25 to 0.26
The most significant change yet: Sapper is now built on Svelte 3.
#### Importing Sapper
Your app's runtime is now built to `src/node_modules/@sapper` this allows you to easily import it from anywhere in your source code. Update your `server.js`...
```diff
// src/server.js
-import * as sapper from '../__sapper__/server.js';
+import * as sapper from '@sapper/server';
```
...and client.js:
```diff
-import * as sapper from '../__sapper__/client.js';
+import * as sapper from '@sapper/app';
sapper.start({
target: document.querySelector('#sapper')
});
```
The same applies to imports like `goto` and `prefetchRoutes`.
#### Webpack config
If you're using webpack, you must update your configuration to recognise `.mjs` and `.svelte` files:
```js
resolve: {
extensions: ['.mjs', '.js', '.json', '.svelte', '.html']
}
```
If you're using .svelte files (recommended), you'll also need to tell `svelte-loader` to expect them:
```diff
-test: /\.html$/
+test: /\.(svelte|html)$/
```
#### Session data
Passing data from server to client is now accomplished with a `session` function passed to the middleware:
```js
// src/server.js
sapper.middleware({
session: (req, res) => ({
// session data goes here
})
})
```
This data is available in `preload` functions as the second argument:
```html
<!-- SomeComponent.svelte -->
<script context="module">
export function preload(page, session) {
const { path, params, query } = page; // as before
if (!session.user) return this.redirect(302, 'login');
// ...
}
</script>
```
#### Stores
It is also available, along with `page` and `preloading`, as a store inside components:
```html
<script>
import * as sapper from '@sapper/app';
const { page, preloading, session } = sapper.stores();
</script>
```
`page` and `preloading` are [readable stores](https://svelte.dev/tutorial/readable-stores), while `session` is [writable](https://svelte.dev/tutorial/writable-stores). Writing to the session store (for example, after the user logs in) will cause any `preload` functions that rely on session data to re-run; it will not persist anything to the server.
#### Layouts
Your layout components should now use a `<slot>` element to render nested routes, instead of `<svelte:component>`:
```diff
<main>
- <svelte:component this={child.component} {...child.props}/>
+ <slot></slot>
</main>
```
The layout component itself receives a `segment` prop, which is equivalent to `child.segment` in earlier versions.
### 0.21 to 0.22
Instead of importing middleware from the `sapper` package, or importing the client runtime from `sapper/runtime.js`, the app is *compiled into* the generated files:
```diff
// src/client.js
-import { init } from 'sapper/runtime.js';
-import { manifest } from './manifest/client.js';
+import * as sapper from '../__sapper__/client.js';
-init({
+sapper.start({
target: document.querySelector('#sapper'),
- manifest
});
```
```diff
// src/server.js
import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
-import sapper from 'sapper';
-import { manifest } from './manifest/server.js';
+import * as sapper from '../__sapper__/server.js';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
polka() // You can also use Express
.use(
compression({ threshold: 0 }),
- sirv('assets', { dev }),
+ sirv('static', { dev }),
- sapper({ manifest })
+ sapper.middleware()
)
.listen(PORT, err => {
if (err) console.log('error', err);
});
```
```diff
// src/service-worker.js
-import { assets, shell, routes, timestamp } from './manifest/service-worker.js';
+import { files, shell, routes, timestamp } from '../__sapper__/service-worker.js';
```
In addition, the default build and export directories are now `__sapper__/build` and `__sapper__/export` respectively.
### 0.20 to 0.21
* The `app` directory is now `src`
* The `routes` directory is now `src/routes`
* The `assets` directory is now `static` (remember to update your `src/server.js` file to reflect this change as well)
* Instead of having three separate config files (`webpack/client.config.js`, `webpack/server.config.js` and `webpack/service-worker.config.js`), there is a single `webpack.config.js` file that exports `client`, `server` and `serviceworker` configs.
### 0.17 to 0.18
The `sapper/webpack/config.js` file (required in the `webpack/*.config.js` files) is now `sapper/config/webpack.js`.
### 0.14 to 0.15
This release changed how routing is handled, resulting in a number of changes.
Instead of a single `App.html` component, you can place `_layout.html` components in any directory under `routes`. You should move `app/App.html` to `routes/_layout.html` and modify it like so:
```diff
-<!-- app/App.html -->
+<!-- routes/_layout.html -->
-<Nav path={props.path}/>
+<Nav segment={child.segment}/>
-<svelte:component this={Page} {...props}/>
+<svelte:component this={child.component} {...child.props}/>
```
You will then need to remove `App` from your client and server entry points, and replace `routes` with `manifest`:
```diff
// app/client.js
import { init } from 'sapper/runtime.js';
-import { routes } from './manifest/client.js';
-import App from './App.html';
+import { manifest } from './manifest/client.js';
init({
target: document.querySelector('#sapper'),
- routes,
- App
+ manifest
});
```
```diff
// app/server.js
import sirv from 'sirv';
import polka from 'polka';
import sapper from 'sapper';
import compression from 'compression';
-import { routes } from './manifest/server.js';
-import App from './App.html';
+import { manifest } from './manifest/server.js';
polka()
.use(
compression({ threshold: 0 }),
sirv('assets'),
- sapper({ routes, App })
+ sapper({ manifest })
)
.listen(process.env.PORT)
.catch(err => {
console.log('error', err);
});
```
`preload` functions no longer take the entire request object on the server; instead, they receive the same argument as on the client.
### 0.13 to 0.14
The `4xx.html` and `5xx.html` error pages have been replaced with a single page, `_error.html`. In addition to the regular `params`, `query` and `path` props, it receives `status` and `error`.
### 0.11 to 0.12
In earlier versions, each page was a completely standalone component. Upon navigation, the entire page would be torn down and a new one created. Typically, each page would import a shared `<Layout>` component to achieve visual consistency.
As of 0.12, this changes: we have a single `<App>` component, defined in `app/App.html`, which controls the rendering of the rest of the app. See [sapper-template](https://github.com/sveltejs/sapper-template/blob/master/app/App.html) for an example.
This component is rendered with the following values:
* `Page` — a component constructor for the current page
* `props` — an object with `params`, `query`, and any data returned from the page's `preload` function
* `preloading``true` during preload, `false` otherwise. Useful for showing progress indicators
Sapper needs to know about your app component. To that end, you will need to modify your `app/server.js` and `app/client.js`:
```diff
// app/server.js
import polka from 'polka';
import sapper from 'sapper';
import serve from 'serve-static';
import { routes } from './manifest/server.js';
+import App from './App.html';
polka()
.use(
serve('assets'),
- sapper({ routes })
+ sapper({ App, routes })
)
.listen(process.env.PORT);
```
```diff
// app/client.js
import { init } from 'sapper/runtime.js';
import { routes } from './manifest/client.js';
+import App from './App.html';
-init(target: document.querySelector('#sapper'), routes);
+init({
+ target: document.querySelector('#sapper'),
+ routes,
+ App
+});
```
Once your `App.html` has been created and your server and client apps updated, you can remove any `<Layout>` components from your individual pages.
### <0.9 to 0.10
##### app/template.html
* Your `<head>` element must contain `%sapper.base%` (see ([base URLs](docs#Base_URLs))
* Remove references to your service worker; this is now handled by `%sapper.scripts%`
##### Pages
* Your `preload` functions should now use `this.fetch` instead of `fetch`. `this.fetch` allows you to make credentialled requests on the server, and means that you no longer need to create a `global.fetch` object in `app/server.js`.
### 0.6 to 0.7
Consult [sapper-template](https://github.com/sveltejs/sapper-template) for full examples of all the below points.
##### package.json
To start a dev server, use `sapper dev` rather than `node server.js`. In all likelihood, your package.json will have an `npm run dev` script that will need to be updated.
##### Entry points
As of version 0.7, Sapper expects to find your entry points — for client, server and service worker — in an `app` folder. Instead of using magically-injected `__variables__`, each entry point imports from its corresponding file in the `app/manifests` folder. These are automatically generated by Sapper.
```js
// app/client.js (formerly templates/main.js)
import { init } from 'sapper/runtime.js';
import { routes } from './manifest/client.js';
init(document.querySelector('#sapper'), routes);
if (module.hot) module.hot.accept(); // enable hot reloading
```
```js
// app/server.js (formerly server.js)
// Note that we're now using ES module syntax, because this
// file is processed by webpack like the rest of your app
import sapper from 'sapper';
import { routes } from './manifest/server.js';
// ..other imports
// we now pass the `routes` object to the Sapper middleware
app.use(sapper({
routes
}));
```
```js
// app/service-worker.js (formerly templates/service-worker.js)
import { assets, shell, timestamp, routes } from './manifest/service-worker.js';
// replace e.g. `__assets__` with `assets` in the rest of the file
```
##### Templates and error pages
In previous versions, we had `templates/2xx.html`, `templates/4xx.html` and `templates/5xx.html`. Now, we have a single template, `app/template.html`, which should look like your old `templates/2xx.html`.
For handling error states, we have a 'special' route: `routes/_error.html`.
This page is just like any other, except that it will get rendered whenever an error states is reached. The component has access to `status` and `error` values.
Note that you can now use `this.error(statusCode, error)` inside your `preload` functions.
##### Webpack configs
Your webpack configs now live in a `webpack` directory:
* `webpack.client.config.js` is now `webpack/client.config.js`
* `webpack.server.config.js` is now `webpack/server.config.js`
If you have a service worker, you should also have a `webpack/service-worker.config.js` file. See [sapper-template](https://github.com/sveltejs/sapper-template) for an example.

4
site/cypress.json Normal file
View File

@@ -0,0 +1,4 @@
{
"baseUrl": "http://localhost:3000",
"video": false
}

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,19 @@
describe('Sapper template app', () => {
beforeEach(() => {
cy.visit('/')
});
it('has the correct <h1>', () => {
cy.contains('h1', 'Great success!')
});
it('navigates to /about', () => {
cy.get('nav a').contains('about').click();
cy.url().should('include', '/about');
});
it('navigates to /blog', () => {
cy.get('nav a').contains('blog').click();
cy.url().should('include', '/blog');
});
});

View File

@@ -0,0 +1,17 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

3250
site/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
site/package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "sapper.svelte.dev",
"description": "sapper.svelte.dev",
"version": "0.0.1",
"scripts": {
"dev": "sapper dev",
"export": "sapper export --legacy",
"stage": "netlify deploy --dir=__sapper__/export",
"deploy": "netlify deploy --dir=__sapper__/export --prod",
"prestage": "npm run export",
"predeploy": "npm run export",
"cy:run": "cypress run",
"cy:open": "cypress open",
"test": "run-p --race dev cy:run"
},
"dependencies": {
"@polka/send": "^0.4.0",
"compression": "^1.7.4",
"highlight.js": "^9.15.6",
"marked": "^0.6.2",
"polka": "^0.5.2",
"prismjs": "^1.16.0",
"sirv": "^0.4.2"
},
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"@babel/runtime": "^7.4.4",
"@sveltejs/site-kit": "^1.0.4",
"npm-run-all": "^4.1.5",
"rollup": "^1.11.3",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^9.3.4",
"rollup-plugin-node-resolve": "^4.2.3",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^4.0.4",
"sapper": "^0.27.1",
"svelte": "^3.2.2"
}
}

87
site/rollup.config.js Normal file
View File

@@ -0,0 +1,87 @@
import resolve from 'rollup-plugin-node-resolve';
import replace from 'rollup-plugin-replace';
import commonjs from 'rollup-plugin-commonjs';
import svelte from 'rollup-plugin-svelte';
import babel from 'rollup-plugin-babel';
import { terser } from 'rollup-plugin-terser';
import config from 'sapper/config/rollup.js';
import pkg from './package.json';
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const legacy = !!process.env.SAPPER_LEGACY_BUILD;
export default {
client: {
input: config.client.input(),
output: config.client.output(),
plugins: [
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
svelte({
dev,
hydratable: true,
emitCss: true
}),
resolve(),
commonjs(),
legacy && babel({
extensions: ['.js', '.mjs', '.html', '.svelte'],
runtimeHelpers: true,
exclude: ['node_modules/@babel/**'],
presets: [
['@babel/preset-env', {
targets: '> 0.25%, not dead'
}]
],
plugins: [
'@babel/plugin-syntax-dynamic-import',
['@babel/plugin-transform-runtime', {
useESModules: true
}]
]
}),
!dev && terser({
module: true
})
],
},
server: {
input: config.server.input(),
output: config.server.output(),
plugins: [
replace({
'process.browser': false,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
svelte({
generate: 'ssr',
dev
}),
resolve(),
commonjs()
],
external: Object.keys(pkg.dependencies).concat(
require('module').builtinModules || Object.keys(process.binding('natives'))
),
},
serviceworker: {
input: config.serviceworker.input(),
output: config.serviceworker.output(),
plugins: [
resolve(),
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode)
}),
commonjs(),
!dev && terser()
]
}
};

6
site/src/client.js Normal file
View File

@@ -0,0 +1,6 @@
import '@sveltejs/site-kit/base.css';
import * as sapper from '@sapper/app';
sapper.start({
target: document.querySelector('#sapper')
});

View File

@@ -0,0 +1,71 @@
<script>
const dev = process.env.NODE_ENV === 'development';
export let status;
export let error;
// we don't want to use <svelte:window bind:online> here,
// because we only care about the online state when
// the page first loads
let online = typeof navigator !== 'undefined'
? navigator.onLine
: true;
</script>
<style>
.container {
padding: var(--top-offset) var(--side-nav) 6rem var(--side-nav);
}
h1, p { margin: 0 auto }
h1 {
font-size: 2.8em;
font-weight: 300;
margin: 0 0 0.5em 0;
}
p { margin: 1em auto }
.error {
background-color: #da106e;
color: white;
padding: 12px 16px;
font: 600 16px/1.7 var(--font);
border-radius: 2px;
}
/* @media (min-width: 480px) {
h1 { font-size: 4em }
} */
</style>
<svelte:head>
<title>{status}</title>
</svelte:head>
<div class="container">
{#if online}
<h1>Yikes!</h1>
{#if error.message}
<p class="error">{status}: {error.message}</p>
{:else}
<p class="error">Encountered a {status} error</p>
{/if}
{#if dev && error.stack}
<pre>{error.stack}</pre>
{:else}
{#if status >= 500}
<p>Please try reloading the page.</p>
{/if}
<p>If the error persists, please drop by <a href="https://svelte.dev/chat">Discord chatroom</a> and let us know, or raise an issue on <a href="https://github.com/sveltejs/svelte">GitHub</a>. Thanks!</p>
{/if}
{:else}
<h1>It looks like you're offline</h1>
<p>Reload the page once you've found the internet.</p>
{/if}
</div>

View File

@@ -0,0 +1,42 @@
<script>
import { stores } from '@sapper/app';
import { Icons, Icon, Nav, NavItem } from '@sveltejs/site-kit';
export let segment;
const { page } = stores();
</script>
<style>
:global(html) {
--prime: rgb(21, 151, 148) !important; /* TODO remove .theme-default from shared, so we don't need !important */
}
main {
position: relative;
margin: 0 auto;
/* padding: var(--nav-h) var(--side-nav) 0 var(--side-nav); */
padding: var(--nav-h) 0 0 0;
overflow-x: hidden;
}
</style>
<Icons/>
<Nav {segment} {page} logo="sapper-logo-horizontal.svg">
<NavItem segment="docs">Docs</NavItem>
<NavItem external="https://svelte.dev">Svelte</NavItem>
<NavItem external="https://svelte.dev/chat" title="Discord Chat">
<Icon name="message-square"/>
</NavItem>
<NavItem external="https://github.com/sveltejs/sapper" title="GitHub Repo">
<Icon name="github"/>
</NavItem>
</Nav>
<main>
<slot></slot>
</main>

View File

@@ -0,0 +1,14 @@
import send from '@polka/send';
import generate_docs from '../../utils/generate_docs.js';
let json;
export function get(req, res) {
if (!json || process.env.NODE_ENV !== 'production') {
json = JSON.stringify(generate_docs('docs')); // TODO it errors if I send the non-stringified value
}
send(res, 200, json, {
'Content-Type': 'application/json'
});
}

View File

@@ -0,0 +1,22 @@
<script context="module">
export async function preload() {
const sections = await this.fetch(`docs.json`).then(r => r.json());
return { sections };
}
</script>
<script>
import { Docs } from '@sveltejs/site-kit'
export let sections;
</script>
<svelte:head>
<title>Docs • Sapper</title>
<meta name="twitter:title" content="Sapper docs">
<meta name="twitter:description" content="The next small thing in web development">
<meta name="Description" content="The next small thing in web development">
</svelte:head>
<Docs {sections}/>

View File

@@ -0,0 +1,64 @@
<script>
import { Hero, Blurb } from '@sveltejs/site-kit';
</script>
<style>
</style>
<svelte:head>
<title>Sapper • The next small thing in web development</title>
</svelte:head>
<Hero
title="Sapper"
tagline="The next small thing in web development"
outline="sapper-logo-outline.svg"
logotype="sapper-logotype.svg"
/>
<Blurb>
<a href="https://svelte.dev" slot="one">
<h2>Powered by Svelte</h2>
<p>Sapper is an application framework powered by Svelte — build bigger apps with a smaller footprint</p>
<span class="learn-more">learn more</span>
</a>
<a href="docs" slot="two">
<h2>Best of both worlds</h2>
<p>All the SEO and progressive enhancement of a server-rendered app, with the slick navigation of an SPA</p>
<span class="learn-more">learn more</span>
</a>
<a href="docs" slot="three">
<h2>Build fast</h2>
<p>Hit the ground running with advanced routing, server-side rendering, code-splitting, offline support and more</p>
<span class="learn-more">learn more</span>
</a>
<div class="description" slot="what">
<p>Sapper is a framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing.</p>
<p>Unlike single-page apps, Sapper doesn't compromise on SEO, progressive enhancement or the initial load experience — but unlike traditional server-rendered apps, navigation is instantaneous for that app-like feel.</p>
<p><a href="https://svelte.dev/blog/sapper-towards-the-ideal-web-app-framework">Read the introductory blog post</a> to learn more.</p>
</div>
<div style="grid-area: start; display: flex; flex-direction: column; min-width: 0" slot="how">
<pre class="language-bash" style="margin: 0 0 1em 0; min-width: 0; min-height: 0">
# for Rollup
npx degit "sveltejs/sapper-template#rollup" my-app
# for webpack
npx degit "sveltejs/sapper-template#webpack" my-app
cd my-app
npm install
npm run dev & open http://localhost:3000
</pre>
<p class="cta"><a rel="prefetch" href="docs">Learn Sapper</a></p>
</div>
</Blurb>

View File

@@ -0,0 +1,14 @@
import send from '@polka/send';
import generate_docs from '../../utils/generate_docs.js';
let json;
export function get(req, res) {
if (!json || process.env.NODE_ENV !== 'production') {
json = JSON.stringify(generate_docs('migrating')); // TODO it errors if I send the non-stringified value
}
send(res, 200, json, {
'Content-Type': 'application/json'
});
}

View File

@@ -0,0 +1,22 @@
<script context="module">
export async function preload() {
const sections = await this.fetch(`migrating.json`).then(r => r.json());
return { sections };
}
</script>
<script>
import { Docs } from '@sveltejs/site-kit'
export let sections;
</script>
<svelte:head>
<title>Migration • Sapper</title>
<meta name="twitter:title" content="Sapper migration guides">
<meta name="twitter:description" content="The next small thing in web development">
<meta name="Description" content="The next small thing in web development">
</svelte:head>
<Docs {sections}/>

17
site/src/server.js Normal file
View File

@@ -0,0 +1,17 @@
import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
import * as sapper from '@sapper/server';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
polka() // You can also use Express
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, err => {
if (err) console.log('error', err);
});

View File

@@ -0,0 +1,82 @@
import { timestamp, files, shell, routes } from '@sapper/service-worker';
const ASSETS = `cache${timestamp}`;
// `shell` is an array of all the files generated by the bundler,
// `files` is an array of everything in the `static` directory
const to_cache = shell.concat(files);
const cached = new Set(to_cache);
self.addEventListener('install', event => {
event.waitUntil(
caches
.open(ASSETS)
.then(cache => cache.addAll(to_cache))
.then(() => {
self.skipWaiting();
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(async keys => {
// delete old caches
for (const key of keys) {
if (key !== ASSETS) await caches.delete(key);
}
self.clients.claim();
})
);
});
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
const url = new URL(event.request.url);
// don't try to handle e.g. data: URIs
if (!url.protocol.startsWith('http')) return;
// ignore dev server requests
if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
// always serve static files and bundler-generated assets from cache
if (url.host === self.location.host && cached.has(url.pathname)) {
event.respondWith(caches.match(event.request));
return;
}
// for pages, you might want to serve a shell `service-worker-index.html` file,
// which Sapper has generated for you. It's not right for every
// app, but if it's right for yours then uncomment this section
/*
if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
event.respondWith(caches.match('/service-worker-index.html'));
return;
}
*/
if (event.request.cache === 'only-if-cached') return;
// for everything else, try the network first, falling back to
// cache if the user is offline. (If the pages never change, you
// might prefer a cache-first approach to a network-first one.)
event.respondWith(
caches
.open(`offline${timestamp}`)
.then(async cache => {
try {
const response = await fetch(event.request);
cache.put(event.request, response.clone());
return response;
} catch(err) {
const response = await cache.match(event.request);
if (response) return response;
throw err;
}
})
);
});

52
site/src/template.html Normal file
View File

@@ -0,0 +1,52 @@
<!doctype html>
<html lang='en' class="theme-default typo-default">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
<meta name='theme-color' content='#159794'>
%sapper.base%
<link href=prism.css rel=stylesheet>
<link rel='manifest' href='manifest.json'>
<link rel='icon' type='image/png' href='favicon.png'>
<meta name='twitter:card' content='summary_large_image'>
<meta name='twitter:site' content='@sveltejs'>
<meta name='twitter:creator' content='@sveltejs'>
<meta name='twitter:image' content='https://sapper.svelte.dev/images/twitter-card.png'>
<!-- Sapper generates a <style> tag containing critical CSS
for the current page. CSS for the rest of the app is
lazily loaded when it precaches secondary pages -->
%sapper.styles%
<style>
.hljs, .hljs-subst { color: #333; }
.hljs-comment { color: #999; }
.hljs-tag { color: #170; }
.hljs-keyword { color: #708; }
.hljs-attr { color: #333; }
.hljs-number { color: #164; }
.hljs-type, .hljs-string, .hljs-selector-id, .hljs-selector-class, .hljs-quote, .hljs-template-tag, .hljs-deletion { color: rgb(16,94,16); }
.hljs-title, .hljs-section { color: rgb(16,94,16); }
.hljs-literal { color: #170; }
.hljs-meta { color: #555; }
.hljs-meta-string { color: #555; }
</style>
<!-- This contains the contents of the <svelte:head> component, if
the current page has one -->
%sapper.head%
</head>
<body>
<!-- The application will be rendered inside this element,
because `app/client.js` references it -->
<div id='sapper'>%sapper.html%</div>
<!-- Sapper creates a <script> tag containing `app/client.js`
and anything else it needs to hydrate the app and
initialise the router -->
%sapper.scripts%
</body>
</html>

View File

@@ -0,0 +1,158 @@
// TODO put this in site-kit? svelte.dev uses Prism instead of hljs
import fs from 'fs';
import path from 'path';
import { SLUG_SEPARATOR, SLUG_PRESERVE_UNICODE } from '../../config';
import { extract_frontmatter, extract_metadata, langs, link_renderer } from '@sveltejs/site-kit/utils/markdown.js';
import { make_session_slug_processor } from '@sveltejs/site-kit/utils/slug';
import marked from 'marked';
import hljs from 'highlight.js';
const escaped = {
'"': '&quot;',
"'": '&#39;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
};
const unescaped = Object.keys(escaped).reduce(
(unescaped, key) => ((unescaped[escaped[key]] = key), unescaped),
{}
);
function unescape(str) {
return String(str).replace(/&.+?;/g, match => unescaped[match] || match);
}
const block_types = [
'blockquote',
'html',
'heading',
'hr',
'list',
'listitem',
'paragraph',
'table',
'tablerow',
'tablecell'
];
export default function generate_docs(dir) {
const make_slug = make_session_slug_processor({
separator: SLUG_SEPARATOR,
preserve_unicode: SLUG_PRESERVE_UNICODE
});
return fs
.readdirSync(`content/${dir}`)
.filter(file => file[0] !== '.' && path.extname(file) === '.md')
.map(file => {
const markdown = fs.readFileSync(`content/${dir}/${file}`, 'utf-8');
const { content, metadata } = extract_frontmatter(markdown);
const section_slug = make_slug(metadata.title);
const subsections = [];
const renderer = new marked.Renderer();
let block_open = false;
renderer.link = link_renderer;
renderer.hr = () => {
block_open = true;
return '<div class="side-by-side"><div class="copy">';
};
renderer.code = (source, lang) => {
source = source.replace(/^ +/gm, match =>
match.split(' ').join('\t')
);
const lines = source.split('\n');
const meta = extract_metadata(lines[0], lang);
let prefix = '';
// let class_name = 'code-block';
let class_name = '';
if (meta) {
source = lines.slice(1).join('\n');
const filename = meta.filename || (lang === 'html' && 'App.svelte');
if (filename) {
prefix = `<span class='filename'>${prefix} ${filename}</span>`;
class_name += ' named';
}
}
if (meta && meta.hidden) {
return '';
}
const plang = langs[lang];
const { value: highlighted } = hljs.highlight(lang, source);
// const highlighted = PrismJS.highlight(
// source,
// PrismJS.languages[plang],
// lang
// );
const html = `<div class='${class_name}'>${prefix}<pre class='language-${plang}'><code>${highlighted}</code></pre></div>`;
if (block_open) {
block_open = false;
return `</div><div class="code">${html}</div></div>`;
}
return html;
};
renderer.heading = (text, level, rawtext) => {
const slug = level <= 4 && make_slug(rawtext);
if (level === 3 || level === 4) {
const title = unescape(
text
.replace(/<\/?code>/g, '')
.replace(/\.(\w+)(\((.+)?\))?/, (m, $1, $2, $3) => {
if ($3) return `.${$1}(...)`;
if ($2) return `.${$1}()`;
return `.${$1}`;
})
);
subsections.push({ slug, title, level });
}
return `
<h${level}>
<span id="${slug}" class="offset-anchor" ${level > 4 ? 'data-scrollignore' : ''}></span>
<a href="${dir}#${slug}" class="anchor" aria-hidden="true"></a>
${text}
</h${level}>`;
};
block_types.forEach(type => {
const fn = renderer[type];
renderer[type] = function() {
return fn.apply(this, arguments);
};
});
const html = marked(content, { renderer });
const hashes = {};
return {
html: html.replace(/@@(\d+)/g, (m, id) => hashes[id] || m),
metadata,
subsections,
slug: section_slug,
file,
};
});
}

BIN
site/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<g fill="none" stroke="white" stroke-width="2">
<line x1='5' y1='12' x2='19' y2='12' />
<polyline points='12 5 19 12 12 19' />
</g>
</svg>

After

Width:  |  Height:  |  Size: 275 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path style="fill: #aa1e1e" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" />
</svg>

After

Width:  |  Height:  |  Size: 229 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path style="stroke: #676778; stroke-width: 2; fill: none" d="M2,8 L12,16 L22,8"/>
</svg>

After

Width:  |  Height:  |  Size: 221 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path style="fill: #aa1e1e" d="M19.5,3.09L20.91,4.5L16.41,9H20V11H13V4H15V7.59L19.5,3.09M20.91,19.5L19.5,20.91L15,16.41V20H13V13H20V15H16.41L20.91,19.5M4.5,3.09L9,7.59V4H11V11H4V9H7.59L3.09,4.5L4.5,3.09M3.09,19.5L7.59,15H4V13H11V20H9V16.41L4.5,20.91L3.09,19.5Z" />
</svg>

After

Width:  |  Height:  |  Size: 405 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path fill="#aa1e1e" d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
</svg>

After

Width:  |  Height:  |  Size: 212 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path fill="#999" d="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" />
</svg>

After

Width:  |  Height:  |  Size: 213 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<g style="fill: none; stroke: white; stroke-width: 2;">
<path d='M20 14.66V20a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h5.34' />
<polygon points='18 2 22 6 12 16 8 16 8 12 18 2' />
</g>
</svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path style="fill: #aa1e1e" d="M9.5,13.09L10.91,14.5L6.41,19H10V21H3V14H5V17.59L9.5,13.09M10.91,9.5L9.5,10.91L5,6.41V10H3V3H10V5H6.41L10.91,9.5M14.5,13.09L19,17.59V14H21V21H14V19H17.59L13.09,14.5L14.5,13.09M13.09,9.5L17.59,5H14V3H21V10H19V6.41L14.5,10.91L13.09,9.5Z" />
</svg>

After

Width:  |  Height:  |  Size: 411 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path fill="#aa1e1e" d="M12,5C16.97,5 21,7.69 21,11C21,12.68 19.96,14.2 18.29,15.29C19.36,14.42 20,13.32 20,12.13C20,9.29 16.42,7 12,7V10L8,6L12,2V5M12,19C7.03,19 3,16.31 3,13C3,11.32 4.04,9.8 5.71,8.71C4.64,9.58 4,10.68 4,11.88C4,14.71 7.58,17 12,17V14L16,18L12,22V19Z" />
</svg>

After

Width:  |  Height:  |  Size: 415 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path fill="#aa1e1e" d="M3,4V12.5L6,9.5L9,13C10,14 10,15 10,15V21H14V14C14,14 14,13 13.47,12C12.94,11 12,10 12,10L9,6.58L11.5,4M18,4L13.54,8.47L14,9C14,9 14.93,10 15.47,11C15.68,11.4 15.8,11.79 15.87,12.13L21,7" />
</svg>

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<g fill="none" stroke="#333">
<path d="M9,7L6,7A2 2 0 0 0 6,17L9,17"/>
<path d="M15,7L18,7A2 2 0 0 1 18,17L15,17"/>
<path d="M7,12L17,12"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 290 B

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<circle id="spinner" cx="12" cy="12" r="8" fill="none" stroke="white" stroke-width="3" stroke-dasharray="50.2 50" />
<animate href="#spinner" attributeName="stroke-dashoffset" values="52;0;52" dur="5s" repeatCount="indefinite" />
<animateTransform href="#spinner" attributeName="transform" type="rotate" values="0 12 12;360 12 12;0 12 12" dur="9s" repeatCount="indefinite"/>
</svg>

After

Width:  |  Height:  |  Size: 519 B

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" viewBox="0 0 24 24">
<path fill="#aa1e1e" d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" />
</svg>

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

20
site/static/manifest.json Normal file
View File

@@ -0,0 +1,20 @@
{
"background_color": "#ffffff",
"theme_color": "#159794",
"name": "Sapper",
"short_name": "Sapper",
"display": "minimal-ui",
"start_url": "/",
"icons": [
{
"src": "images/sapper-android-chrome-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "images/sapper-android-chrome-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

96
site/static/prism.css Normal file
View File

@@ -0,0 +1,96 @@
/*
-----------------------------------------------
syntax-highlighting [prism]
-----------------------------------------------
*/
/* colors --------------------------------- */
pre[class*='language-'] {
--background: var(--back-light);
--base: hsl(45, 7%, 45%);
--comment: hsl(210, 25%, 60%);
--keyword: hsl(204, 58%, 45%);
--function: hsl(19, 67%, 45%);
--string: hsl(41, 37%, 45%);
--number: hsl(102, 27%, 50%);
--tags: var(--function);
--important: var(--string);
}
/* type-base ------------------------------ */
code[class*='language-'],
pre[class*='language-'] {
background: none;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
font: 300 var(--code-fs)/1.7 var(--font-mono);
color: var(--base);
tab-size: 2;
-moz-tab-size: 2;
-webkit-hyphens: none;
hyphens: none;
}
/* code-blocks ---------------------------- */
pre[class*='language-'] {
overflow: auto;
padding: 1.5rem 2rem;
margin: .8rem 0 2.4rem;
/* max-width: var(--code-w); */
border-radius: var(--border-r);
box-shadow: 1px 1px 1px rgba(68, 68, 68, .12) inset;
}
:not(pre) > code[class*='language-'],
pre[class*='language-'] {
background: var(--background);
}
/* tokens --------------------------------- */
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata { color: var(--comment) }
.token.punctuation { color: var(--base) }
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted { color: var(--tags) }
.token.boolean,
.token.number { color: var(--number) }
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted { color: var(--string) }
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable { color: var(--base) }
.token.atrule,
.token.attr-value,
.token.function,
.token.class-name { color: var(--function) }
.token.keyword { color: var(--keyword) }
.token.regex,
.token.important { color: var(--important) }
.token.important,
.token.bold { font-weight: bold }
.token.italic { font-style: italic }
.token.entity { cursor: help }

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="svelte" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 519 139" style="enable-background:new 0 0 519 139;" xml:space="preserve">
<style type="text/css">
.st0{fill:#159497;}
.st1{fill:#FFFFFF;}
.st2{fill:#4A4A55;}
</style>
<path id="back" class="st0" d="M110.2,28.4C99.8,13.5,79.3,9.1,64.4,18.6L38.4,35.2c-7.1,4.5-12,11.7-13.5,20
c-1.2,6.9-0.2,14,3.1,20.2c-2.2,3.4-3.8,7.2-4.5,11.2c-1.5,8.4,0.5,17.1,5.5,24.1c10.4,14.9,30.9,19.3,45.8,9.8l26.1-16.6
c7.1-4.5,12-11.7,13.5-20c1.2-6.9,0.1-14-3.1-20.2c2.2-3.4,3.8-7.2,4.5-11.2C117.2,44.1,115.2,35.5,110.2,28.4"/>
<path id="front" class="st1" d="M61.9,112.2c-8.4,2.2-17.3-1.1-22.2-8.2c-3-4.2-4.2-9.4-3.3-14.5c0.1-0.8,0.4-1.6,0.6-2.4l0.5-1.5
l1.3,1c3.1,2.2,6.5,4,10.2,5.1l1,0.3l-0.1,1c-0.1,1.4,0.3,2.7,1.1,3.8c1.5,2.1,4.2,3.1,6.7,2.5c0.6-0.2,1.1-0.4,1.6-0.7l26-16.6
c1.3-0.8,2.2-2.1,2.4-3.6c0.3-1.5-0.1-3.1-1-4.4c-1.5-2.1-4.2-3.1-6.7-2.5c-0.6,0.2-1.1,0.4-1.6,0.7l-10,6.3c-1.6,1-3.4,1.8-5.3,2.3
c-8.4,2.2-17.3-1.1-22.2-8.2c-3-4.2-4.2-9.4-3.2-14.5c0.9-5,3.8-9.4,8.1-12.1L72,29.3c1.6-1,3.4-1.8,5.3-2.3
c8.4-2.2,17.3,1.1,22.2,8.2c3,4.2,4.2,9.4,3.3,14.5c-0.2,0.8-0.4,1.6-0.6,2.4l-0.5,1.5l-1.3-1c-3.1-2.3-6.5-4-10.2-5.1l-1-0.3l0.1-1
c0.1-1.4-0.3-2.8-1.1-3.9c-1.5-2.1-4.2-3.1-6.7-2.4c-0.6,0.2-1.1,0.4-1.6,0.7L53.8,57.3c-1.3,0.8-2.2,2.1-2.5,3.6
c-0.3,1.5,0.1,3.1,1,4.4c1.5,2.1,4.1,3.1,6.7,2.5c0.6-0.2,1.1-0.4,1.6-0.7l10-6.3c1.6-1,3.4-1.8,5.3-2.3c8.4-2.2,17.3,1.1,22.2,8.2
c3,4.2,4.2,9.4,3.3,14.5c-0.9,5-3.8,9.4-8.1,12.1l-26.1,16.6C65.6,110.9,63.8,111.7,61.9,112.2"/>
<g>
<path class="st2" d="M150.8,85.9l8.3-3.1c2.5,5.4,7.3,9,13.7,9c6.5,0,10.8-3.2,10.8-9.2c0-6.5-6.5-8.8-13.5-11.2
c-8.3-3-17.4-6.2-17.4-17.1c0-8.4,6.9-15.6,19-15.6c10.1,0,16.4,5,18.4,11.8l-8.2,2.7c-1.4-3.4-5.1-6-10.9-6
c-5.7,0-9.1,2.6-9.1,7.2c0,4.9,5.5,6.8,12,9c8.6,3.2,18.8,6.8,18.8,19.2c0,11.4-8.9,17.8-20.4,17.8
C161.9,100.4,153.8,94.8,150.8,85.9z"/>
<path class="st2" d="M241,85.7h-24.1l-4.9,13.7h-9.2l21.5-59.6h9.1L255,99.4h-9.2L241,85.7z M231,57.6c-0.7-1.9-1.6-4.7-2-6.7
c-0.5,2-1.4,4.8-2,6.7l-7.3,20.3h18.6L231,57.6z"/>
<path class="st2" d="M268.3,39.8h23.9c14.6,0,19.7,9.3,19.7,18.2c0,8.3-5.5,18.2-19.7,18.2h-15.1v23.2h-8.9V39.8z M302.8,58
c0-4.2-2.1-9.7-10-9.7h-15.6v19.4h15.6C300.2,67.7,302.8,62.6,302.8,58z"/>
<path class="st2" d="M326,39.8h23.9c14.6,0,19.7,9.3,19.7,18.2c0,8.3-5.5,18.2-19.7,18.2h-15.1v23.2H326V39.8z M360.4,58
c0-4.2-2.1-9.7-10-9.7h-15.6v19.4h15.6C357.9,67.7,360.4,62.6,360.4,58z"/>
<path class="st2" d="M383.7,39.8h37v8.3h-28.1v16.4h18.1v8.3h-18.1V91h30v8.3h-38.8V39.8z"/>
<path class="st2" d="M438.7,39.8h25.1c14.6,0,19.2,8.7,19.2,17.6c0,6.9-3.8,14.5-12.9,16.9l12.3,25.1h-10.1L460.5,75h-12.9v24.4
h-8.9V39.8z M464,66.5c7.4,0,9.9-4.5,9.9-9.1c0-4.2-2-9.1-9.9-9.1h-16.4v18.2H464z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 98.4 118.3" style="enable-background:new 0 0 98.4 118.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#159497;stroke-width:0.25;}
</style>
<path class="st0" d="M92,15.7C81.1,0.1,59.5-4.6,43.8,5.4L16.3,22.9C8.8,27.6,3.6,35.3,2.1,44c-1.3,7.3-0.2,14.8,3.3,21.3
c-2.4,3.6-4,7.6-4.7,11.8c-1.6,8.9,0.5,18.1,5.7,25.4c11,15.7,32.6,20.3,48.2,10.4l27.5-17.5c7.5-4.7,12.7-12.4,14.2-21.1
c1.3-7.3,0.2-14.8-3.3-21.3c2.4-3.6,4-7.6,4.7-11.8C99.4,32.2,97.3,23.1,92,15.7"/>
<path class="st0" d="M41.1,104c-8.9,2.3-18.2-1.2-23.4-8.7c-3.2-4.4-4.4-9.9-3.5-15.3c0.2-0.9,0.4-1.7,0.6-2.6l0.5-1.6l1.4,1
c3.3,2.4,6.9,4.2,10.8,5.4l1,0.3l-0.1,1c-0.1,1.4,0.3,2.9,1.1,4.1c1.6,2.3,4.4,3.4,7.1,2.7c0.6-0.2,1.2-0.4,1.7-0.7l27.4-17.5
c1.4-0.9,2.3-2.2,2.6-3.8c0.3-1.6-0.1-3.3-1-4.6c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7l-10.5,6.7
c-1.7,1.1-3.6,1.9-5.6,2.4c-8.9,2.3-18.2-1.2-23.4-8.7c-3.1-4.4-4.4-9.9-3.4-15.3c0.9-5.2,4.1-9.9,8.6-12.7l27.4-17.5
c1.7-1.1,3.6-1.9,5.6-2.5c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.2,0.9-0.4,1.7-0.7,2.6L83,42.4l-1.4-1
c-3.3-2.4-6.9-4.2-10.8-5.4l-1-0.3l0.1-1c0.1-1.4-0.3-2.9-1.1-4.1c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7L32.6,46.2
C31.2,47,30.3,48.4,30,50c-0.3,1.6,0.1,3.3,1,4.6c1.6,2.3,4.4,3.3,7.1,2.6c0.6-0.2,1.2-0.4,1.7-0.7l10.5-6.7
c1.7-1.1,3.6-1.9,5.6-2.5c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.9,5.2-4.1,9.9-8.6,12.7l-27.4,17.5
C45,102.6,43.1,103.5,41.1,104"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="svelte" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 256 300" style="enable-background:new 0 0 256 300;" xml:space="preserve">
<style type="text/css">
.st0{fill:#159497;}
.st1{fill:#FFFFFF;}
.st2{fill:#4A4A55;}
</style>
<path id="back" class="st0" d="M191.9,52.1c-16.3-23.4-48.6-30.3-71.9-15.5L79,62.8c-11.2,7-18.9,18.5-21.3,31.5
c-1.9,10.8-0.2,22,4.9,31.7c-3.5,5.3-5.9,11.3-7,17.6c-2.4,13.3,0.7,26.9,8.6,37.9c16.3,23.4,48.6,30.3,71.9,15.4l41-26.1
c11.2-7,18.9-18.5,21.2-31.5c2-10.8,0.2-22-4.9-31.8c3.5-5.3,5.9-11.3,7-17.6C202.8,76.7,199.7,63,191.9,52.1"/>
<path id="front" class="st1" d="M115.9,183.7c-13.2,3.4-27.2-1.7-34.9-12.9c-4.7-6.6-6.6-14.8-5.2-22.8c0.2-1.3,0.6-2.6,1-3.8
l0.8-2.4l2.1,1.5c4.8,3.6,10.3,6.3,16,8l1.5,0.5l-0.1,1.5c-0.2,2.2,0.4,4.3,1.7,6.1c2.3,3.4,6.5,4.9,10.5,3.9
c0.9-0.2,1.7-0.6,2.5-1.1l41-26.1c2-1.3,3.4-3.4,3.9-5.7c0.4-2.4-0.1-4.9-1.6-6.9c-2.3-3.4-6.5-4.9-10.5-3.9
c-0.9,0.2-1.7,0.6-2.5,1.1l-15.6,10c-2.6,1.6-5.4,2.9-8.3,3.7c-13.2,3.4-27.2-1.8-34.9-13c-4.7-6.6-6.6-14.8-5.2-22.8
c1.4-7.8,6-14.7,12.8-18.9l41-26.1c2.6-1.6,5.4-2.9,8.3-3.7c13.2-3.4,27.2,1.8,34.9,13c4.7,6.6,6.6,14.8,5.1,22.8
c-0.2,1.3-0.6,2.6-1,3.8l-0.8,2.3l-2.1-1.5c-4.8-3.6-10.2-6.3-16-8l-1.5-0.5l0.1-1.5c0.2-2.2-0.4-4.3-1.6-6.1
c-2.3-3.4-6.5-4.9-10.5-3.9c-0.9,0.2-1.7,0.6-2.5,1.1l-41,26.1c-2,1.3-3.4,3.3-3.8,5.7c-0.4,2.4,0.1,4.9,1.6,6.9
c2.4,3.4,6.5,4.9,10.5,3.9c0.9-0.2,1.7-0.6,2.5-1.1l15.6-10c2.6-1.6,5.4-2.9,8.3-3.7c13.2-3.4,27.2,1.7,34.9,12.9
c4.7,6.6,6.6,14.8,5.2,22.8c-1.4,7.8-6,14.7-12.8,18.9l-41,26.1C121.7,181.7,118.8,182.9,115.9,183.7"/>
<g>
<path class="st2" d="M31.8,257.4l4.9-1.8c1.4,3.1,4.3,5.3,8,5.3c3.8,0,6.3-1.8,6.3-5.4c0-3.8-3.8-5.1-7.8-6.6
c-4.8-1.7-10.1-3.6-10.1-10c0-4.9,4-9.1,11.1-9.1c5.9,0,9.5,2.9,10.7,6.9l-4.8,1.6c-0.8-2-3-3.5-6.4-3.5c-3.3,0-5.3,1.5-5.3,4.2
c0,2.8,3.2,4,7,5.3c5,1.8,11,4,11,11.2c0,6.7-5.2,10.4-11.9,10.4C38.2,265.9,33.5,262.6,31.8,257.4z"/>
<path class="st2" d="M84.3,257.3H70.3l-2.8,8h-5.4l12.6-34.8H80l12.5,34.8h-5.4L84.3,257.3z M78.5,240.9c-0.4-1.1-0.9-2.7-1.2-3.9
c-0.3,1.2-0.8,2.8-1.2,3.9l-4.3,11.8h10.9L78.5,240.9z"/>
<path class="st2" d="M100.3,230.5h14c8.5,0,11.5,5.4,11.5,10.6c0,4.9-3.2,10.6-11.5,10.6h-8.8v13.5h-5.2V230.5z M120.4,241.2
c0-2.4-1.2-5.7-5.8-5.7h-9.1v11.3h9.1C118.9,246.8,120.4,243.8,120.4,241.2z"/>
<path class="st2" d="M133.9,230.5h14c8.5,0,11.5,5.4,11.5,10.6c0,4.9-3.2,10.6-11.5,10.6h-8.8v13.5h-5.2V230.5z M154,241.2
c0-2.4-1.2-5.7-5.8-5.7h-9.1v11.3h9.1C152.5,246.8,154,243.8,154,241.2z"/>
<path class="st2" d="M167.5,230.5h21.6v4.9h-16.4v9.5h10.6v4.9h-10.6v10.6h17.5v4.9h-22.6V230.5z"/>
<path class="st2" d="M199.6,230.5h14.7c8.5,0,11.2,5.1,11.2,10.3c0,4-2.2,8.4-7.5,9.8l7.2,14.7h-5.9l-6.9-14.2h-7.5v14.2h-5.2
V230.5z M214.4,246.1c4.3,0,5.8-2.6,5.8-5.3c0-2.4-1.2-5.3-5.8-5.3h-9.6v10.6H214.4z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 98.2 118" style="enable-background:new 0 0 98.2 118;" xml:space="preserve">
<style type="text/css">
.st0{fill:#159497;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M91.9,15.6C81-0.1,59.3-4.7,43.7,5.3L16.2,22.8C8.7,27.5,3.5,35.2,2,43.9c-1.3,7.3-0.2,14.8,3.3,21.3
c-2.4,3.6-4,7.6-4.7,11.8c-1.6,8.9,0.5,18.1,5.7,25.4c11,15.7,32.6,20.3,48.2,10.4L82,95.3c7.5-4.7,12.7-12.4,14.2-21.1
c1.3-7.3,0.2-14.8-3.3-21.3c2.4-3.6,4-7.6,4.7-11.8C99.2,32.1,97.2,22.9,91.9,15.6"/>
<path class="st1" d="M41,103.9c-8.9,2.3-18.2-1.2-23.4-8.7c-3.2-4.4-4.4-9.9-3.5-15.3c0.2-0.9,0.4-1.7,0.6-2.6l0.5-1.6l1.4,1
c3.3,2.4,6.9,4.2,10.8,5.4l1,0.3l-0.1,1c-0.1,1.4,0.3,2.9,1.1,4.1c1.6,2.3,4.4,3.4,7.1,2.7c0.6-0.2,1.2-0.4,1.7-0.7l27.4-17.5
c1.4-0.9,2.3-2.2,2.6-3.8c0.3-1.6-0.1-3.3-1-4.6c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7L48,68.3
c-1.7,1.1-3.6,1.9-5.6,2.4c-8.9,2.3-18.2-1.2-23.4-8.7c-3.1-4.4-4.4-9.9-3.4-15.3c0.9-5.2,4.1-9.9,8.6-12.7l27.4-17.5
c1.7-1.1,3.6-1.9,5.6-2.5c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.2,0.9-0.4,1.7-0.7,2.6l-0.5,1.6l-1.4-1
c-3.3-2.4-6.9-4.2-10.8-5.4l-1-0.3l0.1-1c0.1-1.4-0.3-2.9-1.1-4.1c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7L32.5,46.1
c-1.4,0.9-2.3,2.2-2.6,3.8c-0.3,1.6,0.1,3.3,1,4.6c1.6,2.3,4.4,3.3,7.1,2.6c0.6-0.2,1.2-0.4,1.7-0.7l10.5-6.7
c1.7-1.1,3.6-1.9,5.6-2.5c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.9,5.2-4.1,9.9-8.6,12.7l-27.4,17.5
C44.9,102.5,43,103.3,41,103.9"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 332.2 61.7" style="enable-background:new 0 0 332.2 61.7;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4A4A55;}
</style>
<g>
<path class="st0" d="M0,47.2l8.3-3.1c2.5,5.4,7.3,9,13.7,9c6.5,0,10.8-3.2,10.8-9.2c0-6.5-6.5-8.8-13.5-11.2
C11.2,29.7,2,26.5,2,15.6C2,7.2,8.9,0,21,0c10.1,0,16.4,5,18.4,11.8l-8.2,2.7c-1.4-3.4-5.1-6-10.9-6c-5.7,0-9.1,2.6-9.1,7.2
c0,4.9,5.5,6.8,12,9c8.6,3.2,18.8,6.8,18.8,19.2c0,11.4-8.9,17.8-20.4,17.8C11.1,61.7,3,56,0,47.2z"/>
<path class="st0" d="M90.2,46.9H66.1l-4.9,13.7H52L73.6,1h9.1l21.5,59.6H95L90.2,46.9z M80.2,18.8c-0.7-1.9-1.6-4.7-2-6.7
c-0.5,2-1.4,4.8-2,6.7l-7.3,20.3h18.6L80.2,18.8z"/>
<path class="st0" d="M117.5,1h23.9c14.6,0,19.7,9.3,19.7,18.2c0,8.3-5.5,18.2-19.7,18.2h-15.1v23.2h-8.9V1z M151.9,19.2
c0-4.2-2.1-9.7-10-9.7h-15.6V29H142C149.4,29,151.9,23.8,151.9,19.2z"/>
<path class="st0" d="M175.2,1h23.9c14.6,0,19.7,9.3,19.7,18.2c0,8.3-5.5,18.2-19.7,18.2H184v23.2h-8.9V1z M209.6,19.2
c0-4.2-2.1-9.7-10-9.7H184V29h15.6C207,29,209.6,23.8,209.6,19.2z"/>
<path class="st0" d="M232.8,1h37v8.3h-28.1v16.4h18.1v8.3h-18.1v18.2h30v8.3h-38.8V1z"/>
<path class="st0" d="M287.9,1H313c14.6,0,19.2,8.7,19.2,17.6c0,6.9-3.8,14.5-12.9,16.9l12.3,25.1h-10.1l-11.8-24.4h-12.9v24.4h-8.9
V1z M313.2,27.8c7.4,0,9.9-4.5,9.9-9.1c0-4.2-2-9.1-9.9-9.1h-16.4v18.2H313.2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -19,6 +19,7 @@ type Opts = {
static?: string; static?: string;
legacy?: boolean; legacy?: boolean;
bundler?: 'rollup' | 'webpack'; bundler?: 'rollup' | 'webpack';
ext?: string;
oncompile?: ({ type, result }: { type: string, result: CompileResult }) => void; oncompile?: ({ type, result }: { type: string, result: CompileResult }) => void;
}; };
@@ -32,6 +33,7 @@ export async function build({
bundler, bundler,
legacy = false, legacy = false,
ext,
oncompile = noop oncompile = noop
}: Opts = {}) { }: Opts = {}) {
bundler = validate_bundler(bundler); bundler = validate_bundler(bundler);
@@ -68,7 +70,7 @@ export async function build({
fs.writeFileSync(`${dest}/template.html`, minify_html(template)); fs.writeFileSync(`${dest}/template.html`, minify_html(template));
const manifest_data = create_manifest_data(routes); const manifest_data = create_manifest_data(routes, ext);
// create src/node_modules/@sapper/app.mjs and server.mjs // create src/node_modules/@sapper/app.mjs and server.mjs
create_app({ create_app({

View File

@@ -28,7 +28,8 @@ type Opts = {
hot?: boolean, hot?: boolean,
'devtools-port'?: number, 'devtools-port'?: number,
bundler?: 'rollup' | 'webpack', bundler?: 'rollup' | 'webpack',
port?: number port?: number,
ext: string
}; };
export function dev(opts: Opts) { export function dev(opts: Opts) {
@@ -67,6 +68,7 @@ class Watcher extends EventEmitter {
unique_warnings: Set<string>; unique_warnings: Set<string>;
unique_errors: Set<string>; unique_errors: Set<string>;
} }
ext: string;
constructor({ constructor({
cwd = '.', cwd = '.',
@@ -80,7 +82,8 @@ class Watcher extends EventEmitter {
hot, hot,
'devtools-port': devtools_port, 'devtools-port': devtools_port,
bundler, bundler,
port = +process.env.PORT port = +process.env.PORT,
ext
}: Opts) { }: Opts) {
super(); super();
@@ -95,7 +98,7 @@ class Watcher extends EventEmitter {
output: path.resolve(cwd, output), output: path.resolve(cwd, output),
static: path.resolve(cwd, static_files) static: path.resolve(cwd, static_files)
}; };
this.ext = ext;
this.port = port; this.port = port;
this.closed = false; this.closed = false;
@@ -161,7 +164,7 @@ class Watcher extends EventEmitter {
let manifest_data: ManifestData; let manifest_data: ManifestData;
try { try {
manifest_data = create_manifest_data(routes); manifest_data = create_manifest_data(routes, this.ext);
create_app({ create_app({
bundler: this.bundler, bundler: this.bundler,
manifest_data, manifest_data,
@@ -189,7 +192,7 @@ class Watcher extends EventEmitter {
}, },
() => { () => {
try { try {
const new_manifest_data = create_manifest_data(routes); const new_manifest_data = create_manifest_data(routes, this.ext);
create_app({ create_app({
bundler: this.bundler, bundler: this.bundler,
manifest_data, // TODO is this right? not new_manifest_data? manifest_data, // TODO is this right? not new_manifest_data?
@@ -206,15 +209,19 @@ class Watcher extends EventEmitter {
}); });
} }
} }
), )
fs.watch(`${src}/template.html`, () => {
this.dev_server.send({
action: 'reload'
});
})
); );
if (this.live) {
this.filewatchers.push(
fs.watch(`${src}/template.html`, () => {
this.dev_server.send({
action: 'reload'
});
})
);
}
let deferred = new Deferred(); let deferred = new Deferred();
// TODO watch the configs themselves? // TODO watch the configs themselves?
@@ -252,7 +259,7 @@ class Watcher extends EventEmitter {
this.dev_server.send({ this.dev_server.send({
status: 'completed' status: 'completed'
}); });
} else { } else if (this.live) {
this.dev_server.send({ this.dev_server.send({
action: 'reload' action: 'reload'
}); });
@@ -267,48 +274,54 @@ class Watcher extends EventEmitter {
}); });
}; };
const start_server = () => {
// we need to give the child process its own DevTools port,
// otherwise Node will try to use the parent's (and fail)
const debugArgRegex = /--inspect(?:-brk|-port)?|--debug-port/;
const execArgv = process.execArgv.slice();
if (execArgv.some((arg: string) => !!arg.match(debugArgRegex))) {
execArgv.push(`--inspect-port=${this.devtools_port}`);
}
this.proc = child_process.fork(`${dest}/server/server.js`, [], {
cwd: process.cwd(),
env: Object.assign({
PORT: this.port
}, process.env),
stdio: ['ipc'],
execArgv
});
this.proc.stdout.on('data', chunk => {
this.emit('stdout', chunk);
});
this.proc.stderr.on('data', chunk => {
this.emit('stderr', chunk);
});
this.proc.on('message', message => {
if (message.__sapper__ && message.event === 'basepath') {
this.emit('basepath', {
basepath: message.basepath
});
}
});
this.proc.on('exit', emitFatal);
};
if (this.proc) { if (this.proc) {
this.proc.removeListener('exit', emitFatal); this.proc.removeListener('exit', emitFatal);
this.proc.kill(); this.proc.kill();
this.proc.on('exit', restart); this.proc.on('exit', () => {
start_server();
restart();
});
} else { } else {
start_server();
restart(); restart();
} }
// we need to give the child process its own DevTools port,
// otherwise Node will try to use the parent's (and fail)
const debugArgRegex = /--inspect(?:-brk|-port)?|--debug-port/;
const execArgv = process.execArgv.slice();
if (execArgv.some((arg: string) => !!arg.match(debugArgRegex))) {
execArgv.push(`--inspect-port=${this.devtools_port}`);
}
this.proc = child_process.fork(`${dest}/server/server.js`, [], {
cwd: process.cwd(),
env: Object.assign({
PORT: this.port
}, process.env),
stdio: ['ipc'],
execArgv
});
this.proc.stdout.on('data', chunk => {
this.emit('stdout', chunk);
});
this.proc.stderr.on('data', chunk => {
this.emit('stderr', chunk);
});
this.proc.on('message', message => {
if (message.__sapper__ && message.event === 'basepath') {
this.emit('basepath', {
basepath: message.basepath
});
}
});
this.proc.on('exit', emitFatal);
}); });
} }
}); });

View File

@@ -18,7 +18,9 @@ type Opts = {
cwd?: string, cwd?: string,
static?: string, static?: string,
basepath?: string, basepath?: string,
host_header?: string,
timeout?: number | false, timeout?: number | false,
concurrent?: number,
oninfo?: ({ message }: { message: string }) => void; oninfo?: ({ message }: { message: string }) => void;
onfile?: ({ file, size, status }: { file: string, size: number, status: number }) => void; onfile?: ({ file, size, status }: { file: string, size: number, status: number }) => void;
}; };
@@ -43,7 +45,9 @@ async function _export({
build_dir = '__sapper__/build', build_dir = '__sapper__/build',
export_dir = '__sapper__/export', export_dir = '__sapper__/export',
basepath = '', basepath = '',
host_header,
timeout = 5000, timeout = 5000,
concurrent = 8,
oninfo = noop, oninfo = noop,
onfile = noop onfile = noop
}: Opts = {}) { }: Opts = {}) {
@@ -87,6 +91,7 @@ async function _export({
const seen = new Set(); const seen = new Set();
const saved = new Set(); const saved = new Set();
const q = yootils.queue(concurrent);
function save(url: string, status: number, type: string, body: string) { function save(url: string, status: number, type: string, body: string) {
const { pathname } = resolve(origin, url); const { pathname } = resolve(origin, url);
@@ -123,25 +128,30 @@ async function _export({
async function handle(url: URL) { async function handle(url: URL) {
let pathname = url.pathname; let pathname = url.pathname;
if (pathname !== '/service-worker-index.html') { if (pathname !== '/service-worker-index.html') {
pathname = pathname.replace(root.pathname, '') || '/' pathname = pathname.replace(root.pathname, '') || '/'
} }
if (seen.has(pathname)) return; if (seen.has(pathname)) return;
seen.add(pathname); seen.add(pathname);
const timeout_deferred = new Deferred(); const r = await q.add(async () => {
const the_timeout = setTimeout(() => { const timeout_deferred = new Deferred();
timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`)); const the_timeout = setTimeout(() => {
}, timeout); timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`));
}, timeout);
const r = await Promise.race([ const r = await Promise.race([
fetch(url.href, { fetch(url.href, {
redirect: 'manual' headers: { host: host_header || host },
}), redirect: 'manual'
timeout_deferred.promise }),
]); timeout_deferred.promise
]);
clearTimeout(the_timeout); // prevent it hanging at the end clearTimeout(the_timeout); // prevent it hanging at the end
return r;
}) as Response;
let type = r.headers.get('Content-Type'); let type = r.headers.get('Content-Type');
@@ -149,6 +159,8 @@ async function _export({
const range = ~~(r.status / 100); const range = ~~(r.status / 100);
let tasks = [];
if (range === 2) { if (range === 2) {
if (type === 'text/html') { if (type === 'text/html') {
// parse link rel=preload headers and embed them in the HTML // parse link rel=preload headers and embed them in the HTML
@@ -159,11 +171,10 @@ async function _export({
`<link rel="preload" as=${JSON.stringify(ref.as)} href=${JSON.stringify(ref.uri)}></head>`) `<link rel="preload" as=${JSON.stringify(ref.as)} href=${JSON.stringify(ref.uri)}></head>`)
} }
}); });
if (pathname !== '/service-worker-index.html') { if (pathname !== '/service-worker-index.html') {
const cleaned = clean_html(body); const cleaned = clean_html(body);
const q = yootils.queue(8);
const base_match = /<base ([\s\S]+?)>/m.exec(cleaned); const base_match = /<base ([\s\S]+?)>/m.exec(cleaned);
const base_href = base_match && get_href(base_match[1]); const base_href = base_match && get_href(base_match[1]);
const base = resolve(url.href, base_href); const base = resolve(url.href, base_href);
@@ -179,12 +190,10 @@ async function _export({
const url = resolve(base.href, href); const url = resolve(base.href, href);
if (url.protocol === protocol && url.host === host) { if (url.protocol === protocol && url.host === host) {
q.add(() => handle(url)); tasks.push(handle(url));
} }
} }
} }
await q.close();
} }
} }
} }
@@ -195,20 +204,25 @@ async function _export({
type = 'text/html'; type = 'text/html';
body = `<script>window.location.href = "${location.replace(origin, '')}"</script>`; body = `<script>window.location.href = "${location.replace(origin, '')}"</script>`;
await handle(resolve(root.href, location)); tasks.push(handle(resolve(root.href, location)));
} }
save(pathname, r.status, type, body); save(pathname, r.status, type, body);
await Promise.all(tasks);
} }
return ports.wait(port) try {
.then(() => handle(root)) await ports.wait(port);
.then(() => handle(resolve(root.href, 'service-worker-index.html'))) await handle(root);
.then(() => proc.kill()) await handle(resolve(root.href, 'service-worker-index.html'));
.catch(err => { await q.close();
proc.kill();
throw err; proc.kill()
}); } catch (err) {
proc.kill();
throw err;
}
} }
function get_href(attrs: string) { function get_href(attrs: string) {

View File

@@ -31,6 +31,7 @@ prog.command('dev')
.option('--static', 'Static files directory', 'static') .option('--static', 'Static files directory', 'static')
.option('--output', 'Sapper output directory', 'src/node_modules/@sapper') .option('--output', 'Sapper output directory', 'src/node_modules/@sapper')
.option('--build-dir', 'Development build directory', '__sapper__/dev') .option('--build-dir', 'Development build directory', '__sapper__/dev')
.option('--ext', 'Custom Route Extension', '.svelte .html')
.action(async (opts: { .action(async (opts: {
port: number, port: number,
open: boolean, open: boolean,
@@ -43,7 +44,8 @@ prog.command('dev')
routes: string, routes: string,
static: string, static: string,
output: string, output: string,
'build-dir': string 'build-dir': string,
ext: string
}) => { }) => {
const { dev } = await import('./api/dev'); const { dev } = await import('./api/dev');
@@ -59,7 +61,8 @@ prog.command('dev')
'dev-port': opts['dev-port'], 'dev-port': opts['dev-port'],
live: opts.live, live: opts.live,
hot: opts.hot, hot: opts.hot,
bundler: opts.bundler bundler: opts.bundler,
ext: opts.ext
}); });
let first = true; let first = true;
@@ -151,6 +154,7 @@ prog.command('build [dest]')
.option('--src', 'Source directory', 'src') .option('--src', 'Source directory', 'src')
.option('--routes', 'Routes directory', 'src/routes') .option('--routes', 'Routes directory', 'src/routes')
.option('--output', 'Sapper output directory', 'src/node_modules/@sapper') .option('--output', 'Sapper output directory', 'src/node_modules/@sapper')
.option('--ext', 'Custom Route Extension', '.svelte .html')
.example(`build custom-dir -p 4567`) .example(`build custom-dir -p 4567`)
.action(async (dest = '__sapper__/build', opts: { .action(async (dest = '__sapper__/build', opts: {
port: string, port: string,
@@ -159,12 +163,13 @@ prog.command('build [dest]')
cwd: string, cwd: string,
src: string, src: string,
routes: string, routes: string,
output: string output: string,
ext: string
}) => { }) => {
console.log(`> Building...`); console.log(`> Building...`);
try { try {
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, dest); await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, dest, opts.ext);
const launcher = path.resolve(dest, 'index.js'); const launcher = path.resolve(dest, 'index.js');
@@ -189,6 +194,8 @@ prog.command('export [dest]')
.describe('Export your app as static files (if possible)') .describe('Export your app as static files (if possible)')
.option('--build', '(Re)build app before exporting', true) .option('--build', '(Re)build app before exporting', true)
.option('--basepath', 'Specify a base path') .option('--basepath', 'Specify a base path')
.option('--host', 'Host header to use when crawling site')
.option('--concurrent', 'Concurrent requests', 8)
.option('--timeout', 'Milliseconds to wait for a page (--no-timeout to disable)', 5000) .option('--timeout', 'Milliseconds to wait for a page (--no-timeout to disable)', 5000)
.option('--legacy', 'Create separate legacy build') .option('--legacy', 'Create separate legacy build')
.option('--bundler', 'Specify a bundler (rollup or webpack, blank for auto)') .option('--bundler', 'Specify a bundler (rollup or webpack, blank for auto)')
@@ -198,11 +205,14 @@ prog.command('export [dest]')
.option('--static', 'Static files directory', 'static') .option('--static', 'Static files directory', 'static')
.option('--output', 'Sapper output directory', 'src/node_modules/@sapper') .option('--output', 'Sapper output directory', 'src/node_modules/@sapper')
.option('--build-dir', 'Intermediate build directory', '__sapper__/build') .option('--build-dir', 'Intermediate build directory', '__sapper__/build')
.option('--ext', 'Custom Route Extension', '.svelte .html')
.action(async (dest = '__sapper__/export', opts: { .action(async (dest = '__sapper__/export', opts: {
build: boolean, build: boolean,
legacy: boolean, legacy: boolean,
bundler?: 'rollup' | 'webpack', bundler?: 'rollup' | 'webpack',
basepath?: string, basepath?: string,
host?: string,
concurrent: number,
timeout: number | false, timeout: number | false,
cwd: string, cwd: string,
src: string, src: string,
@@ -210,11 +220,12 @@ prog.command('export [dest]')
static: string, static: string,
output: string, output: string,
'build-dir': string, 'build-dir': string,
ext: string
}) => { }) => {
try { try {
if (opts.build) { if (opts.build) {
console.log(`> Building...`); console.log(`> Building...`);
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, opts['build-dir']); await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, opts['build-dir'], opts.ext);
console.error(`\n> Built in ${elapsed(start)}`); console.error(`\n> Built in ${elapsed(start)}`);
} }
@@ -227,7 +238,9 @@ prog.command('export [dest]')
build_dir: opts['build-dir'], build_dir: opts['build-dir'],
export_dir: dest, export_dir: dest,
basepath: opts.basepath, basepath: opts.basepath,
host_header: opts.host,
timeout: opts.timeout, timeout: opts.timeout,
concurrent: opts.concurrent,
oninfo: event => { oninfo: event => {
console.log(colors.bold().cyan(`> ${event.message}`)); console.log(colors.bold().cyan(`> ${event.message}`));
@@ -262,7 +275,8 @@ async function _build(
src: string, src: string,
routes: string, routes: string,
output: string, output: string,
dest: string dest: string,
ext: string
) { ) {
const { build } = await import('./api/build'); const { build } = await import('./api/build');
@@ -273,7 +287,8 @@ async function _build(
src, src,
routes, routes,
dest, dest,
ext,
output,
oncompile: event => { oncompile: event => {
let banner = `built ${event.type}`; let banner = `built ${event.type}`;
let c = (txt: string) => colors.cyan(txt); let c = (txt: string) => colors.cyan(txt);

Some files were not shown because too many files have changed in this diff Show More