Compare commits

...

703 Commits

Author SHA1 Message Date
Rich Harris
03c5f5b446 -> v0.24.0 2018-10-27 12:40:27 -04:00
Rich Harris
6655b1b49d Merge pull request #503 from sveltejs/gh-490
redirect to external URLs
2018-10-27 12:36:10 -04:00
Rich Harris
eebf076f23 Merge pull request #501 from sveltejs/gh-497
consistent query parameter handling between client and server
2018-10-27 12:35:58 -04:00
Rich Harris
198be28f4b Merge pull request #504 from sveltejs/gh-480
change scroll[X|Y] to page[X|Y]Offset
2018-10-27 12:35:47 -04:00
Rich Harris
4f720446b2 change scroll[X|Y] to page[X|Y]Offset - closes #480 2018-10-27 12:26:53 -04:00
Rich Harris
e69cb3639a redirect to external URLs - closes #490 2018-10-27 12:14:28 -04:00
Rich Harris
1b1a86764f Merge pull request #502 from sveltejs/gh-495
strip leading slash from basepath
2018-10-27 11:50:23 -04:00
Rich Harris
f50f83c4a4 strip leading slash from basepath - fixes #495 2018-10-27 11:41:02 -04:00
Rich Harris
eadefd996b consistent query parameter handling between client and server - fixes #497 2018-10-27 11:36:18 -04:00
Rich Harris
ab52aabd1d Merge pull request #500 from sveltejs/dont-buffer-logs
don't buffer stdout/stderr
2018-10-27 11:18:50 -04:00
Rich Harris
c5a80543b3 reduce flakiness of unrelated test 2018-10-27 11:12:10 -04:00
Rich Harris
cfd95ac024 dont buffer stdout/stderr 2018-10-27 11:02:34 -04:00
Rich Harris
73ff95c677 Merge pull request #498 from mrkishi/build-dir
Add missing posixify to `build_dir`
2018-10-27 10:17:58 -04:00
mrkishi
382fe6b7b9 Add missing posixify 2018-10-26 11:03:29 -03:00
Rich Harris
3b714c0de3 -> v0.23.5 2018-10-24 21:40:04 -04:00
Rich Harris
28186227a9 add tests 2018-10-24 21:20:27 -04:00
Rich Harris
2ac0f2bf3d Merge branch 'search-params-decoding' of https://github.com/mrkishi/sapper into mrkishi-search-params-decoding 2018-10-24 21:10:35 -04:00
Rich Harris
4991f3b359 support non-native promise implementations 2018-10-24 21:05:25 -04:00
Rich Harris
65128118c7 Merge branch '487-async-route-errors' of https://github.com/nikku/sapper into nikku-487-async-route-errors 2018-10-24 20:58:52 -04:00
Rich Harris
3eced6fa4d Merge pull request #492 from sveltejs/lazy-css
fix lazy css bug, add tests
2018-10-24 20:58:08 -04:00
mrkishi
c4aee66c32 Fix search params decoding 2018-10-24 21:19:03 -03:00
Rich Harris
410c52df41 fix lazy css bug, add tests 2018-10-24 18:48:38 -04:00
Rich Harris
ffd56e2a20 -> v0.23.4 2018-10-24 15:51:44 -04:00
Rich Harris
1e5a87cf71 Merge pull request #491 from sveltejs/empty-href-export
ignore empty anchors when exporting
2018-10-24 15:51:28 -04:00
Rich Harris
281e183c61 ignore empty anchors when exporting 2018-10-24 15:38:45 -04:00
Nico Rehwaldt
3fe7b55955 async -> Promise.reject 2018-10-20 22:46:42 +02:00
Nico Rehwaldt
464924ed67 handle async route errors
Related to #487
2018-10-20 22:40:21 +02:00
Rich Harris
e5d7d8ab2b -> v0.23.3 2018-10-16 16:16:11 -04:00
Rich Harris
d3e560325d Merge pull request #477 from sveltejs/clear-errors
clear errors on successful render
2018-10-16 16:15:28 -04:00
Rich Harris
64e5065aa5 clear errors on successful render 2018-10-16 15:59:57 -04:00
Rich Harris
cb45bb0fbe -> v0.23.2 2018-10-16 08:58:02 -04:00
Rich Harris
f39455014a update deps 2018-10-16 08:44:22 -04:00
Rich Harris
4fe8df3696 Merge pull request #471 from sveltejs/missing-css
include css depended upon by entry point, even if also depended on by a lazily-loaded component
2018-10-16 08:38:20 -04:00
Rich Harris
4fdc7055c1 -> v0.23.1 2018-10-15 22:10:18 -04:00
Rich Harris
cca417a85a simplify, preserve monomorphism 2018-10-15 22:07:23 -04:00
Rich Harris
635c13a175 Merge branch 'tags-from-other-pages' of https://github.com/DayBr3ak/sapper into DayBr3ak-tags-from-other-pages 2018-10-15 21:43:39 -04:00
Rich Harris
2e3aef8b21 simplify 2018-10-15 21:36:40 -04:00
Rich Harris
44736754ad fix file extension 2018-10-15 21:27:52 -04:00
Benjamin GROENEVELD
a399d87d9b handle tag click from another page 2018-10-13 21:59:47 +02:00
Benjamin GROENEVELD
a68c62ce91 Fix hash link reliability (fix #434) 2018-10-13 16:45:14 +02:00
Rich Harris
1b9b559d82 include css depended upon by entry point, even if also depended on by a lazily-loaded component 2018-10-11 23:18:51 -04:00
Rich Harris
abcac75826 -> v0.23.0 2018-10-09 15:03:02 -04:00
Rich Harris
4118c566d1 Merge pull request #470 from aubergene/ssi
Allow SSI comments
2018-10-09 15:02:21 -04:00
Rich Harris
0b76f12394 Merge pull request #469 from sveltejs/remove-env-vars
replace magic env vars with documented CLI flags
2018-10-09 14:59:30 -04:00
Julian Burgess
e51cb85c7c Allow SSI directives 2018-10-09 19:50:25 +01:00
Rich Harris
6ae9a5e7c5 replace magic env vars with documented CLI flags 2018-10-09 08:33:25 -04:00
Rich Harris
52f40f9e63 Improve internal API 2018-10-08 19:21:15 -04:00
Rich Harris
5e59855a15 overhaul tests 2018-10-07 18:23:43 -04:00
Rich Harris
18acef3190 -> v0.22.10 2018-10-05 21:25:49 -04:00
Rich Harris
d7f6ca8b4d Merge pull request #466 from sveltejs/css-basepath
no need to use basepath in <link>
2018-10-05 21:22:15 -04:00
Rich Harris
00321932ef no need to use basepath in <link> 2018-10-05 21:16:27 -04:00
Rich Harris
7eb1ec727c add tests for #376 2018-10-05 20:59:45 -04:00
Rich Harris
3f586e19a1 minor tweaks 2018-10-05 20:57:15 -04:00
Daniil Khanin
05b702938f implement sapper no scroll 2018-10-05 00:13:08 +03:00
Rich Harris
3026e7c36e remove some leftover logging 2018-10-02 11:39:38 -04:00
Rich Harris
27a5aed83e -> v0.22.9 2018-10-02 11:13:36 -04:00
Rich Harris
bb04af41bd fix legacy builds 2018-10-02 11:13:09 -04:00
Rich Harris
9403799393 -> v0.22.8 2018-10-02 10:37:19 -04:00
Rich Harris
472c0c198a Merge pull request #462 from sveltejs/overwrite-css-placeholders
ensure CSS placeholders are overwritten
2018-10-02 10:36:39 -04:00
Rich Harris
02256ae214 ensure CSS placeholders are overwritten 2018-10-02 10:27:02 -04:00
Rich Harris
e2d325ec9f -> v0.22.7 2018-10-01 22:58:44 -04:00
Rich Harris
954bcba333 Merge pull request #460 from sveltejs/cookies
more robust cookies
2018-10-01 22:58:30 -04:00
Rich Harris
709c9992e3 more robust cookies 2018-10-01 22:47:11 -04:00
Rich Harris
9773781262 -> v0.22.6 2018-10-01 20:55:25 -04:00
Rich Harris
48b1fafc33 Merge pull request #459 from sveltejs/gh-458
inject a <script> with crossOrigin - fixes #458
2018-10-01 20:48:42 -04:00
Rich Harris
d1624add66 Merge pull request #456 from mrkishi/windows-paths
Fix rollup input paths on Windows
2018-10-01 20:48:21 -04:00
Rich Harris
e2206d0e0d inject a <script> with crossOrigin - fixes #458 2018-10-01 18:55:38 -04:00
Rich Harris
9cd4da4c39 -> v0.22.5 2018-10-01 17:54:29 -04:00
Rich Harris
6ded1a5975 slap self in face 2018-10-01 17:52:57 -04:00
mrkishi
584ddd1c85 Fix rollup input paths on Windows 2018-10-01 17:47:44 -03:00
Rich Harris
4071acf7c0 -> v0.22.4 2018-10-01 15:53:40 -04:00
Rich Harris
e8773d3196 Merge pull request #455 from sveltejs/deconflict-server
put server assets in subfolder
2018-10-01 15:52:14 -04:00
Rich Harris
01a519a4d9 fix everything i just broke 2018-10-01 15:47:01 -04:00
Rich Harris
d9ad1d1b10 put server assets in subfolder 2018-10-01 15:21:35 -04:00
Rich Harris
0826a58995 -> v0.22.3 2018-10-01 12:13:52 -04:00
Rich Harris
6a74097b0c ensure dev client is not imported if server imports client.js 2018-10-01 12:13:03 -04:00
Rich Harris
278be67228 -> v0.22.2 2018-09-30 22:06:19 -04:00
Rich Harris
64921dfc3c make directories relative to project 2018-09-30 22:05:55 -04:00
Rich Harris
c8962ccf8c update gitignore 2018-09-30 21:12:46 -04:00
Rich Harris
664c093391 -> v0.22.1 2018-09-30 21:10:17 -04:00
Rich Harris
4375feac83 -> v0.22.0 2018-09-30 21:05:31 -04:00
Rich Harris
4d7d448597 Merge pull request #453 from sveltejs/gh-444
move app logic into templates (#444)
2018-09-30 21:03:10 -04:00
Rich Harris
2e2b8dcd83 small tweak 2018-09-30 18:26:44 -04:00
Rich Harris
b915bab070 gah cant use agadoo because of placeholder imports 2018-09-30 18:21:28 -04:00
Rich Harris
8530d06d00 golf things down a bit 2018-09-30 18:11:40 -04:00
Rich Harris
a43764a971 put everything in __sapper__ 2018-09-30 14:55:54 -04:00
Rich Harris
4f6efbda79 node 6 support 2018-09-30 12:54:50 -04:00
Rich Harris
5573258a10 update tests 2018-09-30 11:32:58 -04:00
Rich Harris
2185f89669 enforce client app treeshakeability 2018-09-30 10:50:25 -04:00
Rich Harris
e30842caa8 move app logic into templates (#444) 2018-09-30 10:30:00 -04:00
Rich Harris
ff24877d8f -> v0.21.1 2018-09-23 23:21:37 -04:00
Rich Harris
9cf90ce01d -> v0.21.0 2018-09-23 21:48:53 -04:00
Rich Harris
e7f9ddae86 Merge pull request #446 from mrkishi/escaping-issues
Fix filenames
2018-09-23 21:47:28 -04:00
Rich Harris
ffa1e1f704 Merge branch 'master' into escaping-issues 2018-09-23 21:41:02 -04:00
Rich Harris
80bb958b47 Merge pull request #443 from sveltejs/gh-432
update folder structure
2018-09-23 21:39:05 -04:00
mrkishi
532f559fc5 Update package-lock.json 2018-09-21 15:40:12 -03:00
mrkishi
0bd1b0b8e2 Fix filename escaping issues 2018-09-21 15:34:59 -03:00
Rich Harris
10c5ff4169 remove test app manifest from repo 2018-09-19 17:51:56 -04:00
Rich Harris
273823dfd7 move config into single file, add errors to help people migrate 2018-09-19 16:27:30 -04:00
Rich Harris
8f064fe5ac update folder structure (#432) 2018-09-19 12:02:11 -04:00
Rich Harris
f29e7efbd6 const -> let 2018-09-19 11:25:04 -04:00
Rich Harris
e66e3cd7eb -> v0.20.4 2018-09-19 11:11:42 -04:00
Rich Harris
ff415b391b Merge pull request #436 from nsivertsen/devtools
Enable debugging in Chrome and VS Code - fixes #435
2018-09-19 11:09:19 -04:00
Rich Harris
91182ad0a2 Merge pull request #441 from silentworks/bugfix/legacy-manifest
Fix for legacy manifest file
2018-09-19 10:43:50 -04:00
Andrew Smith
467041a3cd Fix for legacy manifest file 2018-09-13 19:45:30 +01:00
Nikolai Sivertsen
520949c5e1 Enable debugging in Chrome and VS Code - fixes 435 2018-09-12 16:55:45 +02:00
Rich Harris
8c07d9d2ac -> v0.20.3 2018-09-08 10:24:21 -04:00
Rich Harris
7bd684a80e Merge pull request #428 from nolanlawson/cache-control-service-worker
Set service-worker max-age to 0
2018-09-08 10:20:04 -04:00
Rich Harris
cbb5e8755b Use MDN recommendation for preventing SW caching 2018-09-08 10:12:55 -04:00
Rich Harris
7ef72dbb77 Merge pull request #429 from nolanlawson/consistent-cache-control
Use consistent cache-control:max-age=600 for HTML pages
2018-09-08 09:40:56 -04:00
Nolan Lawson
87ff9c2aeb fix test 2018-09-07 17:25:17 -07:00
Nolan Lawson
2d1f535314 use consistent cache-control:max-age=600 for HTML pages 2018-09-07 16:46:40 -07:00
Rich Harris
cd1b53b80d Merge pull request #424 from nolanlawson/csp-nonce
Allow scripts to contain a CSP nonce
2018-09-07 19:46:07 -04:00
Rich Harris
0a7be736c0 snake_case 2018-09-07 19:45:40 -04:00
Nolan Lawson
5ee53a98c6 set service-worker max-age to 0 2018-09-07 16:28:38 -07:00
Rich Harris
0e8ed6612c -> v0.20.2 2018-09-07 18:19:39 -04:00
Rich Harris
5ec748b95d Merge pull request #427 from sveltejs/gh-426
handle value-less query string parameters
2018-09-07 18:18:28 -04:00
Rich Harris
64b16715cd handle value-less query string parameters - fixes #426 2018-09-07 18:02:58 -04:00
Rich Harris
9ea5e5e251 Merge pull request #425 from nolanlawson/cache-control-immutable
Add cache-control:immutable for immutable assets
2018-09-07 17:12:23 -04:00
Rich Harris
68b78f56d6 remove unused file 2018-09-07 16:42:56 -04:00
Nolan Lawson
68e93a8fa0 add cache-control:immutable for immutable assets 2018-09-07 11:07:50 -07:00
Nolan Lawson
e377515867 allow scripts to contain a CSP nonce 2018-09-07 09:46:53 -07:00
Rich Harris
99ae39b8a8 -> v0.20.1 2018-09-06 18:57:11 -04:00
Rich Harris
1b489f4687 -> v0.20.0 2018-09-04 07:35:24 -04:00
Rich Harris
91f2c6e49c Merge pull request #418 from nsivertsen/sourcemaps
Enable source maps by default in dev mode when using rollup
2018-09-04 07:32:15 -04:00
Rich Harris
f5e07e9f78 Merge pull request #419 from sveltejs/gh-417
decode req.params
2018-09-04 07:31:53 -04:00
Rich Harris
17297a9794 Merge pull request #414 from sveltejs/gh-347-b
decode req.path during export
2018-09-04 07:31:36 -04:00
Rich Harris
9ef4f33e38 decode query params 2018-09-03 20:09:25 -04:00
Rich Harris
30966ee7f2 decode req.params - fixes #417 2018-09-03 18:36:07 -04:00
Nikolai Sivertsen
ae90f774e1 Enable source maps by default in dev mode when using rollup 2018-09-04 00:26:18 +02:00
Rich Harris
0706b5f50a decode req.path during export 2018-09-03 09:02:13 -04:00
Rich Harris
499b377bfd -> v0.19.3 2018-09-03 07:55:30 -04:00
Rich Harris
1baeb79d4b Merge branch 'master' of github.com:sveltejs/sapper 2018-09-03 07:54:42 -04:00
Rich Harris
0cc5ff95d6 minor tidy up 2018-09-03 07:54:39 -04:00
Rich Harris
e90525c1e8 Merge pull request #413 from sveltejs/gh-347
better unicode handling - fixes #347, i think
2018-09-03 07:47:41 -04:00
Rich Harris
6ccae0cd33 better unicode handling - fixes #347, i think 2018-09-02 23:00:39 -04:00
Rich Harris
8b60d568dc -> v0.19.2 2018-09-02 21:56:45 -04:00
Rich Harris
64c2394c9d Merge branch 'master' of github.com:sveltejs/sapper 2018-09-02 21:54:13 -04:00
Rich Harris
b28037291a Merge pull request #412 from sveltejs/gh-315
allow reserved words as route names
2018-09-02 21:54:09 -04:00
Rich Harris
bf9cbe2f3b print details of webpack errors - fixes #403 2018-09-02 21:53:46 -04:00
Rich Harris
2c507b5a2e allow reserved words as route names - fixes #315 2018-09-02 21:46:25 -04:00
Rich Harris
4a92fbbbfa Merge pull request #410 from sveltejs/gh-220
ignore things that look like temp files when generating manifest data
2018-09-02 21:13:33 -04:00
Rich Harris
b16440ff0f Merge pull request #411 from sveltejs/gh-235
ignore clicks on <a> elements without hrefs
2018-09-02 21:13:18 -04:00
Rich Harris
64223b572b ignore clicks on <a> elements without hrefs - fixes #235 2018-09-02 20:56:50 -04:00
Rich Harris
1b6dfd3580 ignore things that look like temp files when generating manifest data - fixes #220 2018-09-02 20:33:00 -04:00
Rich Harris
c0b833862a -> v0.19.1 2018-09-02 18:41:32 -04:00
Rich Harris
45f4c47a3e oops that wasnt quite right 2018-09-02 18:41:17 -04:00
Rich Harris
48b87edb5b Merge branch 'master' of github.com:sveltejs/sapper 2018-09-02 18:27:20 -04:00
Rich Harris
f9f283603e Merge pull request #409 from sveltejs/fix-redirects
don't include origin in export redirects
2018-09-02 18:26:56 -04:00
Rich Harris
a56ee6bdb7 regenerate lockfile 2018-09-02 17:01:43 -04:00
Rich Harris
a18af2a473 dont include origin in export redirects 2018-09-02 17:01:29 -04:00
Rich Harris
fe5a8fb1e7 dont include .map files in package 2018-09-02 14:53:12 -04:00
Rich Harris
57a26e3511 -> v0.19.0 2018-09-02 14:50:01 -04:00
Rich Harris
bebb0dd595 CSS extraction and code-splitting
closes #388
2018-09-02 14:46:27 -04:00
Rich Harris
afba0491ed -> v0.18.7 2018-08-31 16:40:50 -04:00
Rich Harris
350d37e210 Merge pull request #397 from sveltejs/gh-280
Implement differential bundling
2018-08-31 16:39:20 -04:00
Rich Harris
96fc19e939 update shimport 2018-08-31 16:32:56 -04:00
Rich Harris
5be3809d9e -> v0.18.6 2018-08-31 11:13:24 -04:00
Rich Harris
15cc4bf296 bundle webpack-format-messages 2018-08-31 11:13:03 -04:00
Rich Harris
c7cce985e3 serve legacy assets if such there be 2018-08-30 23:13:34 -04:00
Rich Harris
e00b315dec emit legacy build 2018-08-30 22:58:07 -04:00
Rich Harris
afcd643035 -> v0.18.5 2018-08-30 20:41:03 -04:00
Rich Harris
7cc2a03aae oops 2018-08-30 20:38:40 -04:00
Rich Harris
002718b609 -> v0.18.4 2018-08-30 20:00:58 -04:00
Rich Harris
45d216c64d Merge pull request #396 from sveltejs/gh-381
implement --dev-port flag
2018-08-30 19:59:09 -04:00
Rich Harris
3d69d483d7 merge master -> gh-381 2018-08-30 19:49:28 -04:00
Rich Harris
54da524467 implement --dev-port flag - fixes #381 2018-08-30 19:46:14 -04:00
Rich Harris
ee95240ca6 Merge pull request #394 from sveltejs/gh-390
omit trailing slash from server route matchers
2018-08-30 19:37:23 -04:00
Rich Harris
74d5d1f9c0 Merge pull request #395 from sveltejs/gh-391
Remove webpack annotations when building with Rollup
2018-08-30 19:37:01 -04:00
Rich Harris
8c2688b1be remove url-parse 2018-08-30 19:34:59 -04:00
Rich Harris
e170e4af9b use builtin url module 2018-08-30 19:29:36 -04:00
Rich Harris
bc31c73c33 omit trailing slash from server route matchers - fixes #390 2018-08-30 18:48:10 -04:00
Rich Harris
7798f8f684 minor tidy up 2018-08-30 18:41:37 -04:00
Rich Harris
70fd7038b0 skip webpack annotations when using Rollup 2018-08-30 18:38:46 -04:00
Rich Harris
c6af2ddfa3 Merge pull request #393 from sveltejs/gh-392
handle non-Sapper responses when exporting
2018-08-30 18:29:17 -04:00
Rich Harris
65d0172abe handle non-Sapper responses when exporting - fixes #392 2018-08-30 18:22:40 -04:00
Rich Harris
1e22031765 -> v0.18.3 2018-08-29 22:52:01 -04:00
Rich Harris
46bf8f2b78 -> v0.18.2 2018-08-29 22:43:21 -04:00
Rich Harris
553db81b7b -> v0.18.1 2018-08-29 22:29:46 -04:00
Rich Harris
67cc29ed38 Merge pull request #386 from sveltejs/gh-385
implement --live
2018-08-29 22:28:42 -04:00
Rich Harris
36f930f489 use --live by default, if using Rollup or --no-hot 2018-08-29 22:19:53 -04:00
Rich Harris
3b098caa6e implement --live and --hot - fixes #385 2018-08-29 21:51:08 -04:00
Rich Harris
d63b9437b5 -> v0.18.0 2018-08-29 21:08:15 -04:00
Rich Harris
e51c733e3f Merge pull request #379 from sveltejs/gh-130
Rollup support
2018-08-29 21:02:48 -04:00
Rich Harris
708fe4c74b rebuild lockfile 2018-08-29 20:53:48 -04:00
Rich Harris
4259fc8e58 update shimport, minor tidy up 2018-08-29 20:20:02 -04:00
Rich Harris
f05a8e52a0 deprecate, dont break 2018-08-29 18:01:27 -04:00
Rich Harris
76cb6d97f3 oops 2018-08-29 18:01:17 -04:00
Rich Harris
5d0b7af47b i feel good about this 2018-08-29 17:40:13 -04:00
Rich Harris
bb737eeb32 success i think? 2018-08-29 17:37:20 -04:00
Rich Harris
86dee17040 come ON 2018-08-29 17:29:17 -04:00
Rich Harris
01a709e017 ffffuuuuuuuuuuu 2018-08-29 17:27:34 -04:00
Rich Harris
f87f0e3b80 stab in the dark 2018-08-29 17:24:48 -04:00
Rich Harris
8226e9bc1f starting to lose my sense of humour 2018-08-29 17:17:36 -04:00
Rich Harris
d6d0a15015 WHAT IS HAPPENING 2018-08-29 17:10:00 -04:00
Rich Harris
ddec58ebd4 ffs 2018-08-29 17:05:56 -04:00
Rich Harris
9d904b3911 argh 2018-08-29 17:02:21 -04:00
Rich Harris
c36df0d650 try to diagnose latest windows idiocy 2018-08-29 16:58:50 -04:00
Rich Harris
ae19288797 update rollup 2018-08-29 16:45:29 -04:00
Rich Harris
de308d5bb0 rebuild lockfile 2018-08-29 16:29:37 -04:00
Rich Harris
99b096a5c4 rimraf before mkdirp 2018-08-29 16:05:41 -04:00
Rich Harris
36fc8a947b use shimport 2018-08-29 15:58:27 -04:00
Rich Harris
6393a30b13 print nice build summaries 2018-08-29 15:03:10 -04:00
Rich Harris
458be49b35 emit errors and warnings, albeit clumsily 2018-08-29 13:26:31 -04:00
Rich Harris
f8d742bdd0 hashing 2018-08-29 10:55:52 -04:00
Rich Harris
7e698f1613 use watchChange plugin hook to detect invalidations 2018-08-28 18:06:31 -04:00
Rich Harris
70b5cc86dc replace client_assets.json with build.json, include bundler name 2018-08-28 17:56:46 -04:00
Rich Harris
19a5dcad1d update CLI 2018-08-28 17:29:27 -04:00
Rich Harris
85e25d6380 add a --bundler option, for forcing rollup or webpack 2018-08-28 17:29:14 -04:00
Rich Harris
6e2383b66b add shimport 2018-08-28 17:28:40 -04:00
Rich Harris
200c5fcbd2 get tests passing again 2018-08-25 14:01:07 -04:00
Rich Harris
9cbb8bdc33 first stab at supporting Rollup (#130) 2018-08-25 12:42:27 -04:00
Rich Harris
3d39836cfb prevent deprecation warnings 2018-08-25 09:12:06 -04:00
Rich Harris
24f2855f89 create a facade over webpack, to support alternative compilers 2018-08-25 09:11:45 -04:00
Rich Harris
d5bf206d2a Merge pull request #378 from sveltejs/export-timeout
export should fail on timeouts
2018-08-25 08:14:18 -04:00
Rich Harris
8abc01551e export should fail on timeouts 2018-08-24 14:25:48 -04:00
Rich Harris
62b8a79e9f -> v0.17.1 2018-08-23 11:23:40 -04:00
Rich Harris
7f255563a4 Merge pull request #371 from sveltejs/show-files-on-error
show which file is causing an error/warning
2018-08-23 11:21:04 -04:00
Rich Harris
32f4a50f25 show which file is causing an error/warning 2018-08-23 11:09:02 -04:00
Rich Harris
b1a9be2dc3 -> v0.17.0 2018-08-19 22:07:04 -04:00
Rich Harris
c5456d3033 Merge pull request #365 from sveltejs/cheap-watch
Use cheap-watch
2018-08-19 22:05:33 -04:00
Rich Harris
9b33dad589 merge master -> cheap-watch 2018-08-19 19:03:12 -04:00
Rich Harris
4315a46ff2 -> v0.16.1 2018-08-19 18:55:41 -04:00
Rich Harris
0fb5827968 fix file watching 2018-08-19 18:55:09 -04:00
Rich Harris
f9bf23dc43 use cheap-watch instead of chokidar (#364) 2018-08-19 18:52:01 -04:00
Rich Harris
611017fd28 -> v0.16.0 2018-08-19 17:39:02 -04:00
Rich Harris
72b265a35f Merge pull request #363 from sveltejs/bundle-deps
[WIP] Slim down the bundle
2018-08-19 17:36:18 -04:00
Rich Harris
e0d533f2ea bundle more stuff 2018-08-19 16:15:04 -04:00
Rich Harris
dba83641e4 remove glob and cheerio from dependencies 2018-08-19 15:20:14 -04:00
Rich Harris
14e5c8e761 update lockfile 2018-08-16 12:48:50 -04:00
Rich Harris
cbbf4a95db -> v0.15.8 2018-08-16 12:46:13 -04:00
Rich Harris
55b7ffd2ed Merge pull request #361 from sveltejs/handle-unknown-preload-errors
handle unknown preload errors
2018-08-16 12:44:27 -04:00
Rich Harris
9f4d4e70de can remove this, preloading is set false on render 2018-08-16 12:39:54 -04:00
Rich Harris
deef1bbfcf handle unknown preload errors 2018-08-16 12:25:23 -04:00
Seth Thompson
17b0fc0d0c nit 2018-08-11 17:52:53 -04:00
Seth Thompson
3c44c511e4 make sure page has expected preloading value 2018-08-11 17:51:44 -04:00
Seth Thompson
7cf1b9613a prefetching should not set root preloading value, closes #352 2018-08-11 12:26:27 -04:00
Rich Harris
99e5a9601c -> v0.15.7 2018-08-09 20:14:37 -04:00
Rich Harris
4c9c1dccf5 Merge pull request #350 from sveltejs/gh-344
pass response object to store getter
2018-08-09 20:13:17 -04:00
Rich Harris
2cddd5afa0 Merge pull request #345 from sveltejs/fix/redirect
Fix Preload's Redirect
2018-08-09 20:08:51 -04:00
Rich Harris
8c6a0c4773 Merge branch 'master' into gh-344 2018-08-09 20:03:37 -04:00
Rich Harris
af5063552d Merge pull request #351 from sveltejs/argh-windows
doh
2018-08-09 20:02:51 -04:00
Rich Harris
419d154794 fffffuuuuu 2018-08-09 19:53:26 -04:00
Rich Harris
abda059be5 doh 2018-08-09 19:46:09 -04:00
Rich Harris
444908cac5 pass response object to store getter - fixes #344 2018-08-08 10:57:10 -04:00
Luke Edwards
c6da26e1a0 add redirect test to root (“/“) 2018-08-06 20:29:28 -07:00
Luke Edwards
aad87857ce fix: replace leading slash in preload’s redirect 2018-08-06 20:28:28 -07:00
Rich Harris
666c113297 -> v0.15.6 2018-08-06 22:36:17 -04:00
Rich Harris
84a58f34a0 add test for exporting with custom basepath 2018-08-06 22:35:02 -04:00
Rich Harris
75f5b5c721 Merge pull request #342 from aubergene/gh-338
Remove basepath from deferred urls and add trailing slash to root
2018-08-06 22:06:09 -04:00
Julian Burgess
a176a3b79b Remove basepath from deferred urls and add trailing slash to root request 2018-08-06 16:43:02 +01:00
Rich Harris
1627a5767a -> v0.15.5 2018-08-03 01:18:12 -04:00
Rich Harris
6ff3a9e9ab Merge branch 'master' of github.com:sveltejs/sapper 2018-08-03 01:16:43 -04:00
Rich Harris
3ce2bd30f9 Use npm ci instead of npm install (#336)
* dont write server_info.json either - second half of #318

* use npm ci

* update lockfile

* try this
2018-08-03 01:16:26 -04:00
Rich Harris
de4f99807f Merge branch 'master' of github.com:sveltejs/sapper 2018-08-03 00:11:09 -04:00
Rich Harris
eae8351f77 Better/faster exporting
* add --build and --build-dir options to sapper export (#325)

* tweak export logging, update port-authority to prevent timeout bug

* better logging of export progress

* handle case where linked resource is already fetched

* default to .sapper/dev instead of .sapper

* handle query params and redirects

* dont write server_info.json either - second half of #318

* update changelog

* update lockfile

* try to track down ci test failures

* err wut

* curiouser and curiouser

* ok, seems to work now
2018-08-03 00:10:58 -04:00
Rich Harris
d386308301 update changelog 2018-08-02 13:23:12 -04:00
Rich Harris
13afbc84d7 dont write server_info.json either - second half of #318 2018-08-02 10:59:23 -04:00
Rich Harris
31327b3780 Merge pull request #333 from sveltejs/gh-332
only blur activeElement if there is one
2018-08-02 10:43:27 -04:00
Rich Harris
81f483d7b8 Merge pull request #334 from sveltejs/gh-318
dont emit client_info.json
2018-08-02 08:40:30 -04:00
Rich Harris
1bcf20511b dont emit client_info.json - fixes #318 2018-08-01 21:46:26 -04:00
Rich Harris
003fa8ab2c only blur activeElement if there is one - fixes #332 2018-08-01 21:34:30 -04:00
Rich Harris
d1fcd07c92 -> v0.15.4 2018-08-01 08:47:41 -04:00
Rich Harris
47a6d6f662 Merge pull request #326 from lukeed/feat/ignore
Add `ignore` option
2018-08-01 08:46:49 -04:00
Luke Edwards
4b2b6440d0 fix: use more specific ignore pattern;
~> leaked into another test’s route
2018-07-31 16:54:29 -07:00
Rich Harris
fc855f30f8 -> v0.15.3 2018-07-31 15:06:47 -04:00
Rich Harris
4a75fff4ec Merge pull request #329 from sveltejs/parallel-exports
parallelize exports
2018-07-31 15:05:38 -04:00
Rich Harris
7b7b695938 Merge pull request #320 from sveltejs/gh-319
Some dev documentation
2018-07-31 14:59:57 -04:00
Rich Harris
2fca2e295f Merge pull request #328 from sveltejs/export-no-minify-js
don't minify JS when minifying HTML
2018-07-31 14:59:27 -04:00
Rich Harris
eae991d369 parallelize exports 2018-07-31 14:56:29 -04:00
Rich Harris
c2b393d3fd dont minify JS when minifying HTML 2018-07-31 14:43:58 -04:00
Luke Edwards
566addd406 add tests for opts.ignore 2018-07-29 14:17:13 -07:00
Luke Edwards
3d77dacbd6 attach ignore options to test app, w/ matching routes 2018-07-29 14:17:04 -07:00
Luke Edwards
51b4f9cbbf add opts.ignore support 2018-07-29 14:01:44 -07:00
Robert Hall
1d611be83e Using npm 2018-07-25 15:50:56 -06:00
Robert Hall
1782904994 Some dev documentation 2018-07-25 10:10:22 -06:00
Rich Harris
e3ddbfc181 Merge pull request #314 from sveltejs/fix-child-segment
fix child.segment bug
2018-07-23 17:08:25 -04:00
Rich Harris
8e3830b646 fix child.segment bug 2018-07-23 17:02:35 -04:00
Rich Harris
b28cdff233 -> v0.15.2 2018-07-23 16:38:49 -04:00
Rich Harris
7f586ff1a3 Merge pull request #313 from sveltejs/gh-312
Skip layout components where none is provided
2018-07-23 16:37:30 -04:00
Rich Harris
731d4f535c skip layout components where none is provided - fixes #312 2018-07-23 16:31:00 -04:00
Rich Harris
f8c731ca21 failing tests for #312 2018-07-23 14:31:11 -04:00
Rich Harris
39eb3be01e -> v0.15.1 2018-07-22 21:25:33 -04:00
Rich Harris
d0bb728e25 -> v0.15.0 2018-07-22 21:04:03 -04:00
Rich Harris
58de0f9c99 Nested routes
Fixes #262
2018-07-22 21:00:37 -04:00
Rich Harris
b75ae7ba96 -> v0.14.2 2018-07-14 21:05:39 -04:00
Rich Harris
091e38082e Merge pull request #307 from sveltejs/unsafe-replacements
prevent unsafe replacements of preloaded data etc
2018-07-15 02:05:20 +01:00
Rich Harris
74acf93c7a prevent unsafe replacements of preloaded data etc 2018-07-14 20:56:05 -04:00
Rich Harris
0e3775397f Merge pull request #306 from sveltejs/refactor-route-generation
Refactor route generation
2018-07-05 08:25:59 -04:00
Rich Harris
8dc52a04e4 split pages and server routes into separate arrays 2018-07-05 08:14:07 -04:00
Rich Harris
008b607c01 generate pages and server routes separately 2018-07-04 10:53:41 -04:00
Rich Harris
d01a407137 -> v0.14.1 2018-07-02 10:24:54 -04:00
Rich Harris
c0c717d9ec Merge pull request #283 from elcobvg/feature/route-regexp
Feature: use regexp in routes
2018-07-02 10:23:30 -04:00
Elco Brouwer von Gonzenbach
4f011bfc37 Convert whitespace to tabs. Add some unit tests for create routes 2018-07-02 11:52:17 +07:00
Elco Brouwer von Gonzenbach
6c4ab32cf0 Sync update to sapper 0.14.0 2018-06-29 06:57:01 +07:00
Rich Harris
09b4dc1b9a -> v0.14.0 2018-06-28 13:35:21 -04:00
Rich Harris
bdd5a54527 Merge pull request #301 from sveltejs/gh-297
treat foo/index.json.js as foo.json.js
2018-06-28 13:27:57 -04:00
Rich Harris
b7bb4db8c1 treat foo/index.json.js as foo.json.js - fixes #297 2018-06-28 13:20:41 -04:00
Rich Harris
5b5f33d3cf error on 4xx.html and 5xx.html 2018-06-28 12:57:59 -04:00
Rich Harris
9611656b76 Merge pull request #300 from sveltejs/gh-293
[WIP] simplify rendering of error pages
2018-06-28 12:52:41 -04:00
Rich Harris
e9a71774d5 Merge pull request #299 from sveltejs/gh-270
return a Promise from goto
2018-06-28 12:18:32 -04:00
Rich Harris
2205b8aec5 oops 2018-06-28 12:03:27 -04:00
Rich Harris
5c4e4d5d36 only navigate if route is found - fixes #279 2018-06-28 11:45:38 -04:00
Rich Harris
e87247493f replace 4xx.html and 5xx.html with _error.html 2018-06-28 11:38:21 -04:00
Rich Harris
0aeb63a05b simplify rendering of error pages 2018-06-27 18:35:41 -04:00
Rich Harris
57eeb5659a return a Promise from goto - fixes #270 2018-06-27 17:32:20 -04:00
Rich Harris
f821c19528 -> v0.13.6 2018-06-27 17:20:45 -04:00
Rich Harris
b9a120164a Merge pull request #298 from sveltejs/gh-296
fix req.baseUrl synthesis
2018-06-27 17:19:55 -04:00
Rich Harris
087356f781 fix req.baseUrl synthesis 2018-06-27 16:57:04 -04:00
Rich Harris
31110a5326 -> v0.13.5 2018-06-26 18:30:46 -04:00
Rich Harris
667a68768c Merge pull request #294 from sveltejs/gh-289
fix handling of fatal errors
2018-06-26 18:29:56 -04:00
Rich Harris
5075981a90 fix handling of fatal errors - fixes #289 2018-06-26 13:49:11 -04:00
Rich Harris
611dc4f6be -> v0.13.4 2018-06-18 22:45:51 -04:00
Rich Harris
0b43eaa992 blur active element on navigation - fixes #287 2018-06-18 22:44:55 -04:00
Rich Harris
47cdc1c4c8 wait until server restarts before emitting hot reload update 2018-06-18 22:31:10 -04:00
Rich Harris
31c071ad72 Merge branch 'master' of github.com:sveltejs/sapper 2018-06-18 12:22:42 -04:00
Rich Harris
e91edaee12 Merge pull request #286 from sveltejs/invalidate-client-assets
always refresh client_assets in dev
2018-06-18 12:22:37 -04:00
Rich Harris
34c1fee5db Merge branch 'master' of github.com:sveltejs/sapper 2018-06-17 13:02:13 -04:00
Rich Harris
5375422633 Merge pull request #285 from sveltejs/event-on-exit
emit a fatal event if server crashes
2018-06-17 13:02:03 -04:00
Rich Harris
1dafe934b0 initialise rebuild stats 2018-06-17 13:01:51 -04:00
Rich Harris
e1a33c6a9b always refresh client_assets in dev 2018-06-17 13:00:27 -04:00
Rich Harris
0800fa016b emit a fatal event if server crashes 2018-06-16 20:18:32 -04:00
Rich Harris
8f3454c3b1 -> v0.13.3 2018-06-16 13:49:44 -04:00
Rich Harris
f0d7a1aaab change fatal events to be clonable, for IPC purposes 2018-06-16 13:49:19 -04:00
Rich Harris
8240595d70 -> v0.13.2 2018-06-15 10:58:55 -04:00
Rich Harris
658d8dd50c Merge pull request #284 from sveltejs/emit-basepath
Emit basepath event
2018-06-15 10:57:04 -04:00
Rich Harris
9eeeaa24c1 emit a basepath event on first run 2018-06-14 17:20:46 -04:00
Rich Harris
9c4a3592ff remove some unused code 2018-06-14 16:34:16 -04:00
Elco Brouwer von Gonzenbach
0e2c2ca101 Correct errors in param pattern and matching patterns 2018-06-13 10:00:39 +07:00
Elco Brouwer von Gonzenbach
8015be8069 Correct spacing 2018-06-12 09:15:54 +07:00
Elco Brouwer von Gonzenbach
e39ad59589 Add regexp option to routes 2018-06-12 09:11:54 +07:00
Rich Harris
be7cff4818 -> v0.13.1 2018-06-05 14:41:12 -04:00
Rich Harris
d6632cf312 Merge pull request #281 from sveltejs/gh-276
reinstate ten second interval between heartbeats
2018-06-05 20:40:17 +02:00
Rich Harris
f6e012ec73 reinstate ten second interval between heartbeats - fixes #276 2018-06-05 14:35:37 -04:00
Rich Harris
087acd5765 -> v0.13.0 2018-05-28 19:49:40 -04:00
Rich Harris
43bf6e8d8a Merge pull request #273 from sveltejs/gh-272
[WIP] expose dev, build, export APIs
2018-05-28 19:47:08 -04:00
Rich Harris
78be6aa343 add api.js 2018-05-28 18:04:18 -04:00
Rich Harris
8ba57969c2 oops 2018-05-26 12:39:44 -04:00
Rich Harris
58d2f605fc expose find_page method 2018-05-26 12:31:52 -04:00
Rich Harris
e0b4319c7d preserve webpack stats, write client assets elsewhere 2018-05-26 12:31:43 -04:00
Rich Harris
98d0df4320 work around webpack silliness 2018-05-23 22:05:40 -04:00
Rich Harris
6aa3ce4f05 make stdout etc available via api 2018-05-23 21:56:35 -04:00
Rich Harris
046db325f1 update deps 2018-05-23 20:41:48 -04:00
Rich Harris
1a4bace5f4 add dev API 2018-05-23 09:05:24 -04:00
Rich Harris
0dbf75f100 create API for exporting 2018-05-22 11:57:24 -04:00
Rich Harris
4f49fd8d5c create build API 2018-05-22 11:32:06 -04:00
Rich Harris
86f71e1faf Merge pull request #268 from lukeed/dep/colors
Update colors dep
2018-05-22 08:01:23 -04:00
Luke Edwards
147e2c64b5 update webpack-format-messages 2018-05-17 13:03:25 -07:00
Luke Edwards
9063057b0c swap clorox —> ansi-colors 2018-05-15 09:00:55 -07:00
Rich Harris
25f0d94595 -> v0.12.0 2018-05-05 10:00:58 -04:00
Rich Harris
8155df2e22 Merge branch 'gh-157' 2018-05-05 09:58:12 -04:00
Rich Harris
bb51470004 Merge pull request #259 from sveltejs/gh-157
switch to single App component model
2018-05-05 09:57:28 -04:00
Rich Harris
53446e2ec7 Merge branch 'master' into gh-157 2018-05-05 09:45:04 -04:00
Rich Harris
c4c09550eb Merge pull request #260 from sveltejs/another-sorting-bug
fix sorting
2018-05-05 09:43:16 -04:00
Rich Harris
da47fdec96 fix sorting 2018-05-05 09:38:24 -04:00
Rich Harris
971342ac7a set preloading: true when appropriate 2018-05-04 23:23:41 -04:00
Rich Harris
3becc1cbe2 error on incorrect init args 2018-05-04 23:06:10 -04:00
Rich Harris
8ee5346900 switch to single App component model (#157) 2018-05-04 22:46:41 -04:00
Rich Harris
9e4b79c6ff Merge pull request #258 from sveltejs/gh-208
exit with code 1 if build/export fails
2018-05-04 17:48:29 -04:00
Rich Harris
4ec1c65395 exit with code 1 if build/export fails - fixes #208 2018-05-04 17:42:37 -04:00
Rich Harris
c743d11b3b -> v0.11.1 2018-05-04 17:22:34 -04:00
Rich Harris
b525eb6480 get tests passing 2018-05-04 17:19:39 -04:00
Rich Harris
210d03fb06 Merge branch 'collision' of https://github.com/akihikodaki/sapper into akihikodaki-collision 2018-05-04 17:08:55 -04:00
Rich Harris
0685cc4cbe Merge branch 'master' of github.com:sveltejs/sapper 2018-05-04 17:08:47 -04:00
Rich Harris
9e2d0a7fbc Merge branch 'master' into collision 2018-05-04 17:05:18 -04:00
Rich Harris
a751a3b731 Merge pull request #254 from akihikodaki/dot_strict
Ignore files and directories with leading dots except .well-known
2018-05-04 17:04:27 -04:00
Rich Harris
bc7faeeab9 remove unused esm package 2018-05-04 16:55:57 -04:00
Rich Harris
a88c1de2f6 Merge pull request #256 from johnmuhl/esm
replace discontinued @std/esm package with esm
2018-05-04 16:53:35 -04:00
john muhl
a231795c4c replace discontinued @std/esm package with esm 2018-05-04 13:56:17 -05:00
Akihiko Odaki
ba7525c676 Ignore files and directories with leading dots except .well-known 2018-05-04 22:18:23 +09:00
Rich Harris
4843e9a40a -> v0.11.0 2018-05-03 23:51:04 -04:00
Rich Harris
ca4a1ca9b0 Merge pull request #251 from sveltejs/client-info
only save the bits of client_info we need
2018-05-03 23:49:30 -04:00
Rich Harris
ad7c872ee3 Merge pull request #250 from sveltejs/gh-240
implement --launcher
2018-05-03 23:49:06 -04:00
Rich Harris
4f98324a8a oops, missed one 2018-05-03 23:46:41 -04:00
Rich Harris
1fcf3f79ee only save the bits of client_info we need 2018-05-03 23:42:19 -04:00
Rich Harris
0b5741194a Merge pull request #205 from sveltejs/gh-140
prefetch on mouse stop
2018-05-03 23:31:08 -04:00
Rich Harris
9653d4c6ce Merge pull request #249 from sveltejs/gh-241
allow process.env.NODE_ENV to be overridden when building
2018-05-03 23:30:55 -04:00
Rich Harris
4fa5ed5e2c simplify 2018-05-03 23:30:09 -04:00
Rich Harris
f4eac2515f fix tests 2018-05-03 23:23:02 -04:00
Rich Harris
1a5364ae9d on second thoughts, default to build/index.js 2018-05-03 23:16:56 -04:00
Rich Harris
d7a9074c69 implement --launcher 2018-05-03 23:04:05 -04:00
Rich Harris
00adb53802 allow process.env.NODE_ENV to be overridden when building (#241) 2018-05-03 22:21:00 -04:00
Rich Harris
b10edddc96 cheat 2018-05-03 22:01:14 -04:00
Rich Harris
93b2d12438 Merge branch 'master' into gh-140 2018-05-03 21:54:52 -04:00
Rich Harris
7303e811be update tests, move test app to v2 2018-05-03 21:54:23 -04:00
Rich Harris
992d89027d Merge branch 'master' into collision 2018-05-03 21:44:28 -04:00
Rich Harris
3531cc587d -> v0.10.7 2018-05-03 21:42:50 -04:00
Rich Harris
562a91fa57 Merge pull request #245 from johnmuhl/patch-1
Include process.env in exporter server options
2018-05-03 21:40:50 -04:00
Rich Harris
93128a0156 Merge pull request #243 from akihikodaki/dot
Accept directory entries which starts with dot as routes
2018-05-03 21:39:46 -04:00
Rich Harris
d7a2132966 Merge pull request #234 from akihikodaki/master
Do not encode characters allowed in path when generating routes
2018-05-03 21:37:59 -04:00
John Muhl
56ac1aea9d match sapper start 2018-04-22 22:50:05 -05:00
John Muhl
37a9fb62e2 Include process.env in exporter server options 2018-04-22 20:29:47 -05:00
Rich Harris
a70e88b1f4 -> v0.10.6 2018-04-19 13:03:12 -04:00
Akihiko Odaki
6f9ce9ce85 Accept directory entries which starts with dot as routes
It allows to implement .well-known URIs.
2018-04-19 22:04:03 +09:00
Akihiko Odaki
917dd60cc3 Allow to have middleware for the path same with a HTML page
HTTP allows to change the type of the content to serve by Accept field in
the request. The middleware for the path same with a HTML page will
be inserted before the HTML renderer, and can take advantage of this
feature, using expressjs's "accepts" method, for example.
2018-04-15 23:11:08 +09:00
Akihiko Odaki
b13cc6f39a Do not encode characters allowed in path when generating routes
In RFC 3986, some characters not allowed in query, which encodeURIComponent
is designed for, is allowed in path.
A notable example is "@", which is commonly included in paths of social
profile pages. Such characters should not be encoded.

The new encoding function is conforming to the RFC.
2018-04-14 01:08:27 +09:00
Rich-Harris
2758382c68 -> v0.10.5 2018-04-06 18:32:44 -07:00
Rich Harris
dd7f1ff99c Merge pull request #231 from sveltejs/fix-missing-service-worker
fix missing service worker
2018-04-06 21:31:53 -04:00
Rich-Harris
45142cd037 fix missing service worker 2018-04-06 14:44:50 -07:00
Rich-Harris
ceb1caf1de -> v0.10.4 2018-04-03 21:43:30 -04:00
Rich Harris
7e263a3076 Merge pull request #227 from naturalethic/upgrade-chokidar-disable-globbing-issue-212
Upgrade chokidar disable globbing issue 212
2018-04-03 21:42:34 -04:00
Rich Harris
ec88d4a430 Remove unnecessary globbing pattern 2018-04-03 21:38:28 -04:00
Joshua Kifer
909ea72108 Update dev.ts 2018-04-03 14:04:08 -07:00
Joshua Kifer
cd09d75d99 Merge branch 'master' into upgrade-chokidar-disable-globbing-issue-212 2018-04-03 13:00:07 -07:00
Joshua Kifer
0e3abe489a Re-upgrade chokidar, disable globbing 2018-04-03 12:58:06 -07:00
Joshua Kifer
a5d141d2f1 Update (#1)
* Downgrade chokidar to 1.7.0

* -> v0.10.3
2018-04-03 12:49:55 -07:00
Rich-Harris
87eae6164b -> v0.10.3 2018-04-02 15:39:34 -04:00
Rich Harris
97e00f5a9c Merge pull request #226 from naturalethic/downgrade-chokidar
Downgrade chokidar to 1.7.0
2018-04-02 15:38:40 -04:00
Joshua Kifer
bd55558b5e Downgrade chokidar to 1.7.0 2018-04-02 12:24:57 -07:00
Rich-Harris
25dc4b3a4c -> v0.10.2 2018-03-25 15:20:48 -04:00
Rich Harris
72c27b78a3 Merge pull request #215 from sveltejs/stable-sort
Stable sort
2018-03-25 15:19:43 -04:00
Rich-Harris
25809ec409 enforce stable sort 2018-03-25 15:12:35 -04:00
Rich-Harris
3220c522d7 attach store to error pages 2018-03-20 16:08:23 -04:00
Rich Harris
d5d25f1d30 -> v0.10.1 2018-03-18 22:47:11 -04:00
Rich Harris
7ccd6ba329 Merge pull request #207 from sveltejs/fix-fetch-paths
fix server-side fetch paths
2018-03-18 22:41:01 -04:00
Rich Harris
35c30ae2c5 fix server-side fetch paths 2018-03-18 22:36:55 -04:00
Rich Harris
2c61f6d396 -> v0.10.0 2018-03-18 22:17:53 -04:00
Rich Harris
86233a8eab Merge pull request #206 from sveltejs/sapper-base-error
app/template.html must have %sapper.base%
2018-03-18 22:13:24 -04:00
Rich Harris
c140b128ee expect %sapper.base% 2018-03-18 22:06:11 -04:00
Rich Harris
a6b1527fd3 try using mousemove in tests 2018-03-18 21:53:13 -04:00
Rich Harris
c2f3a2aac0 prefetch on mouse stop (#140) 2018-03-18 21:41:47 -04:00
Rich Harris
66ac9773c0 Merge pull request #204 from nolanlawson/ignore-source-maps
Add support for sourcemap *.map files
2018-03-18 21:23:26 -04:00
Rich Harris
e60714bb98 Merge pull request #203 from sveltejs/gh-178-fetch
implement this.fetch
2018-03-18 21:21:00 -04:00
Nolan Lawson
52dfd6e939 Don't preload .map files 2018-03-18 16:31:53 -07:00
Nolan Lawson
fc2312eba6 Add full *.map file support 2018-03-18 16:17:41 -07:00
Nolan Lawson
cf90476255 Ignore source map files in %sapper.scripts% 2018-03-18 11:13:24 -07:00
Rich Harris
1e8d7d10ab implement this.fetch (#178) 2018-03-17 19:21:25 -04:00
Rich Harris
cf6621b83c Merge pull request #202 from sveltejs/gh-178-store
add server- and client-side store management (#178)
2018-03-17 16:10:48 -04:00
Rich Harris
9812cbd71c add server- and client-side store management (#178) 2018-03-17 13:45:59 -04:00
Rich Harris
67a81a3cac Merge pull request #201 from sveltejs/simplify-tests
simplify tests
2018-03-17 13:24:42 -04:00
Rich Harris
67463683cc simplify tests 2018-03-17 13:18:18 -04:00
Rich Harris
b94481b716 Support being mounted on a path — fixes #180 2018-03-17 11:55:02 -04:00
Rich Harris
a95ddee48d add protocol to startup message 2018-03-16 10:51:19 -04:00
Rich Harris
953694f77f update deps 2018-03-16 10:51:04 -04:00
Rich Harris
2f24cb0429 -> v0.9.6 2018-03-11 22:01:06 -04:00
Rich Harris
687071902d -> v0.9.5 2018-03-11 21:30:17 -04:00
Rich Harris
cd3fcfdf3c move deps to devDeps 2018-03-11 21:29:34 -04:00
Rich Harris
dad48e4abd Merge pull request #197 from sveltejs/fix-clorox
workaround clorox bug
2018-03-11 21:29:12 -04:00
Rich Harris
37d3d57694 workaround clorox bug 2018-03-11 20:51:14 -04:00
Rich Harris
9a5d273590 -> v0.9.4 2018-03-11 19:00:39 -04:00
Rich Harris
3816fe71ad Merge pull request #196 from sveltejs/gh-186
implement --open
2018-03-11 16:52:41 -04:00
Rich Harris
69f5b9cac7 implement --open - fixes #186 2018-03-11 16:01:13 -04:00
Rich Harris
ad14320dc3 Merge pull request #195 from sveltejs/export-logs
show which files are being exported
2018-03-11 15:50:13 -04:00
Rich Harris
43563bd8e5 show which files are being exported 2018-03-11 15:44:53 -04:00
Rich Harris
02d558b97c Merge pull request #194 from sveltejs/gh-172
minify HTML on export
2018-03-11 15:19:07 -04:00
Rich Harris
866286c95e minify HTML on export 2018-03-11 14:57:10 -04:00
Rich Harris
e1b5e336dc Merge pull request #193 from sveltejs/gh-15
minify HTML templates
2018-03-11 14:14:39 -04:00
Rich Harris
1d71b86c0f remove all hard-coded locations (#181) 2018-03-11 13:43:11 -04:00
Rich Harris
bdc248f09a minify HTML at build time (also fixes #181) 2018-03-11 13:35:29 -04:00
Rich Harris
be63ea7c96 add SAPPER_BASE and SAPPER_APP environment variables 2018-03-11 13:29:39 -04:00
Rich Harris
819ec0b776 minify HTML templates - fixes #15 2018-03-11 13:10:43 -04:00
Rich Harris
d22d37fb18 Merge pull request #192 from sveltejs/misc-tidy-up
a few small tweaks
2018-03-11 13:10:15 -04:00
Rich Harris
8ec433581a a few small tweaks 2018-03-11 12:54:35 -04:00
Rich Harris
0d0e4d664e -> v0.9.3 2018-03-10 23:32:06 -05:00
Rich Harris
4348fad16d -> v0.9.2 2018-03-10 23:29:41 -05:00
Rich Harris
4314897a78 -> v0.9.1 2018-03-10 23:28:14 -05:00
Rich Harris
b1c57466c0 -> v0.9.0 2018-03-10 23:24:03 -05:00
Rich Harris
ef55fc5ddd move built files to dist 2018-03-10 23:20:11 -05:00
Rich Harris
e011fce935 Merge pull request #191 from sveltejs/gh-173
use code-splitting etc
2018-03-10 23:14:53 -05:00
Rich Harris
ba3d9c85c5 clorox seems to have a bug with inverse (TODO investigate) 2018-03-10 23:09:02 -05:00
Rich Harris
cddd7adaad lazy-load stuff 2018-03-10 23:00:36 -05:00
Rich Harris
d8412f33ba ignore some files 2018-03-10 22:33:33 -05:00
Rich Harris
254e41b11e use code-splitting etc 2018-03-10 22:26:53 -05:00
Rich Harris
491c5e3b92 Merge pull request #190 from sveltejs/gh-57
allow server routes to be .ts files (or anything else)
2018-03-10 20:46:25 -05:00
Rich Harris
4441ceb91d Merge pull request #189 from sveltejs/introduce-spelling-error
use wrong spelling of "grey"
2018-03-10 20:45:56 -05:00
Rich Harris
77e418cd21 Merge pull request #188 from sveltejs/gh-112
use devalue instead of serialize-javascript
2018-03-10 20:45:39 -05:00
Rich Harris
4171786953 ensure directories are not mistaken for routes 2018-03-10 20:38:48 -05:00
Rich Harris
5f7cbadd8d remove extensions from entry points 2018-03-10 20:16:46 -05:00
Rich Harris
9bac32eea4 allow server routes to be .ts files (or anything else) - fixes #57 2018-03-10 20:08:23 -05:00
Rich Harris
3a19657ad9 use wrong spelling of "grey" 2018-03-10 20:04:44 -05:00
Rich Harris
d1b6d029e9 merge master -> gh-112 2018-03-10 19:36:08 -05:00
Rich Harris
45b1147228 use devalue instead of serialize-javascript - fixes #112 2018-03-10 19:27:04 -05:00
Rich Harris
c827fda703 Merge pull request #187 from lukeed/fix/chalk
Replace Chalk with Clorox
2018-03-10 16:11:35 -05:00
Luke Edwards
dd39909371 replace chalk with clorox 2018-03-10 12:30:36 -08:00
Rich Harris
fb24c862f3 Merge pull request #185 from sveltejs/gh-165
use window.location.hostname in dev client
2018-03-10 10:44:48 -05:00
Rich Harris
542115f82e Merge pull request #184 from sveltejs/gh-169-b
Fix hard-coded port
2018-03-10 10:28:21 -05:00
Rich Harris
61000a4795 use window.location.hostname in dev client - fixes #165 2018-03-10 10:26:00 -05:00
Rich Harris
7f98d50e15 treat PORT=xxxx the same as --port xxxx 2018-03-10 10:09:18 -05:00
Rich Harris
c580259c07 dont hardcode port 2018-03-10 10:01:42 -05:00
Rich Harris
e8f3aff0da Merge pull request #183 from sveltejs/use-polka
Use polka, remove unused dependencies
2018-03-10 09:54:22 -05:00
Rich Harris
c82031a8e5 remove some unused deps 2018-03-10 09:48:52 -05:00
Rich Harris
1eed1023aa switch to polka, remove unused deps 2018-03-10 09:39:07 -05:00
Rich Harris
c1a2d93da6 Merge pull request #182 from sveltejs/gh-177
kill child process if webpack crashes
2018-03-10 08:51:05 -05:00
Rich Harris
504654b58e kill child process if webpack crashes - fixes #177 2018-03-09 21:02:05 -05:00
Rich Harris
b1067103a4 -> v0.8.4 2018-03-07 15:57:46 -05:00
Rich Harris
06af8e87da Merge pull request #175 from sveltejs/fix-route-sorting
fix route sorting
2018-03-07 14:32:56 -05:00
Rich Harris
8bb0999878 fix route sorting 2018-03-07 14:26:30 -05:00
Rich Harris
b5a8d29c37 -> v0.8.3 2018-03-05 15:27:38 -05:00
Rich Harris
5925636b16 Merge pull request #170 from sveltejs/gh-169
better CLI
2018-03-05 15:25:33 -05:00
Rich Harris
bc232007c3 simplify sade initialisation 2018-03-05 15:17:34 -05:00
Rich Harris
ffaacb4c99 use fs.existsSync 2018-03-05 15:11:44 -05:00
Rich Harris
47b50f2c0e admin 2018-03-05 15:09:59 -05:00
Rich Harris
a66ac00d42 tidy up tests 2018-03-05 15:09:51 -05:00
Rich Harris
0f8c04b03d use port-authority 2018-03-05 15:09:33 -05:00
Rich Harris
d9d93f41c4 add get-port back as dev dependency, for testing 2018-03-05 13:57:30 -05:00
Rich Harris
5289fc11d8 better CLI, more port control. fixes #169 2018-03-05 13:51:11 -05:00
Rich Harris
dd6c51567a -> v0.8.2 2018-03-04 22:55:00 -05:00
Rich Harris
01ff84f241 Merge pull request #167 from sveltejs/gh-166
rename preloadRoutes to prefetchRoutes
2018-03-04 22:54:11 -05:00
Rich Harris
329c113723 rename preloadRoutes to prefetchRoutes 2018-03-04 22:38:55 -05:00
Rich Harris
2ad10b380f -> v0.8.1 2018-03-04 21:37:55 -05:00
Rich Harris
e6314cde96 Merge pull request #164 from sveltejs/gh-163
add sapper start task
2018-03-04 21:36:51 -05:00
Rich Harris
b64e25a177 add sapper start task 2018-03-04 21:28:31 -05:00
Rich Harris
49bc1b00a9 -> v0.8.0 2018-03-04 19:31:14 -05:00
Rich Harris
24bfcc8d2d Merge pull request #162 from sveltejs/gh-103
URI-encode routes
2018-03-04 19:11:22 -05:00
Rich Harris
b405e5878e Merge branch 'master' into gh-103 2018-03-04 19:00:28 -05:00
Rich Harris
ef0ca58a21 Merge pull request #161 from sveltejs/gh-160
add preloadRoutes function
2018-03-04 19:00:14 -05:00
Rich Harris
854147fa6c URI-encodes routes - fixes #103 2018-03-04 18:59:35 -05:00
Rich Harris
50ecc5c130 preload errors 2018-03-04 18:26:58 -05:00
Rich Harris
7e2f5f8fb6 add preloadRoutes function - closes #160 2018-03-04 18:05:33 -05:00
Rich Harris
acef0e808f Merge branch 'logging' 2018-03-04 16:17:18 -05:00
Rich Harris
248573f510 fix deprecation warning 2018-03-04 16:17:10 -05:00
Rich Harris
e91955fdad Merge pull request #158 from sveltejs/logging
vastly better logging
2018-03-04 15:49:14 -05:00
Rich Harris
368e6d5cb1 bump timeout for travis 2018-03-04 15:27:35 -05:00
Rich Harris
1984203e87 vastly better logging 2018-03-04 15:11:36 -05:00
Rich Harris
0165c14fd9 update webpack/config 2018-03-04 12:35:13 -05:00
Rich Harris
bdb9d49187 simpler wait-for-port 2018-03-04 11:25:59 -05:00
Rich Harris
4d79cb81ed Merge pull request #153 from sveltejs/stability
various stability improvements
2018-03-03 23:02:24 -05:00
Rich Harris
181b0711ec replace mime, having weird bundling problems with it 2018-03-03 22:50:10 -05:00
Rich Harris
1b282e7b0d remove unused get_asset_handler 2018-03-03 21:54:19 -05:00
Rich Harris
99853c5181 various stability improvements 2018-03-03 21:48:50 -05:00
Rich Harris
ff3b43443e Merge pull request #152 from sveltejs/gh-148
various improvements to manifest generation - fixes #148
2018-03-03 16:35:27 -05:00
Rich Harris
2622692f69 various improvements to manifest generation - fixes #148 2018-03-03 15:39:40 -05:00
Rich Harris
7625302ec7 Merge pull request #150 from sveltejs/gh-133
sapper build defaults to `build` dir, sapper export defaults to `export` dir
2018-03-03 15:07:35 -05:00
Rich Harris
09422e3c5a Merge branch 'master' into gh-133 2018-03-03 14:34:22 -05:00
Rich Harris
a96fb93bfb Fix test failures — closes #132 2018-03-03 14:33:47 -05:00
Rich Harris
17d7ca36f1 update test 2018-03-03 12:20:46 -05:00
Rich Harris
b73e5eaa8e sapper build defaults to build dir, sapper export defaults to export dir (#133) 2018-03-03 12:09:06 -05:00
Rich Harris
d9cb572271 -> v0.7.6 2018-02-28 17:14:38 -05:00
Rich Harris
34c28f36cd Merge pull request #147 from sveltejs/gh-138
don't serve error page for unhandled server route errors
2018-02-28 17:11:52 -05:00
Rich Harris
5dd04eb35c tidy up - next is unused 2018-02-28 16:59:39 -05:00
Rich Harris
b1d072d43a dont serve error page for unhandled server route errors - fixes #138 2018-02-28 16:54:38 -05:00
Rich Harris
5ad3f3f1d5 Merge pull request #146 from sveltejs/gh-145
prevent client-side navigation to server routes
2018-02-28 16:20:42 -05:00
Rich Harris
58754c6d15 use blog.json instead of blog/list.json 2018-02-28 16:17:07 -05:00
Rich Harris
c36780fdc8 prevent client-side navigation to server routes - fixes #145 2018-02-28 14:23:19 -05:00
Rich Harris
9bebb56bd6 -> v0.7.5 2018-02-28 13:40:42 -05:00
Rich Harris
f475634d8d Merge pull request #144 from sveltejs/gh-139
allow dynamic parameters inside route parts
2018-02-28 12:26:00 -05:00
Rich Harris
58c1eb9fa8 allow dynamic parameters inside route parts - fixes #139 2018-02-28 09:39:21 -05:00
Rich Harris
631afbbfe4 -> v0.7.4 2018-02-27 20:10:48 -05:00
Rich Harris
1cc9acb4f1 Merge pull request #142 from sveltejs/gh-141
force NODE_ENV=production in build/export
2018-02-27 20:09:05 -05:00
Rich Harris
19005110f1 huh, not sure why that changed 2018-02-27 19:58:49 -05:00
Rich Harris
21ee8ad39d force NODE_ENV=production in build/export 2018-02-27 19:49:58 -05:00
Rich Harris
906b0c7ad5 Merge pull request #134 from sveltejs/source-map-support
install source-map-support
2018-02-27 19:46:17 -05:00
Rich Harris
896fd410d1 install source-map-support 2018-02-20 14:01:25 -05:00
Rich Harris
c0cc877456 -> v0.7.3 2018-02-20 12:23:32 -05:00
Rich Harris
3ed9ce27a1 Merge pull request #131 from sveltejs/webpack-insanity
handle case where webpack asset is an array instead of a string
2018-02-20 12:18:29 -05:00
Rich Harris
edba45b809 Merge pull request #129 from sveltejs/robustify-hmr
ensure old server is killed before listening for port on new server
2018-02-20 12:18:10 -05:00
Rich Harris
43c1890235 handle case where webpack asset is an array instead of a string 2018-02-20 11:11:55 -05:00
Rich Harris
605929053c ensure old server is killed before listening for port on new server 2018-02-20 11:11:02 -05:00
Rich Harris
2752c73ebb -> v0.7.2 2018-02-18 17:24:12 -05:00
Rich Harris
2547db39ac -> v0.7.1 2018-02-18 17:09:55 -05:00
Rich Harris
1285739cc5 -> v0.7.1 2018-02-18 17:08:35 -05:00
Rich Harris
14d64e854a -> v0.7.0 2018-02-18 13:00:36 -05:00
Rich Harris
c419c73550 fix tests 2018-02-18 12:55:56 -05:00
Rich Harris
835b94175d workaround weird webpack(?) bug by not deleting old files 2018-02-18 12:49:37 -05:00
Rich Harris
25bdcf9957 Merge branch 'gh-83' 2018-02-18 12:12:45 -05:00
Rich Harris
792ccf5c6a robustify dev server port selection 2018-02-18 12:12:25 -05:00
Rich Harris
4ca8195037 Merge pull request #127 from sveltejs/gh-83
implement this.redirect in preload
2018-02-18 10:36:05 -05:00
Rich Harris
cb12231053 implement this.error 2018-02-18 09:48:32 -05:00
Rich Harris
d55401d45b see if this fixes windows tests 2018-02-18 08:27:07 -05:00
Rich Harris
99d4eafb0b argh these flaky tests are killing me 2018-02-18 08:18:14 -05:00
Rich Harris
bff6f550be implement this.redirect in preload (#83) 2018-02-17 22:56:47 -05:00
Rich Harris
f8ea9ebda1 Merge pull request #126 from sveltejs/gh-91
[WIP] restructure everything
2018-02-17 19:49:12 -05:00
Rich Harris
181d7b4a61 need to come up with a more robust approach to this... 2018-02-17 19:27:34 -05:00
Rich Harris
beb415c65d argh 2018-02-17 19:22:05 -05:00
Rich Harris
5bbd7ead17 and again 2018-02-17 19:16:06 -05:00
Rich Harris
e11405d555 try increasing timeout 2018-02-17 19:12:47 -05:00
Rich Harris
9fe0ca2c22 remove 4xx and 5xx pages when initing 2018-02-17 19:00:06 -05:00
Rich Harris
f2eb95d546 various minor fixes 2018-02-17 18:40:20 -05:00
Rich Harris
ab1ca60363 basic sapper dev task, with HMR 2018-02-17 17:44:57 -05:00
Rich Harris
d95f52f8e9 fix tests, add WIP dev task 2018-02-17 11:15:26 -05:00
Rich Harris
b02183af53 fix exporting 2018-02-16 14:25:53 -05:00
Rich Harris
f9828f9fd2 work in progress 2018-02-16 12:01:55 -05:00
Rich Harris
9a760c570f move webpack configs in test app 2018-02-14 12:25:40 -05:00
Rich Harris
0f390920a8 fix some typescript stuff 2018-02-14 12:25:24 -05:00
Rich Harris
9adb6ca7e6 Merge pull request #120 from sveltejs/gh-92
implement sapper upgrade
2018-02-14 10:28:01 -05:00
Rich Harris
24980651c0 that was a weird typo 2018-02-13 22:08:29 -05:00
Rich Harris
7c6436a99c implement sapper upgrade 2018-02-13 18:46:53 -05:00
Rich Harris
f6b26f1b07 Merge pull request #119 from sveltejs/better-cli
Better CLI
2018-02-13 17:30:08 -05:00
Rich Harris
55b60369f9 add --help to CLI 2018-02-13 17:12:55 -05:00
Rich Harris
2be9dd1883 Move build.ts and export.ts into src/cli - fixes #115 2018-02-13 15:57:02 -05:00
Rich Harris
b29700f725 update electron to squelch vuln warning 2018-02-13 15:50:04 -05:00
Rich Harris
7188ce0d0d -> v0.6.4 2018-02-06 10:51:24 -05:00
Rich Harris
4f8ce19fe1 Merge pull request #114 from lukeed/fix/prod-hmr
Fix: Remove HMR attempts in Prod~!
2018-02-06 10:39:19 -05:00
Rich Harris
a85f2921e8 Merge pull request #113 from lukeed/feat/map-config
Consolidate Rollup config file
2018-02-06 10:37:34 -05:00
Luke Edwards
7a2ed16884 fix HMR attempts in production 2018-02-05 14:09:24 -08:00
Luke Edwards
08e575fee0 shrink repetitive rollup.config file 2018-02-05 13:43:10 -08:00
Rich Harris
7dbcab74d3 remove logging 2018-02-03 16:35:10 -05:00
Rich Harris
9b1b545194 -> v0.6.3 2018-02-03 16:26:53 -05:00
Rich Harris
7b01242f3e always build in prod mode 2018-02-03 16:26:47 -05:00
Rich Harris
15b1fbf8a6 ignore non-html responses when crawling, and close middleware when done 2018-02-03 16:17:28 -05:00
Rich Harris
8f1d2e0a04 -> v0.6.2 2018-02-03 13:56:38 -05:00
Rich Harris
dfb8692d78 handle unspecified type in sapper export 2018-02-03 13:56:14 -05:00
Rich Harris
09d3c4d85e add prepublish script 2018-02-03 13:49:21 -05:00
Rich Harris
1e623dde29 -> v0.6.1 2018-02-03 13:48:30 -05:00
Rich Harris
5104abf329 -> v0.6.0 2018-02-03 13:09:31 -05:00
Rich Harris
6554fc8616 Merge branch 'restructure' 2018-02-03 13:07:36 -05:00
Rich Harris
cd01b7e6db Merge branch 'master' of github.com:sveltejs/sapper 2018-02-03 12:24:59 -05:00
Rich Harris
bfa3da6d3d Merge branch 'issue_109-patch-1' of https://github.com/samhatchett/sapper into samhatchett-issue_109-patch-1 2018-02-03 12:07:53 -05:00
Rich Harris
6ee092f8d4 Merge pull request #107 from lukastaegert/fix-search-param-handling
Fix query param handling
2018-02-03 12:05:57 -05:00
Rich Harris
ac70004f77 remove runtime.js.map (again?) 2018-02-03 12:04:39 -05:00
Rich Harris
3449f1eb37 add test for #105 2018-02-03 12:04:14 -05:00
Rich Harris
16cb1fccc6 Merge pull request #105 from thgh/patch-2
Fix route not found when url is /?
2018-02-03 12:03:36 -05:00
Rich Harris
b20c1c029f Merge branch 'thgh-patch-1' 2018-02-03 11:50:37 -05:00
Rich Harris
7abfb1aab1 Merge branch 'restructure' 2018-02-03 11:50:12 -05:00
Rich Harris
205c2defe4 Merge branch 'patch-1' of https://github.com/thgh/sapper into thgh-patch-1 2018-02-03 11:45:54 -05:00
Rich Harris
09a6eec83e Merge pull request #101 from sveltejs/restructure
Restructure codebase
2018-02-03 11:44:57 -05:00
Rich Harris
2cabf61ea7 ignore runtime.js.map 2018-02-03 11:41:33 -05:00
Sam Hatchett
71cfdd2907 fixes api route default content-type
fixes issue where api routes were being defaulted to text/html. Page routes should be text/html, but api routes could be json, zip files, etc., and express does some type-guessing to assist in case the user code does not specify the content-type.
2018-01-31 11:08:25 -05:00
Lukas Taegert
297f4276de Fix query param handling by
* not using a for-of loop on an iterator that is transpiled wrongly
* not using URL.searchParams which is only supported by rather new
  browsers
2018-01-26 23:29:34 +01:00
Thomas Ghysels
422e31e183 Fix route not found when url is /? 2018-01-26 02:48:56 +01:00
Thomas Ghysels
b53ee061c0 Fix DOMException when location.hash is invalid selector 2018-01-26 02:24:49 +01:00
Rich Harris
8bad37205d convert to typescript 2018-01-21 16:28:02 -05:00
Rich Harris
fd0dd4fe58 and again... 2018-01-21 16:14:16 -05:00
Rich Harris
4940644ae3 more tidying up 2018-01-21 16:11:46 -05:00
Rich Harris
fb8d952eeb more tidying up 2018-01-21 16:02:42 -05:00
Rich Harris
fc631c4866 make route handling more explicit 2018-01-21 15:41:13 -05:00
Rich Harris
03ce2ea998 tidy up a bit 2018-01-21 15:04:22 -05:00
Rich Harris
dd8deb2d8a wip 3 2018-01-21 14:41:11 -05:00
Rich Harris
7d721abb2a wip 2 2018-01-21 11:15:32 -05:00
Rich Harris
39b1fa89ce wip 2018-01-21 11:03:23 -05:00
Rich Harris
7a3506420f Merge pull request #100 from sveltejs/gh-99
return a promise from init
2018-01-20 20:16:46 -05:00
Rich Harris
72ae4a1c64 ugh still need to wait for requests to complete 2018-01-20 19:55:21 -05:00
Rich Harris
a09c33d6a5 return a promise from init - fixes #99 2018-01-20 19:49:41 -05:00
Rich Harris
4590aa313c Merge pull request #96 from mrkishi/master
Identify js and html route clashes
2018-01-20 18:36:50 -05:00
Rich Harris
d11bd954e0 seems tests weren't actually running in appveyor? (#98)
well this was harder than it should have been
2018-01-20 18:34:30 -05:00
Rich Harris
c15959710b huh. not sure where this broke 2018-01-20 13:39:49 -05:00
Rich Harris
bb8ff74f68 remove noise from tests 2018-01-20 13:21:53 -05:00
Rich Harris
2cbbe91490 Merge branch 'master' of github.com:sveltejs/sapper 2018-01-20 12:53:16 -05:00
Rich Harris
faeddd8add work around Svelte hydration failures 2018-01-20 12:53:06 -05:00
Rich Harris
d77722c042 Merge branch 'chexxor-patch-1' 2018-01-20 12:40:35 -05:00
Rich Harris
61daba7a64 add hydration test, add window.init function to make it possible 2018-01-20 12:40:31 -05:00
Rich Harris
54ff8cc2e6 Merge branch 'patch-1' of https://github.com/chexxor/sapper into chexxor-patch-1 2018-01-20 12:37:48 -05:00
Rich Harris
e6fcafe09b Merge pull request #95 from lukeed/fix/express-reliance
Remove Express Helpers
2018-01-20 12:10:43 -05:00
Rich Harris
a305d3cea1 Merge pull request #97 from sveltejs/gh-90
return service worker from generate_asset_cache
2018-01-20 12:09:11 -05:00
Rich Harris
75e70207b8 return service worker from generate_asset_cache - fixes #90 2018-01-20 12:02:32 -05:00
mrkishi
8a8526d9ed Identify js and html route clashes 2018-01-20 01:42:13 -02:00
Luke Edwards
9a76229bb6 test: shim setHeader method 2018-01-19 13:56:01 -08:00
Luke Edwards
f4e46e6e6c replace Express shorthands w/ native counterparts 2018-01-19 13:54:55 -08:00
Alex Berg
90cd347112 Fix typo 2018-01-19 01:25:20 -06:00
Alex Berg
5adfdd6fe0 Hydrate on first load, not destroy 2018-01-19 01:22:08 -06:00
Rich Harris
a6dc61a182 -> v0.5.1 2018-01-16 08:47:50 -05:00
Rich Harris
96666d05ec only write to filesystem in dev mode 2018-01-16 08:47:44 -05:00
Rich Harris
6390ba692b -> v0.5.0 2018-01-15 10:28:48 -05:00
Rich Harris
0e131cc81e Merge pull request #88 from sveltejs/gh-9
Exporting
2018-01-15 10:24:35 -05:00
Rich Harris
bd3d5713cb Merge pull request #64 from lukeed/lazy-chokidar
Lazily Require Chokidar
2018-01-15 10:04:37 -05:00
Rich Harris
9ec23c47ad update test 2018-01-15 10:04:24 -05:00
Rich Harris
b7bb69925e rename extract to export, for familiarity to next.js devs 2018-01-15 10:03:27 -05:00
Rich Harris
25124f6ee7 Merge branch 'master' into lazy-chokidar 2018-01-15 09:58:39 -05:00
Rich Harris
73d491cd19 Merge branch 'master' into lazy-chokidar 2018-01-15 09:58:03 -05:00
Rich Harris
e25fceb4b8 node 6 is the new IE 2018-01-15 09:36:12 -05:00
Rich Harris
3807147c57 add missing dependency 2018-01-14 22:45:59 -05:00
Rich Harris
a523ba58ff save pages as index.html, ignore hashes 2018-01-14 22:45:51 -05:00
Rich Harris
fe03fd3a52 update lockfile 2018-01-14 18:21:37 -05:00
Rich Harris
89c430a0cb slightly different approach to extracting 2018-01-14 18:19:51 -05:00
Rich Harris
8ef312849c always write service-worker.js and shell index.html file 2018-01-14 18:16:10 -05:00
Rich Harris
4200446684 ignore trailing slash in pathnames 2018-01-14 18:15:40 -05:00
Rich Harris
681ed005b8 remove unused deps 2018-01-14 18:15:12 -05:00
Rich Harris
d457af8d51 update test 2018-01-14 18:14:07 -05:00
Rich Harris
0c158b9e1f Merge branch 'master' of https://github.com/freedmand/sapper into freedmand-master 2018-01-14 13:48:07 -05:00
Rich Harris
50011e2077 always print stdout/stderr, to avoid wild goose chase debugging 2018-01-14 13:47:23 -05:00
Rich Harris
f27b7973e3 Build before extracting 2018-01-14 13:46:25 -05:00
Rich Harris
2af2ab3cb9 Ensure output dir exists, return Promise 2018-01-14 13:45:47 -05:00
Rich Harris
6a4dc1901c Enforce prod mode, return a Promise so it can be used programmatically 2018-01-14 13:45:01 -05:00
Rich Harris
fbbc0e9e19 Merge branch 'master' of https://github.com/freedmand/sapper into freedmand-master 2018-01-14 12:29:15 -05:00
Rich Harris
1213c3da46 fix lockfile mess 2018-01-14 12:27:35 -05:00
Rich Harris
4cc2104088 add back test that got lost in a merge conflict somewhere 2018-01-14 12:23:07 -05:00
Rich Harris
d6dda371ca typo 2018-01-14 12:22:41 -05:00
Rich Harris
304c06085e Merge branch 'master' into master 2018-01-14 12:19:47 -05:00
Rich Harris
33b6450e34 Merge branch 'master' into master 2018-01-14 12:16:09 -05:00
Rich Harris
8faa98af6a -> v0.4.0 2018-01-14 12:07:06 -05:00
Rich Harris
14df138528 Merge pull request #87 from sveltejs/gh-3
Serialize preloaded data for initial page and serve to client
2018-01-14 11:50:18 -05:00
Rich Harris
44285cdb2f serialized preloaded data and send to client as initial payload - fixes #3 2018-01-14 11:22:00 -05:00
Rich Harris
bd656cfd5b Merge branch 'fix-tests-without-async' into gh-3 2018-01-14 10:45:55 -05:00
Rich Harris
c4b4bd587d use %sapper.scripts% 2018-01-14 10:45:21 -05:00
Rich Harris
2abfdb03d5 wait for window.READY so that this tests we dont double-preload 2018-01-14 10:44:38 -05:00
Rich Harris
a80ac3a8b8 change tests back to non-async versions 2018-01-14 00:43:57 -05:00
Rich Harris
887cb09386 merge 2018-01-14 00:21:55 -05:00
Rich Harris
cfeeafded4 Merge pull request #84 from sveltejs/test-in-node-6
test in node 6
2018-01-14 00:18:23 -05:00
Rich Harris
2cae674033 Merge branch 'master' into test-in-node-6 2018-01-14 00:14:40 -05:00
Rich Harris
7c0f32662d remove async from new test 2018-01-14 00:10:20 -05:00
Rich Harris
b4fb1c3268 Merge branch 'master' of github.com:sveltejs/sapper 2018-01-14 00:02:32 -05:00
Rich Harris
ecd0f673a9 test in node 6 2018-01-14 00:01:51 -05:00
Rich Harris
40d16852f7 prevent unnecessary promise chains, and hoist handler function 2018-01-14 00:01:10 -05:00
Rich Harris
133be03791 Merge pull request #72 from nolanlawson/fix-style-loader
Explicitly load style-loader/css-loader deps
2018-01-13 23:33:38 -05:00
Rich Harris
727a76ebb5 fall through to 404 if no handler exists for method 2018-01-13 23:29:41 -05:00
Rich Harris
e3c047831a add test for #77 2018-01-13 23:29:16 -05:00
Emil Tholin
81b5e0d764 Handle DELETE requests with del export 2018-01-10 19:24:22 +01:00
Emil Tholin
98e904dcfc Remove async/await from test.js 2018-01-07 12:22:40 +01:00
Nolan Lawson
ca51372150 Explicitly load style-loader/css-loader deps
This appears to fix sveltejs/sapper-template#27
2018-01-06 17:21:40 -08:00
Luke Edwards
7cef1f1120 only return from show-url if in server context 2018-01-06 16:11:39 -08:00
Luke Edwards
1b73baabce [temp - debug]: log out the preload value 2018-01-06 15:40:40 -08:00
Luke Edwards
5aa01b922b add page.title() Nightmare action 2018-01-06 15:16:18 -08:00
Luke Edwards
f0bc68be88 use built-in for document.title 2018-01-06 15:07:43 -08:00
Luke Edwards
be7c53becc quick attempt at fixing test for travis 2018-01-06 14:53:14 -08:00
freedmand
9ea4137b87 Add option to extract server-side routes at directories other than /api.
Also clarifies some texts and documentation.
2018-01-05 19:21:25 -08:00
freedmand
7588911108 Removes all async/await from the extraction pipeline, and adds unit tests for extracted client pages that match a regular expression 2018-01-05 14:56:58 -08:00
freedmand
fc8280adea Fixes small issue with reading chunk files 2018-01-05 14:42:04 -08:00
freedmand
d08f9eb5a4 Fixes funky indentation in extraction unit test 2018-01-05 14:29:46 -08:00
Emil Tholin
2b3472b1b1 Remove async/await from lib 2018-01-05 23:19:40 +01:00
freedmand
30ddb3dd7e Adds a sapper extract CLI command, which scrapes the server to run as a static website starting at the site's root.
TESTED=Basic unit test ensuring relevant routes are added.
2018-01-05 14:16:30 -08:00
Luke Edwards
0c891ba79e require chokidar lazily; dev-mode only 2018-01-05 00:39:17 -08:00
Rich Harris
ee94f355d5 update marked due to vulnerability 2018-01-04 17:17:08 -05:00
Rich Harris
bea9b7965a update package-lock 2018-01-04 17:13:17 -05:00
322 changed files with 14647 additions and 11965 deletions

View File

@@ -1,38 +0,0 @@
{
"root": true,
"rules": {
"semi": [ 2, "always" ],
"space-before-blocks": [ 2, "always" ],
"no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ],
"no-cond-assign": 0,
"no-unused-vars": 2,
"object-shorthand": [ 2, "always" ],
"no-const-assign": 2,
"no-class-assign": 2,
"no-this-before-super": 2,
"no-var": 2,
"no-unreachable": 2,
"valid-typeof": 2,
"quote-props": [ 2, "as-needed" ],
"one-var": [ 2, "never" ],
"prefer-arrow-callback": 2,
"prefer-const": [ 2, { "destructuring": "all" } ],
"arrow-spacing": 2,
"no-inner-declarations": 0
},
"env": {
"es6": true,
"browser": true,
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings"
],
"parserOptions": {
"ecmaVersion": 8,
"sourceType": "module"
}
}

12
.gitignore vendored
View File

@@ -1,5 +1,15 @@
.DS_Store
yarn.lock
yarn-error.log
node_modules
cypress/screenshots
test/app/.sapper
runtime.js
test/app/src/manifest
__sapper__
test/app/export
test/app/build
sapper
runtime.js
dist
!rollup.config.js
templates/*.js

View File

@@ -3,6 +3,7 @@ sudo: false
language: node_js
node_js:
- "6"
- "stable"
env:
@@ -17,5 +18,4 @@ addons:
install:
- export DISPLAY=':99.0'
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
- npm install
- (cd test/app && npm install)
- npm ci || npm i

View File

@@ -1,5 +1,487 @@
# sapper changelog
## 0.24.0
* Handle external URLs in `this.redirect` ([#490](https://github.com/sveltejs/sapper/issues/490))
* Strip leading `/` from basepath ([#495](https://github.com/sveltejs/sapper/issues/495))
* Treat duplicate query string parameters as arrays ([#497](https://github.com/sveltejs/sapper/issues/497))
* Don't buffer `stdout` and `stderr` ([#305](https://github.com/sveltejs/sapper/issues/305))
* Posixify `build_dir` ([#498](https://github.com/sveltejs/sapper/pull/498))
* Use `page[XY]Offset` instead of `scroll[XY]` ([#480](https://github.com/sveltejs/sapper/issues/480))
## 0.23.5
* Include lazily-imported CSS in main CSS chunk ([#492](https://github.com/sveltejs/sapper/pull/492))
* Make search param decoding spec-compliant ([#493](https://github.com/sveltejs/sapper/pull/493))
* Handle async route errors ([#488](https://github.com/sveltejs/sapper/pull/488))
## 0.23.4
* Ignore empty anchors when exporting ([#491](https://github.com/sveltejs/sapper/pull/491))
## 0.23.3
* Clear `error` and `status` on successful render ([#477](https://github.com/sveltejs/sapper/pull/477))
## 0.23.2
* Fix entry point CSS ([#471](https://github.com/sveltejs/sapper/pull/471))
## 0.23.1
* Scroll to deeplink that matches current URL ([#472](https://github.com/sveltejs/sapper/pull/472))
* Scroll to deeplink on another page ([#341](https://github.com/sveltejs/sapper/issues/341))
## 0.23.0
* Overhaul internal APIs ([#468](https://github.com/sveltejs/sapper/pull/468))
* Remove unused `sapper start` and `sapper upgrade` ([#468](https://github.com/sveltejs/sapper/pull/468))
* Remove magic environment variables ([#469](https://github.com/sveltejs/sapper/pull/469))
* Preserve SSI comments ([#470](https://github.com/sveltejs/sapper/pull/470))
## 0.22.10
* Handle `sapper-noscroll` attribute on `<a>` elements ([#376](https://github.com/sveltejs/sapper/issues/376))
* Fix CSS paths when using a base path ([#466](https://github.com/sveltejs/sapper/pull/466))
## 0.22.9
* Fix legacy builds ([#462](https://github.com/sveltejs/sapper/pull/462))
## 0.22.8
* Ensure CSS placeholders are overwritten ([#462](https://github.com/sveltejs/sapper/pull/462))
## 0.22.7
* Fix cookies ([#460](https://github.com/sveltejs/sapper/pull/460))
## 0.22.6
* Normalise chunk filenames on Windows ([#456](https://github.com/sveltejs/sapper/pull/456))
* Load modules with credentials ([#458](https://github.com/sveltejs/sapper/pull/458))
## 0.22.5
* Fix `sapper dev`. Oops.
## 0.22.4
* Ensure launcher does not overwrite a module ([#455](https://github.com/sveltejs/sapper/pull/455))
## 0.22.3
* Prevent server from accidentally importing dev client
## 0.22.2
* Make paths in generated code relative to project
## 0.22.1
* Fix `pkg.files`
## 0.22.0
* Move generated files into `__sapper__` ([#453](https://github.com/sveltejs/sapper/pull/453))
* Change default build and export directories to `__sapper__/build` and `__sapper__/export` ([#453](https://github.com/sveltejs/sapper/pull/453))
## 0.21.1
* Read template from build directory in production
## 0.21.0
* Change project folder structure ([#432](https://github.com/sveltejs/sapper/issues/432))
* Escape filenames ([#446](https://github.com/sveltejs/sapper/pull/446/))
## 0.20.4
* Fix legacy build CSS ([#439](https://github.com/sveltejs/sapper/issues/439))
* Enable debugging in Chrome and VSCode ([#435](https://github.com/sveltejs/sapper/issues/435))
## 0.20.3
* Inject `nonce` attribute if `res.locals.nonce` is present ([#424](https://github.com/sveltejs/sapper/pull/424))
* Prevent service worker caching ([#428](https://github.com/sveltejs/sapper/pull/428))
* Consistent caching for HTML responses ([#429](https://github.com/sveltejs/sapper/pull/429))
## 0.20.2
* Add `immutable` cache control header for hashed assets ([#425](https://github.com/sveltejs/sapper/pull/425))
* Handle value-less query string params ([#426](https://github.com/sveltejs/sapper/issues/426))
## 0.20.1
* Update shimport
## 0.20.0
* Decode `req.params` and `req.query` ([#417](https://github.com/sveltejs/sapper/issues/417))
* Decode URLs before writing files in `sapper export` ([#414](https://github.com/sveltejs/sapper/pull/414))
* Generate server sourcemaps for Rollup apps in dev mode ([#418](https://github.com/sveltejs/sapper/pull/418))
## 0.19.3
* Better unicode route handling ([#347](https://github.com/sveltejs/sapper/issues/347))
## 0.19.2
* Ignore editor tmp files ([#220](https://github.com/sveltejs/sapper/issues/220))
* Ignore clicks an `<a>` element without `href` ([#235](https://github.com/sveltejs/sapper/issues/235))
* Allow routes that are reserved JavaScript words ([#315](https://github.com/sveltejs/sapper/issues/315))
* Print out webpack errors ([#403](https://github.com/sveltejs/sapper/issues/403))
## 0.19.1
* Don't include local origin in export redirects ([#409](https://github.com/sveltejs/sapper/pull/409))
## 0.19.0
* Extract styles out of JS into .css files, for Rollup apps ([#388](https://github.com/sveltejs/sapper/issues/388))
* Fix `prefetchRoutes` ([#380](https://github.com/sveltejs/sapper/issues/380))
## 0.18.7
* Support differential bundling for Rollup apps via a `--legacy` flag ([#280](https://github.com/sveltejs/sapper/issues/280))
## 0.18.6
* Bundle missing dependency
## 0.18.5
* Bugfix
## 0.18.4
* Handle non-Sapper responses when exporting ([#382](https://github.com/sveltejs/sapper/issues/392))
* Add `--dev-port` flag to `sapper dev` ([#381](https://github.com/sveltejs/sapper/issues/381))
## 0.18.3
* Fix service worker Rollup build config
## 0.18.2
* Update `pkg.files`
## 0.18.1
* Add live reloading ([#385](https://github.com/sveltejs/sapper/issues/385))
## 0.18.0
* Rollup support ([#379](https://github.com/sveltejs/sapper/pull/379))
* Fail `export` if a page times out (configurable with `--timeout`) ([#378](https://github.com/sveltejs/sapper/pull/378))
## 0.17.1
* Print which file is causing build errors/warnings ([#371](https://github.com/sveltejs/sapper/pull/371))
## 0.17.0
* Use `cheap-watch` instead of `chokidar` ([#364](https://github.com/sveltejs/sapper/issues/364))
## 0.16.1
* Fix file watching regression in previous version
## 0.16.0
* Slim down installed package ([#363](https://github.com/sveltejs/sapper/pull/363))
## 0.15.8
* Only set `preloading: true` on navigation, not prefetch ([#352](https://github.com/sveltejs/sapper/issues/352))
* Provide fallback for missing preload errors ([#361](https://github.com/sveltejs/sapper/pull/361))
## 0.15.7
* Strip leading slash from redirects ([#291](https://github.com/sveltejs/sapper/issues/291))
* Pass `(req, res)` to store getter ([#344](https://github.com/sveltejs/sapper/issues/344))
## 0.15.6
* Fix exporting with custom basepath ([#342](https://github.com/sveltejs/sapper/pull/342))
## 0.15.5
* Faster `export` with more explanatory output ([#335](https://github.com/sveltejs/sapper/pull/335))
* Only blur `activeElement` if it exists ([#332](https://github.com/sveltejs/sapper/issues/332))
* Don't emit `client_info.json` or `server_info.json` ([#318](https://github.com/sveltejs/sapper/issues/318))
## 0.15.4
* Add `ignore` option ([#326](https://github.com/sveltejs/sapper/pull/326))
## 0.15.3
* Crawl pages in parallel when exporting ([#329](https://github.com/sveltejs/sapper/pull/329))
* Don't minify inline JS when exporting ([#328](https://github.com/sveltejs/sapper/pull/328))
## 0.15.2
* Collapse component chains where no intermediate layout component is specified ([#312](https://github.com/sveltejs/sapper/issues/312))
## 0.15.1
* Prevent confusing error when no root layout is specified
## 0.15.0
* Nested routes (consult [migration guide](https://sapper.svelte.technology/guide#0-14-to-0-15) and docs on [layouts](https://sapper.svelte.technology/guide#layouts)) ([#262](https://github.com/sveltejs/sapper/issues/262))
## 0.14.2
* Prevent unsafe replacements ([#307](https://github.com/sveltejs/sapper/pull/307))
## 0.14.1
* Route parameters can be qualified with regex characters ([#283](https://github.com/sveltejs/sapper/pull/283))
## 0.14.0
* `4xx.html` and `5xx.html` are replaced with `_error.html` ([#209](https://github.com/sveltejs/sapper/issues/209))
* Treat `foo/index.json.js` and `foo.json.js` as equivalents ([#297](https://github.com/sveltejs/sapper/issues/297))
* Return a promise from `goto` ([#270](https://github.com/sveltejs/sapper/issues/270))
* Use store when rendering error pages ([#293](https://github.com/sveltejs/sapper/issues/293))
* Prevent console errors when visiting an error page ([#279](https://github.com/sveltejs/sapper/issues/279))
## 0.13.6
* Fix `baseUrl` synthesis ([#296](https://github.com/sveltejs/sapper/issues/296))
## 0.13.5
* Fix handling of fatal errors ([#289](https://github.com/sveltejs/sapper/issues/289))
## 0.13.4
* Focus `<body>` after navigation ([#287](https://github.com/sveltejs/sapper/issues/287))
* Fix timing of hot reload updates
* Emit `fatal` event if server crashes ([#285](https://github.com/sveltejs/sapper/pull/285))
* Emit `stdout` and `stderr` events on dev watcher ([#285](https://github.com/sveltejs/sapper/pull/285))
* Always refresh client assets in dev ([#286](https://github.com/sveltejs/sapper/pull/286))
* Correctly initialise rebuild stats
## 0.13.3
* Make `fatal` events clonable for IPC purposes
## 0.13.2
* Emit a `basepath` event ([#284](https://github.com/sveltejs/sapper/pull/284))
## 0.13.1
* Reinstate ten-second interval between dev server heartbeats ([#276](https://github.com/sveltejs/sapper/issues/276))
## 0.13.0
* Expose `dev`, `build`, `export` and `find_page` APIs ([#272](https://github.com/sveltejs/sapper/issues/272))
## 0.12.0
* Each app has a single `<App>` component. See the [migration guide](https://sapper.svelte.technology/guide#0-11-to-0-12) for more information ([#157](https://github.com/sveltejs/sapper/issues/157))
* Process exits with error code 1 if build/export fails ([#208](https://github.com/sveltejs/sapper/issues/208))
## 0.11.1
* Limit routes with leading dots to `.well-known` URIs ([#252](https://github.com/sveltejs/sapper/issues/252))
* Allow server routes to sit in front of pages ([#236](https://github.com/sveltejs/sapper/pull/236))
## 0.11.0
* Create launcher file ([#240](https://github.com/sveltejs/sapper/issues/240))
* Only keep necessary parts of webpack stats ([#251](https://github.com/sveltejs/sapper/pull/251))
* Allow `NODE_ENV` to be overridden when building ([#241](https://github.com/sveltejs/sapper/issues/241))
## 0.10.7
* Allow routes to have a leading `.` ([#243](https://github.com/sveltejs/sapper/pull/243))
* Only encode necessary characters in routes ([#234](https://github.com/sveltejs/sapper/pull/234))
* Preserve existing `process.env` when exporting ([#245](https://github.com/sveltejs/sapper/pull/245))
## 0.10.6
* Fix error reporting in `sapper start`
## 0.10.5
* Fix missing service worker ([#231](https://github.com/sveltejs/sapper/pull/231))
## 0.10.4
* Upgrade chokidar, this time with a fix ([#227](https://github.com/sveltejs/sapper/pull/227))
## 0.10.3
* Downgrade chokidar ([#212](https://github.com/sveltejs/sapper/issues/212))
## 0.10.2
* Attach `store` to error pages
* Fix sorting edge case ([#215](https://github.com/sveltejs/sapper/pull/215))
## 0.10.1
* Fix server-side `fetch` paths ([#207](https://github.com/sveltejs/sapper/pull/207))
## 0.10.0
* Support mounting on a path (this requires `app/template.html` to include `%sapper.base%`) ([#180](https://github.com/sveltejs/sapper/issues/180))
* Support per-request server-side `Store` with client-side hydration ([#178](https://github.com/sveltejs/sapper/issues/178))
* Add `this.fetch` to `preload`, with credentials support ([#178](https://github.com/sveltejs/sapper/issues/178))
* Exclude sourcemaps from preload links and `<script>` block ([#204](https://github.com/sveltejs/sapper/pull/204))
* Register service worker in `<script>` block
## 0.9.6
* Whoops — `tslib` is a runtime dependency
## 0.9.5
* Stringify clorox output ([#197](https://github.com/sveltejs/sapper/pull/197))
## 0.9.4
* Add `SAPPER_BASE` and `SAPPER_APP` environment variables ([#181](https://github.com/sveltejs/sapper/issues/181))
* Minify template in `sapper build` ([#15](https://github.com/sveltejs/sapper/issues/15))
* Minify all HTML files in `sapper export` ([#172](https://github.com/sveltejs/sapper/issues/172))
* Log exported files ([#195](https://github.com/sveltejs/sapper/pull/195))
* Add `--open`/`-o` flag to `sapper dev` and `sapper start` ([#186](https://github.com/sveltejs/sapper/issues/186))
## 0.9.3
* Fix path to `sapper-dev-client`
## 0.9.2
* Include `dist` files in package
## 0.9.1
* Include `sapper` bin
## 0.9.0
* Use `devalue` instead of `serialize-javascript`, allowing `preload` to return non-POJOs and cyclical/repeated references, but *not* functions ([#112](https://github.com/sveltejs/sapper/issues/112))
* Kill child process if webpack crashes ([#177](https://github.com/sveltejs/sapper/issues/177))
* Support HMR on remote devices ([#165](https://github.com/sveltejs/sapper/issues/165))
* Remove hard-coded port (([#169](https://github.com/sveltejs/sapper/issues/169)))
* Allow non-JS files, e.g. TypeScript to be used as entry points and server routes ([#57](https://github.com/sveltejs/sapper/issues/57))
* Faster startup ([#173](https://github.com/sveltejs/sapper/issues/173))
## 0.8.4
* Fix route sorting ([#175](https://github.com/sveltejs/sapper/pull/175))
## 0.8.3
* Automatically select available port, or use `--port` flag for `dev` and `start` ([#169](https://github.com/sveltejs/sapper/issues/169))
* Show stats after build/export ([#168](https://github.com/sveltejs/sapper/issues/168))
* Various CLI improvements ([#170](https://github.com/sveltejs/sapper/pull/170))
## 0.8.2
* Rename `preloadRoutes` to `prefetchRoutes` ([#166](https://github.com/sveltejs/sapper/issues/166))
## 0.8.1
* Add `sapper start` command, for running an app built with `sapper build` ([#163](https://github.com/sveltejs/sapper/issues/163))
## 0.8.0
* Update to webpack 4
* Add `preloadRoutes` function — secondary routes are no longer automatically preloaded ([#160](https://github.com/sveltejs/sapper/issues/160))
* `sapper build` outputs to `build`, `sapper build custom-dir` outputs to `custom-dir` ([#150](https://github.com/sveltejs/sapper/pull/150))
* `sapper export` outputs to `export`, `sapper export custom-dir` outputs to `custom-dir` ([#150](https://github.com/sveltejs/sapper/pull/150))
* Improved logging ([#158](https://github.com/sveltejs/sapper/pull/158))
* URI-encode routes ([#103](https://github.com/sveltejs/sapper/issues/103))
* Various performance and stability improvements ([#152](https://github.com/sveltejs/sapper/pull/152))
## 0.7.6
* Prevent client-side navigation to server route ([#145](https://github.com/sveltejs/sapper/issues/145))
* Don't serve error page for server route errors ([#138](https://github.com/sveltejs/sapper/issues/138))
## 0.7.5
* Allow dynamic parameters inside route parts ([#139](https://github.com/sveltejs/sapper/issues/139))
## 0.7.4
* Force `NODE_ENV='production'` when running `build` or `export` ([#141](https://github.com/sveltejs/sapper/issues/141))
* Use source-map-support ([#134](https://github.com/sveltejs/sapper/pull/134))
## 0.7.3
* Handle webpack assets that are arrays instead of strings ([#131](https://github.com/sveltejs/sapper/pull/131))
* Wait for new server to start before broadcasting HMR update ([#129](https://github.com/sveltejs/sapper/pull/129))
## 0.7.2
* Add `hmr-client.js` to package
* Wait until first successful client build before creating service-worker.js
## 0.7.1
* Add missing `tslib` dependency
## 0.7.0
* Restructure app layout (see [migration guide](https://sapper.svelte.technology/guide#0-6-to-0-7)) ([#126](https://github.com/sveltejs/sapper/pull/126))
* Support `this.redirect(status, location)` and `this.error(status, error)` in `preload` functions ([#127](https://github.com/sveltejs/sapper/pull/127))
* Add `sapper dev` command
* Add `sapper --help` command
## 0.6.4
* Prevent phantom HMR requests in production mode ([#114](https://github.com/sveltejs/sapper/pull/114))
## 0.6.3
* Ignore non-HTML responses when crawling during `export`
* Build in prod mode for `export`
## 0.6.2
* Handle unspecified type in `sapper export`
## 0.6.1
* Fix `pkg.files` and `pkg.bin`
## 0.6.0
* Hydrate on first load, and only on first load ([#93](https://github.com/sveltejs/sapper/pull/93))
* Identify clashes between page and server routes ([#96](https://github.com/sveltejs/sapper/pull/96))
* Remove Express-specific utilities, for compatbility with Polka et al ([#94](https://github.com/sveltejs/sapper/issues/94))
* Return a promise from `init` when first page has rendered ([#99](https://github.com/sveltejs/sapper/issues/99))
* Handle invalid hash links ([#104](https://github.com/sveltejs/sapper/pull/104))
* Avoid `URLSearchParams` ([#107](https://github.com/sveltejs/sapper/pull/107))
* Don't automatically set `Content-Type` for server routes ([#111](https://github.com/sveltejs/sapper/pull/111))
* Handle empty query string routes, e.g. `/?` ([#105](https://github.com/sveltejs/sapper/pull/105))
## 0.5.1
* Only write service-worker.js to filesystem in dev mode ([#90](https://github.com/sveltejs/sapper/issues/90))
## 0.5.0
* Experimental support for `sapper export` ([#9](https://github.com/sveltejs/sapper/issues/9))
* Lazily load chokidar, for faster startup ([#64](https://github.com/sveltejs/sapper/pull/64))
## 0.4.0
* `%sapper.main%` has been replaced with `%sapper.scripts%` ([#86](https://github.com/sveltejs/sapper/issues/86))
* Node 6 support ([#67](https://github.com/sveltejs/sapper/pull/67))
* Explicitly load css-loader and style-loader ([#72](https://github.com/sveltejs/sapper/pull/72))
* DELETE requests are handled with `del` exports ([#77](https://github.com/sveltejs/sapper/issues/77))
* Send preloaded data for first route to client, where possible ([#3](https://github.com/sveltejs/sapper/issues/3))
## 0.3.2
* Expose `prefetch` function ([#61](https://github.com/sveltejs/sapper/pull/61))

View File

@@ -31,6 +31,44 @@ npm run build
npm start
```
## Development
Pull requests are encouraged and always welcome. [Pick an issue](https://github.com/sveltejs/sapper/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) and help us out!
To install and work on Sapper locally:
```bash
git clone git@github.com:sveltejs/sapper.git
cd sapper
npm install
npm run dev
```
### Linking to a Live Project
You can make changes locally to Sapper and test it against a local Sapper project. For a quick project that takes almost no setup, use the default [sapper-template](https://github.com/sveltejs/sapper-template) project. Instruction on setup are found in that project repository.
To link Sapper to your project, from the root of your local Sapper git checkout:
```bash
cd sapper
npm link
```
Then, to link from `sapper-template` (or any other given project):
```bash
cd sapper-template
npm link sapper
```
You should be good to test changes locally.
### Running Tests
```bash
npm run test
```
## License

1
api.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./dist/api.js');

View File

@@ -10,8 +10,12 @@ build: off
environment:
matrix:
# node.js
- nodejs_version: stable
- nodejs_version: 10.5
install:
- ps: Install-Product node $env:nodejs_version
- npm install
- npm ci
test_script:
- node --version && npm --version
- npm test

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env node
const cmd = process.argv[2];
if (cmd === 'build') {
process.env.NODE_ENV = 'production';
require('../lib/build.js')();
}

1
config/rollup.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('../dist/rollup.js');

1
config/webpack.js Normal file
View File

@@ -0,0 +1 @@
module.exports = require('../dist/webpack.js');

View File

@@ -1,9 +0,0 @@
{
"baseUrl": "http://localhost:3000",
"videoRecording": false,
"fixturesFolder": "test/cypress/fixtures",
"integrationFolder": "test/cypress/integration",
"pluginsFile": false,
"screenshotsFolder": "test/cypress/screenshots",
"supportFile": "test/cypress/support/index.js"
}

1
index.js Normal file
View File

@@ -0,0 +1 @@
throw new Error(`As of Sapper 0.22, you should not import 'sapper' directly. See https://sapper.svelte.technology/guide#0-21-to-0-22 for more information`);

View File

@@ -1,42 +0,0 @@
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const rimraf = require('rimraf');
const { client, server } = require('./utils/compilers.js');
const create_app = require('./utils/create_app.js');
const generate_asset_cache = require('./utils/generate_asset_cache.js');
const { dest } = require('./config.js');
module.exports = () => {
mkdirp.sync(dest);
rimraf.sync(path.join(dest, '**/*'));
// create main.js and server-routes.js
create_app();
function handleErrors(err, stats) {
if (err) {
console.error(err ? err.details || err.stack || err.message || err : 'Unknown error');
process.exit(1);
}
if (stats.hasErrors()) {
console.log(stats.toString({ colors: true }));
process.exit(1);
}
}
client.run((err, clientStats) => {
handleErrors(err, clientStats);
const clientInfo = clientStats.toJson();
fs.writeFileSync(path.join(dest, 'stats.client.json'), JSON.stringify(clientInfo, null, ' '));
server.run((err, serverStats) => {
handleErrors(err, serverStats);
const serverInfo = serverStats.toJson();
fs.writeFileSync(path.join(dest, 'stats.server.json'), JSON.stringify(serverInfo, null, ' '));
generate_asset_cache(clientInfo, serverInfo);
});
});
};

View File

@@ -1,21 +0,0 @@
const path = require('path');
const mkdirp = require('mkdirp');
const rimraf = require('rimraf');
exports.dev = process.env.NODE_ENV !== 'production';
exports.templates = path.resolve(process.env.SAPPER_TEMPLATES || 'templates');
exports.src = path.resolve(process.env.SAPPER_ROUTES || 'routes');
exports.dest = path.resolve(process.env.SAPPER_DEST || '.sapper');
if (exports.dev) {
mkdirp.sync(exports.dest);
rimraf.sync(path.join(exports.dest, '**/*'));
}
exports.entry = {
client: path.resolve(exports.templates, '.main.rendered.js'),
server: path.resolve(exports.dest, 'server-entry.js')
};

View File

@@ -1,238 +0,0 @@
const fs = require('fs');
const path = require('path');
const route_manager = require('./route_manager.js');
const templates = require('./templates.js');
const create_app = require('./utils/create_app.js');
const create_watcher = require('./utils/create_watcher.js');
const compilers = require('./utils/compilers.js');
const generate_asset_cache = require('./utils/generate_asset_cache.js');
const escape_html = require('escape-html');
const { dest, dev } = require('./config.js');
function connect_dev() {
create_app();
const watcher = create_watcher();
let asset_cache;
const middleware = compose_handlers([
require('webpack-hot-middleware')(compilers.client, {
reload: true,
path: '/__webpack_hmr',
heartbeat: 10 * 1000
}),
async (req, res, next) => {
asset_cache = await watcher.ready;
next();
},
set_req_pathname,
get_asset_handler({
filter: pathname => pathname === '/index.html',
type: 'text/html',
cache: 'max-age=600',
fn: () => asset_cache.client.index
}),
get_asset_handler({
filter: pathname => pathname === '/service-worker.js',
type: 'application/javascript',
cache: 'max-age=600',
fn: () => asset_cache.client.service_worker
}),
get_asset_handler({
filter: pathname => pathname.startsWith('/client/'),
type: 'application/javascript',
cache: 'max-age=31536000',
fn: pathname => asset_cache.client.chunks[pathname]
}),
get_route_handler(() => asset_cache),
get_not_found_handler(() => asset_cache)
]);
middleware.close = () => {
watcher.close();
// TODO shut down chokidar
};
return middleware;
}
function connect_prod() {
const asset_cache = generate_asset_cache(
read_json(path.join(dest, 'stats.client.json')),
read_json(path.join(dest, 'stats.server.json'))
);
const middleware = compose_handlers([
set_req_pathname,
get_asset_handler({
filter: pathname => pathname === '/index.html',
type: 'text/html',
cache: 'max-age=600',
fn: () => asset_cache.client.index
}),
get_asset_handler({
filter: pathname => pathname === '/service-worker.js',
type: 'application/javascript',
cache: 'max-age=600',
fn: () => asset_cache.client.service_worker
}),
get_asset_handler({
filter: pathname => pathname.startsWith('/client/'),
type: 'application/javascript',
cache: 'max-age=31536000',
fn: pathname => asset_cache.client.chunks[pathname]
}),
get_route_handler(() => asset_cache),
get_not_found_handler(() => asset_cache)
]);
// here for API consistency between dev, and prod, but
// doesn't actually need to do anything
middleware.close = () => {};
return middleware;
}
module.exports = dev ? connect_dev : connect_prod;
function set_req_pathname(req, res, next) {
req.pathname = req.url.replace(/\?.+/, '');
next();
}
function get_asset_handler(opts) {
return (req, res, next) => {
if (!opts.filter(req.pathname)) return next();
res.set({
'Content-Type': opts.type,
'Cache-Control': opts.cache
});
res.end(opts.fn(req.pathname));
};
}
function get_route_handler(fn) {
return async function handle_route(req, res, next) {
const url = req.pathname;
const { client, server } = fn();
// whatever happens, we're going to serve some HTML
res.set({
'Content-Type': 'text/html'
});
try {
for (const route of route_manager.routes) {
if (route.test(url)) {
req.params = route.exec(url);
const mod = require(server.entry)[route.id];
if (route.type === 'page') {
// preload main.js and current route
// TODO detect other stuff we can preload? images, CSS, fonts?
res.set('Link', `<${client.main_file}>;rel="preload";as="script", <${client.routes[route.id]}>;rel="preload";as="script"`);
const data = { params: req.params, query: req.query };
if (mod.preload) {
const promise = Promise.resolve(mod.preload(req)).then(preloaded => {
Object.assign(data, preloaded);
return mod.render(data);
});
await templates.stream(res, 200, {
main: client.main_file,
html: promise.then(rendered => rendered.html),
head: promise.then(({ head }) => `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`),
styles: promise.then(({ css }) => (css && css.code ? `<style>${css.code}</style>` : ''))
});
} else {
const { html, head, css } = mod.render(data);
const page = templates.render(200, {
main: client.main_file,
html,
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
styles: (css && css.code ? `<style>${css.code}</style>` : '')
});
res.end(page);
}
}
else {
const handler = mod[req.method.toLowerCase()];
if (handler) handler(req, res, next);
}
return;
}
}
next();
} catch(err) {
res.status(500);
res.end(templates.render(500, {
title: (err && err.name) || 'Internal server error',
url,
error: escape_html(err && (err.details || err.message || err) || 'Unknown error'),
stack: err && err.stack.split('\n').slice(1).join('\n')
}));
}
};
}
function get_not_found_handler(fn) {
return function handle_not_found(req, res) {
const asset_cache = fn();
res.status(404);
res.end(templates.render(404, {
title: 'Not found',
status: 404,
method: req.method,
main: asset_cache.client.main_file,
url: req.url
}));
};
}
function compose_handlers(handlers) {
return (req, res, next) => {
let i = 0;
function go() {
const handler = handlers[i];
if (handler) {
handler(req, res, () => {
i += 1;
go();
});
} else {
next();
}
}
go();
};
}
function read_json(file) {
return JSON.parse(fs.readFileSync(file, 'utf-8'));
}

View File

@@ -1,31 +0,0 @@
const glob = require('glob');
const chokidar = require('chokidar');
const create_routes = require('./utils/create_routes.js');
const { src, dev } = require('./config.js');
const callbacks = [];
exports.onchange = fn => {
callbacks.push(fn);
};
function update() {
exports.routes = create_routes(
glob.sync('**/*.+(html|js|mjs)', { cwd: src })
);
callbacks.forEach(fn => fn());
}
update();
if (dev) {
const watcher = chokidar.watch(`${src}/**/*.+(html|js|mjs)`, {
ignoreInitial: true,
persistent: false
});
watcher.on('add', update);
watcher.on('change', update);
watcher.on('unlink', update);
}

View File

@@ -1,90 +0,0 @@
const fs = require('fs');
const glob = require('glob');
const chokidar = require('chokidar');
const { dev } = require('./config.js');
let templates;
function create_templates() {
templates = glob.sync('*.html', { cwd: 'templates' })
.map(file => {
const template = fs.readFileSync(`templates/${file}`, 'utf-8');
const status = file.replace('.html', '').toLowerCase();
if (!/^[0-9x]{3}$/.test(status)) {
throw new Error(`Bad template — should be a valid status code like 404.html, or a wildcard like 2xx.html`);
}
const specificity = (
(status[0] === 'x' ? 0 : 4) +
(status[1] === 'x' ? 0 : 2) +
(status[2] === 'x' ? 0 : 1)
);
const pattern = new RegExp(`^${status.split('').map(d => d === 'x' ? '\\d' : d).join('')}$`);
return {
test: status => pattern.test(status),
specificity,
render: data => {
return template.replace(/%sapper\.(\w+)%/g, (match, key) => {
return key in data ? data[key] : '';
});
},
stream: async (res, data) => {
let i = 0;
do {
const start = template.indexOf('%sapper', i);
if (start === -1) {
res.end(template.slice(i));
return;
}
res.write(template.slice(i, start));
const end = template.indexOf('%', start + 1);
if (end === -1) {
throw new Error(`Bad template`); // TODO validate ahead of time
}
const tag = template.slice(start + 1, end);
const match = /sapper\.(\w+)/.exec(tag);
if (!match || !(match[1] in data)) throw new Error(`Bad template`); // TODO ditto
res.write(await data[match[1]]);
i = end + 1;
} while (i < template.length);
}
};
})
.sort((a, b) => b.specificity - a.specificity);
}
create_templates();
if (dev) {
const watcher = chokidar.watch('templates/**.html', {
ignoreInitial: true,
persistent: false
});
watcher.on('add', create_templates);
watcher.on('change', create_templates);
watcher.on('unlink', create_templates);
}
exports.render = (status, data) => {
const template = templates.find(template => template.test(status));
if (template) return template.render(data);
return `Missing template for status code ${status}`;
};
exports.stream = (res, status, data) => {
const template = templates.find(template => template.test(status));
if (template) return template.stream(res, data);
return `Missing template for status code ${status}`;
};

View File

@@ -1,11 +0,0 @@
const path = require('path');
const relative = require('require-relative');
const webpack = relative('webpack', process.cwd());
exports.client = webpack(
require(path.resolve('webpack.client.config.js'))
);
exports.server = webpack(
require(path.resolve('webpack.server.config.js'))
);

View File

@@ -1,83 +0,0 @@
const fs = require('fs');
const path = require('path');
const chokidar = require('chokidar');
const route_manager = require('../route_manager.js');
const { src, entry, dev } = require('../config.js');
function posixify(file) {
return file.replace(/[/\\]/g, '/');
}
function create_app() {
const { routes } = route_manager;
function create_client_main() {
const template = fs.readFileSync('templates/main.js', 'utf-8');
const code = `[${
routes
.filter(route => route.type === 'page')
.map(route => {
const params = route.dynamic.length === 0 ?
'{}' :
`{ ${route.dynamic.map((part, i) => `${part}: match[${i + 1}]`).join(', ') } }`;
const file = posixify(`${src}/${route.file}`);
return `{ pattern: ${route.pattern}, params: match => (${params}), load: () => import(/* webpackChunkName: "${route.id}" */ '${file}') }`
})
.join(', ')
}]`;
let main = template
.replace(/__app__/g, posixify(path.resolve(__dirname, '../../runtime/app.js')))
.replace(/__routes__/g, code)
.replace(/__dev__/g, String(dev));
if (dev) {
const hmr_client = posixify(require.resolve(`webpack-hot-middleware/client`));
main += `\n\nimport('${hmr_client}?path=/__webpack_hmr&timeout=20000'); if (module.hot) module.hot.accept();`
}
fs.writeFileSync(entry.client, main);
// need to fudge the mtime, because webpack is soft in the head
const { atime, mtime } = fs.statSync(entry.client);
fs.utimesSync(entry.client, new Date(atime.getTime() - 999999), new Date(mtime.getTime() - 999999));
}
function create_server_routes() {
const imports = routes
.map(route => {
const file = posixify(`${src}/${route.file}`);
return route.type === 'page' ?
`import ${route.id} from '${file}';` :
`import * as ${route.id} from '${file}';`;
})
.join('\n');
const exports = `export { ${routes.map(route => route.id)} };`;
fs.writeFileSync(entry.server, `${imports}\n\n${exports}`);
const { atime, mtime } = fs.statSync(entry.server);
fs.utimesSync(entry.server, new Date(atime.getTime() - 999999), new Date(mtime.getTime() - 999999));
}
create_client_main();
create_server_routes();
}
if (dev) {
route_manager.onchange(create_app);
const watcher = chokidar.watch(`templates/main.js`, {
ignoreInitial: true,
persistent: false
});
watcher.on('add', create_app);
watcher.on('change', create_app);
watcher.on('unlink', create_app);
}
module.exports = create_app;

View File

@@ -1,90 +0,0 @@
const path = require('path');
module.exports = function create_matchers(files) {
const routes = files
.map(file => {
if (/(^|\/|\\)_/.test(file)) return;
const parts = file.replace(/\.(html|js|mjs)$/, '').split('/'); // glob output is always posix-style
if (parts[parts.length - 1] === 'index') parts.pop();
const id = (
parts.join('_').replace(/[[\]]/g, '$').replace(/^\d/, '_$&').replace(/[^a-zA-Z0-9_$]/g, '_')
) || '_';
const dynamic = parts
.filter(part => part[0] === '[')
.map(part => part.slice(1, -1));
let pattern_string = '';
let i = parts.length;
let nested = true;
while (i--) {
const part = parts[i];
const dynamic = part[0] === '[';
if (dynamic) {
pattern_string = nested ? `(?:\\/([^/]+)${pattern_string})?` : `\\/([^/]+)${pattern_string}`;
} else {
nested = false;
pattern_string = `\\/${part}${pattern_string}`;
}
}
const pattern = new RegExp(`^${pattern_string || '\\/'}$`);
const test = url => pattern.test(url);
const exec = url => {
const match = pattern.exec(url);
if (!match) return;
const params = {};
dynamic.forEach((param, i) => {
params[param] = match[i + 1];
});
return params;
};
return {
id,
type: path.extname(file) === '.html' ? 'page' : 'route',
file,
pattern,
test,
exec,
parts,
dynamic
};
})
.filter(Boolean)
.sort((a, b) => {
let same = true;
for (let i = 0; true; i += 1) {
const a_part = a.parts[i];
const b_part = b.parts[i];
if (!a_part && !b_part) {
if (same) throw new Error(`The ${a.file} and ${b.file} routes clash`);
return 0;
}
if (!a_part) return -1;
if (!b_part) return 1;
const a_is_dynamic = a_part[0] === '[';
const b_is_dynamic = b_part[0] === '[';
if (a_is_dynamic === b_is_dynamic) {
if (!a_is_dynamic) same = false;
continue;
}
return a_is_dynamic ? 1 : -1;
}
});
return routes;
};

View File

@@ -1,75 +0,0 @@
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const compilers = require('./compilers.js');
const generate_asset_cache = require('./generate_asset_cache.js');
const { dest } = require('../config.js');
function deferred() {
const d = {};
d.promise = new Promise((fulfil, reject) => {
d.fulfil = fulfil;
d.reject = reject;
});
return d;
}
module.exports = function create_watcher() {
const deferreds = {
client: deferred(),
server: deferred()
};
const invalidate = () => Promise.all([
deferreds.client.promise,
deferreds.server.promise
]).then(([client_stats, server_stats]) => {
const client_info = client_stats.toJson();
fs.writeFileSync(path.join(dest, 'stats.client.json'), JSON.stringify(client_info, null, ' '));
const server_info = server_stats.toJson();
fs.writeFileSync(path.join(dest, 'stats.server.json'), JSON.stringify(server_info, null, ' '));
return generate_asset_cache(
client_stats.toJson(),
server_stats.toJson()
);
});
function watch_compiler(type) {
const compiler = compilers[type];
compiler.plugin('invalid', filename => {
console.log(chalk.cyan(`${type} bundle invalidated, file changed: ${chalk.bold(filename)}`));
deferreds[type] = deferred();
watcher.ready = invalidate();
});
compiler.plugin('failed', err => {
deferreds[type].reject(err);
});
return compiler.watch({}, (err, stats) => {
if (stats.hasErrors()) {
deferreds[type].reject(stats.toJson().errors[0]);
} else {
deferreds[type].fulfil(stats);
}
});
}
const watcher = {
ready: invalidate(),
client: watch_compiler('client'),
server: watch_compiler('server'),
close: () => {
watcher.client.close();
watcher.server.close();
}
};
return watcher;
};

View File

@@ -1,79 +0,0 @@
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const templates = require('../templates.js');
const route_manager = require('../route_manager.js');
const { dest, dev } = require('../config.js');
function ensure_array(thing) {
return Array.isArray(thing) ? thing : [thing]; // omg webpack what the HELL are you doing
}
module.exports = function generate_asset_cache(clientInfo, serverInfo) {
const main_file = `/client/${ensure_array(clientInfo.assetsByChunkName.main)[0]}`;
const chunk_files = clientInfo.assets.map(chunk => `/client/${chunk.name}`);
const service_worker = generate_service_worker(chunk_files);
const index = generate_index(main_file);
if (dev) {
fs.writeFileSync(path.join(dest, 'service-worker.js'), service_worker);
fs.writeFileSync(path.join(dest, 'index.html'), index);
}
return {
client: {
main_file,
chunk_files,
main: read(`${dest}${main_file}`),
chunks: chunk_files.reduce((lookup, file) => {
lookup[file] = read(`${dest}${file}`);
return lookup;
}, {}),
routes: route_manager.routes.reduce((lookup, route) => {
lookup[route.id] = `/client/${ensure_array(clientInfo.assetsByChunkName[route.id])[0]}`;
return lookup;
}, {}),
index,
service_worker
},
server: {
entry: path.resolve(dest, 'server', serverInfo.assetsByChunkName.main)
}
};
};
function generate_service_worker(chunk_files) {
const assets = glob.sync('**', { cwd: 'assets', nodir: true });
const route_code = `[${
route_manager.routes
.filter(route => route.type === 'page')
.map(route => `{ pattern: ${route.pattern} }`)
.join(', ')
}]`;
return read('templates/service-worker.js')
.replace(/__timestamp__/g, Date.now())
.replace(/__assets__/g, JSON.stringify(assets))
.replace(/__shell__/g, JSON.stringify(chunk_files.concat('/index.html')))
.replace(/__routes__/g, route_code);
}
function generate_index(main_file) {
return templates.render(200, {
styles: '',
head: '',
html: '<noscript>Please enable JavaScript!</noscript>',
main: main_file
});
}
function read(file) {
return fs.readFileSync(file, 'utf-8');
}

View File

@@ -1,3 +1,5 @@
--require source-map-support/register
--require ts-node/register
--recursive
test/unit/**/*.js
test/common/test.js
test/unit/*/*.ts
test/apps/*/test.ts

9124
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,57 +1,78 @@
{
"name": "sapper",
"version": "0.3.2",
"version": "0.24.0",
"description": "Military-grade apps, engineered by Svelte",
"main": "lib/index.js",
"bin": {
"sapper": "cli/index.js"
"sapper": "./sapper"
},
"files": [
"cli",
"lib",
"runtime",
"runtime.js",
"webpack"
"*.js",
"webpack",
"config",
"sapper",
"dist/*.js",
"templates/*.js"
],
"directories": {
"test": "test"
},
"dependencies": {
"chalk": "^2.3.0",
"chokidar": "^1.7.0",
"escape-html": "^1.0.3",
"mkdirp": "^0.5.1",
"relative": "^3.0.2",
"require-relative": "^0.8.7",
"rimraf": "^2.6.2",
"webpack": "^3.10.0",
"webpack-hot-middleware": "^2.21.0"
"html-minifier": "^3.5.20",
"shimport": "0.0.11",
"source-map-support": "^0.5.9",
"sourcemap-codec": "^1.4.3",
"string-hash": "^1.1.3",
"tslib": "^1.9.3"
},
"devDependencies": {
"css-loader": "^0.28.7",
"eslint": "^4.13.1",
"eslint-plugin-import": "^2.8.0",
"express": "^4.16.2",
"get-port": "^3.2.0",
"mocha": "^4.0.1",
"nightmare": "^2.10.0",
"node-fetch": "^1.7.3",
"npm-run-all": "^4.1.2",
"rollup": "^0.53.0",
"rollup-plugin-typescript": "^0.8.1",
"style-loader": "^0.19.1",
"svelte": "^1.49.1",
"svelte-loader": "^2.3.2",
"tslib": "^1.8.1",
"typescript": "^2.6.2",
"wait-on": "^2.0.2"
"@types/mkdirp": "^0.5.2",
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.0",
"@types/puppeteer": "^1.9.0",
"@types/rimraf": "^2.0.2",
"agadoo": "^1.0.1",
"cheap-watch": "^1.0.0",
"cookie": "^0.3.1",
"devalue": "^1.0.4",
"eslint": "^5.7.0",
"eslint-plugin-import": "^2.14.0",
"kleur": "^2.0.2",
"mkdirp": "^0.5.1",
"mocha": "^5.2.0",
"node-fetch": "^2.2.0",
"npm-run-all": "^4.1.3",
"polka": "^0.5.1",
"port-authority": "^1.0.5",
"pretty-bytes": "^5.1.0",
"puppeteer": "^1.9.0",
"require-relative": "^0.8.7",
"rimraf": "^2.6.2",
"rollup": "^0.66.6",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-json": "^3.1.0",
"rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-string": "^2.0.2",
"rollup-plugin-svelte": "^4.3.2",
"rollup-plugin-typescript": "^1.0.0",
"sade": "^1.4.1",
"sander": "^0.6.0",
"sirv": "^0.2.2",
"svelte": "^2.13.5",
"svelte-loader": "^2.11.0",
"ts-node": "^7.0.1",
"typescript": "^3.1.3",
"webpack": "^4.20.2",
"webpack-format-messages": "^2.0.3"
},
"scripts": {
"cy:open": "cypress open",
"test": "mocha --opts mocha.opts",
"pretest": "npm run build",
"build": "rollup -c",
"dev": "rollup -cw"
"build": "rm -rf dist && rollup -c",
"prepare": "npm run build",
"dev": "rollup -cw",
"prepublishOnly": "npm test",
"update_mime_types": "curl http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types | grep -e \"^[^#]\" > templates/src/server/middleware/mime-types.md"
},
"repository": "https://github.com/sveltejs/sapper",
"keywords": [

View File

@@ -1,17 +1,65 @@
import typescript from 'rollup-plugin-typescript';
import string from 'rollup-plugin-string';
import json from 'rollup-plugin-json';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import pkg from './package.json';
import { builtinModules } from 'module';
export default [
// runtime.js
{
input: 'src/runtime/index.ts',
const external = [].concat(
Object.keys(pkg.dependencies),
Object.keys(process.binding('natives')),
'sapper/core.js'
);
function template(kind, external, target) {
return {
input: `templates/src/${kind}/index.ts`,
output: {
file: 'runtime.js',
file: `templates/${kind}.js`,
format: 'es'
},
external,
plugins: [
resolve(),
commonjs(),
string({
include: '**/*.md'
}),
typescript({
typescript: require('typescript'),
target
})
]
};
}
export default [
template('client', ['__ROOT__', '__ERROR__'], 'ES2017'),
template('server', builtinModules, 'ES2015'),
{
input: [
`src/api.ts`,
`src/cli.ts`,
`src/core.ts`,
`src/config/rollup.ts`,
`src/config/webpack.ts`
],
output: {
dir: 'dist',
format: 'cjs',
sourcemap: true
},
external,
plugins: [
json(),
resolve(),
commonjs(),
typescript({
typescript: require('typescript')
})
]
],
experimentalCodeSplitting: true
}
];

View File

@@ -1,2 +0,0 @@
console.error('sapper/runtime/app.js has been deprecated in favour of sapper/runtime.js');
export * from '../runtime.js';

2
sapper Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env node
require('./dist/cli.js');

40
sapper-dev-client.js Normal file
View File

@@ -0,0 +1,40 @@
let source;
function check() {
if (typeof module === 'undefined') return;
if (module.hot.status() === 'idle') {
module.hot.check(true).then(modules => {
console.log(`[SAPPER] applied HMR update`);
});
}
}
export function connect(port) {
if (source || !window.EventSource) return;
source = new EventSource(`http://${window.location.hostname}:${port}/__sapper__`);
window.source = source;
source.onopen = function(event) {
console.log(`[SAPPER] dev client connected`);
};
source.onerror = function(error) {
console.error(error);
};
source.onmessage = function(event) {
const data = JSON.parse(event.data);
if (!data) return; // just a heartbeat
if (data.action === 'reload') {
window.location.reload();
}
if (data.status === 'completed') {
check();
}
};
}

4
src/api.ts Normal file
View File

@@ -0,0 +1,4 @@
export { dev } from './api/dev';
export { build } from './api/build';
export { export } from './api/export';
export { find_page } from './api/find_page';

133
src/api/build.ts Normal file
View File

@@ -0,0 +1,133 @@
import * as fs from 'fs';
import * as path from 'path';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import minify_html from './utils/minify_html';
import { create_compilers, create_main_manifests, create_manifest_data, create_serviceworker_manifest } from '../core';
import { copy_shimport } from './utils/copy_shimport';
import read_template from '../core/read_template';
import { CompileResult } from '../core/create_compilers/interfaces';
import { noop } from './utils/noop';
import validate_bundler from './utils/validate_bundler';
type Opts = {
cwd?: string;
src?: string;
routes?: string;
dest?: string;
output?: string;
static?: string;
legacy?: boolean;
bundler?: 'rollup' | 'webpack';
oncompile?: ({ type, result }: { type: string, result: CompileResult }) => void;
};
export async function build({
cwd,
src = 'src',
routes = 'src/routes',
output = '__sapper__',
static: static_files = 'static',
dest = '__sapper__/build',
bundler,
legacy = false,
oncompile = noop
}: Opts = {}) {
bundler = validate_bundler(bundler);
cwd = path.resolve(cwd);
src = path.resolve(cwd, src);
dest = path.resolve(cwd, dest);
routes = path.resolve(cwd, routes);
output = path.resolve(cwd, output);
static_files = path.resolve(cwd, static_files);
dest = path.resolve(cwd, dest);
if (legacy && bundler === 'webpack') {
throw new Error(`Legacy builds are not supported for projects using webpack`);
}
rimraf.sync(path.join(dest, '**/*'));
mkdirp.sync(`${dest}/client`);
copy_shimport(dest);
// minify src/template.html
// TODO compile this to a function? could be quicker than str.replace(...).replace(...).replace(...)
const template = read_template(src);
// remove this in a future version
if (template.indexOf('%sapper.base%') === -1) {
const error = new Error(`As of Sapper v0.10, your template.html file must include %sapper.base% in the <head>`);
error.code = `missing-sapper-base`;
throw error;
}
fs.writeFileSync(`${dest}/template.html`, minify_html(template));
const manifest_data = create_manifest_data(routes);
// create src/manifest/client.js and src/manifest/server.js
create_main_manifests({
bundler,
manifest_data,
cwd,
src,
dest,
routes,
output,
dev: false
});
const { client, server, serviceworker } = await create_compilers(bundler, cwd, src, dest, true);
const client_result = await client.compile();
oncompile({
type: 'client',
result: client_result
});
const build_info = client_result.to_json(manifest_data, { src, routes, dest });
if (legacy) {
process.env.SAPPER_LEGACY_BUILD = 'true';
const { client } = await create_compilers(bundler, cwd, src, dest, true);
const client_result = await client.compile();
oncompile({
type: 'client (legacy)',
result: client_result
});
client_result.to_json(manifest_data, { src, routes, dest });
build_info.legacy_assets = client_result.assets;
delete process.env.SAPPER_LEGACY_BUILD;
}
fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify(build_info));
const server_stats = await server.compile();
oncompile({
type: 'server',
result: server_stats
});
let serviceworker_stats;
if (serviceworker) {
create_serviceworker_manifest({
manifest_data,
output,
client_files: client_result.chunks.map(chunk => `client/${chunk.file}`),
static_files
});
serviceworker_stats = await serviceworker.compile();
oncompile({
type: 'serviceworker',
result: serviceworker_stats
});
}
}

507
src/api/dev.ts Normal file
View File

@@ -0,0 +1,507 @@
import * as path from 'path';
import * as fs from 'fs';
import * as http from 'http';
import * as child_process from 'child_process';
import * as ports from 'port-authority';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import { EventEmitter } from 'events';
import { create_manifest_data, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core';
import { Compiler, Compilers } from '../core/create_compilers';
import { CompileResult } from '../core/create_compilers/interfaces';
import Deferred from './utils/Deferred';
import validate_bundler from './utils/validate_bundler';
import { copy_shimport } from './utils/copy_shimport';
import { ManifestData, FatalEvent, ErrorEvent, ReadyEvent, InvalidEvent } from '../interfaces';
import read_template from '../core/read_template';
import { noop } from './utils/noop';
type Opts = {
cwd?: string,
src?: string,
dest?: string,
routes?: string,
output?: string,
static?: string,
'dev-port'?: number,
live?: boolean,
hot?: boolean,
'devtools-port'?: number,
bundler?: 'rollup' | 'webpack',
port?: number
};
export function dev(opts: Opts) {
return new Watcher(opts);
}
class Watcher extends EventEmitter {
bundler: 'rollup' | 'webpack';
dirs: {
cwd: string;
src: string;
dest: string;
routes: string;
output: string;
static: string;
}
port: number;
closed: boolean;
dev_port: number;
live: boolean;
hot: boolean;
devtools_port: number;
dev_server: DevServer;
proc: child_process.ChildProcess;
filewatchers: Array<{ close: () => void }>;
deferred: Deferred;
crashed: boolean;
restarting: boolean;
current_build: {
changed: Set<string>;
rebuilding: Set<string>;
unique_warnings: Set<string>;
unique_errors: Set<string>;
}
constructor({
cwd = '.',
src = 'src',
routes = 'src/routes',
output = '__sapper__',
static: static_files = 'static',
dest = '__sapper__/dev',
'dev-port': dev_port,
live,
hot,
'devtools-port': devtools_port,
bundler,
port = +process.env.PORT
}: Opts) {
super();
cwd = path.resolve(cwd);
this.bundler = validate_bundler(bundler);
this.dirs = {
cwd,
src: path.resolve(cwd, src),
dest: path.resolve(cwd, dest),
routes: path.resolve(cwd, routes),
output: path.resolve(cwd, output),
static: path.resolve(cwd, static_files)
};
this.port = port;
this.closed = false;
this.dev_port = dev_port;
this.live = live;
this.hot = hot;
this.devtools_port = devtools_port;
this.filewatchers = [];
this.current_build = {
changed: new Set(),
rebuilding: new Set(),
unique_errors: new Set(),
unique_warnings: new Set()
};
// remove this in a future version
const template = read_template(src);
if (template.indexOf('%sapper.base%') === -1) {
const error = new Error(`As of Sapper v0.10, your template.html file must include %sapper.base% in the <head>`);
error.code = `missing-sapper-base`;
throw error;
}
process.env.NODE_ENV = 'development';
process.on('exit', () => {
this.close();
});
this.init();
}
async init() {
if (this.port) {
if (!await ports.check(this.port)) {
this.emit('fatal', <FatalEvent>{
message: `Port ${this.port} is unavailable`
});
return;
}
} else {
this.port = await ports.find(3000);
}
const { cwd, src, dest, routes, output, static: static_files } = this.dirs;
rimraf.sync(dest);
mkdirp.sync(`${dest}/client`);
if (this.bundler === 'rollup') copy_shimport(dest);
if (!this.dev_port) this.dev_port = await ports.find(10000);
// Chrome looks for debugging targets on ports 9222 and 9229 by default
if (!this.devtools_port) this.devtools_port = await ports.find(9222);
let manifest_data: ManifestData;
try {
manifest_data = create_manifest_data(routes);
create_main_manifests({
bundler: this.bundler,
manifest_data,
dev: true,
dev_port: this.dev_port,
cwd, src, dest, routes, output
});
} catch (err) {
this.emit('fatal', <FatalEvent>{
message: err.message
});
return;
}
this.dev_server = new DevServer(this.dev_port);
this.filewatchers.push(
watch_dir(
routes,
({ path: file, stats }) => {
if (stats.isDirectory()) {
return path.basename(file)[0] !== '_';
}
return true;
},
() => {
try {
const new_manifest_data = create_manifest_data(routes);
create_main_manifests({
bundler: this.bundler,
manifest_data, // TODO is this right? not new_manifest_data?
dev: true,
dev_port: this.dev_port,
cwd, src, dest, routes, output
});
manifest_data = new_manifest_data;
} catch (err) {
this.emit('error', <ErrorEvent>{
message: err.message
});
}
}
),
fs.watch(`${src}/template.html`, () => {
this.dev_server.send({
action: 'reload'
});
})
);
let deferred = new Deferred();
// TODO watch the configs themselves?
const compilers: Compilers = await create_compilers(this.bundler, cwd, src, dest, false);
const emitFatal = () => {
this.emit('fatal', <FatalEvent>{
message: `Server crashed`
});
this.crashed = true;
this.proc = null;
};
this.watch(compilers.server, {
name: 'server',
invalid: filename => {
this.restart(filename, 'server');
},
handle_result: (result: CompileResult) => {
deferred.promise.then(() => {
const restart = () => {
this.crashed = false;
ports.wait(this.port)
.then((() => {
this.emit('ready', <ReadyEvent>{
port: this.port,
process: this.proc
});
if (this.hot && this.bundler === 'webpack') {
this.dev_server.send({
status: 'completed'
});
} else {
this.dev_server.send({
action: 'reload'
});
}
}))
.catch(err => {
if (this.crashed) return;
this.emit('fatal', <FatalEvent>{
message: `Server is not listening on port ${this.port}`
});
});
};
if (this.proc) {
this.proc.removeListener('exit', emitFatal);
this.proc.kill();
this.proc.on('exit', restart);
} else {
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);
});
}
});
this.watch(compilers.client, {
name: 'client',
invalid: filename => {
this.restart(filename, 'client');
deferred = new Deferred();
// TODO we should delete old assets. due to a webpack bug
// i don't even begin to comprehend, this is apparently
// quite difficult
},
handle_result: (result: CompileResult) => {
fs.writeFileSync(
path.join(dest, 'build.json'),
// TODO should be more explicit that to_json has effects
JSON.stringify(result.to_json(manifest_data, this.dirs), null, ' ')
);
const client_files = result.chunks.map(chunk => `client/${chunk.file}`);
create_serviceworker_manifest({
manifest_data,
output,
client_files,
static_files
});
deferred.fulfil();
// we need to wait a beat before watching the service
// worker, because of some webpack nonsense
setTimeout(watch_serviceworker, 100);
}
});
let watch_serviceworker = compilers.serviceworker
? () => {
watch_serviceworker = noop;
this.watch(compilers.serviceworker, {
name: 'service worker'
});
}
: noop;
}
close() {
if (this.closed) return;
this.closed = true;
if (this.dev_server) this.dev_server.close();
if (this.proc) this.proc.kill();
this.filewatchers.forEach(watcher => {
watcher.close();
});
}
restart(filename: string, type: string) {
if (this.restarting) {
this.current_build.changed.add(filename);
this.current_build.rebuilding.add(type);
} else {
this.restarting = true;
this.current_build = {
changed: new Set([filename]),
rebuilding: new Set([type]),
unique_warnings: new Set(),
unique_errors: new Set()
};
process.nextTick(() => {
this.emit('invalid', <InvalidEvent>{
changed: Array.from(this.current_build.changed),
invalid: {
server: this.current_build.rebuilding.has('server'),
client: this.current_build.rebuilding.has('client'),
serviceworker: this.current_build.rebuilding.has('serviceworker'),
}
});
this.restarting = false;
});
}
}
watch(compiler: Compiler, { name, invalid = noop, handle_result = noop }: {
name: string,
invalid?: (filename: string) => void;
handle_result?: (result: CompileResult) => void;
}) {
compiler.oninvalid(invalid);
compiler.watch((err?: Error, result?: CompileResult) => {
if (err) {
this.emit('error', <ErrorEvent>{
type: name,
message: err.message
});
} else {
this.emit('build', {
type: name,
duration: result.duration,
errors: result.errors,
warnings: result.warnings
});
handle_result(result);
}
});
}
}
const INTERVAL = 10000;
class DevServer {
clients: Set<http.ServerResponse>;
interval: NodeJS.Timer;
_: http.Server;
constructor(port: number, interval = 10000) {
this.clients = new Set();
this._ = http.createServer((req, res) => {
if (req.url !== '/__sapper__') return;
req.socket.setKeepAlive(true);
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Cache-Control',
'Content-Type': 'text/event-stream;charset=utf-8',
'Cache-Control': 'no-cache, no-transform',
'Connection': 'keep-alive',
// While behind nginx, event stream should not be buffered:
// http://nginx.org/docs/http/ngx_http_proxy_module.html#proxy_buffering
'X-Accel-Buffering': 'no'
});
res.write('\n');
this.clients.add(res);
req.on('close', () => {
this.clients.delete(res);
});
});
this._.listen(port);
this.interval = setInterval(() => {
this.send(null);
}, INTERVAL);
}
close() {
this._.close();
clearInterval(this.interval);
}
send(data: any) {
this.clients.forEach(client => {
client.write(`data: ${JSON.stringify(data)}\n\n`);
});
}
}
function watch_dir(
dir: string,
filter: ({ path, stats }: { path: string, stats: fs.Stats }) => boolean,
callback: () => void
) {
let watch: any;
let closed = false;
import('cheap-watch').then(CheapWatch => {
if (closed) return;
watch = new CheapWatch({ dir, filter, debounce: 50 });
watch.on('+', ({ isNew }: { isNew: boolean }) => {
if (isNew) callback();
});
watch.on('-', callback);
watch.init();
});
return {
close: () => {
if (watch) watch.close();
closed = true;
}
};
}

194
src/api/export.ts Normal file
View File

@@ -0,0 +1,194 @@
import * as child_process from 'child_process';
import * as path from 'path';
import * as sander from 'sander';
import * as url from 'url';
import fetch from 'node-fetch';
import * as ports from 'port-authority';
import clean_html from './utils/clean_html';
import minify_html from './utils/minify_html';
import Deferred from './utils/Deferred';
import { noop } from './utils/noop';
type Opts = {
build_dir?: string,
export_dir?: string,
cwd?: string,
static?: string,
basepath?: string,
timeout?: number | false,
oninfo?: ({ message }: { message: string }) => void;
onfile?: ({ file, size, status }: { file: string, size: number, status: number }) => void;
};
function resolve(from: string, to: string) {
return url.parse(url.resolve(from, to));
}
type URL = url.UrlWithStringQuery;
export { _export as export };
async function _export({
cwd,
static: static_files = 'static',
build_dir = '__sapper__/build',
export_dir = '__sapper__/export',
basepath = '',
timeout = 5000,
oninfo = noop,
onfile = noop
}: Opts = {}) {
basepath = basepath.replace(/^\//, '')
cwd = path.resolve(cwd);
static_files = path.resolve(cwd, static_files);
build_dir = path.resolve(cwd, build_dir);
export_dir = path.resolve(cwd, export_dir, basepath);
// Prep output directory
sander.rimrafSync(export_dir);
sander.copydirSync(static_files).to(export_dir);
sander.copydirSync(build_dir, 'client').to(export_dir, 'client');
if (sander.existsSync(build_dir, 'service-worker.js')) {
sander.copyFileSync(build_dir, 'service-worker.js').to(export_dir, 'service-worker.js');
}
if (sander.existsSync(build_dir, 'service-worker.js.map')) {
sander.copyFileSync(build_dir, 'service-worker.js.map').to(export_dir, 'service-worker.js.map');
}
const port = await ports.find(3000);
const protocol = 'http:';
const host = `localhost:${port}`;
const origin = `${protocol}//${host}`;
const root = resolve(origin, basepath);
if (!root.href.endsWith('/')) root.href += '/';
oninfo({
message: `Crawling ${root.href}`
});
const proc = child_process.fork(path.resolve(`${build_dir}/server/server.js`), [], {
cwd,
env: Object.assign({
PORT: port,
NODE_ENV: 'production',
SAPPER_EXPORT: 'true'
}, process.env)
});
const seen = new Set();
const saved = new Set();
function save(path: string, status: number, type: string, body: string) {
const { pathname } = resolve(origin, path);
let file = decodeURIComponent(pathname.slice(1));
if (saved.has(file)) return;
saved.add(file);
const is_html = type === 'text/html';
if (is_html) {
file = file === '' ? 'index.html' : `${file}/index.html`;
body = minify_html(body);
}
onfile({
file,
size: body.length,
status
});
sander.writeFileSync(export_dir, file, body);
}
proc.on('message', message => {
if (!message.__sapper__ || message.event !== 'file') return;
save(message.url, message.status, message.type, message.body);
});
async function handle(url: URL) {
const pathname = (url.pathname.replace(root.pathname, '') || '/');
if (seen.has(pathname)) return;
seen.add(pathname);
const timeout_deferred = new Deferred();
const the_timeout = setTimeout(() => {
timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`));
}, timeout);
const r = await Promise.race([
fetch(url.href, {
redirect: 'manual'
}),
timeout_deferred.promise
]);
clearTimeout(the_timeout); // prevent it hanging at the end
let type = r.headers.get('Content-Type');
let body = await r.text();
const range = ~~(r.status / 100);
if (range === 2) {
if (type === 'text/html') {
const urls: URL[] = [];
const cleaned = clean_html(body);
const base_match = /<base ([\s\S]+?)>/m.exec(cleaned);
const base_href = base_match && get_href(base_match[1]);
const base = resolve(url.href, base_href);
let match;
let pattern = /<a ([\s\S]+?)>/gm;
while (match = pattern.exec(cleaned)) {
const attrs = match[1];
const href = get_href(attrs);
if (href) {
const url = resolve(base.href, href);
if (url.protocol === protocol && url.host === host) {
urls.push(url);
}
}
}
await Promise.all(urls.map(handle));
}
}
if (range === 3) {
const location = r.headers.get('Location');
type = 'text/html';
body = `<script>window.location.href = "${location.replace(origin, '')}"</script>`;
await handle(resolve(root.href, location));
}
save(pathname, r.status, type, body);
}
return ports.wait(port)
.then(() => handle(root))
.then(() => proc.kill())
.catch(err => {
proc.kill();
throw err;
});
}
function get_href(attrs: string) {
const match = /href\s*=\s*(?:"(.*?)"|'(.+?)'|([^\s>]+))/.exec(attrs);
return match[1] || match[2] || match[3];
}

13
src/api/find_page.ts Normal file
View File

@@ -0,0 +1,13 @@
import { create_manifest_data } from '../core';
export function find_page(pathname: string, cwd = 'src/routes') {
const { pages } = create_manifest_data(cwd);
for (let i = 0; i < pages.length; i += 1) {
const page = pages[i];
if (page.pattern.test(pathname)) {
return page.parts[page.parts.length - 1].component.file;
}
}
}

12
src/api/utils/Deferred.ts Normal file
View File

@@ -0,0 +1,12 @@
export default class Deferred {
promise: Promise<any>;
fulfil: (value?: any) => void;
reject: (error: Error) => void;
constructor() {
this.promise = new Promise((fulfil, reject) => {
this.fulfil = fulfil;
this.reject = reject;
});
}
}

View File

@@ -0,0 +1,7 @@
export default function clean_html(html: string) {
return html
.replace(/<!\[CDATA\[[\s\S]*?\]\]>/gm, '')
.replace(/(<script[\s\S]*?>)[\s\S]*?<\/script>/gm, '$1</' + 'script>')
.replace(/(<style[\s\S]*?>)[\s\S]*?<\/style>/gm, '$1</' + 'style>')
.replace(/<!--[\s\S]*?-->/gm, '');
}

View File

@@ -0,0 +1,9 @@
import * as fs from 'fs';
export function copy_shimport(dest: string) {
const shimport_version = require('shimport/package.json').version;
fs.writeFileSync(
`${dest}/client/shimport@${shimport_version}.js`,
fs.readFileSync(require.resolve('shimport/index.js'))
);
}

View File

@@ -0,0 +1,22 @@
import { minify } from 'html-minifier';
export default function minify_html(html: string) {
return minify(html, {
collapseBooleanAttributes: true,
collapseWhitespace: true,
conservativeCollapse: true,
decodeEntities: true,
html5: true,
ignoreCustomComments: [/^#/],
minifyCSS: true,
minifyJS: false,
removeAttributeQuotes: true,
removeComments: true,
removeOptionalTags: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
sortAttributes: true,
sortClassName: true
});
}

1
src/api/utils/noop.ts Normal file
View File

@@ -0,0 +1 @@
export function noop() {}

View File

@@ -0,0 +1,38 @@
import * as fs from 'fs';
export default function validate_bundler(bundler?: 'rollup' | 'webpack') {
if (!bundler) {
bundler = (
fs.existsSync('rollup.config.js') ? 'rollup' :
fs.existsSync('webpack.config.js') ? 'webpack' :
null
);
if (!bundler) {
// TODO remove in a future version
deprecate_dir('rollup');
deprecate_dir('webpack');
throw new Error(`Could not find rollup.config.js or webpack.config.js`);
}
}
if (bundler !== 'rollup' && bundler !== 'webpack') {
throw new Error(`'${bundler}' is not a valid option for --bundler — must be either 'rollup' or 'webpack'`);
}
return bundler;
}
function deprecate_dir(bundler: 'rollup' | 'webpack') {
try {
const stats = fs.statSync(bundler);
if (!stats.isDirectory()) return;
} catch (err) {
// do nothing
return;
}
// TODO link to docs, once those docs exist
throw new Error(`As of Sapper 0.21, build configuration should be placed in a single ${bundler}.config.js file`);
}

285
src/cli.ts Executable file
View File

@@ -0,0 +1,285 @@
import * as fs from 'fs';
import * as path from 'path';
import sade from 'sade';
import colors from 'kleur';
import * as pkg from '../package.json';
import { elapsed, repeat, left_pad, format_milliseconds } from './utils';
import { InvalidEvent, ErrorEvent, FatalEvent, BuildEvent, ReadyEvent } from './interfaces';
const prog = sade('sapper').version(pkg.version);
if (process.argv[2] === 'start') {
// remove this in a future version
console.error(colors.bold.red(`'sapper start' has been removed`));
console.error(`Use 'node [build_dir]' instead`);
process.exit(1);
}
const start = Date.now();
prog.command('dev')
.describe('Start a development server')
.option('-p, --port', 'Specify a port')
.option('-o, --open', 'Open a browser window')
.option('--dev-port', 'Specify a port for development server')
.option('--hot', 'Use hot module replacement (requires webpack)', true)
.option('--live', 'Reload on changes if not using --hot', true)
.option('--bundler', 'Specify a bundler (rollup or webpack)')
.option('--cwd', 'Current working directory', '.')
.option('--src', 'Source directory', 'src')
.option('--routes', 'Routes directory', 'src/routes')
.option('--static', 'Static files directory', 'static')
.option('--output', 'Sapper output directory', '__sapper__')
.option('--build-dir', 'Development build directory', '__sapper__/dev')
.action(async (opts: {
port: number,
open: boolean,
'dev-port': number,
live: boolean,
hot: boolean,
bundler?: 'rollup' | 'webpack',
cwd: string,
src: string,
routes: string,
static: string,
output: string,
'build-dir': string
}) => {
const { dev } = await import('./api/dev');
try {
const watcher = dev({
cwd: opts.cwd,
src: opts.src,
routes: opts.routes,
static: opts.static,
output: opts.output,
dest: opts['build-dir'],
port: opts.port,
'dev-port': opts['dev-port'],
live: opts.live,
hot: opts.hot,
bundler: opts.bundler
});
let first = true;
watcher.on('stdout', data => {
process.stdout.write(data);
});
watcher.on('stderr', data => {
process.stderr.write(data);
});
watcher.on('ready', async (event: ReadyEvent) => {
if (first) {
console.log(colors.bold.cyan(`> Listening on http://localhost:${event.port}`));
if (opts.open) {
const { exec } = await import('child_process');
exec(`open http://localhost:${event.port}`);
}
first = false;
}
});
watcher.on('invalid', (event: InvalidEvent) => {
const changed = event.changed.map(filename => path.relative(process.cwd(), filename)).join(', ');
console.log(`\n${colors.bold.cyan(changed)} changed. rebuilding...`);
});
watcher.on('error', (event: ErrorEvent) => {
console.log(colors.red(`${event.type}`));
console.log(colors.red(event.message));
});
watcher.on('fatal', (event: FatalEvent) => {
console.log(colors.bold.red(`> ${event.message}`));
if (event.log) console.log(event.log);
});
watcher.on('build', (event: BuildEvent) => {
if (event.errors.length) {
console.log(colors.bold.red(`${event.type}`));
event.errors.filter(e => !e.duplicate).forEach(error => {
if (error.file) console.log(colors.bold(error.file));
console.log(error.message);
});
const hidden = event.errors.filter(e => e.duplicate).length;
if (hidden > 0) {
console.log(`${hidden} duplicate ${hidden === 1 ? 'error' : 'errors'} hidden\n`);
}
} else if (event.warnings.length) {
console.log(colors.bold.yellow(`${event.type}`));
event.warnings.filter(e => !e.duplicate).forEach(warning => {
if (warning.file) console.log(colors.bold(warning.file));
console.log(warning.message);
});
const hidden = event.warnings.filter(e => e.duplicate).length;
if (hidden > 0) {
console.log(`${hidden} duplicate ${hidden === 1 ? 'warning' : 'warnings'} hidden\n`);
}
} else {
console.log(`${colors.bold.green(`${event.type}`)} ${colors.gray(`(${format_milliseconds(event.duration)})`)}`);
}
});
} catch (err) {
console.log(colors.bold.red(`> ${err.message}`));
process.exit(1);
}
});
prog.command('build [dest]')
.describe('Create a production-ready version of your app')
.option('-p, --port', 'Default of process.env.PORT', '3000')
.option('--bundler', 'Specify a bundler (rollup or webpack, blank for auto)')
.option('--legacy', 'Create separate legacy build')
.option('--cwd', 'Current working directory', '.')
.option('--src', 'Source directory', 'src')
.option('--routes', 'Routes directory', 'src/routes')
.option('--output', 'Sapper output directory', '__sapper__')
.example(`build custom-dir -p 4567`)
.action(async (dest = '__sapper__/build', opts: {
port: string,
legacy: boolean,
bundler?: 'rollup' | 'webpack',
cwd: string,
src: string,
routes: string,
output: string
}) => {
console.log(`> Building...`);
try {
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, dest);
const launcher = path.resolve(dest, 'index.js');
fs.writeFileSync(launcher, `
// generated by sapper build at ${new Date().toISOString()}
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
process.env.PORT = process.env.PORT || ${opts.port || 3000};
console.log('Starting server on port ' + process.env.PORT);
require('./server/server.js');
`.replace(/^\t+/gm, '').trim());
console.error(`\n> Finished in ${elapsed(start)}. Type ${colors.bold.cyan(`node ${dest}`)} to run the app.`);
} catch (err) {
console.log(`${colors.bold.red(`> ${err.message}`)}`);
process.exit(1);
}
});
prog.command('export [dest]')
.describe('Export your app as static files (if possible)')
.option('--build', '(Re)build app before exporting', true)
.option('--basepath', 'Specify a base path')
.option('--timeout', 'Milliseconds to wait for a page (--no-timeout to disable)', 5000)
.option('--legacy', 'Create separate legacy build')
.option('--bundler', 'Specify a bundler (rollup or webpack, blank for auto)')
.option('--cwd', 'Current working directory', '.')
.option('--src', 'Source directory', 'src')
.option('--routes', 'Routes directory', 'src/routes')
.option('--static', 'Static files directory', 'static')
.option('--output', 'Sapper output directory', '__sapper__')
.option('--build-dir', 'Intermediate build directory', '__sapper__/build')
.action(async (dest = '__sapper__/export', opts: {
build: boolean,
legacy: boolean,
bundler?: 'rollup' | 'webpack',
basepath?: string,
timeout: number | false,
cwd: string,
src: string,
routes: string,
static: string,
output: string,
'build-dir': string,
}) => {
try {
if (opts.build) {
console.log(`> Building...`);
await _build(opts.bundler, opts.legacy, opts.cwd, opts.src, opts.routes, opts.output, opts['build-dir']);
console.error(`\n> Built in ${elapsed(start)}`);
}
const { export: _export } = await import('./api/export');
const { default: pb } = await import('pretty-bytes');
await _export({
cwd: opts.cwd,
static: opts.static,
build_dir: opts['build-dir'],
export_dir: dest,
basepath: opts.basepath,
timeout: opts.timeout,
oninfo: event => {
console.log(colors.bold.cyan(`> ${event.message}`));
},
onfile: event => {
const size_color = event.size > 150000 ? colors.bold.red : event.size > 50000 ? colors.bold.yellow : colors.bold.gray;
const size_label = size_color(left_pad(pb(event.size), 10));
const file_label = event.status === 200
? event.file
: colors.bold[event.status >= 400 ? 'red' : 'yellow'](`(${event.status}) ${event.file}`);
console.log(`${size_label} ${file_label}`);
}
});
console.error(`\n> Finished in ${elapsed(start)}. Type ${colors.bold.cyan(`npx serve ${dest}`)} to run the app.`);
} catch (err) {
console.error(colors.bold.red(`> ${err.message}`));
process.exit(1);
}
});
prog.parse(process.argv);
async function _build(
bundler: 'rollup' | 'webpack',
legacy: boolean,
cwd: string,
src: string,
routes: string,
output: string,
dest: string
) {
const { build } = await import('./api/build');
await build({
bundler,
legacy,
cwd,
src,
routes,
dest,
oncompile: event => {
let banner = `built ${event.type}`;
let c = colors.cyan;
const { warnings } = event.result;
if (warnings.length > 0) {
banner += ` with ${warnings.length} ${warnings.length === 1 ? 'warning' : 'warnings'}`;
c = colors.yellow;
}
console.log();
console.log(c(`┌─${repeat('─', banner.length)}─┐`));
console.log(c(`${colors.bold(banner) }`));
console.log(c(`└─${repeat('─', banner.length)}─┘`));
console.log(event.result.print());
}
});
}

7
src/config/env.ts Normal file
View File

@@ -0,0 +1,7 @@
export let dev: boolean;
export let src: string;
export let dest: string;
export const set_dev = (_: boolean) => dev = _;
export const set_src = (_: string) => src = _;
export const set_dest = (_: string) => dest = _;

53
src/config/rollup.ts Normal file
View File

@@ -0,0 +1,53 @@
import { dev, src, dest } from './env';
export default {
dev,
client: {
input: () => {
return `${src}/client.js`
},
output: () => {
let dir = `${dest}/client`;
if (process.env.SAPPER_LEGACY_BUILD) dir += `/legacy`;
return {
dir,
entryFileNames: '[name].[hash].js',
chunkFileNames: '[name].[hash].js',
format: 'esm',
sourcemap: dev
};
}
},
server: {
input: () => {
return {
server: `${src}/server.js`
};
},
output: () => {
return {
dir: `${dest}/server`,
format: 'cjs',
sourcemap: dev
};
}
},
serviceworker: {
input: () => {
return `${src}/service-worker.js`;
},
output: () => {
return {
file: `${dest}/service-worker.js`,
format: 'iife'
}
}
}
};

55
src/config/webpack.ts Normal file
View File

@@ -0,0 +1,55 @@
import { dev, src, dest } from './env';
export default {
dev,
client: {
entry: () => {
return {
main: `${src}/client`
};
},
output: () => {
return {
path: `${dest}/client`,
filename: '[hash]/[name].js',
chunkFilename: '[hash]/[name].[id].js',
publicPath: `client/`
};
}
},
server: {
entry: () => {
return {
server: `${src}/server`
};
},
output: () => {
return {
path: `${dest}/server`,
filename: '[name].js',
chunkFilename: '[hash]/[name].[id].js',
libraryTarget: 'commonjs2'
};
}
},
serviceworker: {
entry: () => {
return {
'service-worker': `${src}/service-worker`
};
},
output: () => {
return {
path: dest,
filename: '[name].js',
chunkFilename: '[name].[id].[hash].js'
}
}
}
};

3
src/core.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from './core/create_manifests';
export { default as create_compilers } from './core/create_compilers/index';
export { default as create_manifest_data } from './core/create_manifest_data';

View File

@@ -0,0 +1,167 @@
import * as path from 'path';
import relative from 'require-relative';
import { CompileResult } from './interfaces';
import RollupResult from './RollupResult';
let rollup: any;
export default class RollupCompiler {
_: Promise<any>;
_oninvalid: (filename: string) => void;
_start: number;
input: string;
warnings: any[];
errors: any[];
chunks: any[];
css_files: Array<{ id: string, code: string }>;
constructor(config: any) {
this._ = this.get_config(config);
this.input = null;
this.warnings = [];
this.errors = [];
this.chunks = [];
this.css_files = [];
}
async get_config(mod: any) {
// TODO this is hacky, and doesn't need to apply to all three compilers
(mod.plugins || (mod.plugins = [])).push({
name: 'sapper-internal',
options: (opts: any) => {
this.input = opts.input;
},
renderChunk: (code: string, chunk: any) => {
this.chunks.push(chunk);
},
transform: (code: string, id: string) => {
if (/\.css$/.test(id)) {
this.css_files.push({ id, code });
return ``;
}
}
});
const onwarn = mod.onwarn || ((warning: any, handler: (warning: any) => void) => {
handler(warning);
});
mod.onwarn = (warning: any) => {
onwarn(warning, (warning: any) => {
this.warnings.push(warning);
});
};
return mod;
}
oninvalid(cb: (filename: string) => void) {
this._oninvalid = cb;
}
async compile(): Promise<CompileResult> {
const config = await this._;
const start = Date.now();
try {
const bundle = await rollup.rollup(config);
await bundle.write(config.output);
return new RollupResult(Date.now() - start, this);
} catch (err) {
if (err.filename) {
// TODO this is a bit messy. Also, can
// Rollup emit other kinds of error?
err.message = [
`Failed to build — error in ${err.filename}: ${err.message}`,
err.frame
].filter(Boolean).join('\n');
}
throw err;
}
}
async watch(cb: (err?: Error, stats?: any) => void) {
const config = await this._;
const watcher = rollup.watch(config);
watcher.on('change', (id: string) => {
this.chunks = [];
this.warnings = [];
this.errors = [];
this._oninvalid(id);
});
watcher.on('event', (event: any) => {
switch (event.code) {
case 'FATAL':
// TODO kill the process?
if (event.error.filename) {
// TODO this is a bit messy. Also, can
// Rollup emit other kinds of error?
event.error.message = [
`Failed to build — error in ${event.error.filename}: ${event.error.message}`,
event.error.frame
].filter(Boolean).join('\n');
}
cb(event.error);
break;
case 'ERROR':
this.errors.push(event.error);
cb(null, new RollupResult(Date.now() - this._start, this));
break;
case 'START':
case 'END':
// TODO is there anything to do with this info?
break;
case 'BUNDLE_START':
this._start = Date.now();
break;
case 'BUNDLE_END':
cb(null, new RollupResult(Date.now() - this._start, this));
break;
default:
console.log(`Unexpected event ${event.code}`);
}
});
}
static async load_config(cwd: string) {
if (!rollup) rollup = relative('rollup', cwd);
const input = path.resolve(cwd, 'rollup.config.js');
const bundle = await rollup.rollup({
input,
external: (id: string) => {
return (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json';
}
});
const { code } = await bundle.generate({ format: 'cjs' });
// temporarily override require
const defaultLoader = require.extensions['.js'];
require.extensions['.js'] = (module: any, filename: string) => {
if (filename === input) {
module._compile(code, filename);
} else {
defaultLoader(module, filename);
}
};
const config: any = require(input);
delete require.cache[input];
return config;
}
}

View File

@@ -0,0 +1,118 @@
import * as path from 'path';
import colors from 'kleur';
import pb from 'pretty-bytes';
import RollupCompiler from './RollupCompiler';
import extract_css from './extract_css';
import { left_pad } from '../../utils';
import { CompileResult, BuildInfo, CompileError, Chunk, CssFile } from './interfaces';
import { ManifestData, Dirs } from '../../interfaces';
export default class RollupResult implements CompileResult {
duration: number;
errors: CompileError[];
warnings: CompileError[];
chunks: Chunk[];
assets: Record<string, string>;
css_files: CssFile[];
css: {
main: string,
chunks: Record<string, string[]>
};
summary: string;
constructor(duration: number, compiler: RollupCompiler) {
this.duration = duration;
this.errors = compiler.errors.map(munge_warning_or_error);
this.warnings = compiler.warnings.map(munge_warning_or_error); // TODO emit this as they happen
this.chunks = compiler.chunks.map(chunk => ({
file: chunk.fileName,
imports: chunk.imports.filter(Boolean),
modules: Object.keys(chunk.modules)
}));
this.css_files = compiler.css_files;
// TODO populate this properly. We don't have named chunks, as in
// webpack, but we can have a route -> [chunk] map or something
this.assets = {};
if (typeof compiler.input === 'string') {
compiler.chunks.forEach(chunk => {
if (compiler.input in chunk.modules) {
this.assets.main = chunk.fileName;
}
});
} else {
for (const name in compiler.input) {
const file = compiler.input[name];
this.assets[name] = compiler.chunks.find(chunk => file in chunk.modules).fileName;
}
}
this.summary = compiler.chunks.map(chunk => {
const size_color = chunk.code.length > 150000 ? colors.bold.red : chunk.code.length > 50000 ? colors.bold.yellow : colors.bold.white;
const size_label = left_pad(pb(chunk.code.length), 10);
const lines = [size_color(`${size_label} ${chunk.fileName}`)];
const deps = Object.keys(chunk.modules)
.map(file => {
return {
file: path.relative(process.cwd(), file),
size: chunk.modules[file].renderedLength
};
})
.filter(dep => dep.size > 0)
.sort((a, b) => b.size - a.size);
const total_unminified = deps.reduce((t, d) => t + d.size, 0);
deps.forEach((dep, i) => {
const c = i === deps.length - 1 ? '└' : '│';
let line = ` ${c} ${dep.file}`;
if (deps.length > 1) {
const p = (100 * dep.size / total_unminified).toFixed(1);
line += ` (${p}%)`;
}
lines.push(colors.gray(line));
});
return lines.join('\n');
}).join('\n');
}
to_json(manifest_data: ManifestData, dirs: Dirs): BuildInfo {
// TODO extract_css has side-effects that don't belong
// in a method called to_json
return {
bundler: 'rollup',
shimport: require('shimport/package.json').version,
assets: this.assets,
css: extract_css(this, manifest_data.components, dirs)
};
}
print() {
const blocks: string[] = this.warnings.map(warning => {
return warning.file
? `> ${colors.bold(warning.file)}\n${warning.message}`
: `> ${warning.message}`;
});
blocks.push(this.summary);
return blocks.join('\n\n');
}
}
function munge_warning_or_error(warning_or_error: any) {
return {
file: warning_or_error.filename,
message: [warning_or_error.message, warning_or_error.frame].filter(Boolean).join('\n')
};
}

View File

@@ -0,0 +1,46 @@
import relative from 'require-relative';
import { CompileResult } from './interfaces';
import WebpackResult from './WebpackResult';
let webpack: any;
export class WebpackCompiler {
_: any;
constructor(config: any) {
if (!webpack) webpack = relative('webpack', process.cwd());
this._ = webpack(config);
}
oninvalid(cb: (filename: string) => void) {
this._.hooks.invalid.tap('sapper', cb);
}
compile(): Promise<CompileResult> {
return new Promise((fulfil, reject) => {
this._.run((err: Error, stats: any) => {
if (err) {
reject(err);
process.exit(1);
}
const result = new WebpackResult(stats);
if (result.errors.length) {
console.error(stats.toString({ colors: true }));
reject(new Error(`Encountered errors while building app`));
}
else {
fulfil(result);
}
});
});
}
watch(cb: (err?: Error, stats?: any) => void) {
this._.watch({}, (err?: Error, stats?: any) => {
cb(err, stats && new WebpackResult(stats));
});
}
}

View File

@@ -0,0 +1,73 @@
import format_messages from 'webpack-format-messages';
import { CompileResult, BuildInfo, CompileError, Chunk, CssFile } from './interfaces';
import { ManifestData, Dirs } from '../../interfaces';
const locPattern = /\((\d+):(\d+)\)$/;
function munge_warning_or_error(message: string) {
// TODO this is all a bit rube goldberg...
const lines = message.split('\n');
const file = lines.shift()
.replace('', '') // careful — there is a special character at the beginning of this string
.replace('', '')
.replace('./', '');
let line = null;
let column = null;
const match = locPattern.exec(lines[0]);
if (match) {
lines[0] = lines[0].replace(locPattern, '');
line = +match[1];
column = +match[2];
}
return {
file,
message: lines.join('\n')
};
}
export default class WebpackResult implements CompileResult {
duration: number;
errors: CompileError[];
warnings: CompileError[];
chunks: Chunk[];
assets: Record<string, string>;
css_files: CssFile[];
stats: any;
constructor(stats: any) {
this.stats = stats;
const info = stats.toJson();
const messages = format_messages(stats);
this.errors = messages.errors.map(munge_warning_or_error);
this.warnings = messages.warnings.map(munge_warning_or_error);
this.duration = info.time;
this.chunks = info.assets.map((chunk: { name: string }) => ({ file: chunk.name }));
this.assets = info.assetsByChunkName;
}
to_json(manifest_data: ManifestData, dirs: Dirs): BuildInfo {
return {
bundler: 'webpack',
shimport: null, // webpack has its own loader
assets: this.assets,
css: {
// TODO
main: null,
chunks: {}
}
};
}
print() {
return this.stats.toString({ colors: true });
}
}

View File

@@ -0,0 +1,243 @@
import * as fs from 'fs';
import * as path from 'path';
import hash from 'string-hash';
import * as codec from 'sourcemap-codec';
import { PageComponent, Dirs } from '../../interfaces';
import { CompileResult, Chunk } from './interfaces';
import { posixify } from '../../utils'
const inline_sourcemap_header = 'data:application/json;charset=utf-8;base64,';
function extract_sourcemap(raw: string, id: string) {
let raw_map: string;
let map = null;
const code = raw.replace(/\/\*#\s+sourceMappingURL=(.+)\s+\*\//g, (m, url) => {
if (raw_map) {
// TODO should not happen!
throw new Error(`Found multiple sourcemaps in single CSS file (${id})`);
}
raw_map = url;
return '';
}).trim();
if (raw_map) {
if (raw_map.startsWith(inline_sourcemap_header)) {
const json = Buffer.from(raw_map.slice(inline_sourcemap_header.length), 'base64').toString();
map = JSON.parse(json);
} else {
// TODO do we want to handle non-inline sourcemaps? could be a rabbit hole
}
}
return {
code,
map
};
}
type SourceMap = {
version: 3;
file: string;
sources: string[];
sourcesContent: string[];
names: string[];
mappings: string;
};
function get_css_from_modules(modules: string[], css_map: Map<string, string>, dirs: Dirs) {
const parts: string[] = [];
const mappings: number[][][] = [];
const combined_map: SourceMap = {
version: 3,
file: null,
sources: [],
sourcesContent: [],
names: [],
mappings: null
};
modules.forEach(module => {
if (!/\.css$/.test(module)) return;
const css = css_map.get(module);
const { code, map } = extract_sourcemap(css, module);
parts.push(code);
if (map) {
const lines = codec.decode(map.mappings);
if (combined_map.sources.length > 0 || combined_map.names.length > 0) {
lines.forEach(line => {
line.forEach(segment => {
// adjust source index
segment[1] += combined_map.sources.length;
// adjust name index
if (segment[4]) segment[4] += combined_map.names.length;
});
});
}
combined_map.sources.push(...map.sources);
combined_map.sourcesContent.push(...map.sourcesContent);
combined_map.names.push(...map.names);
mappings.push(...lines);
}
});
if (parts.length > 0) {
combined_map.mappings = codec.encode(mappings);
combined_map.sources = combined_map.sources.map(source => path.relative(`${dirs.dest}/client`, source));
return {
code: parts.join('\n'),
map: combined_map
};
}
return null;
}
export default function extract_css(client_result: CompileResult, components: PageComponent[], dirs: Dirs) {
const result: {
main: string | null;
chunks: Record<string, string[]>
} = {
main: null,
chunks: {}
};
if (!client_result.css_files) return; // Rollup-only for now
let asset_dir = `${dirs.dest}/client`;
if (process.env.SAPPER_LEGACY_BUILD) asset_dir += '/legacy';
const unclaimed = new Set(client_result.css_files.map(x => x.id));
const lookup = new Map();
client_result.chunks.forEach(chunk => {
lookup.set(chunk.file, chunk);
});
const css_map = new Map();
client_result.css_files.forEach(css_module => {
css_map.set(css_module.id, css_module.code);
});
const chunks_with_css = new Set();
// concatenate and emit CSS
client_result.chunks.forEach(chunk => {
const css_modules = chunk.modules.filter(m => css_map.has(m));
if (!css_modules.length) return;
const css = get_css_from_modules(css_modules, css_map, dirs);
const { code, map } = css;
const output_file_name = chunk.file.replace(/\.js$/, '.css');
map.file = output_file_name;
map.sources = map.sources.map(source => path.relative(`${asset_dir}`, source));
fs.writeFileSync(`${asset_dir}/${output_file_name}`, `${code}\n/* sourceMappingURL=./${output_file_name}.map */`);
fs.writeFileSync(`${asset_dir}/${output_file_name}.map`, JSON.stringify(map, null, ' '));
chunks_with_css.add(chunk);
});
const entry = path.resolve(dirs.src, 'client.js');
const entry_chunk = client_result.chunks.find(chunk => chunk.modules.indexOf(entry) !== -1);
const entry_chunk_dependencies: Set<Chunk> = new Set([entry_chunk]);
const entry_css_modules: string[] = [];
// recursively find the chunks this component depends on
entry_chunk_dependencies.forEach(chunk => {
chunk.imports.forEach(file => {
entry_chunk_dependencies.add(lookup.get(file));
});
if (chunks_with_css.has(chunk)) {
chunk.modules.forEach(file => {
unclaimed.delete(file);
if (css_map.has(file)) {
entry_css_modules.push(file);
}
});
}
});
// figure out which (css-having) chunks each component depends on
components.forEach(component => {
const resolved = path.resolve(dirs.routes, component.file);
const chunk: Chunk = client_result.chunks.find(chunk => chunk.modules.indexOf(resolved) !== -1);
if (!chunk) {
// this should never happen!
throw new Error(`Could not find chunk that owns ${component.file}`);
}
const chunk_dependencies: Set<Chunk> = new Set([chunk]);
const css_dependencies: string[] = [];
// recursively find the chunks this component depends on
chunk_dependencies.forEach(chunk => {
chunk.imports.forEach(file => {
chunk_dependencies.add(lookup.get(file));
});
if (chunks_with_css.has(chunk)) {
css_dependencies.push(chunk.file.replace(/\.js$/, '.css'));
chunk.modules.forEach(file => {
unclaimed.delete(file);
});
}
});
result.chunks[component.file] = css_dependencies;
});
fs.readdirSync(asset_dir).forEach(file => {
if (fs.statSync(`${asset_dir}/${file}`).isDirectory()) return;
const source = fs.readFileSync(`${asset_dir}/${file}`, 'utf-8');
const replaced = source.replace(/["']__SAPPER_CSS_PLACEHOLDER:(.+?)__["']/g, (m, route) => {
return JSON.stringify(result.chunks[route]);
});
fs.writeFileSync(`${asset_dir}/${file}`, replaced);
});
unclaimed.forEach(file => {
entry_css_modules.push(file);
});
const leftover = get_css_from_modules(entry_css_modules, css_map, dirs);
if (leftover) {
const { code, map } = leftover;
const main_hash = hash(code);
const output_file_name = `main.${main_hash}.css`;
map.file = output_file_name;
map.sources = map.sources.map(source => path.relative(asset_dir, source));
fs.writeFileSync(`${asset_dir}/${output_file_name}`, `${code}\n/* sourceMappingURL=client/${output_file_name}.map */`);
fs.writeFileSync(`${asset_dir}/${output_file_name}.map`, JSON.stringify(map, null, ' '));
result.main = output_file_name;
}
return result;
}

View File

@@ -0,0 +1,72 @@
import * as path from 'path';
import RollupCompiler from './RollupCompiler';
import { WebpackCompiler } from './WebpackCompiler';
import { set_dev, set_src, set_dest } from '../../config/env';
export type Compiler = RollupCompiler | WebpackCompiler;
export type Compilers = {
client: Compiler;
server: Compiler;
serviceworker?: Compiler;
}
export default async function create_compilers(
bundler: 'rollup' | 'webpack',
cwd: string,
src: string,
dest: string,
dev: boolean
): Promise<Compilers> {
set_dev(dev);
set_src(src);
set_dest(dest);
if (bundler === 'rollup') {
const config = await RollupCompiler.load_config(cwd);
validate_config(config, 'rollup');
normalize_rollup_config(config.client);
normalize_rollup_config(config.server);
if (config.serviceworker) {
normalize_rollup_config(config.serviceworker);
}
return {
client: new RollupCompiler(config.client),
server: new RollupCompiler(config.server),
serviceworker: config.serviceworker && new RollupCompiler(config.serviceworker)
};
}
if (bundler === 'webpack') {
const config = require(path.resolve(cwd, 'webpack.config.js'));
validate_config(config, 'webpack');
return {
client: new WebpackCompiler(config.client),
server: new WebpackCompiler(config.server),
serviceworker: config.serviceworker && new WebpackCompiler(config.serviceworker)
};
}
// this shouldn't be possible...
throw new Error(`Invalid bundler option '${bundler}'`);
}
function validate_config(config: any, bundler: 'rollup' | 'webpack') {
if (!config.client || !config.server) {
throw new Error(`${bundler}.config.js must export a { client, server, serviceworker? } object`);
}
}
function normalize_rollup_config(config: any) {
if (typeof config.input === 'string') {
config.input = path.normalize(config.input);
} else {
for (const name in config.input) {
config.input[name] = path.normalize(config.input[name]);
}
}
}

View File

@@ -0,0 +1,39 @@
import { ManifestData, Dirs } from '../../interfaces';
export type Chunk = {
file: string;
imports: string[];
modules: string[];
}
export type CssFile = {
id: string;
code: string;
};
export class CompileError {
file: string;
message: string;
}
export interface CompileResult {
duration: number;
errors: CompileError[];
warnings: CompileError[];
chunks: Chunk[];
assets: Record<string, string>;
css_files: CssFile[];
to_json: (manifest_data: ManifestData, dirs: Dirs) => BuildInfo
}
export type BuildInfo = {
bundler: string;
shimport: string;
assets: Record<string, string>;
legacy_assets?: Record<string, string>;
css: {
main: string | null,
chunks: Record<string, string[]>
}
}

View File

@@ -0,0 +1,306 @@
import * as fs from 'fs';
import * as path from 'path';
import { Page, PageComponent, ServerRoute, ManifestData } from '../interfaces';
import { posixify, reserved_words } from '../utils';
export default function create_manifest_data(cwd: string): ManifestData {
// TODO remove in a future version
if (!fs.existsSync(cwd)) {
throw new Error(`As of Sapper 0.21, the routes/ directory should become src/routes/`);
}
const components: PageComponent[] = [];
const pages: Page[] = [];
const server_routes: ServerRoute[] = [];
const default_layout: PageComponent = {
default: true,
name: '_default_layout',
file: null
};
function walk(
dir: string,
parent_segments: Part[][],
parent_params: string[],
stack: Array<{
component: PageComponent,
params: string[]
}>
) {
const items = fs.readdirSync(dir)
.map(basename => {
const resolved = path.join(dir, basename);
const file = path.relative(cwd, resolved);
const is_dir = fs.statSync(resolved).isDirectory();
const ext = path.extname(basename);
if (!is_dir && !/^\.[a-z]+$/i.test(ext)) return null; // filter out tmp files etc
const segment = is_dir
? basename
: basename.slice(0, -path.extname(basename).length);
const parts = get_parts(segment);
const is_index = is_dir ? false : basename.startsWith('index.');
const is_page = ext === '.html';
parts.forEach(part => {
if (/\]\[/.test(part.content)) {
throw new Error(`Invalid route ${file} — parameters must be separated`);
}
if (part.qualifier && /[\(\)\?\:]/.test(part.qualifier.slice(1, -1))) {
throw new Error(`Invalid route ${file} — cannot use (, ), ? or : in route qualifiers`);
}
});
return {
basename,
parts,
file: posixify(file),
is_dir,
is_index,
is_page
};
})
.filter(Boolean)
.sort(comparator);
items.forEach(item => {
if (item.basename[0] === '_') return;
if (item.basename[0] === '.') {
if (item.file !== '.well-known') return;
}
const segments = parent_segments.slice();
if (item.is_index && segments.length > 0) {
const last_segment = segments[segments.length - 1].slice();
const suffix = item.basename
.slice(0, -path.extname(item.basename).length).
replace('index', '');
if (suffix) {
const last_part = last_segment[last_segment.length - 1];
if (last_part.dynamic) {
last_segment.push({ dynamic: false, content: suffix });
} else {
last_segment[last_segment.length - 1] = {
dynamic: false,
content: `${last_part.content}${suffix}`
};
}
segments[segments.length - 1] = last_segment;
}
} else {
segments.push(item.parts);
}
const params = parent_params.slice();
params.push(...item.parts.filter(p => p.dynamic).map(p => p.content));
if (item.is_dir) {
const index = path.join(dir, item.basename, '_layout.html');
const component = fs.existsSync(index) && {
name: `${get_slug(item.file)}__layout`,
file: `${item.file}/_layout.html`
};
if (component) components.push(component);
walk(
path.join(dir, item.basename),
segments,
params,
component
? stack.concat({ component, params })
: stack.concat(null)
);
}
else if (item.is_page) {
const component = {
name: get_slug(item.file),
file: item.file
};
const parts = stack.concat({
component,
params
});
components.push(component);
if (item.basename === 'index.html') {
pages.push({
pattern: get_pattern(parent_segments, true),
parts
});
} else {
pages.push({
pattern: get_pattern(segments, true),
parts
});
}
}
else {
server_routes.push({
name: `route_${get_slug(item.file)}`,
pattern: get_pattern(segments, false),
file: item.file,
params: params
});
}
});
}
const root_file = path.join(cwd, '_layout.html');
const root = fs.existsSync(root_file)
? {
name: 'main',
file: '_layout.html'
}
: default_layout;
walk(cwd, [], [], []);
// check for clashes
const seen_pages: Map<string, Page> = new Map();
pages.forEach(page => {
const pattern = page.pattern.toString();
if (seen_pages.has(pattern)) {
const file = page.parts.pop().component.file;
const other_page = seen_pages.get(pattern);
const other_file = other_page.parts.pop().component.file;
throw new Error(`The ${other_file} and ${file} pages clash`);
}
seen_pages.set(pattern, page);
});
const seen_routes: Map<string, ServerRoute> = new Map();
server_routes.forEach(route => {
const pattern = route.pattern.toString();
if (seen_routes.has(pattern)) {
const other_route = seen_routes.get(pattern);
throw new Error(`The ${other_route.file} and ${route.file} routes clash`);
}
seen_routes.set(pattern, route);
});
return {
root,
components,
pages,
server_routes
};
}
type Part = {
content: string;
dynamic: boolean;
qualifier?: string;
};
function comparator(
a: { basename: string, parts: Part[], file: string, is_index: boolean },
b: { basename: string, parts: Part[], file: string, is_index: boolean }
) {
if (a.is_index !== b.is_index) return a.is_index ? -1 : 1;
const max = Math.max(a.parts.length, b.parts.length);
for (let i = 0; i < max; i += 1) {
const a_sub_part = a.parts[i];
const b_sub_part = b.parts[i];
if (!a_sub_part) return 1; // b is more specific, so goes first
if (!b_sub_part) return -1;
if (a_sub_part.dynamic !== b_sub_part.dynamic) {
return a_sub_part.dynamic ? 1 : -1;
}
if (!a_sub_part.dynamic && a_sub_part.content !== b_sub_part.content) {
return (
(b_sub_part.content.length - a_sub_part.content.length) ||
(a_sub_part.content < b_sub_part.content ? -1 : 1)
);
}
// If both parts dynamic, check for regexp patterns
if (a_sub_part.dynamic && b_sub_part.dynamic) {
const regexp_pattern = /\((.*?)\)/;
const a_match = regexp_pattern.exec(a_sub_part.content);
const b_match = regexp_pattern.exec(b_sub_part.content);
if (!a_match && b_match) {
return 1; // No regexp, so less specific than b
}
if (!b_match && a_match) {
return -1;
}
if (a_match && b_match && a_match[1] !== b_match[1]) {
return b_match[1].length - a_match[1].length;
}
}
}
}
function get_parts(part: string): Part[] {
return part.split(/\[(.+)\]/)
.map((str, i) => {
if (!str) return null;
const dynamic = i % 2 === 1;
const [, content, qualifier] = dynamic
? /([^(]+)(\(.+\))?$/.exec(str)
: [, str, null];
return {
content,
dynamic,
qualifier
};
})
.filter(Boolean);
}
function get_slug(file: string) {
let name = file
.replace(/[\\\/]index/, '')
.replace(/_default([\/\\index])?\.html$/, 'index')
.replace(/[\/\\]/g, '_')
.replace(/\.\w+$/, '')
.replace(/\[([^(]+)(?:\([^(]+\))?\]/, '$$$1')
.replace(/[^a-zA-Z0-9_$]/g, c => {
return c === '.' ? '_' : `$${c.charCodeAt(0)}`
});
if (reserved_words.has(name)) name += '_';
return name;
}
function get_pattern(segments: Part[][], add_trailing_slash: boolean) {
return new RegExp(
`^` +
segments.map(segment => {
return '\\/' + segment.map(part => {
return part.dynamic
? part.qualifier || '([^\\/]+?)'
: encodeURI(part.content.normalize())
.replace(/\?/g, '%3F')
.replace(/#/g, '%23')
.replace(/%5B/g, '[')
.replace(/%5D/g, ']');
}).join('');
}).join('') +
(add_trailing_slash ? '\\\/?$' : '$')
);
}

View File

@@ -0,0 +1,245 @@
import * as fs from 'fs';
import * as path from 'path';
import { posixify, stringify, walk, write_if_changed } from '../utils';
import { Page, PageComponent, ManifestData } from '../interfaces';
export function create_main_manifests({
bundler,
manifest_data,
dev_port,
dev,
cwd,
src,
dest,
routes,
output
}: {
bundler: string,
manifest_data: ManifestData;
dev_port?: number;
dev: boolean;
cwd: string;
src: string;
dest: string;
routes: string;
output: string
}) {
if (!fs.existsSync(output)) fs.mkdirSync(output);
const path_to_routes = path.relative(output, routes);
const client_manifest = generate_client(manifest_data, path_to_routes, bundler, dev, dev_port);
const server_manifest = generate_server(manifest_data, path_to_routes, cwd, src, dest, dev);
write_if_changed(
`${output}/_layout.html`,
`<svelte:component this={child.component} {...child.props}/>`
);
write_if_changed(`${output}/client.js`, client_manifest);
write_if_changed(`${output}/server.js`, server_manifest);
}
export function create_serviceworker_manifest({ manifest_data, output, client_files, static_files }: {
manifest_data: ManifestData;
output: string;
client_files: string[];
static_files: string;
}) {
let files: string[];
if (fs.existsSync(static_files)) {
files = walk(static_files);
} else {
// TODO remove in a future version
if (fs.existsSync('assets')) {
throw new Error(`As of Sapper 0.21, the assets/ directory should become static/`);
}
files = [];
}
let code = `
// This file is generated by Sapper — do not edit it!
export const timestamp = ${Date.now()};
export const files = [\n\t${files.map((x: string) => stringify(x)).join(',\n\t')}\n];
export { files as assets }; // legacy
export const shell = [\n\t${client_files.map((x: string) => stringify(x)).join(',\n\t')}\n];
export const routes = [\n\t${manifest_data.pages.map((r: Page) => `{ pattern: ${r.pattern} }`).join(',\n\t')}\n];
`.replace(/^\t\t/gm, '').trim();
write_if_changed(`${output}/service-worker.js`, code);
}
function generate_client(
manifest_data: ManifestData,
path_to_routes: string,
bundler: string,
dev: boolean,
dev_port?: number
) {
const template_file = path.resolve(__dirname, '../templates/client.js');
const template = fs.readFileSync(template_file, 'utf-8');
const page_ids = new Set(manifest_data.pages.map(page =>
page.pattern.toString()));
const server_routes_to_ignore = manifest_data.server_routes.filter(route =>
!page_ids.has(route.pattern.toString()));
const component_indexes: Record<string, number> = {};
const components = `[
${manifest_data.components.map((component, i) => {
const annotation = bundler === 'webpack'
? `/* webpackChunkName: "${component.name}" */ `
: '';
const source = get_file(path_to_routes, component);
component_indexes[component.name] = i;
return `{
js: () => import(${annotation}${stringify(source)}),
css: "__SAPPER_CSS_PLACEHOLDER:${stringify(component.file, false)}__"
}`;
}).join(',\n\t\t')}
]`.replace(/^\t/gm, '').trim();
let needs_decode = false;
let pages = `[
${manifest_data.pages.map(page => `{
// ${page.parts[page.parts.length - 1].component.file}
pattern: ${page.pattern},
parts: [
${page.parts.map(part => {
if (part === null) return 'null';
if (part.params.length > 0) {
needs_decode = true;
const props = part.params.map((param, i) => `${param}: d(match[${i + 1}])`);
return `{ i: ${component_indexes[part.component.name]}, params: match => ({ ${props.join(', ')} }) }`;
}
return `{ i: ${component_indexes[part.component.name]} }`;
}).join(',\n\t\t\t\t')}
]
}`).join(',\n\n\t\t')}
]`.replace(/^\t/gm, '').trim();
if (needs_decode) {
pages = `(d => ${pages})(decodeURIComponent)`
}
let footer = '';
if (dev) {
const sapper_dev_client = posixify(
path.resolve(__dirname, '../sapper-dev-client.js')
);
footer = `
if (typeof window !== 'undefined') {
import(${stringify(sapper_dev_client)}).then(client => {
client.connect(${dev_port});
});
}`.replace(/^\t{3}/gm, '');
}
return `// This file is generated by Sapper — do not edit it!\n` + template
.replace('__ROOT__', stringify(get_file(path_to_routes, manifest_data.root), false))
.replace('__ERROR__', stringify(posixify(`${path_to_routes}/_error.html`), false))
.replace('__IGNORE__', `[${server_routes_to_ignore.map(route => route.pattern).join(', ')}]`)
.replace('__COMPONENTS__', components)
.replace('__PAGES__', pages) +
footer;
}
function generate_server(
manifest_data: ManifestData,
path_to_routes: string,
cwd: string,
src: string,
dest: string,
dev: boolean
) {
const template_file = path.resolve(__dirname, '../templates/server.js');
const template = fs.readFileSync(template_file, 'utf-8');
const imports = [].concat(
manifest_data.server_routes.map(route =>
`import * as __${route.name} from ${stringify(posixify(`${path_to_routes}/${route.file}`))};`),
manifest_data.components.map(component =>
`import __${component.name} from ${stringify(get_file(path_to_routes, component))};`),
`import root from ${stringify(get_file(path_to_routes, manifest_data.root))};`,
`import error from ${stringify(posixify(`${path_to_routes}/_error.html`))};`
);
let code = `
${imports.join('\n')}
const d = decodeURIComponent;
export const manifest = {
server_routes: [
${manifest_data.server_routes.map(route => `{
// ${route.file}
pattern: ${route.pattern},
handlers: __${route.name},
params: ${route.params.length > 0
? `match => ({ ${route.params.map((param, i) => `${param}: d(match[${i + 1}])`).join(', ')} })`
: `() => ({})`}
}`).join(',\n\n\t\t\t\t')}
],
pages: [
${manifest_data.pages.map(page => `{
// ${page.parts[page.parts.length - 1].component.file}
pattern: ${page.pattern},
parts: [
${page.parts.map(part => {
if (part === null) return 'null';
const props = [
`name: "${part.component.name}"`,
`file: ${stringify(part.component.file)}`,
`component: __${part.component.name}`
];
if (part.params.length > 0) {
const params = part.params.map((param, i) => `${param}: d(match[${i + 1}])`);
props.push(`params: match => ({ ${params.join(', ')} })`);
}
return `{ ${props.join(', ')} }`;
}).join(',\n\t\t\t\t\t\t')}
]
}`).join(',\n\n\t\t\t\t')}
],
root,
error
};`.replace(/^\t\t/gm, '').trim();
const build_dir = posixify(path.relative(cwd, dest));
const src_dir = posixify(path.relative(cwd, src));
return `// This file is generated by Sapper — do not edit it!\n` + template
.replace('__BUILD__DIR__', JSON.stringify(build_dir))
.replace('__SRC__DIR__', JSON.stringify(src_dir))
.replace('__DEV__', dev ? 'true' : 'false')
.replace(/const manifest = __MANIFEST__;/, code);
}
function get_file(path_to_routes: string, component: PageComponent) {
if (component.default) {
return `./_layout.html`;
}
return posixify(`${path_to_routes}/${component.file}`);
}

16
src/core/read_template.ts Normal file
View File

@@ -0,0 +1,16 @@
import * as fs from 'fs';
export default function read_template(dir: string) {
try {
return fs.readFileSync(`${dir}/template.html`, 'utf-8');
} catch (err) {
if (fs.existsSync(`app/template.html`)) {
throw new Error(`As of Sapper 0.21, the default folder structure has been changed:
app/ --> src/
routes/ --> src/routes/
assets/ --> static/`);
}
throw err;
}
}

100
src/interfaces.ts Normal file
View File

@@ -0,0 +1,100 @@
import * as child_process from 'child_process';
import { CompileResult } from './core/create_compilers/interfaces';
export type Route = {
id: string;
handlers: {
type: 'page' | 'route';
file: string;
}[];
pattern: RegExp;
test: (url: string) => boolean;
exec: (url: string) => Record<string, string>;
parts: string[];
params: string[];
};
export type Template = {
render: (data: Record<string, string>) => string;
stream: (req, res, data: Record<string, string | Promise<string>>) => void;
};
export type Store = {
get: () => any;
};
export type PageComponent = {
default?: boolean;
name: string;
file: string;
};
export type Page = {
pattern: RegExp;
parts: Array<{
component: PageComponent;
params: string[];
}>
};
export type ServerRoute = {
name: string;
pattern: RegExp;
file: string;
params: string[];
};
export type Dirs = {
dest: string,
src: string,
routes: string
};
export type ManifestData = {
root: PageComponent;
components: PageComponent[];
pages: Page[];
server_routes: ServerRoute[];
};
export type ReadyEvent = {
port: number;
process: child_process.ChildProcess;
};
export type ErrorEvent = {
type: string;
message: string;
};
export type FatalEvent = {
message: string;
};
export type InvalidEvent = {
changed: string[];
invalid: {
client: boolean;
server: boolean;
serviceworker: boolean;
}
};
export type BuildEvent = {
type: string;
errors: Array<{ file: string, message: string, duplicate: boolean }>;
warnings: Array<{ file: string, message: string, duplicate: boolean }>;
duration: number;
result: CompileResult;
};
export type FileEvent = {
file: string;
size: number;
};
export type FailureEvent = {
};
export type DoneEvent = {};

View File

@@ -1,221 +0,0 @@
import { detach, findAnchor, scroll_state, which } from './utils';
import { Component, ComponentConstructor, Params, Query, Route, RouteData, ScrollPosition } from './interfaces';
export let component: Component;
let target: Node;
let routes: Route[];
const history = typeof window !== 'undefined' ? window.history : {
pushState: (state: any, title: string, href: string) => {},
replaceState: (state: any, title: string, href: string) => {},
scrollRestoration: ''
};
const scroll_history: Record<string, ScrollPosition> = {};
let uid = 1;
let cid: number;
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
}
function select_route(url: URL): { route: Route, data: RouteData } {
if (url.origin !== window.location.origin) return null;
for (const route of routes) {
const match = route.pattern.exec(url.pathname);
if (match) {
const params = route.params(match);
const query: Record<string, string | true> = {};
for (const [key, value] of url.searchParams) query[key] = value || true;
return { route, data: { params, query } };
}
}
}
let current_token: {};
function render(Component: ComponentConstructor, data: any, scroll: ScrollPosition, token: {}) {
if (current_token !== token) return;
if (component) {
component.destroy();
} else {
// first load — remove SSR'd <head> contents
const start = document.querySelector('#sapper-head-start');
const end = document.querySelector('#sapper-head-end');
if (start && end) {
while (start.nextSibling !== end) detach(start.nextSibling);
detach(start);
detach(end);
}
// preload additional routes
routes.reduce((promise: Promise<any>, route) => promise.then(route.load), Promise.resolve());
}
component = new Component({
target,
data,
hydrate: !!component
});
if (scroll) {
window.scrollTo(scroll.x, scroll.y);
}
}
function prepare_route(Component, data) {
return Promise.resolve(
Component.preload ? Component.preload(data) : {}
).then(preloaded => {
Object.assign(data, preloaded)
return { Component, data };
});
}
function navigate(url: URL, id: number) {
const selected = select_route(url);
if (selected) {
if (id) {
// popstate or initial navigation
cid = id;
} else {
// clicked on a link. preserve scroll state
scroll_history[cid] = scroll_state();
id = cid = ++uid;
scroll_history[cid] = { x: 0, y: 0 };
}
const loaded = prefetching && prefetching.href === url.href ?
prefetching.promise :
selected.route.load().then(mod => prepare_route(mod.default, selected.data));
prefetching = null;
const token = current_token = {};
loaded.then(({ Component, data }) => {
render(Component, data, scroll_history[id], token);
});
cid = id;
return true;
}
}
function handle_click(event: MouseEvent) {
// Adapted from https://github.com/visionmedia/page.js
// MIT license https://github.com/visionmedia/page.js#license
if (which(event) !== 1) return;
if (event.metaKey || event.ctrlKey || event.shiftKey) return;
if (event.defaultPrevented) return;
const a: HTMLAnchorElement | SVGAElement = <HTMLAnchorElement | SVGAElement>findAnchor(<Node>event.target);
if (!a) return;
// check if link is inside an svg
// in this case, both href and target are always inside an object
const svg = typeof a.href === 'object' && a.href.constructor.name === 'SVGAnimatedString';
const href = String(svg ? (<SVGAElement>a).href.baseVal : a.href);
if (href === window.location.href) {
event.preventDefault();
return;
}
// Ignore if tag has
// 1. 'download' attribute
// 2. rel='external' attribute
if (a.hasAttribute('download') || a.getAttribute('rel') === 'external') return;
// Ignore if <a> has a target
if (svg ? (<SVGAElement>a).target.baseVal : a.target) return;
const url = new URL(href);
// Don't handle hash changes
if (url.pathname === window.location.pathname && url.search === window.location.search) return;
if (navigate(url, null)) {
event.preventDefault();
history.pushState({ id: cid }, '', url.href);
}
}
function handle_popstate(event: PopStateEvent) {
scroll_history[cid] = scroll_state();
if (event.state) {
navigate(new URL(window.location.href), event.state.id);
} else {
// hashchange
cid = ++uid;
history.replaceState({ id: cid }, '', window.location.href);
}
}
let prefetching: {
href: string;
promise: Promise<{ Component: ComponentConstructor, data: any }>;
} = null;
export function prefetch(href: string) {
const selected = select_route(new URL(href));
if (selected) {
prefetching = {
href,
promise: selected.route.load().then(mod => prepare_route(mod.default, selected.data))
};
}
}
function handle_touchstart_mouseover(event: MouseEvent | TouchEvent) {
const a: HTMLAnchorElement = <HTMLAnchorElement>findAnchor(<Node>event.target);
if (!a || a.rel !== 'prefetch') return;
prefetch(a.href);
}
let inited: boolean;
export function init(_target: Node, _routes: Route[]) {
target = _target;
routes = _routes;
if (!inited) { // this check makes HMR possible
window.addEventListener('click', handle_click);
window.addEventListener('popstate', handle_popstate);
// prefetch
window.addEventListener('touchstart', handle_touchstart_mouseover);
window.addEventListener('mouseover', handle_touchstart_mouseover);
inited = true;
}
setTimeout(() => {
const { hash, href } = window.location;
const deep_linked = hash && document.querySelector(hash);
scroll_history[uid] = deep_linked ?
{ x: 0, y: deep_linked.getBoundingClientRect().top } :
scroll_state();
history.replaceState({ id: uid }, '', href);
navigate(new URL(window.location.href), uid);
});
}
export function goto(href: string, opts = { replaceState: false }) {
if (navigate(new URL(href, window.location.href), null)) {
if (history) history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', href);
} else {
window.location.href = href;
}
}

View File

@@ -1,23 +0,0 @@
export type Params = Record<string, string>;
export type Query = Record<string, string | true>;
export type RouteData = { params: Params, query: Query };
export interface ComponentConstructor {
new (options: { target: Node, data: any, hydrate: boolean }): Component;
preload: (data: { params: Params, query: Query }) => Promise<any>;
};
export interface Component {
destroy: () => void;
}
export type Route = {
pattern: RegExp;
params: (match: RegExpExecArray) => Record<string, string>;
load: () => Promise<{ default: ComponentConstructor }>
};
export type ScrollPosition = {
x: number;
y: number;
};

View File

@@ -1,19 +0,0 @@
export function detach(node: Node) {
node.parentNode.removeChild(node);
}
export function findAnchor(node: Node) {
while (node && node.nodeName.toUpperCase() !== 'A') node = node.parentNode; // SVG <a> elements have a lowercase name
return node;
}
export function which(event: MouseEvent) {
return event.which === null ? event.button : event.which;
}
export function scroll_state() {
return {
x: window.scrollX,
y: window.scrollY
};
}

119
src/utils.ts Normal file
View File

@@ -0,0 +1,119 @@
import * as fs from 'fs';
import * as path from 'path';
export function left_pad(str: string, len: number) {
while (str.length < len) str = ` ${str}`;
return str;
}
export function repeat(str: string, i: number) {
let result = '';
while (i--) result += str;
return result;
}
export function format_milliseconds(ms: number) {
if (ms < 1000) return `${ms}ms`;
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
const minutes = ~~(ms / 60000);
const seconds = Math.round((ms % 60000) / 1000);
return `${minutes}m${seconds < 10 ? '0' : ''}${seconds}s`;
}
export function elapsed(start: number) {
return format_milliseconds(Date.now() - start);
}
export function walk(cwd: string, dir = cwd, files: string[] = []) {
fs.readdirSync(dir).forEach(file => {
const resolved = path.resolve(dir, file);
if (fs.statSync(resolved).isDirectory()) {
walk(cwd, resolved, files);
} else {
files.push(posixify(path.relative(cwd, resolved)));
}
});
return files;
}
export function posixify(str: string) {
return str.replace(/\\/g, '/');
}
const previous_contents = new Map();
export function write_if_changed(file: string, code: string) {
if (code !== previous_contents.get(file)) {
previous_contents.set(file, code);
fs.writeFileSync(file, code);
fudge_mtime(file);
}
}
export function stringify(string: string, includeQuotes: boolean = true) {
const quoted = JSON.stringify(string);
return includeQuotes ? quoted : quoted.slice(1, -1);
}
export function fudge_mtime(file: string) {
// need to fudge the mtime so that webpack doesn't go doolally
const { atime, mtime } = fs.statSync(file);
fs.utimesSync(
file,
new Date(atime.getTime() - 999999),
new Date(mtime.getTime() - 999999)
);
}
export const reserved_words = new Set([
'arguments',
'await',
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'enum',
'eval',
'export',
'extends',
'false',
'finally',
'for',
'function',
'if',
'implements',
'import',
'in',
'instanceof',
'interface',
'let',
'new',
'null',
'package',
'private',
'protected',
'public',
'return',
'static',
'super',
'switch',
'this',
'throw',
'true',
'try',
'typeof',
'var',
'void',
'while',
'with',
'yield',
]);

389
templates/src/client/app.ts Normal file
View File

@@ -0,0 +1,389 @@
import RootComponent from '__ROOT__';
import ErrorComponent from '__ERROR__';
import {
Target,
ScrollPosition,
Component,
Redirect,
ComponentLoader,
ComponentConstructor,
RootProps,
Page
} from './types';
import goto from './goto';
const ignore = __IGNORE__;
export const components: ComponentLoader[] = __COMPONENTS__;
export const pages: Page[] = __PAGES__;
let ready = false;
let root_component: Component;
let segments: string[] = [];
let current_token: {};
let root_preload: Promise<any>;
let root_data: any;
const root_props: RootProps = {
path: null,
params: null,
query: null,
child: {
segment: null,
component: null,
props: {}
}
};
export let prefetching: {
href: string;
promise: Promise<{ redirect?: Redirect, data?: any, nullable_depth?: number }>;
} = null;
export function set_prefetching(href, promise) {
prefetching = { href, promise };
}
export let store;
export function set_store(fn) {
store = fn(initial_data.store);
}
export let target: Node;
export function set_target(element) {
target = element;
}
export let uid = 1;
export function set_uid(n) {
uid = n;
}
export let cid: number;
export function set_cid(n) {
cid = n;
}
export const initial_data = typeof __SAPPER__ !== 'undefined' && __SAPPER__;
const _history = typeof history !== 'undefined' ? history : {
pushState: (state: any, title: string, href: string) => {},
replaceState: (state: any, title: string, href: string) => {},
scrollRestoration: ''
};
export { _history as history };
export const scroll_history: Record<string, ScrollPosition> = {};
export function select_route(url: URL): Target {
if (url.origin !== location.origin) return null;
if (!url.pathname.startsWith(initial_data.baseUrl)) return null;
const path = url.pathname.slice(initial_data.baseUrl.length);
// avoid accidental clashes between server routes and pages
if (ignore.some(pattern => pattern.test(path))) return;
for (let i = 0; i < pages.length; i += 1) {
const page = pages[i];
const match = page.pattern.exec(path);
if (match) {
const query: Record<string, string | string[]> = Object.create(null);
if (url.search.length > 0) {
url.search.slice(1).split('&').forEach(searchParam => {
let [, key, value] = /([^=]*)(?:=(.*))?/.exec(decodeURIComponent(searchParam));
value = (value || '').replace(/\+/g, ' ');
if (typeof query[key] === 'string') query[key] = [<string>query[key]];
if (typeof query[key] === 'object') query[key].push(value);
else query[key] = value;
});
}
return { url, path, page, match, query };
}
}
}
export function scroll_state() {
return {
x: pageXOffset,
y: pageYOffset
};
}
export function navigate(target: Target, id: number, noscroll?: boolean, hash?: string): Promise<any> {
let scroll: ScrollPosition;
if (id) {
// popstate or initial navigation
cid = id;
} else {
const current_scroll = scroll_state();
// clicked on a link. preserve scroll state
scroll_history[cid] = current_scroll;
id = cid = ++uid;
scroll_history[cid] = noscroll ? current_scroll : { x: 0, y: 0 };
}
cid = id;
if (root_component) {
root_component.set({ preloading: true });
}
const loaded = prefetching && prefetching.href === target.url.href ?
prefetching.promise :
prepare_page(target);
prefetching = null;
const token = current_token = {};
return loaded.then(({ redirect, data, nullable_depth }) => {
if (redirect) {
return goto(redirect.location, { replaceState: true });
}
render(data, nullable_depth, scroll_history[id], noscroll, hash, token);
if (document.activeElement) document.activeElement.blur();
});
}
function render(data: any, nullable_depth: number, scroll: ScrollPosition, noscroll: boolean, hash: string, token: {}) {
if (current_token !== token) return;
if (root_component) {
// first, clear out highest-level root component
let level = data.child;
for (let i = 0; i < nullable_depth; i += 1) {
if (i === nullable_depth) break;
level = level.props.child;
}
const { component } = level;
level.component = null;
root_component.set({ child: data.child });
// then render new stuff
level.component = component;
root_component.set(data);
} else {
// first load — remove SSR'd <head> contents
const start = document.querySelector('#sapper-head-start');
const end = document.querySelector('#sapper-head-end');
if (start && end) {
while (start.nextSibling !== end) detach(start.nextSibling);
detach(start);
detach(end);
}
Object.assign(data, root_data);
root_component = new RootComponent({
target,
data,
store,
hydrate: true
});
}
if (!noscroll) {
if (hash) {
// scroll is an element id (from a hash), we need to compute y.
const deep_linked = document.querySelector(hash);
if (deep_linked) {
scroll = {
x: 0,
y: deep_linked.getBoundingClientRect().top
};
}
}
scroll_history[cid] = scroll;
if (scroll) scrollTo(scroll.x, scroll.y);
}
Object.assign(root_props, data);
ready = true;
}
export function prepare_page(target: Target): Promise<{
redirect?: Redirect;
data?: any;
nullable_depth?: number;
}> {
const { page, path, query } = target;
const new_segments = path.split('/').filter(Boolean);
let changed_from = 0;
while (
segments[changed_from] &&
new_segments[changed_from] &&
segments[changed_from] === new_segments[changed_from]
) changed_from += 1;
let redirect: Redirect = null;
let error: { statusCode: number, message: Error | string } = null;
const preload_context = {
store,
fetch: (url: string, opts?: any) => fetch(url, opts),
redirect: (statusCode: number, location: string) => {
if (redirect && (redirect.statusCode !== statusCode || redirect.location !== location)) {
throw new Error(`Conflicting redirects`);
}
redirect = { statusCode, location };
},
error: (statusCode: number, message: Error | string) => {
error = { statusCode, message };
}
};
if (!root_preload) {
root_preload = RootComponent.preload
? initial_data.preloaded[0] || RootComponent.preload.call(preload_context, {
path,
query,
params: {}
})
: {};
}
return Promise.all(page.parts.map((part, i) => {
if (i < changed_from) return null;
if (!part) return null;
return load_component(components[part.i]).then(Component => {
const req = {
path,
query,
params: part.params ? part.params(target.match) : {}
};
let preloaded;
if (ready || !initial_data.preloaded[i + 1]) {
preloaded = Component.preload
? Component.preload.call(preload_context, req)
: {};
} else {
preloaded = initial_data.preloaded[i + 1];
}
return Promise.resolve(preloaded).then(preloaded => {
return { Component, preloaded };
});
});
})).catch(err => {
error = { statusCode: 500, message: err };
return [];
}).then(results => {
if (root_data) {
return results;
} else {
return Promise.resolve(root_preload).then(value => {
root_data = value;
return results;
});
}
}).then(results => {
if (redirect) {
return { redirect };
}
segments = new_segments;
const get_params = page.parts[page.parts.length - 1].params || (() => ({}));
const params = get_params(target.match);
if (error) {
const props = {
path,
query,
params,
error: typeof error.message === 'string' ? new Error(error.message) : error.message,
status: error.statusCode
};
return {
data: Object.assign({}, props, {
preloading: false,
child: {
component: ErrorComponent,
props
}
})
};
}
const props = { path, query, error: null, status: null };
const data = {
path,
preloading: false,
child: Object.assign({}, root_props.child, {
segment: segments[0]
})
};
if (changed(query, root_props.query)) data.query = query;
if (changed(params, root_props.params)) data.params = params;
let level = data.child;
let nullable_depth = 0;
for (let i = 0; i < page.parts.length; i += 1) {
const part = page.parts[i];
if (!part) continue;
const get_params = part.params || (() => ({}));
if (i < changed_from) {
level.props.path = path;
level.props.query = query;
level.props.child = Object.assign({}, level.props.child);
nullable_depth += 1;
} else {
level.component = results[i].Component;
level.props = Object.assign({}, level.props, props, {
params: get_params(target.match),
}, results[i].preloaded);
level.props.child = {};
}
level = level.props.child;
level.segment = segments[i + 1];
}
return { data, nullable_depth };
});
}
function load_css(chunk: string) {
const href = `client/${chunk}`;
if (document.querySelector(`link[href="${href}"]`)) return;
return new Promise((fulfil, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = href;
link.onload = () => fulfil();
link.onerror = reject;
document.head.appendChild(link);
});
}
export function load_component(component: ComponentLoader): Promise<ComponentConstructor> {
// TODO this is temporary — once placeholders are
// always rewritten, scratch the ternary
const promises: Array<Promise<any>> = (typeof component.css === 'string' ? [] : component.css.map(load_css));
promises.unshift(component.js());
return Promise.all(promises).then(values => values[0].default);
}
function detach(node: Node) {
node.parentNode.removeChild(node);
}
function changed(a: Record<string, string | true>, b: Record<string, string | true>) {
return JSON.stringify(a) !== JSON.stringify(b);
}

View File

@@ -0,0 +1,13 @@
import { history, select_route, navigate, cid } from '../app';
export default function goto(href: string, opts = { replaceState: false }) {
const target = select_route(new URL(href, document.baseURI));
if (target) {
history[opts.replaceState ? 'replaceState' : 'pushState']({ id: cid }, '', href);
return navigate(target, null).then(() => {});
}
location.href = href;
return new Promise(f => {}); // never resolves
}

View File

@@ -0,0 +1,4 @@
export { default as start } from './start/index';
export { default as goto } from './goto/index';
export { default as prefetch } from './prefetch/index';
export { default as prefetchRoutes } from './prefetchRoutes/index';

View File

@@ -0,0 +1,14 @@
import { select_route, prefetching, set_prefetching, prepare_page } from '../app';
import { Target } from '../types';
export default function prefetch(href: string) {
const target: Target = select_route(new URL(href, document.baseURI));
if (target) {
if (!prefetching || href !== prefetching.href) {
set_prefetching(href, prepare_page(target));
}
return prefetching.promise;
}
}

View File

@@ -0,0 +1,12 @@
import { components, pages, load_component } from "../app";
export default function prefetchRoutes(pathnames: string[]) {
return pages
.filter(route => {
if (!pathnames) return true;
return pathnames.some(pathname => route.pattern.test(pathname));
})
.reduce((promise: Promise<any>, route) => promise.then(() => {
return Promise.all(route.parts.map(part => part && load_component(components[part.i])));
}), Promise.resolve());
}

View File

@@ -0,0 +1,134 @@
import {
cid,
history,
initial_data,
navigate,
scroll_history,
scroll_state,
select_route,
set_store,
set_target,
uid,
set_uid,
set_cid
} from '../app';
import prefetch from '../prefetch/index';
import { Store, ScrollPosition } from '../types';
export default function start(opts: {
target: Node,
store?: (data: any) => Store
}) {
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
}
set_target(opts.target);
if (opts.store) set_store(opts.store);
addEventListener('click', handle_click);
addEventListener('popstate', handle_popstate);
// prefetch
addEventListener('touchstart', trigger_prefetch);
addEventListener('mousemove', handle_mousemove);
return Promise.resolve().then(() => {
const { hash, href } = location;
history.replaceState({ id: uid }, '', href);
if (!initial_data.error) {
const target = select_route(new URL(location.href));
if (target) return navigate(target, uid, false, hash);
}
});
}
let mousemove_timeout: NodeJS.Timer;
function handle_mousemove(event: MouseEvent) {
clearTimeout(mousemove_timeout);
mousemove_timeout = setTimeout(() => {
trigger_prefetch(event);
}, 20);
}
function trigger_prefetch(event: MouseEvent | TouchEvent) {
const a: HTMLAnchorElement = <HTMLAnchorElement>find_anchor(<Node>event.target);
if (!a || a.rel !== 'prefetch') return;
prefetch(a.href);
}
function handle_click(event: MouseEvent) {
// Adapted from https://github.com/visionmedia/page.js
// MIT license https://github.com/visionmedia/page.js#license
if (which(event) !== 1) return;
if (event.metaKey || event.ctrlKey || event.shiftKey) return;
if (event.defaultPrevented) return;
const a: HTMLAnchorElement | SVGAElement = <HTMLAnchorElement | SVGAElement>find_anchor(<Node>event.target);
if (!a) return;
if (!a.href) return;
// check if link is inside an svg
// in this case, both href and target are always inside an object
const svg = typeof a.href === 'object' && a.href.constructor.name === 'SVGAnimatedString';
const href = String(svg ? (<SVGAElement>a).href.baseVal : a.href);
if (href === location.href) {
if (!location.hash) event.preventDefault();
return;
}
// Ignore if tag has
// 1. 'download' attribute
// 2. rel='external' attribute
if (a.hasAttribute('download') || a.getAttribute('rel') === 'external') return;
// Ignore if <a> has a target
if (svg ? (<SVGAElement>a).target.baseVal : a.target) return;
const url = new URL(href);
// Don't handle hash changes
if (url.pathname === location.pathname && url.search === location.search) return;
const target = select_route(url);
if (target) {
const noscroll = a.hasAttribute('sapper-noscroll');
navigate(target, null, noscroll, url.hash);
event.preventDefault();
history.pushState({ id: cid }, '', url.href);
}
}
function which(event: MouseEvent) {
return event.which === null ? event.button : event.which;
}
function find_anchor(node: Node) {
while (node && node.nodeName.toUpperCase() !== 'A') node = node.parentNode; // SVG <a> elements have a lowercase name
return node;
}
function handle_popstate(event: PopStateEvent) {
scroll_history[cid] = scroll_state();
if (event.state) {
const url = new URL(location.href);
const target = select_route(url);
if (target) {
navigate(target, event.state.id);
} else {
location.href = location.href;
}
} else {
// hashchange
set_uid(uid + 1);
set_cid(uid);
history.replaceState({ id: cid }, '', location.href);
}
}

View File

@@ -0,0 +1,68 @@
export type Params = Record<string, string>;
export type Query = Record<string, string | true>;
export type RouteData = { params: Params, query: Query, path: string };
type Child = {
segment?: string;
props?: any;
component?: Component;
};
export type RootProps = {
path: string;
params: Record<string, string>;
query: Record<string, string>;
child: Child;
};
export interface ComponentConstructor {
new (options: { target: Node, data: any, store: Store, hydrate: boolean }): Component;
preload: (props: { params: Params, query: Query }) => Promise<any>;
};
export interface Component {
set: (data: any) => void;
destroy: () => void;
}
export type ComponentLoader = {
js: () => Promise<{ default: ComponentConstructor }>,
css: string[]
};
export type Page = {
pattern: RegExp;
parts: Array<{
i: number;
params?: (match: RegExpExecArray) => Record<string, string>;
}>;
};
export type Manifest = {
ignore: RegExp[];
root: ComponentConstructor;
error: () => Promise<{ default: ComponentConstructor }>;
pages: Page[]
};
export type ScrollPosition = {
x: number;
y: number;
};
export type Target = {
url: URL;
path: string;
page: Page;
match: RegExpExecArray;
query: Record<string, string | string[]>;
};
export type Redirect = {
statusCode: number;
location: string;
};
export type Store = {
get: () => any;
}

View File

@@ -0,0 +1 @@
export { default as middleware } from './middleware/index';

View File

@@ -0,0 +1,338 @@
import * as fs from 'fs';
import * as path from 'path';
import cookie from 'cookie';
import devalue from 'devalue';
import fetch from 'node-fetch';
import { URL, resolve } from 'url';
import { build_dir, dev, src_dir, IGNORE } from '../placeholders';
import { Manifest, Page, Props, Req, Res, Store } from './types';
export function get_page_handler(
manifest: Manifest,
store_getter: (req: Req, res: Res) => Store
) {
const get_build_info = dev
? () => JSON.parse(fs.readFileSync(path.join(build_dir, 'build.json'), 'utf-8'))
: (assets => () => assets)(JSON.parse(fs.readFileSync(path.join(build_dir, 'build.json'), 'utf-8')));
const template = dev
? () => read_template(src_dir)
: (str => () => str)(read_template(build_dir));
const has_service_worker = fs.existsSync(path.join(build_dir, 'service-worker.js'));
const { server_routes, pages } = manifest;
const error_route = manifest.error;
function handle_error(req: Req, res: Res, statusCode: number, error: Error | string) {
handle_page({
pattern: null,
parts: [
{ name: null, component: error_route }
]
}, req, res, statusCode, error || new Error('Unknown error in preload function'));
}
async function handle_page(page: Page, req: Req, res: Res, status = 200, error: Error | string = null) {
const build_info: {
bundler: 'rollup' | 'webpack',
shimport: string | null,
assets: Record<string, string | string[]>,
legacy_assets?: Record<string, string>
} = get_build_info();
res.setHeader('Content-Type', 'text/html');
res.setHeader('Cache-Control', dev ? 'no-cache' : 'max-age=600');
// preload main.js and current route
// TODO detect other stuff we can preload? images, CSS, fonts?
let preloaded_chunks = Array.isArray(build_info.assets.main) ? build_info.assets.main : [build_info.assets.main];
if (!error) {
page.parts.forEach(part => {
if (!part) return;
// using concat because it could be a string or an array. thanks webpack!
preloaded_chunks = preloaded_chunks.concat(build_info.assets[part.name]);
});
}
if (build_info.bundler === 'rollup') {
// TODO add dependencies and CSS
const link = preloaded_chunks
.filter(file => file && !file.match(/\.map$/))
.map(file => `<${req.baseUrl}/client/${file}>;rel="modulepreload"`)
.join(', ');
res.setHeader('Link', link);
} else {
const link = preloaded_chunks
.filter(file => file && !file.match(/\.map$/))
.map(file => `<${req.baseUrl}/client/${file}>;rel="preload";as="script"`)
.join(', ');
res.setHeader('Link', link);
}
const store = store_getter ? store_getter(req, res) : null;
let redirect: { statusCode: number, location: string };
let preload_error: { statusCode: number, message: Error | string };
const preload_context = {
redirect: (statusCode: number, location: string) => {
if (redirect && (redirect.statusCode !== statusCode || redirect.location !== location)) {
throw new Error(`Conflicting redirects`);
}
location = location.replace(/^\//g, ''); // leading slash (only)
redirect = { statusCode, location };
},
error: (statusCode: number, message: Error | string) => {
preload_error = { statusCode, message };
},
fetch: (url: string, opts?: any) => {
const parsed = new URL(url, `http://127.0.0.1:${process.env.PORT}${req.baseUrl ? req.baseUrl + '/' :''}`);
if (opts) {
opts = Object.assign({}, opts);
const include_cookies = (
opts.credentials === 'include' ||
opts.credentials === 'same-origin' && parsed.origin === `http://127.0.0.1:${process.env.PORT}`
);
if (include_cookies) {
if (!opts.headers) opts.headers = {};
const cookies = Object.assign(
{},
cookie.parse(req.headers.cookie || ''),
cookie.parse(opts.headers.cookie || '')
);
const set_cookie = res.getHeader('Set-Cookie');
(Array.isArray(set_cookie) ? set_cookie : [set_cookie]).forEach(str => {
const match = /([^=]+)=([^;]+)/.exec(<string>str);
if (match) cookies[match[1]] = match[2];
});
const str = Object.keys(cookies)
.map(key => `${key}=${cookies[key]}`)
.join('; ');
opts.headers.cookie = str;
}
}
return fetch(parsed.href, opts);
},
store
};
let preloaded;
let match;
try {
const root_preloaded = manifest.root.preload
? manifest.root.preload.call(preload_context, {
path: req.path,
query: req.query,
params: {}
})
: {};
match = error ? null : page.pattern.exec(req.path);
preloaded = await Promise.all([root_preloaded].concat(page.parts.map(part => {
if (!part) return null;
return part.component.preload
? part.component.preload.call(preload_context, {
path: req.path,
query: req.query,
params: part.params ? part.params(match) : {}
})
: {};
})));
} catch (err) {
preload_error = { statusCode: 500, message: err };
preloaded = []; // appease TypeScript
}
try {
if (redirect) {
const location = resolve(req.baseUrl || '/', redirect.location);
res.statusCode = redirect.statusCode;
res.setHeader('Location', location);
res.end();
return;
}
if (preload_error) {
handle_error(req, res, preload_error.statusCode, preload_error.message);
return;
}
const serialized = {
preloaded: `[${preloaded.map(data => try_serialize(data)).join(',')}]`,
store: store && try_serialize(store.get())
};
const segments = req.path.split('/').filter(Boolean);
const props: Props = {
path: req.path,
query: req.query,
params: {},
child: null
};
if (error) {
props.error = error instanceof Error ? error : { message: error };
props.status = status;
}
const data = Object.assign({}, props, preloaded[0], {
params: {},
child: {
segment: segments[0]
}
});
let level = data.child;
for (let i = 0; i < page.parts.length; i += 1) {
const part = page.parts[i];
if (!part) continue;
const get_params = part.params || (() => ({}));
Object.assign(level, {
component: part.component,
props: Object.assign({}, props, {
params: get_params(match)
}, preloaded[i + 1])
});
level.props.child = <Props["child"]>{
segment: segments[i + 1]
};
level = level.props.child;
}
const { html, head, css } = manifest.root.render(data, {
store
});
let script = `__SAPPER__={${[
error && `error:1`,
`baseUrl:"${req.baseUrl}"`,
serialized.preloaded && `preloaded:${serialized.preloaded}`,
serialized.store && `store:${serialized.store}`
].filter(Boolean).join(',')}};`;
if (has_service_worker) {
script += `if('serviceWorker' in navigator)navigator.serviceWorker.register('${req.baseUrl}/service-worker.js');`;
}
const file = [].concat(build_info.assets.main).filter(file => file && /\.js$/.test(file))[0];
const main = `${req.baseUrl}/client/${file}`;
if (build_info.bundler === 'rollup') {
if (build_info.legacy_assets) {
const legacy_main = `${req.baseUrl}/client/legacy/${build_info.legacy_assets.main}`;
script += `(function(){try{eval("async function x(){}");var main="${main}"}catch(e){main="${legacy_main}"};var s=document.createElement("script");try{new Function("if(0)import('')")();s.src=main;s.type="module";s.crossOrigin="use-credentials";}catch(e){s.src="${req.baseUrl}/client/shimport@${build_info.shimport}.js";s.setAttribute("data-main",main);}document.head.appendChild(s);}());`;
} else {
script += `var s=document.createElement("script");try{new Function("if(0)import('')")();s.src="${main}";s.type="module";s.crossOrigin="use-credentials";}catch(e){s.src="${req.baseUrl}/client/shimport@${build_info.shimport}.js";s.setAttribute("data-main","${main}")}document.head.appendChild(s)`;
}
} else {
script += `</script><script src="${main}">`;
}
let styles: string;
// TODO make this consistent across apps
// TODO embed build_info in placeholder.ts
if (build_info.css && build_info.css.main) {
const css_chunks = new Set();
if (build_info.css.main) css_chunks.add(build_info.css.main);
page.parts.forEach(part => {
if (!part) return;
const css_chunks_for_part = build_info.css.chunks[part.file];
if (css_chunks_for_part) {
css_chunks_for_part.forEach(file => {
css_chunks.add(file);
});
}
});
styles = Array.from(css_chunks)
.map(href => `<link rel="stylesheet" href="client/${href}">`)
.join('')
} else {
styles = (css && css.code ? `<style>${css.code}</style>` : '');
}
// users can set a CSP nonce using res.locals.nonce
const nonce_attr = (res.locals && res.locals.nonce) ? ` nonce="${res.locals.nonce}"` : '';
const body = template()
.replace('%sapper.base%', () => `<base href="${req.baseUrl}/">`)
.replace('%sapper.scripts%', () => `<script${nonce_attr}>${script}</script>`)
.replace('%sapper.html%', () => html)
.replace('%sapper.head%', () => `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`)
.replace('%sapper.styles%', () => styles);
res.statusCode = status;
res.end(body);
} catch(err) {
if (error) {
// we encountered an error while rendering the error page — oops
res.statusCode = 500;
res.end(`<pre>${escape_html(err.message)}</pre>`);
} else {
handle_error(req, res, 500, err);
}
}
}
return function find_route(req: Req, res: Res, next: () => void) {
if (req[IGNORE]) return next();
if (!server_routes.some(route => route.pattern.test(req.path))) {
for (const page of pages) {
if (page.pattern.test(req.path)) {
handle_page(page, req, res);
return;
}
}
}
handle_error(req, res, 404, 'Not found');
};
}
function read_template(dir = build_dir) {
return fs.readFileSync(`${dir}/template.html`, 'utf-8');
}
function try_serialize(data: any) {
try {
return devalue(data);
} catch (err) {
return null;
}
}
function escape_html(html: string) {
const chars: Record<string, string> = {
'"' : 'quot',
"'": '#39',
'&': 'amp',
'<' : 'lt',
'>' : 'gt'
};
return html.replace(/["'&<>]/g, c => `&${chars[c]};`);
}

View File

@@ -0,0 +1,78 @@
import { IGNORE } from '../placeholders';
import { Req, Res, ServerRoute } from './types';
export function get_server_route_handler(routes: ServerRoute[]) {
async function handle_route(route: ServerRoute, req: Req, res: Res, next: () => void) {
req.params = route.params(route.pattern.exec(req.path));
const method = req.method.toLowerCase();
// 'delete' cannot be exported from a module because it is a keyword,
// so check for 'del' instead
const method_export = method === 'delete' ? 'del' : method;
const handle_method = route.handlers[method_export];
if (handle_method) {
if (process.env.SAPPER_EXPORT) {
const { write, end, setHeader } = res;
const chunks: any[] = [];
const headers: Record<string, string> = {};
// intercept data so that it can be exported
res.write = function(chunk: any) {
chunks.push(Buffer.from(chunk));
write.apply(res, arguments);
};
res.setHeader = function(name: string, value: string) {
headers[name.toLowerCase()] = value;
setHeader.apply(res, arguments);
};
res.end = function(chunk?: any) {
if (chunk) chunks.push(Buffer.from(chunk));
end.apply(res, arguments);
process.send({
__sapper__: true,
event: 'file',
url: req.url,
method: req.method,
status: res.statusCode,
type: headers['content-type'],
body: Buffer.concat(chunks).toString()
});
};
}
const handle_next = (err?: Error) => {
if (err) {
res.statusCode = 500;
res.end(err.message);
} else {
process.nextTick(next);
}
};
try {
await handle_method(req, res, handle_next);
} catch (err) {
handle_next(err);
}
} else {
// no matching handler for method
process.nextTick(next);
}
}
return function find_route(req: Req, res: Res, next: () => void) {
if (req[IGNORE]) return next();
for (const route of routes) {
if (route.pattern.test(req.path)) {
handle_route(route, req, res, next);
return;
}
}
next();
};
}

View File

@@ -0,0 +1,143 @@
import * as fs from 'fs';
import * as path from 'path';
import { build_dir, dev, manifest, IGNORE } from '../placeholders';
import { Handler, Req, Res, Store } from './types';
import { get_server_route_handler } from './get_server_route_handler';
import { get_page_handler } from './get_page_handler';
import { lookup } from './mime';
export default function middleware(opts: {
store?: (req: Req, res: Res) => Store,
ignore?: any
} = {}) {
const { store, ignore } = opts;
let emitted_basepath = false;
return compose_handlers([
ignore && ((req: Req, res: Res, next: () => void) => {
req[IGNORE] = should_ignore(req.path, ignore);
next();
}),
(req: Req, res: Res, next: () => void) => {
if (req[IGNORE]) return next();
if (req.baseUrl === undefined) {
let { originalUrl } = req;
if (req.url === '/' && originalUrl[originalUrl.length - 1] !== '/') {
originalUrl += '/';
}
req.baseUrl = originalUrl
? originalUrl.slice(0, -req.url.length)
: '';
}
if (!emitted_basepath && process.send) {
process.send({
__sapper__: true,
event: 'basepath',
basepath: req.baseUrl
});
emitted_basepath = true;
}
if (req.path === undefined) {
req.path = req.url.replace(/\?.*/, '');
}
next();
},
fs.existsSync(path.join(build_dir, 'index.html')) && serve({
pathname: '/index.html',
cache_control: dev ? 'no-cache' : 'max-age=600'
}),
fs.existsSync(path.join(build_dir, 'service-worker.js')) && serve({
pathname: '/service-worker.js',
cache_control: 'no-cache, no-store, must-revalidate'
}),
fs.existsSync(path.join(build_dir, 'service-worker.js.map')) && serve({
pathname: '/service-worker.js.map',
cache_control: 'no-cache, no-store, must-revalidate'
}),
serve({
prefix: '/client/',
cache_control: dev ? 'no-cache' : 'max-age=31536000, immutable'
}),
get_server_route_handler(manifest.server_routes),
get_page_handler(manifest, store)
].filter(Boolean));
}
export function compose_handlers(handlers: Handler[]) {
return (req: Req, res: Res, next: () => void) => {
let i = 0;
function go() {
const handler = handlers[i];
if (handler) {
handler(req, res, () => {
i += 1;
go();
});
} else {
next();
}
}
go();
};
}
export function should_ignore(uri: string, val: any) {
if (Array.isArray(val)) return val.some(x => should_ignore(uri, x));
if (val instanceof RegExp) return val.test(uri);
if (typeof val === 'function') return val(uri);
return uri.startsWith(val.charCodeAt(0) === 47 ? val : `/${val}`);
}
export function serve({ prefix, pathname, cache_control }: {
prefix?: string,
pathname?: string,
cache_control: string
}) {
const filter = pathname
? (req: Req) => req.path === pathname
: (req: Req) => req.path.startsWith(prefix);
const cache: Map<string, Buffer> = new Map();
const read = dev
? (file: string) => fs.readFileSync(path.resolve(build_dir, 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) => {
if (req[IGNORE]) return next();
if (filter(req)) {
const type = lookup(req.path);
try {
const file = decodeURIComponent(req.path.slice(1));
const data = read(file);
res.setHeader('Content-Type', type);
res.setHeader('Cache-Control', cache_control);
res.end(data);
} catch (err) {
res.statusCode = 404;
res.end('not found');
}
} else {
next();
}
};
}

View File

@@ -0,0 +1,767 @@
application/andrew-inset ez
application/applixware aw
application/atom+xml atom
application/atomcat+xml atomcat
application/atomsvc+xml atomsvc
application/ccxml+xml ccxml
application/cdmi-capability cdmia
application/cdmi-container cdmic
application/cdmi-domain cdmid
application/cdmi-object cdmio
application/cdmi-queue cdmiq
application/cu-seeme cu
application/davmount+xml davmount
application/docbook+xml dbk
application/dssc+der dssc
application/dssc+xml xdssc
application/ecmascript ecma
application/emma+xml emma
application/epub+zip epub
application/exi exi
application/font-tdpfr pfr
application/gml+xml gml
application/gpx+xml gpx
application/gxf gxf
application/hyperstudio stk
application/inkml+xml ink inkml
application/ipfix ipfix
application/java-archive jar
application/java-serialized-object ser
application/java-vm class
application/javascript js
application/json json map
application/jsonml+json jsonml
application/lost+xml lostxml
application/mac-binhex40 hqx
application/mac-compactpro cpt
application/mads+xml mads
application/marc mrc
application/marcxml+xml mrcx
application/mathematica ma nb mb
application/mathml+xml mathml
application/mbox mbox
application/mediaservercontrol+xml mscml
application/metalink+xml metalink
application/metalink4+xml meta4
application/mets+xml mets
application/mods+xml mods
application/mp21 m21 mp21
application/mp4 mp4s
application/msword doc dot
application/mxf mxf
application/octet-stream bin dms lrf mar so dist distz pkg bpk dump elc deploy
application/oda oda
application/oebps-package+xml opf
application/ogg ogx
application/omdoc+xml omdoc
application/onenote onetoc onetoc2 onetmp onepkg
application/oxps oxps
application/patch-ops-error+xml xer
application/pdf pdf
application/pgp-encrypted pgp
application/pgp-signature asc sig
application/pics-rules prf
application/pkcs10 p10
application/pkcs7-mime p7m p7c
application/pkcs7-signature p7s
application/pkcs8 p8
application/pkix-attr-cert ac
application/pkix-cert cer
application/pkix-crl crl
application/pkix-pkipath pkipath
application/pkixcmp pki
application/pls+xml pls
application/postscript ai eps ps
application/prs.cww cww
application/pskc+xml pskcxml
application/rdf+xml rdf
application/reginfo+xml rif
application/relax-ng-compact-syntax rnc
application/resource-lists+xml rl
application/resource-lists-diff+xml rld
application/rls-services+xml rs
application/rpki-ghostbusters gbr
application/rpki-manifest mft
application/rpki-roa roa
application/rsd+xml rsd
application/rss+xml rss
application/rtf rtf
application/sbml+xml sbml
application/scvp-cv-request scq
application/scvp-cv-response scs
application/scvp-vp-request spq
application/scvp-vp-response spp
application/sdp sdp
application/set-payment-initiation setpay
application/set-registration-initiation setreg
application/shf+xml shf
application/smil+xml smi smil
application/sparql-query rq
application/sparql-results+xml srx
application/srgs gram
application/srgs+xml grxml
application/sru+xml sru
application/ssdl+xml ssdl
application/ssml+xml ssml
application/tei+xml tei teicorpus
application/thraud+xml tfi
application/timestamped-data tsd
application/vnd.3gpp.pic-bw-large plb
application/vnd.3gpp.pic-bw-small psb
application/vnd.3gpp.pic-bw-var pvb
application/vnd.3gpp2.tcap tcap
application/vnd.3m.post-it-notes pwn
application/vnd.accpac.simply.aso aso
application/vnd.accpac.simply.imp imp
application/vnd.acucobol acu
application/vnd.acucorp atc acutc
application/vnd.adobe.air-application-installer-package+zip air
application/vnd.adobe.formscentral.fcdt fcdt
application/vnd.adobe.fxp fxp fxpl
application/vnd.adobe.xdp+xml xdp
application/vnd.adobe.xfdf xfdf
application/vnd.ahead.space ahead
application/vnd.airzip.filesecure.azf azf
application/vnd.airzip.filesecure.azs azs
application/vnd.amazon.ebook azw
application/vnd.americandynamics.acc acc
application/vnd.amiga.ami ami
application/vnd.android.package-archive apk
application/vnd.anser-web-certificate-issue-initiation cii
application/vnd.anser-web-funds-transfer-initiation fti
application/vnd.antix.game-component atx
application/vnd.apple.installer+xml mpkg
application/vnd.apple.mpegurl m3u8
application/vnd.aristanetworks.swi swi
application/vnd.astraea-software.iota iota
application/vnd.audiograph aep
application/vnd.blueice.multipass mpm
application/vnd.bmi bmi
application/vnd.businessobjects rep
application/vnd.chemdraw+xml cdxml
application/vnd.chipnuts.karaoke-mmd mmd
application/vnd.cinderella cdy
application/vnd.claymore cla
application/vnd.cloanto.rp9 rp9
application/vnd.clonk.c4group c4g c4d c4f c4p c4u
application/vnd.cluetrust.cartomobile-config c11amc
application/vnd.cluetrust.cartomobile-config-pkg c11amz
application/vnd.commonspace csp
application/vnd.contact.cmsg cdbcmsg
application/vnd.cosmocaller cmc
application/vnd.crick.clicker clkx
application/vnd.crick.clicker.keyboard clkk
application/vnd.crick.clicker.palette clkp
application/vnd.crick.clicker.template clkt
application/vnd.crick.clicker.wordbank clkw
application/vnd.criticaltools.wbs+xml wbs
application/vnd.ctc-posml pml
application/vnd.cups-ppd ppd
application/vnd.curl.car car
application/vnd.curl.pcurl pcurl
application/vnd.dart dart
application/vnd.data-vision.rdz rdz
application/vnd.dece.data uvf uvvf uvd uvvd
application/vnd.dece.ttml+xml uvt uvvt
application/vnd.dece.unspecified uvx uvvx
application/vnd.dece.zip uvz uvvz
application/vnd.denovo.fcselayout-link fe_launch
application/vnd.dna dna
application/vnd.dolby.mlp mlp
application/vnd.dpgraph dpg
application/vnd.dreamfactory dfac
application/vnd.ds-keypoint kpxx
application/vnd.dvb.ait ait
application/vnd.dvb.service svc
application/vnd.dynageo geo
application/vnd.ecowin.chart mag
application/vnd.enliven nml
application/vnd.epson.esf esf
application/vnd.epson.msf msf
application/vnd.epson.quickanime qam
application/vnd.epson.salt slt
application/vnd.epson.ssf ssf
application/vnd.eszigno3+xml es3 et3
application/vnd.ezpix-album ez2
application/vnd.ezpix-package ez3
application/vnd.fdf fdf
application/vnd.fdsn.mseed mseed
application/vnd.fdsn.seed seed dataless
application/vnd.flographit gph
application/vnd.fluxtime.clip ftc
application/vnd.framemaker fm frame maker book
application/vnd.frogans.fnc fnc
application/vnd.frogans.ltf ltf
application/vnd.fsc.weblaunch fsc
application/vnd.fujitsu.oasys oas
application/vnd.fujitsu.oasys2 oa2
application/vnd.fujitsu.oasys3 oa3
application/vnd.fujitsu.oasysgp fg5
application/vnd.fujitsu.oasysprs bh2
application/vnd.fujixerox.ddd ddd
application/vnd.fujixerox.docuworks xdw
application/vnd.fujixerox.docuworks.binder xbd
application/vnd.fuzzysheet fzs
application/vnd.genomatix.tuxedo txd
application/vnd.geogebra.file ggb
application/vnd.geogebra.tool ggt
application/vnd.geometry-explorer gex gre
application/vnd.geonext gxt
application/vnd.geoplan g2w
application/vnd.geospace g3w
application/vnd.gmx gmx
application/vnd.google-earth.kml+xml kml
application/vnd.google-earth.kmz kmz
application/vnd.grafeq gqf gqs
application/vnd.groove-account gac
application/vnd.groove-help ghf
application/vnd.groove-identity-message gim
application/vnd.groove-injector grv
application/vnd.groove-tool-message gtm
application/vnd.groove-tool-template tpl
application/vnd.groove-vcard vcg
application/vnd.hal+xml hal
application/vnd.handheld-entertainment+xml zmm
application/vnd.hbci hbci
application/vnd.hhe.lesson-player les
application/vnd.hp-hpgl hpgl
application/vnd.hp-hpid hpid
application/vnd.hp-hps hps
application/vnd.hp-jlyt jlt
application/vnd.hp-pcl pcl
application/vnd.hp-pclxl pclxl
application/vnd.hydrostatix.sof-data sfd-hdstx
application/vnd.ibm.minipay mpy
application/vnd.ibm.modcap afp listafp list3820
application/vnd.ibm.rights-management irm
application/vnd.ibm.secure-container sc
application/vnd.iccprofile icc icm
application/vnd.igloader igl
application/vnd.immervision-ivp ivp
application/vnd.immervision-ivu ivu
application/vnd.insors.igm igm
application/vnd.intercon.formnet xpw xpx
application/vnd.intergeo i2g
application/vnd.intu.qbo qbo
application/vnd.intu.qfx qfx
application/vnd.ipunplugged.rcprofile rcprofile
application/vnd.irepository.package+xml irp
application/vnd.is-xpr xpr
application/vnd.isac.fcs fcs
application/vnd.jam jam
application/vnd.jcp.javame.midlet-rms rms
application/vnd.jisp jisp
application/vnd.joost.joda-archive joda
application/vnd.kahootz ktz ktr
application/vnd.kde.karbon karbon
application/vnd.kde.kchart chrt
application/vnd.kde.kformula kfo
application/vnd.kde.kivio flw
application/vnd.kde.kontour kon
application/vnd.kde.kpresenter kpr kpt
application/vnd.kde.kspread ksp
application/vnd.kde.kword kwd kwt
application/vnd.kenameaapp htke
application/vnd.kidspiration kia
application/vnd.kinar kne knp
application/vnd.koan skp skd skt skm
application/vnd.kodak-descriptor sse
application/vnd.las.las+xml lasxml
application/vnd.llamagraphics.life-balance.desktop lbd
application/vnd.llamagraphics.life-balance.exchange+xml lbe
application/vnd.lotus-1-2-3 123
application/vnd.lotus-approach apr
application/vnd.lotus-freelance pre
application/vnd.lotus-notes nsf
application/vnd.lotus-organizer org
application/vnd.lotus-screencam scm
application/vnd.lotus-wordpro lwp
application/vnd.macports.portpkg portpkg
application/vnd.mcd mcd
application/vnd.medcalcdata mc1
application/vnd.mediastation.cdkey cdkey
application/vnd.mfer mwf
application/vnd.mfmp mfm
application/vnd.micrografx.flo flo
application/vnd.micrografx.igx igx
application/vnd.mif mif
application/vnd.mobius.daf daf
application/vnd.mobius.dis dis
application/vnd.mobius.mbk mbk
application/vnd.mobius.mqy mqy
application/vnd.mobius.msl msl
application/vnd.mobius.plc plc
application/vnd.mobius.txf txf
application/vnd.mophun.application mpn
application/vnd.mophun.certificate mpc
application/vnd.mozilla.xul+xml xul
application/vnd.ms-artgalry cil
application/vnd.ms-cab-compressed cab
application/vnd.ms-excel xls xlm xla xlc xlt xlw
application/vnd.ms-excel.addin.macroenabled.12 xlam
application/vnd.ms-excel.sheet.binary.macroenabled.12 xlsb
application/vnd.ms-excel.sheet.macroenabled.12 xlsm
application/vnd.ms-excel.template.macroenabled.12 xltm
application/vnd.ms-fontobject eot
application/vnd.ms-htmlhelp chm
application/vnd.ms-ims ims
application/vnd.ms-lrm lrm
application/vnd.ms-officetheme thmx
application/vnd.ms-pki.seccat cat
application/vnd.ms-pki.stl stl
application/vnd.ms-powerpoint ppt pps pot
application/vnd.ms-powerpoint.addin.macroenabled.12 ppam
application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm
application/vnd.ms-powerpoint.slide.macroenabled.12 sldm
application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm
application/vnd.ms-powerpoint.template.macroenabled.12 potm
application/vnd.ms-project mpp mpt
application/vnd.ms-word.document.macroenabled.12 docm
application/vnd.ms-word.template.macroenabled.12 dotm
application/vnd.ms-works wps wks wcm wdb
application/vnd.ms-wpl wpl
application/vnd.ms-xpsdocument xps
application/vnd.mseq mseq
application/vnd.musician mus
application/vnd.muvee.style msty
application/vnd.mynfc taglet
application/vnd.neurolanguage.nlu nlu
application/vnd.nitf ntf nitf
application/vnd.noblenet-directory nnd
application/vnd.noblenet-sealer nns
application/vnd.noblenet-web nnw
application/vnd.nokia.n-gage.data ngdat
application/vnd.nokia.n-gage.symbian.install n-gage
application/vnd.nokia.radio-preset rpst
application/vnd.nokia.radio-presets rpss
application/vnd.novadigm.edm edm
application/vnd.novadigm.edx edx
application/vnd.novadigm.ext ext
application/vnd.oasis.opendocument.chart odc
application/vnd.oasis.opendocument.chart-template otc
application/vnd.oasis.opendocument.database odb
application/vnd.oasis.opendocument.formula odf
application/vnd.oasis.opendocument.formula-template odft
application/vnd.oasis.opendocument.graphics odg
application/vnd.oasis.opendocument.graphics-template otg
application/vnd.oasis.opendocument.image odi
application/vnd.oasis.opendocument.image-template oti
application/vnd.oasis.opendocument.presentation odp
application/vnd.oasis.opendocument.presentation-template otp
application/vnd.oasis.opendocument.spreadsheet ods
application/vnd.oasis.opendocument.spreadsheet-template ots
application/vnd.oasis.opendocument.text odt
application/vnd.oasis.opendocument.text-master odm
application/vnd.oasis.opendocument.text-template ott
application/vnd.oasis.opendocument.text-web oth
application/vnd.olpc-sugar xo
application/vnd.oma.dd2+xml dd2
application/vnd.openofficeorg.extension oxt
application/vnd.openxmlformats-officedocument.presentationml.presentation pptx
application/vnd.openxmlformats-officedocument.presentationml.slide sldx
application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx
application/vnd.openxmlformats-officedocument.presentationml.template potx
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx
application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx
application/vnd.osgeo.mapguide.package mgp
application/vnd.osgi.dp dp
application/vnd.osgi.subsystem esa
application/vnd.palm pdb pqa oprc
application/vnd.pawaafile paw
application/vnd.pg.format str
application/vnd.pg.osasli ei6
application/vnd.picsel efif
application/vnd.pmi.widget wg
application/vnd.pocketlearn plf
application/vnd.powerbuilder6 pbd
application/vnd.previewsystems.box box
application/vnd.proteus.magazine mgz
application/vnd.publishare-delta-tree qps
application/vnd.pvi.ptid1 ptid
application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb
application/vnd.realvnc.bed bed
application/vnd.recordare.musicxml mxl
application/vnd.recordare.musicxml+xml musicxml
application/vnd.rig.cryptonote cryptonote
application/vnd.rim.cod cod
application/vnd.rn-realmedia rm
application/vnd.rn-realmedia-vbr rmvb
application/vnd.route66.link66+xml link66
application/vnd.sailingtracker.track st
application/vnd.seemail see
application/vnd.sema sema
application/vnd.semd semd
application/vnd.semf semf
application/vnd.shana.informed.formdata ifm
application/vnd.shana.informed.formtemplate itp
application/vnd.shana.informed.interchange iif
application/vnd.shana.informed.package ipk
application/vnd.simtech-mindmapper twd twds
application/vnd.smaf mmf
application/vnd.smart.teacher teacher
application/vnd.solent.sdkm+xml sdkm sdkd
application/vnd.spotfire.dxp dxp
application/vnd.spotfire.sfs sfs
application/vnd.stardivision.calc sdc
application/vnd.stardivision.draw sda
application/vnd.stardivision.impress sdd
application/vnd.stardivision.math smf
application/vnd.stardivision.writer sdw vor
application/vnd.stardivision.writer-global sgl
application/vnd.stepmania.package smzip
application/vnd.stepmania.stepchart sm
application/vnd.sun.xml.calc sxc
application/vnd.sun.xml.calc.template stc
application/vnd.sun.xml.draw sxd
application/vnd.sun.xml.draw.template std
application/vnd.sun.xml.impress sxi
application/vnd.sun.xml.impress.template sti
application/vnd.sun.xml.math sxm
application/vnd.sun.xml.writer sxw
application/vnd.sun.xml.writer.global sxg
application/vnd.sun.xml.writer.template stw
application/vnd.sus-calendar sus susp
application/vnd.svd svd
application/vnd.symbian.install sis sisx
application/vnd.syncml+xml xsm
application/vnd.syncml.dm+wbxml bdm
application/vnd.syncml.dm+xml xdm
application/vnd.tao.intent-module-archive tao
application/vnd.tcpdump.pcap pcap cap dmp
application/vnd.tmobile-livetv tmo
application/vnd.trid.tpt tpt
application/vnd.triscape.mxs mxs
application/vnd.trueapp tra
application/vnd.ufdl ufd ufdl
application/vnd.uiq.theme utz
application/vnd.umajin umj
application/vnd.unity unityweb
application/vnd.uoml+xml uoml
application/vnd.vcx vcx
application/vnd.visio vsd vst vss vsw
application/vnd.visionary vis
application/vnd.vsf vsf
application/vnd.wap.wbxml wbxml
application/vnd.wap.wmlc wmlc
application/vnd.wap.wmlscriptc wmlsc
application/vnd.webturbo wtb
application/vnd.wolfram.player nbp
application/vnd.wordperfect wpd
application/vnd.wqd wqd
application/vnd.wt.stf stf
application/vnd.xara xar
application/vnd.xfdl xfdl
application/vnd.yamaha.hv-dic hvd
application/vnd.yamaha.hv-script hvs
application/vnd.yamaha.hv-voice hvp
application/vnd.yamaha.openscoreformat osf
application/vnd.yamaha.openscoreformat.osfpvg+xml osfpvg
application/vnd.yamaha.smaf-audio saf
application/vnd.yamaha.smaf-phrase spf
application/vnd.yellowriver-custom-menu cmp
application/vnd.zul zir zirz
application/vnd.zzazz.deck+xml zaz
application/voicexml+xml vxml
application/widget wgt
application/winhlp hlp
application/wsdl+xml wsdl
application/wspolicy+xml wspolicy
application/x-7z-compressed 7z
application/x-abiword abw
application/x-ace-compressed ace
application/x-apple-diskimage dmg
application/x-authorware-bin aab x32 u32 vox
application/x-authorware-map aam
application/x-authorware-seg aas
application/x-bcpio bcpio
application/x-bittorrent torrent
application/x-blorb blb blorb
application/x-bzip bz
application/x-bzip2 bz2 boz
application/x-cbr cbr cba cbt cbz cb7
application/x-cdlink vcd
application/x-cfs-compressed cfs
application/x-chat chat
application/x-chess-pgn pgn
application/x-conference nsc
application/x-cpio cpio
application/x-csh csh
application/x-debian-package deb udeb
application/x-dgc-compressed dgc
application/x-director dir dcr dxr cst cct cxt w3d fgd swa
application/x-doom wad
application/x-dtbncx+xml ncx
application/x-dtbook+xml dtb
application/x-dtbresource+xml res
application/x-dvi dvi
application/x-envoy evy
application/x-eva eva
application/x-font-bdf bdf
application/x-font-ghostscript gsf
application/x-font-linux-psf psf
application/x-font-pcf pcf
application/x-font-snf snf
application/x-font-type1 pfa pfb pfm afm
application/x-freearc arc
application/x-futuresplash spl
application/x-gca-compressed gca
application/x-glulx ulx
application/x-gnumeric gnumeric
application/x-gramps-xml gramps
application/x-gtar gtar
application/x-hdf hdf
application/x-install-instructions install
application/x-iso9660-image iso
application/x-java-jnlp-file jnlp
application/x-latex latex
application/x-lzh-compressed lzh lha
application/x-mie mie
application/x-mobipocket-ebook prc mobi
application/x-ms-application application
application/x-ms-shortcut lnk
application/x-ms-wmd wmd
application/x-ms-wmz wmz
application/x-ms-xbap xbap
application/x-msaccess mdb
application/x-msbinder obd
application/x-mscardfile crd
application/x-msclip clp
application/x-msdownload exe dll com bat msi
application/x-msmediaview mvb m13 m14
application/x-msmetafile wmf wmz emf emz
application/x-msmoney mny
application/x-mspublisher pub
application/x-msschedule scd
application/x-msterminal trm
application/x-mswrite wri
application/x-netcdf nc cdf
application/x-nzb nzb
application/x-pkcs12 p12 pfx
application/x-pkcs7-certificates p7b spc
application/x-pkcs7-certreqresp p7r
application/x-rar-compressed rar
application/x-research-info-systems ris
application/x-sh sh
application/x-shar shar
application/x-shockwave-flash swf
application/x-silverlight-app xap
application/x-sql sql
application/x-stuffit sit
application/x-stuffitx sitx
application/x-subrip srt
application/x-sv4cpio sv4cpio
application/x-sv4crc sv4crc
application/x-t3vm-image t3
application/x-tads gam
application/x-tar tar
application/x-tcl tcl
application/x-tex tex
application/x-tex-tfm tfm
application/x-texinfo texinfo texi
application/x-tgif obj
application/x-ustar ustar
application/x-wais-source src
application/x-x509-ca-cert der crt
application/x-xfig fig
application/x-xliff+xml xlf
application/x-xpinstall xpi
application/x-xz xz
application/x-zmachine z1 z2 z3 z4 z5 z6 z7 z8
application/xaml+xml xaml
application/xcap-diff+xml xdf
application/xenc+xml xenc
application/xhtml+xml xhtml xht
application/xml xml xsl
application/xml-dtd dtd
application/xop+xml xop
application/xproc+xml xpl
application/xslt+xml xslt
application/xspf+xml xspf
application/xv+xml mxml xhvml xvml xvm
application/yang yang
application/yin+xml yin
application/zip zip
audio/adpcm adp
audio/basic au snd
audio/midi mid midi kar rmi
audio/mp4 m4a mp4a
audio/mpeg mpga mp2 mp2a mp3 m2a m3a
audio/ogg oga ogg spx
audio/s3m s3m
audio/silk sil
audio/vnd.dece.audio uva uvva
audio/vnd.digital-winds eol
audio/vnd.dra dra
audio/vnd.dts dts
audio/vnd.dts.hd dtshd
audio/vnd.lucent.voice lvp
audio/vnd.ms-playready.media.pya pya
audio/vnd.nuera.ecelp4800 ecelp4800
audio/vnd.nuera.ecelp7470 ecelp7470
audio/vnd.nuera.ecelp9600 ecelp9600
audio/vnd.rip rip
audio/webm weba
audio/x-aac aac
audio/x-aiff aif aiff aifc
audio/x-caf caf
audio/x-flac flac
audio/x-matroska mka
audio/x-mpegurl m3u
audio/x-ms-wax wax
audio/x-ms-wma wma
audio/x-pn-realaudio ram ra
audio/x-pn-realaudio-plugin rmp
audio/x-wav wav
audio/xm xm
chemical/x-cdx cdx
chemical/x-cif cif
chemical/x-cmdf cmdf
chemical/x-cml cml
chemical/x-csml csml
chemical/x-xyz xyz
font/collection ttc
font/otf otf
font/ttf ttf
font/woff woff
font/woff2 woff2
image/bmp bmp
image/cgm cgm
image/g3fax g3
image/gif gif
image/ief ief
image/jpeg jpeg jpg jpe
image/ktx ktx
image/png png
image/prs.btif btif
image/sgi sgi
image/svg+xml svg svgz
image/tiff tiff tif
image/vnd.adobe.photoshop psd
image/vnd.dece.graphic uvi uvvi uvg uvvg
image/vnd.djvu djvu djv
image/vnd.dvb.subtitle sub
image/vnd.dwg dwg
image/vnd.dxf dxf
image/vnd.fastbidsheet fbs
image/vnd.fpx fpx
image/vnd.fst fst
image/vnd.fujixerox.edmics-mmr mmr
image/vnd.fujixerox.edmics-rlc rlc
image/vnd.ms-modi mdi
image/vnd.ms-photo wdp
image/vnd.net-fpx npx
image/vnd.wap.wbmp wbmp
image/vnd.xiff xif
image/webp webp
image/x-3ds 3ds
image/x-cmu-raster ras
image/x-cmx cmx
image/x-freehand fh fhc fh4 fh5 fh7
image/x-icon ico
image/x-mrsid-image sid
image/x-pcx pcx
image/x-pict pic pct
image/x-portable-anymap pnm
image/x-portable-bitmap pbm
image/x-portable-graymap pgm
image/x-portable-pixmap ppm
image/x-rgb rgb
image/x-tga tga
image/x-xbitmap xbm
image/x-xpixmap xpm
image/x-xwindowdump xwd
message/rfc822 eml mime
model/iges igs iges
model/mesh msh mesh silo
model/vnd.collada+xml dae
model/vnd.dwf dwf
model/vnd.gdl gdl
model/vnd.gtw gtw
model/vnd.mts mts
model/vnd.vtu vtu
model/vrml wrl vrml
model/x3d+binary x3db x3dbz
model/x3d+vrml x3dv x3dvz
model/x3d+xml x3d x3dz
text/cache-manifest appcache
text/calendar ics ifb
text/css css
text/csv csv
text/html html htm
text/n3 n3
text/plain txt text conf def list log in
text/prs.lines.tag dsc
text/richtext rtx
text/sgml sgml sgm
text/tab-separated-values tsv
text/troff t tr roff man me ms
text/turtle ttl
text/uri-list uri uris urls
text/vcard vcard
text/vnd.curl curl
text/vnd.curl.dcurl dcurl
text/vnd.curl.mcurl mcurl
text/vnd.curl.scurl scurl
text/vnd.dvb.subtitle sub
text/vnd.fly fly
text/vnd.fmi.flexstor flx
text/vnd.graphviz gv
text/vnd.in3d.3dml 3dml
text/vnd.in3d.spot spot
text/vnd.sun.j2me.app-descriptor jad
text/vnd.wap.wml wml
text/vnd.wap.wmlscript wmls
text/x-asm s asm
text/x-c c cc cxx cpp h hh dic
text/x-fortran f for f77 f90
text/x-java-source java
text/x-nfo nfo
text/x-opml opml
text/x-pascal p pas
text/x-setext etx
text/x-sfv sfv
text/x-uuencode uu
text/x-vcalendar vcs
text/x-vcard vcf
video/3gpp 3gp
video/3gpp2 3g2
video/h261 h261
video/h263 h263
video/h264 h264
video/jpeg jpgv
video/jpm jpm jpgm
video/mj2 mj2 mjp2
video/mp4 mp4 mp4v mpg4
video/mpeg mpeg mpg mpe m1v m2v
video/ogg ogv
video/quicktime qt mov
video/vnd.dece.hd uvh uvvh
video/vnd.dece.mobile uvm uvvm
video/vnd.dece.pd uvp uvvp
video/vnd.dece.sd uvs uvvs
video/vnd.dece.video uvv uvvv
video/vnd.dvb.file dvb
video/vnd.fvt fvt
video/vnd.mpegurl mxu m4u
video/vnd.ms-playready.media.pyv pyv
video/vnd.uvvu.mp4 uvu uvvu
video/vnd.vivo viv
video/webm webm
video/x-f4v f4v
video/x-fli fli
video/x-flv flv
video/x-m4v m4v
video/x-matroska mkv mk3d mks
video/x-mng mng
video/x-ms-asf asf asx
video/x-ms-vob vob
video/x-ms-wm wm
video/x-ms-wmv wmv
video/x-ms-wmx wmx
video/x-ms-wvx wvx
video/x-msvideo avi
video/x-sgi-movie movie
video/x-smv smv
x-conference/x-cooltalk ice

View File

@@ -0,0 +1,20 @@
import mime_raw from './mime-types.md';
const map: Map<string, string> = new Map();
mime_raw.split('\n').forEach((row: string) => {
const match = /(.+?)\t+(.+)/.exec(row);
if (!match) return;
const type = match[1];
const extensions = match[2].split(' ');
extensions.forEach(ext => {
map.set(ext, type);
});
});
export function lookup(file: string) {
const match = /\.([^\.]+)$/.exec(file);
return match && map.get(match[1]);
}

View File

@@ -0,0 +1,69 @@
import { ClientRequest, ServerResponse } from 'http';
export type ServerRoute = {
pattern: RegExp;
handlers: Record<string, Handler>;
params: (match: RegExpMatchArray) => Record<string, string>;
};
export type Page = {
pattern: RegExp;
parts: Array<{
name: string;
component: Component;
params?: (match: RegExpMatchArray) => Record<string, string>;
}>
};
export type Manifest = {
server_routes: ServerRoute[];
pages: Page[];
root: Component;
error: Component;
}
export type Handler = (req: Req, res: Res, next: () => void) => void;
export type Store = {
get: () => any
};
export type Props = {
path: string;
query: Record<string, string>;
params: Record<string, string>;
error?: { message: string };
status?: number;
child: {
segment: string;
component: Component;
props: Props;
};
[key: string]: any;
};
export interface Req extends ClientRequest {
url: string;
baseUrl: string;
originalUrl: string;
method: string;
path: string;
params: Record<string, string>;
query: Record<string, string>;
headers: Record<string, string>;
}
export interface Res extends ServerResponse {
write: (data: any) => void;
}
export { ServerResponse };
interface Component {
render: (data: any, opts: { store: Store }) => {
head: string;
css: { code: string, map: any };
html: string
},
preload: (data: any) => any | Promise<any>
}

View File

@@ -0,0 +1,11 @@
import { Manifest } from './types';
export const manifest: Manifest = __MANIFEST__;
export const build_dir = __BUILD__DIR__;
export const src_dir = __SRC__DIR__;
export const dev = __DEV__;
export const IGNORE = '__SAPPER__IGNORE__';

6
test/app/.gitignore vendored
View File

@@ -1,6 +0,0 @@
.DS_Store
node_modules
.sapper
yarn.lock
cypress/screenshots
templates/.*

View File

@@ -1,81 +0,0 @@
# sapper-template
The default [Sapper](https://github.com/sveltejs/sapper) template. To clone it and get started:
```bash
npx degit sveltejs/sapper-template my-app
cd my-app
npm install # or yarn!
npm run dev
```
Open up [localhost:3000](http://localhost:3000) and start clicking around.
## Structure
Sapper expects to find three directories in the root of your project — `assets`, `routes` and `templates`.
### assets
The [assets](assets) directory contains any static assets that should be available. These are served using [serve-static](https://github.com/expressjs/serve-static).
In your [service-worker.js](templates/service-worker.js) file, Sapper makes these files available as `__assets__` so that you can cache them (though you can choose not to, for example if you don't want to cache very large files).
### routes
This is the heart of your Sapper app. There are two kinds of routes — *pages*, and *server routes*.
**Pages** are Svelte components written in `.html` 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. (Sapper will preload and cache the code for these subsequent pages, so that navigation is instantaneous.)
**Server routes** are modules written in `.js` files, that export functions corresponding to HTTP methods. Each function receives Express `request` and `response` objects as arguments, plus a `next` function. This is useful for creating a JSON API, for example.
There are three simple rules for naming the files that define your routes:
* A file called `routes/about.html` corresponds to the `/about` route. A file called `routes/blog/[slug].html` corresponds to the `/blog/:slug` route, in which case `params.slug` is available to the route
* The file `routes/index.html` (or `routes/index.js`) corresponds to the root of your app. `routes/about/index.html` is treated the same as `routes/about.html`.
* 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 `routes/_helpers/datetime.js` and it would *not* create a `/_helpers/datetime` route
### templates
This directory should contain the following files at a minimum:
* [2xx.html](templates/2xx.html) — a template for the page to serve for valid requests
* [4xx.html](templates/4xx.html) — a template for 4xx-range errors (such as 404 Not Found)
* [5xx.html](templates/5xx.html) — a template for 5xx-range errors (such as 500 Internal Server Error)
* [main.js](templates/main.js) — this module initialises Sapper
* [service-worker.js](templates/service-worker.js) — your app's service worker
Inside the HTML templates, Sapper will inject various values as indicated by `%sapper.xxxx%` tags. Inside JavaScript files, Sapper will replace strings like `__dev__` with the appropriate value.
In lieu of documentation (bear with us), consult the files to see what variables are available and how they're used.
## Webpack config
Sapper uses webpack to provide code-splitting, dynamic imports and hot module reloading, as well as compiling your Svelte components. As long as you don't do anything daft, you can edit the configuration files to add whatever loaders and plugins you'd like.
## Production mode and deployment
To start a production version of your app, run `npm start`. This will disable hot module replacement, and activate the appropriate webpack plugins.
You can deploy your application to any environment that supports Node 8 or above. As an example, to deploy to [Now](https://zeit.co/now), run these commands:
```bash
npm install -g now
now
```
## Bugs and feedback
Sapper is in early development, and may have the odd rough edge here and there. Please be vocal over on the [Sapper issue tracker](https://github.com/sveltejs/sapper/issues).
## License
[LIL](LICENSE)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -1,45 +0,0 @@
body {
margin: 0;
font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-size: 14px;
line-height: 1.5;
color: #333;
}
main {
position: relative;
max-width: 56em;
background-color: white;
padding: 2em;
margin: 0 auto;
box-sizing: border-box;
}
h1, h2, h3, h4, h5, h6 {
margin: 0 0 0.5em 0;
font-weight: 400;
line-height: 1.2;
}
h1 {
font-size: 2em;
}
a {
color: inherit;
}
code {
font-family: menlo, inconsolata, monospace;
font-size: calc(1em - 2px);
color: #555;
background-color: #f0f0f0;
padding: 0.2em 0.4em;
border-radius: 2px;
}
@media (min-width: 400px) {
body {
font-size: 16px;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -1,20 +0,0 @@
{
"background_color": "#ffffff",
"theme_color": "#aa1e1e",
"name": "TODO",
"short_name": "TODO",
"display": "minimal-ui",
"start_url": "/",
"icons": [
{
"src": "svelte-logo-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "svelte-logo-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +0,0 @@
{
"name": "TODO",
"description": "TODO",
"version": "0.0.1",
"scripts": {
"dev": "node server.js",
"build": "sapper build",
"start": "cross-env NODE_ENV=production node server.js",
"prestart": "npm run build"
},
"dependencies": {
"compression": "^1.7.1",
"cross-env": "^5.1.1",
"css-loader": "^0.28.7",
"express": "^4.16.2",
"extract-text-webpack-plugin": "^3.0.2",
"glob": "^7.1.2",
"marked": "^0.3.7",
"node-fetch": "^1.7.3",
"npm-run-all": "^4.1.2",
"serve-static": "^1.13.1",
"style-loader": "^0.19.0",
"svelte": "^1.49.1",
"svelte-loader": "^2.2.1",
"uglifyjs-webpack-plugin": "^1.1.2",
"webpack": "^3.10.0"
}
}

View File

@@ -1,15 +0,0 @@
<Nav page={{page}}/>
<main>
<slot></slot>
</main>
<script>
import Nav from './Nav.html';
export default {
components: {
Nav
}
};
</script>

View File

@@ -1,54 +0,0 @@
<nav>
<ul>
<li><a href='/'>home</a></li>
<li><a href='/about'>about</a></li>
<li><a href='/slow-preload'>slow preload</a></li>
<li><a rel=prefetch class='{{page === "blog" ? "selected" : ""}}' href='/blog'>blog</a></li>
</ul>
</nav>
<style>
nav {
border-bottom: 1px solid rgba(170,30,30,0.1);
font-weight: 300;
padding: 0 1em;
}
ul {
margin: 0;
padding: 0;
}
/* clearfix */
ul::after {
content: '';
display: block;
clear: both;
}
li {
display: block;
float: left;
}
.selected {
position: relative;
display: inline-block;
}
.selected::after {
position: absolute;
content: '';
width: calc(100% - 1em);
height: 2px;
background-color: rgb(170,30,30);
display: block;
bottom: -1px;
}
a {
text-decoration: none;
padding: 1em 0.5em;
display: block;
}
</style>

View File

@@ -1,28 +0,0 @@
<:Head>
<title>About</title>
</:Head>
<Layout page='about'>
<h1>About this site</h1>
<p>This is the 'about' page. There's not much here.</p>
<button class='goto' on:click='goto("/blog/what-is-sapper")'>What is Sapper?</button>
<button class='prefetch' on:click='goto("/blog/why-the-name")'>Why the name?</button>
</Layout>
<script>
import Layout from './_components/Layout.html';
import { goto, prefetch } from '../../../runtime.js';
export default {
components: {
Layout
},
methods: {
goto,
prefetch
}
};
</script>

View File

@@ -1,23 +0,0 @@
import posts from './_posts.js';
const lookup = {};
posts.forEach(post => {
lookup[post.slug] = JSON.stringify(post);
});
export function get(req, res, next) {
// the `slug` parameter is available because this file
// is called [slug].js
const { slug } = req.params;
if (slug in lookup) {
res.set({
'Content-Type': 'application/json',
'Cache-Control': `no-cache`
});
res.end(lookup[slug]);
} else {
next();
}
}

View File

@@ -1,116 +0,0 @@
// Ordinarily, you'd generate this data from markdown files in your
// repo, or fetch them from a database of some kind. But in order to
// avoid unnecessary dependencies in the starter template, and in the
// service of obviousness, we're just going to leave it here.
// This file is called `_posts.js` rather than `posts.js`, because
// we don't want to create an `/api/blog/posts` route — the leading
// underscore tells Sapper not to do that.
const posts = [
{
title: 'What is Sapper?',
slug: 'what-is-sapper',
html: `
<p>First, you have to know what <a href='https://svelte.technology'>Svelte</a> is. Svelte is a UI framework with a bold new idea: rather than providing a library that you write code with (like React or Vue, for example), it's a compiler that turns your components into highly optimized vanilla JavaScript. If you haven't already read the <a href='https://svelte.technology/blog/frameworks-without-the-framework'>introductory blog post</a>, you should!</p>
<p>Sapper is a Next.js-style framework (<a href='/blog/how-is-sapper-different-from-next'>more on that here</a>) built around Svelte. It makes it embarrassingly easy to create extremely high performance web apps. Out of the box, you get:</p>
<ul>
<li>Code-splitting, dynamic imports and hot module replacement, powered by webpack</li>
<li>Server-side rendering (SSR) with client-side hydration</li>
<li>Service worker for offline support, and all the PWA bells and whistles</li>
<li>The nicest development experience you've ever had, or your money back</li>
</ul>
<p>It's implemented as Express middleware. Everything is set up and waiting for you to get started, but you keep complete control over the server, service worker, webpack config and everything else, so it's as flexible as you need it to be.</p>
`
},
{
title: 'How to use Sapper',
slug: 'how-to-use-sapper',
html: `
<h2>Step one</h2>
<p>Create a new project, using <a href='https://github.com/Rich-Harris/degit'>degit</a>:</p>
<pre><code>npx degit sveltejs/sapper-template my-app
cd my-app
npm install # or yarn!
npm run dev
</code></pre>
<h2>Step two</h2>
<p>Go to <a href='http://localhost:3000'>localhost:3000</a>. Open <code>my-app</code> in your editor. Edit the files in the <code>routes</code> directory or add new ones.</p>
<h2>Step three</h2>
<p>...</p>
<h2>Step four</h2>
<p>Resist overdone joke formats.</p>
`
},
{
title: 'Why the name?',
slug: 'why-the-name',
html: `
<p>In war, the soldiers who build bridges, repair roads, clear minefields and conduct demolitions — all under combat conditions — are known as <em>sappers</em>.</p>
<p>For web developers, the stakes are generally lower than those 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 <strong>S</strong>velte <strong>app</strong> mak<strong>er</strong>, is your courageous and dutiful ally.</p>
`
},
{
title: 'How is Sapper different from Next.js?',
slug: 'how-is-sapper-different-from-next',
html: `
<p><a href='https://github.com/zeit/next.js/'>Next.js</a> is a React framework from <a href='https://zeit.co'>Zeit</a>, and is the inspiration for Sapper. There are a few notable differences, however:</p>
<ul>
<li>It's powered by <a href='https://svelte.technology'>Svelte</a> instead of React, so it's faster and your apps are smaller</li>
<li>Instead of route masking, we encode route parameters in filenames. For example, the page you're looking at right now is <code>routes/blog/[slug].html</code></li>
<li>As well as pages (Svelte components, which render on server or client), you can create <em>server routes</em> in your <code>routes</code> directory. These are just <code>.js</code> files that export functions corresponding to HTTP methods, and receive Express <code>request</code> and <code>response</code> objects as arguments. This makes it very easy to, for example, add a JSON API such as the one powering this very page (look in <code>routes/api/blog</code>)</li>
<li>Links are just <code>&lt;a&gt;</code> elements, rather than framework-specific <code>&lt;Link&gt;</code> components. That means, for example, that <a href='/blog/how-can-i-get-involved'>this link right here</a>, despite being inside a blob of HTML, works with the router as you'd expect.</li>
</ul>
`
},
{
title: 'How can I get involved?',
slug: 'how-can-i-get-involved',
html: `
<p>We're so glad you asked! Come on over to the <a href='https://github.com/sveltejs/svelte'>Svelte</a> and <a href='https://github.com/sveltejs/sapper'>Sapper</a> repos, and join us in the <a href='https://gitter.im/sveltejs/svelte'>Gitter chatroom</a>. Everyone is welcome, especially you!</p>
`
},
{
title: 'A very long post with deep links',
slug: 'a-very-long-post',
html: `
<h2 id='one'>One</h2>
<p>I'll have a vodka rocks. (Mom, it's breakfast time.) And a piece of toast. Let me out that Queen. Fried cheese… with club sauce.</p>
<p>Her lawyers are claiming the seal is worth $250,000. And that's not even including Buster's Swatch. This was a big get for God. What, so the guy we are meeting with can't even grow his own hair? COME ON! She's always got to wedge herself in the middle of us so that she can control everything. Yeah. Mom's awesome. It's, like, Hey, you want to go down to the whirlpool? Yeah, I don't have a husband. I call it Swing City. The CIA should've just Googled for his hideout, evidently. There are dozens of us! DOZENS! Yeah, like I'm going to take a whiz through this $5,000 suit. COME ON.</p>
<h2 id='two'>Two</h2>
<p>Tobias Fünke costume. Heart attack never stopped old big bear.</p>
<p>Nellie is blowing them all AWAY. I will be a bigger and hairier mole than the one on your inner left thigh! I'll sacrifice anything for my children.</p>
<p>Up yours, granny! You couldn't handle it! Hey, Dad. Look at you. You're a year older…and a year closer to death. Buster: Oh yeah, I guess that's kind of funny. Bob Loblaw Law Blog. The guy runs a prison, he can have any piece of ass he wants.</p>
<h2 id='three'>Three</h2>
<p>I prematurely shot my wad on what was supposed to be a dry run, so now I'm afraid I have something of a mess on my hands. Dead Dove DO NOT EAT. Never once touched my per diem. I'd go to Craft Service, get some raw veggies, bacon, Cup-A-Soup…baby, I got a stew goin'. You're losing blood, aren't you? Gob: Probably, my socks are wet. Sure, let the little fruit do it. HUZZAH! Although George Michael had only got to second base, he'd gone in head first, like Pete Rose. I will pack your sweet pink mouth with so much ice cream you'll be the envy of every Jerry and Jane on the block!</p>
<p>Gosh Mom… after all these years, God's not going to take a call from you. Come on, this is a Bluth family celebration. It's no place for children.</p>
<p>And I wouldn't just lie there, if that's what you're thinking. That's not what I WAS thinking. Who? i just dont want him to point out my cracker ass in front of ann. When a man needs to prove to a woman that he's actually… When a man loves a woman… Heyyyyyy Uncle Father Oscar. [Stabbing Gob] White power! Gob: I'm white! Let me take off my assistant's skirt and put on my Barbra-Streisand-in-The-Prince-of-Tides ass-masking therapist pantsuit. In the mid '90s, Tobias formed a folk music band with Lindsay and Maebe which he called Dr. Funke's 100 Percent Natural Good Time Family Band Solution. The group was underwritten by the Natural Food Life Company, a division of Chem-Grow, an Allen Crayne acqusition, which was part of the Squimm Group. Their motto was simple: We keep you alive.</p>
<h2 id='four'>Four</h2>
<p>If you didn't have adult onset diabetes, I wouldn't mind giving you a little sugar. Everybody dance NOW. And the soup of the day is bread. Great, now I'm gonna smell to high heaven like a tuna melt!</p>
<p>That's how Tony Wonder lost a nut. She calls it a Mayonegg. Go ahead, touch the Cornballer. There's a new daddy in town. A discipline daddy.</p>
`
}
];
posts.forEach(post => {
post.html = post.html.replace(/^\t{3}/gm, '');
});
export default posts;

View File

@@ -1,17 +0,0 @@
import posts from './_posts.js';
const contents = JSON.stringify(posts.map(post => {
return {
title: post.title,
slug: post.slug
};
}));
export function get(req, res) {
res.set({
'Content-Type': 'application/json',
'Cache-Control': `max-age=${30 * 60 * 1e3}` // cache for 30 minutes
});
res.end(contents);
}

View File

@@ -1,67 +0,0 @@
<:Head>
<title>{{post.title}}</title>
</:Head>
<Layout page='blog'>
<h1>{{post.title}}</h1>
<div class='content'>
{{{post.html}}}
</div>
</Layout>
<style>
/*
By default, CSS is locally scoped to the component,
and any unused styles are dead-code-eliminated.
In this page, Svelte can't know which elements are
going to appear inside the {{{post.html}}} block,
so we have to use the :global(...) modifier to target
all elements inside .content
*/
.content :global(h2) {
font-size: 1.4em;
font-weight: 500;
}
.content :global(pre) {
background-color: #f9f9f9;
box-shadow: inset 1px 1px 5px rgba(0,0,0,0.05);
padding: 0.5em;
border-radius: 2px;
overflow-x: auto;
}
.content :global(pre) :global(code) {
background-color: transparent;
padding: 0;
}
.content :global(ul) {
line-height: 1.5;
}
.content :global(li) {
margin: 0 0 0.5em 0;
}
</style>
<script>
import Layout from '../_components/Layout.html';
export default {
components: {
Layout
},
preload({ params, query }) {
// the `slug` parameter is available because this file
// is called [slug].html
const { slug } = params;
return fetch(`/api/blog/${slug}`).then(r => r.json()).then(post => {
return { post };
});
}
};
</script>

View File

@@ -1,40 +0,0 @@
<:Head>
<title>Blog</title>
</:Head>
<Layout page='blog'>
<h1>Recent posts</h1>
<ul>
{{#each posts as post}}
<!-- we're using the non-standard `rel=prefetch` attribute to
tell Sapper to load the data for the page as soon as
the user hovers over the link or taps it, instead of
waiting for the 'click' event -->
<li><a rel='prefetch' href='/blog/{{post.slug}}'>{{post.title}}</a></li>
{{/each}}
</ul>
</Layout>
<style>
ul {
margin: 0 0 1em 0;
line-height: 1.5;
}
</style>
<script>
import Layout from '../_components/Layout.html';
export default {
components: {
Layout
},
preload({ params, query }) {
return fetch(`/api/blog`).then(r => r.json()).then(posts => {
return { posts };
});
}
};
</script>

View File

@@ -1,58 +0,0 @@
<:Head>
<title>Sapper project template</title>
</:Head>
<Layout page='home'>
<h1>Great success!</h1>
<figure>
<img src='/great-success.png'>
<figcaption>HIGH FIVE!</figcaption>
</figure>
<p><strong>Try editing this file (routes/index.html) to test hot module reloading.</strong></p>
</Layout>
<style>
h1, figure, p {
text-align: center;
margin: 0 auto;
}
h1 {
font-size: 2.8em;
text-transform: uppercase;
font-weight: 700;
margin: 0 0 0.5em 0;
}
figure {
margin: 0 0 1em 0;
}
img {
width: 100%;
max-width: 400px;
margin: 0 0 1em 0;
}
p {
margin: 1em auto;
}
@media (min-width: 480px) {
h1 {
font-size: 4em;
}
}
</style>
<script>
import Layout from './_components/Layout.html';
export default {
components: {
Layout
}
};
</script>

View File

@@ -1,9 +0,0 @@
<p>URL is {{url}}</p>
<script>
export default {
preload({ url }) {
return { url };
}
};
</script>

View File

@@ -1,24 +0,0 @@
const fs = require('fs');
const app = require('express')();
const compression = require('compression');
const sapper = require('sapper');
const static = require('serve-static');
const { PORT = 3000 } = process.env;
// this allows us to do e.g. `fetch('/api/blog')` on the server
const fetch = require('node-fetch');
global.fetch = (url, opts) => {
if (url[0] === '/') url = `http://localhost:${PORT}${url}`;
return fetch(url, opts);
};
app.use(compression({ threshold: 0 }));
app.use(static('assets'));
app.use(sapper());
app.listen(PORT, () => {
console.log(`listening on port ${PORT}`);
});

View File

@@ -1,37 +0,0 @@
<!doctype>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width'>
<meta name='theme-color' content='#aa1e1e'>
<link rel='stylesheet' href='/global.css'>
<link rel='manifest' href='/manifest.json'>
<link rel='icon' type='image/png' href='/favicon.png'>
<script>
// if ('serviceWorker' in navigator) {
// navigator.serviceWorker.register('/service-worker.js');
// }
</script>
<!-- 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%
<!-- This contains the contents of the <:Head> component, if
the current page has one -->
%sapper.head%
</head>
<body>
<!-- The application will be rendered inside this element,
because `templates/main.js` references it -->
<div id='sapper'>%sapper.html%</div>
<!-- Sapper creates a <script> tag containing `templates/main.js`
and anything else it needs to hydrate the app and
initialise the router -->
<script src='%sapper.main%'></script>
</body>
</html>

View File

@@ -1,44 +0,0 @@
<!doctype>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width'>
<meta name='theme-color' content='#aa1e1e'>
<link rel='manifest' href='/manifest.json'>
<link rel='icon' type='image/png' href='/favicon.png'>
<!-- %sapper.status% is the HTTP status code, e.g. 404 -->
<title>%sapper.status%</title>
<style>
body {
max-width: 800px;
padding: 1em;
margin: 0 auto;
font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
background-color: #f4f4f4;
box-sizing: border-box;
}
h1 {
color: rgb(170,30,30);
border-bottom: 1px solid #aaa;
padding: 0 0 0.5em 0;
margin: 1em 0;
}
pre {
font-family: Menlo, monospace;
font-size: 14px;
line-height: 1.2;
overflow-x: auto;
white-space: pre-wrap;
}
</style>
</head>
<body>
<h1>%sapper.title%</h1>
<p>Could not %sapper.method% %sapper.url%</p>
</body>
</html>

View File

@@ -1,47 +0,0 @@
<!doctype>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width'>
<meta name='theme-color' content='#aa1e1e'>
<link rel='manifest' href='/manifest.json'>
<link rel='icon' type='image/png' href='/favicon.png'>
<title>%sapper.status%</title>
<style>
body {
max-width: 800px;
padding: 1em;
margin: 0 auto;
box-sizing: border-box;
}
h1 {
color: rgb(170,30,30);
border-bottom: 1px solid #aaa;
padding: 0 0 0.5em 0;
margin: 1em 0;
}
pre {
font-family: Menlo, monospace;
font-size: 14px;
line-height: 1.2;
overflow-x: auto;
white-space: pre-wrap;
}
.stack {
font-size: 12px;
color: #999;
}
</style>
</head>
<body>
<h1>%sapper.title%</h1>
<pre>%sapper.error%</pre>
<pre class='stack'>%sapper.stack%</pre>
</body>
</html>

View File

@@ -1,6 +0,0 @@
import { init } from '../../../runtime.js';
// `routes` is an array of route objects injected by Sapper
init(document.querySelector('#sapper'), __routes__);
window.READY = true;

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