@sveltejs/kit 1.0.0-next.291 → 1.0.0-next.292

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,30 +1,42 @@
1
- import fs__default from 'fs';
2
- import path__default from 'path';
3
- import { e as print_config_conflicts, b as copy_assets, p as posixify, d as get_aliases, r as resolve_entry, g as get_runtime_path, l as load_template, m as mkdirp, f as rimraf } from '../cli.js';
4
- import { d as deep_merge, a as create_app, g as generate_tsconfig, c as create_manifest_data } from './tsconfig.js';
1
+ import fs__default, { readFileSync, writeFileSync } from 'fs';
2
+ import path__default, { join, dirname } from 'path';
3
+ import { p as posixify, m as mkdirp, r as rimraf } from './filesystem.js';
4
+ import { all } from './sync.js';
5
+ import { p as print_config_conflicts, b as get_aliases, r as resolve_entry, g as get_runtime_path, l as load_template } from '../cli.js';
5
6
  import { g as generate_manifest } from './index3.js';
6
7
  import vite from 'vite';
7
8
  import { s } from './misc.js';
9
+ import { d as deep_merge } from './object.js';
10
+ import { n as normalize_path, r as resolve, i as is_root_relative } from './url.js';
8
11
  import { svelte } from '@sveltejs/vite-plugin-svelte';
12
+ import { pathToFileURL, URL } from 'url';
13
+ import { installFetch } from '../install-fetch.js';
9
14
  import 'sade';
10
15
  import 'child_process';
11
16
  import 'net';
12
- import 'url';
13
17
  import 'os';
18
+ import 'node:http';
19
+ import 'node:https';
20
+ import 'node:zlib';
21
+ import 'node:stream';
22
+ import 'node:util';
23
+ import 'node:url';
14
24
 
15
25
  /**
16
26
  * @param {{
17
27
  * cwd: string;
18
28
  * assets_base: string;
19
- * config: import('types').ValidatedConfig
20
- * manifest_data: import('types').ManifestData
29
+ * config: import('types').ValidatedConfig;
30
+ * manifest_data: import('types').ManifestData;
21
31
  * output_dir: string;
22
32
  * service_worker_entry_file: string | null;
23
33
  * }} options
34
+ * @param {import('types').Prerendered} prerendered
24
35
  * @param {import('vite').Manifest} client_manifest
25
36
  */
26
37
  async function build_service_worker(
27
38
  { cwd, assets_base, config, manifest_data, output_dir, service_worker_entry_file },
39
+ prerendered,
28
40
  client_manifest
29
41
  ) {
30
42
  // TODO add any assets referenced in template .html file, e.g. favicon?
@@ -57,6 +69,12 @@ async function build_service_worker(
57
69
  .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`)
58
70
  .join(',\n\t\t\t\t')}
59
71
  ];
72
+
73
+ export const prerendered = [
74
+ ${prerendered.paths
75
+ .map((path) => s(normalize_path(path, config.kit.trailingSlash)))
76
+ .join(',\n\t\t\t\t')}
77
+ ];
60
78
  `
61
79
  .replace(/^\t{3}/gm, '')
62
80
  .trim()
@@ -161,14 +179,6 @@ async function build_client({
161
179
  process.env.VITE_SVELTEKIT_APP_VERSION_FILE = `${config.kit.appDir}/version.json`;
162
180
  process.env.VITE_SVELTEKIT_APP_VERSION_POLL_INTERVAL = `${config.kit.version.pollInterval}`;
163
181
 
164
- create_app({
165
- config,
166
- manifest_data,
167
- cwd
168
- });
169
-
170
- copy_assets(path__default.join(config.kit.outDir, 'runtime'));
171
-
172
182
  process.env.VITE_SVELTEKIT_AMP = config.kit.amp ? 'true' : '';
173
183
 
174
184
  const client_out_dir = `${output_dir}/client/${config.kit.appDir}`;
@@ -227,7 +237,10 @@ async function build_client({
227
237
  hydratable: !!config.kit.browser.hydrate
228
238
  }
229
239
  })
230
- ]
240
+ ],
241
+ // prevent Vite copying the contents of `config.kit.files.assets`,
242
+ // if it happens to be 'public' instead of 'static'
243
+ publicDir: false
231
244
  });
232
245
 
233
246
  print_config_conflicts(conflicts, 'kit.vite.', 'build_client');
@@ -585,11 +598,630 @@ function get_methods(cwd, output, manifest_data) {
585
598
  return methods;
586
599
  }
587
600
 
601
+ /** @typedef {{
602
+ * fn: () => Promise<any>,
603
+ * fulfil: (value: any) => void,
604
+ * reject: (error: Error) => void
605
+ * }} Task */
606
+
607
+ /** @param {number} concurrency */
608
+ function queue(concurrency) {
609
+ /** @type {Task[]} */
610
+ const tasks = [];
611
+
612
+ let current = 0;
613
+
614
+ /** @type {(value?: any) => void} */
615
+ let fulfil;
616
+
617
+ /** @type {(error: Error) => void} */
618
+ let reject;
619
+
620
+ let closed = false;
621
+
622
+ const done = new Promise((f, r) => {
623
+ fulfil = f;
624
+ reject = r;
625
+ });
626
+
627
+ done.catch(() => {
628
+ // this is necessary in case a catch handler is never added
629
+ // to the done promise by the user
630
+ });
631
+
632
+ function dequeue() {
633
+ if (current < concurrency) {
634
+ const task = tasks.shift();
635
+
636
+ if (task) {
637
+ current += 1;
638
+ const promise = Promise.resolve(task.fn());
639
+
640
+ promise
641
+ .then(task.fulfil, (err) => {
642
+ task.reject(err);
643
+ reject(err);
644
+ })
645
+ .then(() => {
646
+ current -= 1;
647
+ dequeue();
648
+ });
649
+ } else if (current === 0) {
650
+ closed = true;
651
+ fulfil();
652
+ }
653
+ }
654
+ }
655
+
656
+ return {
657
+ /** @param {() => any} fn */
658
+ add: (fn) => {
659
+ if (closed) throw new Error('Cannot add tasks to a queue that has ended');
660
+
661
+ const promise = new Promise((fulfil, reject) => {
662
+ tasks.push({ fn, fulfil, reject });
663
+ });
664
+
665
+ dequeue();
666
+ return promise;
667
+ },
668
+
669
+ done: () => {
670
+ if (current === 0) {
671
+ closed = true;
672
+ fulfil();
673
+ }
674
+
675
+ return done;
676
+ }
677
+ };
678
+ }
679
+
680
+ const DOCTYPE = 'DOCTYPE';
681
+ const CDATA_OPEN = '[CDATA[';
682
+ const CDATA_CLOSE = ']]>';
683
+ const COMMENT_OPEN = '--';
684
+ const COMMENT_CLOSE = '-->';
685
+
686
+ const TAG_OPEN = /[a-zA-Z]/;
687
+ const TAG_CHAR = /[a-zA-Z0-9]/;
688
+ const ATTRIBUTE_NAME = /[^\t\n\f />"'=]/;
689
+
690
+ const WHITESPACE = /[\s\n\r]/;
691
+
692
+ /** @param {string} html */
693
+ function crawl(html) {
694
+ /** @type {string[]} */
695
+ const hrefs = [];
696
+
697
+ let i = 0;
698
+ main: while (i < html.length) {
699
+ const char = html[i];
700
+
701
+ if (char === '<') {
702
+ if (html[i + 1] === '!') {
703
+ i += 2;
704
+
705
+ if (html.substr(i, DOCTYPE.length).toUpperCase() === DOCTYPE) {
706
+ i += DOCTYPE.length;
707
+ while (i < html.length) {
708
+ if (html[i++] === '>') {
709
+ continue main;
710
+ }
711
+ }
712
+ }
713
+
714
+ // skip cdata
715
+ if (html.substr(i, CDATA_OPEN.length) === CDATA_OPEN) {
716
+ i += CDATA_OPEN.length;
717
+ while (i < html.length) {
718
+ if (html.substr(i, CDATA_CLOSE.length) === CDATA_CLOSE) {
719
+ i += CDATA_CLOSE.length;
720
+ continue main;
721
+ }
722
+
723
+ i += 1;
724
+ }
725
+ }
726
+
727
+ // skip comments
728
+ if (html.substr(i, COMMENT_OPEN.length) === COMMENT_OPEN) {
729
+ i += COMMENT_OPEN.length;
730
+ while (i < html.length) {
731
+ if (html.substr(i, COMMENT_CLOSE.length) === COMMENT_CLOSE) {
732
+ i += COMMENT_CLOSE.length;
733
+ continue main;
734
+ }
735
+
736
+ i += 1;
737
+ }
738
+ }
739
+ }
740
+
741
+ // parse opening tags
742
+ const start = ++i;
743
+ if (TAG_OPEN.test(html[start])) {
744
+ while (i < html.length) {
745
+ if (!TAG_CHAR.test(html[i])) {
746
+ break;
747
+ }
748
+
749
+ i += 1;
750
+ }
751
+
752
+ const tag = html.slice(start, i).toUpperCase();
753
+
754
+ if (tag === 'SCRIPT' || tag === 'STYLE') {
755
+ while (i < html.length) {
756
+ if (
757
+ html[i] === '<' &&
758
+ html[i + 1] === '/' &&
759
+ html.substr(i + 2, tag.length).toUpperCase() === tag
760
+ ) {
761
+ continue main;
762
+ }
763
+
764
+ i += 1;
765
+ }
766
+ }
767
+
768
+ let href = '';
769
+
770
+ while (i < html.length) {
771
+ const start = i;
772
+
773
+ const char = html[start];
774
+ if (char === '>') break;
775
+
776
+ if (ATTRIBUTE_NAME.test(char)) {
777
+ i += 1;
778
+
779
+ while (i < html.length) {
780
+ if (!ATTRIBUTE_NAME.test(html[i])) {
781
+ break;
782
+ }
783
+
784
+ i += 1;
785
+ }
786
+
787
+ const name = html.slice(start, i).toLowerCase();
788
+
789
+ while (WHITESPACE.test(html[i])) i += 1;
790
+
791
+ if (html[i] === '=') {
792
+ i += 1;
793
+ while (WHITESPACE.test(html[i])) i += 1;
794
+
795
+ let value;
796
+
797
+ if (html[i] === "'" || html[i] === '"') {
798
+ const quote = html[i++];
799
+
800
+ const start = i;
801
+ let escaped = false;
802
+
803
+ while (i < html.length) {
804
+ if (!escaped) {
805
+ const char = html[i];
806
+
807
+ if (html[i] === quote) {
808
+ break;
809
+ }
810
+
811
+ if (char === '\\') {
812
+ escaped = true;
813
+ }
814
+ }
815
+
816
+ i += 1;
817
+ }
818
+
819
+ value = html.slice(start, i);
820
+ } else {
821
+ const start = i;
822
+ while (html[i] !== '>' && !WHITESPACE.test(html[i])) i += 1;
823
+ value = html.slice(start, i);
824
+
825
+ i -= 1;
826
+ }
827
+
828
+ if (name === 'href') {
829
+ href = value;
830
+ } else if (name === 'src') {
831
+ hrefs.push(value);
832
+ } else if (name === 'srcset') {
833
+ const candidates = [];
834
+ let insideURL = true;
835
+ value = value.trim();
836
+ for (let i = 0; i < value.length; i++) {
837
+ if (value[i] === ',' && (!insideURL || (insideURL && value[i + 1] === ' '))) {
838
+ candidates.push(value.slice(0, i));
839
+ value = value.substring(i + 1).trim();
840
+ i = 0;
841
+ insideURL = true;
842
+ } else if (value[i] === ' ') {
843
+ insideURL = false;
844
+ }
845
+ }
846
+ candidates.push(value);
847
+ for (const candidate of candidates) {
848
+ const src = candidate.split(WHITESPACE)[0];
849
+ hrefs.push(src);
850
+ }
851
+ }
852
+ } else {
853
+ i -= 1;
854
+ }
855
+ }
856
+
857
+ i += 1;
858
+ }
859
+
860
+ if (href) {
861
+ hrefs.push(href);
862
+ }
863
+ }
864
+ }
865
+
866
+ i += 1;
867
+ }
868
+
869
+ return hrefs;
870
+ }
871
+
872
+ /**
873
+ * Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
874
+ *
875
+ * The first closes the script element, so everything after is treated as raw HTML.
876
+ * The second disables further parsing until `-->`, so the script element might be unexpectedly
877
+ * kept open until until an unrelated HTML comment in the page.
878
+ *
879
+ * U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
880
+ * browsers.
881
+ *
882
+ * @see tests for unsafe parsing examples.
883
+ * @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
884
+ * @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
885
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
886
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
887
+ * @see https://github.com/tc39/proposal-json-superset
888
+ * @type {Record<string, string>}
889
+ */
890
+ const render_json_payload_script_dict = {
891
+ '<': '\\u003C',
892
+ '\u2028': '\\u2028',
893
+ '\u2029': '\\u2029'
894
+ };
895
+
896
+ new RegExp(
897
+ `[${Object.keys(render_json_payload_script_dict).join('')}]`,
898
+ 'g'
899
+ );
900
+
901
+ /**
902
+ * When inside a double-quoted attribute value, only `&` and `"` hold special meaning.
903
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#attribute-value-(double-quoted)-state
904
+ * @type {Record<string, string>}
905
+ */
906
+ const escape_html_attr_dict = {
907
+ '&': '&amp;',
908
+ '"': '&quot;'
909
+ };
910
+
911
+ const escape_html_attr_regex = new RegExp(
912
+ // special characters
913
+ `[${Object.keys(escape_html_attr_dict).join('')}]|` +
914
+ // high surrogate without paired low surrogate
915
+ '[\\ud800-\\udbff](?![\\udc00-\\udfff])|' +
916
+ // a valid surrogate pair, the only match with 2 code units
917
+ // we match it so that we can match unpaired low surrogates in the same pass
918
+ // TODO: use lookbehind assertions once they are widely supported: (?<![\ud800-udbff])[\udc00-\udfff]
919
+ '[\\ud800-\\udbff][\\udc00-\\udfff]|' +
920
+ // unpaired low surrogate (see previous match)
921
+ '[\\udc00-\\udfff]',
922
+ 'g'
923
+ );
924
+
925
+ /**
926
+ * Formats a string to be used as an attribute's value in raw HTML.
927
+ *
928
+ * It escapes unpaired surrogates (which are allowed in js strings but invalid in HTML), escapes
929
+ * characters that are special in attributes, and surrounds the whole string in double-quotes.
930
+ *
931
+ * @param {string} str
932
+ * @returns {string} Escaped string surrounded by double-quotes.
933
+ * @example const html = `<tag data-value=${escape_html_attr('value')}>...</tag>`;
934
+ */
935
+ function escape_html_attr(str) {
936
+ const escaped_str = str.replace(escape_html_attr_regex, (match) => {
937
+ if (match.length === 2) {
938
+ // valid surrogate pair
939
+ return match;
940
+ }
941
+
942
+ return escape_html_attr_dict[match] ?? `&#${match.charCodeAt(0)};`;
943
+ });
944
+
945
+ return `"${escaped_str}"`;
946
+ }
947
+
948
+ /**
949
+ * @typedef {import('types').PrerenderErrorHandler} PrerenderErrorHandler
950
+ * @typedef {import('types').PrerenderOnErrorValue} OnError
951
+ * @typedef {import('types').Logger} Logger
952
+ */
953
+
954
+ /** @type {(details: Parameters<PrerenderErrorHandler>[0] ) => string} */
955
+ function format_error({ status, path, referrer, referenceType }) {
956
+ return `${status} ${path}${referrer ? ` (${referenceType} from ${referrer})` : ''}`;
957
+ }
958
+
959
+ /** @type {(log: Logger, onError: OnError) => PrerenderErrorHandler} */
960
+ function normalise_error_handler(log, onError) {
961
+ switch (onError) {
962
+ case 'continue':
963
+ return (details) => {
964
+ log.error(format_error(details));
965
+ };
966
+ case 'fail':
967
+ return (details) => {
968
+ throw new Error(format_error(details));
969
+ };
970
+ default:
971
+ return onError;
972
+ }
973
+ }
974
+
975
+ const OK = 2;
976
+ const REDIRECT = 3;
977
+
978
+ /**
979
+ * @param {{
980
+ * config: import('types').ValidatedConfig;
981
+ * entries: string[];
982
+ * files: Set<string>;
983
+ * log: Logger;
984
+ * }} opts
985
+ */
986
+ async function prerender({ config, entries, files, log }) {
987
+ /** @type {import('types').Prerendered} */
988
+ const prerendered = {
989
+ pages: new Map(),
990
+ assets: new Map(),
991
+ redirects: new Map(),
992
+ paths: []
993
+ };
994
+
995
+ if (!config.kit.prerender.enabled) {
996
+ return prerendered;
997
+ }
998
+
999
+ installFetch();
1000
+
1001
+ const server_root = join(config.kit.outDir, 'output');
1002
+
1003
+ /** @type {import('types').ServerModule} */
1004
+ const { Server, override } = await import(pathToFileURL(`${server_root}/server/index.js`).href);
1005
+ const { manifest } = await import(pathToFileURL(`${server_root}/server/manifest.js`).href);
1006
+
1007
+ override({
1008
+ paths: config.kit.paths,
1009
+ prerendering: true,
1010
+ read: (file) => readFileSync(join(config.kit.files.assets, file))
1011
+ });
1012
+
1013
+ const server = new Server(manifest);
1014
+
1015
+ const error = normalise_error_handler(log, config.kit.prerender.onError);
1016
+
1017
+ const q = queue(config.kit.prerender.concurrency);
1018
+
1019
+ /**
1020
+ * @param {string} path
1021
+ * @param {boolean} is_html
1022
+ */
1023
+ function output_filename(path, is_html) {
1024
+ const file = path.slice(config.kit.paths.base.length + 1);
1025
+
1026
+ if (file === '') {
1027
+ return 'index.html';
1028
+ }
1029
+
1030
+ if (is_html && !file.endsWith('.html')) {
1031
+ return file + (config.kit.trailingSlash === 'always' ? 'index.html' : '.html');
1032
+ }
1033
+
1034
+ return file;
1035
+ }
1036
+
1037
+ const seen = new Set();
1038
+ const written = new Set();
1039
+
1040
+ /**
1041
+ * @param {string | null} referrer
1042
+ * @param {string} decoded
1043
+ * @param {string} [encoded]
1044
+ */
1045
+ function enqueue(referrer, decoded, encoded) {
1046
+ if (seen.has(decoded)) return;
1047
+ seen.add(decoded);
1048
+
1049
+ const file = decoded.slice(config.kit.paths.base.length + 1);
1050
+ if (files.has(file)) return;
1051
+
1052
+ return q.add(() => visit(decoded, encoded || encodeURI(decoded), referrer));
1053
+ }
1054
+
1055
+ /**
1056
+ * @param {string} decoded
1057
+ * @param {string} encoded
1058
+ * @param {string?} referrer
1059
+ */
1060
+ async function visit(decoded, encoded, referrer) {
1061
+ if (!decoded.startsWith(config.kit.paths.base)) {
1062
+ error({ status: 404, path: decoded, referrer, referenceType: 'linked' });
1063
+ return;
1064
+ }
1065
+
1066
+ /** @type {Map<string, import('types').PrerenderDependency>} */
1067
+ const dependencies = new Map();
1068
+
1069
+ const response = await server.respond(new Request(`http://sveltekit-prerender${encoded}`), {
1070
+ prerender: {
1071
+ default: config.kit.prerender.default,
1072
+ dependencies
1073
+ }
1074
+ });
1075
+
1076
+ const text = await response.text();
1077
+
1078
+ save('pages', response, text, decoded, encoded, referrer, 'linked');
1079
+
1080
+ for (const [dependency_path, result] of dependencies) {
1081
+ // this seems circuitous, but using new URL allows us to not care
1082
+ // whether dependency_path is encoded or not
1083
+ const encoded_dependency_path = new URL(dependency_path, 'http://localhost').pathname;
1084
+ const decoded_dependency_path = decodeURI(encoded_dependency_path);
1085
+
1086
+ const body = result.body ?? new Uint8Array(await result.response.arrayBuffer());
1087
+ save(
1088
+ 'dependencies',
1089
+ result.response,
1090
+ body,
1091
+ decoded_dependency_path,
1092
+ encoded_dependency_path,
1093
+ decoded,
1094
+ 'fetched'
1095
+ );
1096
+ }
1097
+
1098
+ if (config.kit.prerender.crawl && response.headers.get('content-type') === 'text/html') {
1099
+ for (const href of crawl(text)) {
1100
+ if (href.startsWith('data:') || href.startsWith('#')) continue;
1101
+
1102
+ const resolved = resolve(encoded, href);
1103
+ if (!is_root_relative(resolved)) continue;
1104
+
1105
+ const parsed = new URL(resolved, 'http://localhost');
1106
+
1107
+ if (parsed.search) ;
1108
+
1109
+ const pathname = normalize_path(parsed.pathname, config.kit.trailingSlash);
1110
+ enqueue(decoded, decodeURI(pathname), pathname);
1111
+ }
1112
+ }
1113
+ }
1114
+
1115
+ /**
1116
+ * @param {'pages' | 'dependencies'} category
1117
+ * @param {Response} response
1118
+ * @param {string | Uint8Array} body
1119
+ * @param {string} decoded
1120
+ * @param {string} encoded
1121
+ * @param {string | null} referrer
1122
+ * @param {'linked' | 'fetched'} referenceType
1123
+ */
1124
+ function save(category, response, body, decoded, encoded, referrer, referenceType) {
1125
+ const response_type = Math.floor(response.status / 100);
1126
+ const type = /** @type {string} */ (response.headers.get('content-type'));
1127
+ const is_html = response_type === REDIRECT || type === 'text/html';
1128
+
1129
+ const file = output_filename(decoded, is_html);
1130
+ const dest = `${config.kit.outDir}/output/prerendered/${category}/${file}`;
1131
+
1132
+ if (written.has(file)) return;
1133
+ written.add(file);
1134
+
1135
+ if (response_type === REDIRECT) {
1136
+ const location = response.headers.get('location');
1137
+
1138
+ if (location) {
1139
+ mkdirp(dirname(dest));
1140
+
1141
+ log.warn(`${response.status} ${decoded} -> ${location}`);
1142
+
1143
+ writeFileSync(
1144
+ dest,
1145
+ `<meta http-equiv="refresh" content=${escape_html_attr(`0;url=${location}`)}>`
1146
+ );
1147
+
1148
+ let resolved = resolve(encoded, location);
1149
+ if (is_root_relative(resolved)) {
1150
+ resolved = normalize_path(resolved, config.kit.trailingSlash);
1151
+ enqueue(decoded, decodeURI(resolved), resolved);
1152
+ }
1153
+
1154
+ if (!prerendered.redirects.has(decoded)) {
1155
+ prerendered.redirects.set(decoded, {
1156
+ status: response.status,
1157
+ location: resolved
1158
+ });
1159
+
1160
+ prerendered.paths.push(normalize_path(decoded, 'never'));
1161
+ }
1162
+ } else {
1163
+ log.warn(`location header missing on redirect received from ${decoded}`);
1164
+ }
1165
+
1166
+ return;
1167
+ }
1168
+
1169
+ if (response.status === 200) {
1170
+ mkdirp(dirname(dest));
1171
+
1172
+ log.info(`${response.status} ${decoded}`);
1173
+ writeFileSync(dest, body);
1174
+
1175
+ if (is_html) {
1176
+ prerendered.pages.set(decoded, {
1177
+ file
1178
+ });
1179
+ } else {
1180
+ prerendered.assets.set(decoded, {
1181
+ type
1182
+ });
1183
+ }
1184
+
1185
+ prerendered.paths.push(normalize_path(decoded, 'never'));
1186
+ } else if (response_type !== OK) {
1187
+ error({ status: response.status, path: decoded, referrer, referenceType });
1188
+ }
1189
+ }
1190
+
1191
+ if (config.kit.prerender.enabled) {
1192
+ for (const entry of config.kit.prerender.entries) {
1193
+ if (entry === '*') {
1194
+ for (const entry of entries) {
1195
+ enqueue(null, normalize_path(config.kit.paths.base + entry, config.kit.trailingSlash)); // TODO can we pre-normalize these?
1196
+ }
1197
+ } else {
1198
+ enqueue(null, normalize_path(config.kit.paths.base + entry, config.kit.trailingSlash));
1199
+ }
1200
+ }
1201
+
1202
+ await q.done();
1203
+ }
1204
+
1205
+ const rendered = await server.respond(new Request('http://sveltekit-prerender/[fallback]'), {
1206
+ prerender: {
1207
+ fallback: true,
1208
+ default: false,
1209
+ dependencies: new Map()
1210
+ }
1211
+ });
1212
+
1213
+ const file = `${config.kit.outDir}/output/prerendered/fallback.html`;
1214
+ mkdirp(dirname(file));
1215
+ writeFileSync(file, await rendered.text());
1216
+
1217
+ return prerendered;
1218
+ }
1219
+
588
1220
  /**
589
1221
  * @param {import('types').ValidatedConfig} config
590
- * @returns {Promise<import('types').BuildData>}
1222
+ * @param {{ log: import('types').Logger }} opts
591
1223
  */
592
- async function build(config) {
1224
+ async function build(config, { log }) {
593
1225
  const cwd = process.cwd(); // TODO is this necessary?
594
1226
 
595
1227
  const build_dir = path__default.join(config.kit.outDir, 'build');
@@ -600,7 +1232,7 @@ async function build(config) {
600
1232
  rimraf(output_dir);
601
1233
  mkdirp(output_dir);
602
1234
 
603
- generate_tsconfig(config);
1235
+ const { manifest_data } = all(config);
604
1236
 
605
1237
  const options = {
606
1238
  cwd,
@@ -611,10 +1243,7 @@ async function build(config) {
611
1243
  // used relative paths, I _think_ this could get fixed. Issue here:
612
1244
  // https://github.com/vitejs/vite/issues/2009
613
1245
  assets_base: `${config.kit.paths.assets || config.kit.paths.base}/${config.kit.appDir}/`,
614
- manifest_data: create_manifest_data({
615
- config,
616
- cwd
617
- }),
1246
+ manifest_data,
618
1247
  output_dir,
619
1248
  client_entry_file: path__default.relative(cwd, `${get_runtime_path(config)}/client/start.js`),
620
1249
  service_worker_entry_file: resolve_entry(config.kit.files.serviceWorker),
@@ -624,30 +1253,55 @@ async function build(config) {
624
1253
  const client = await build_client(options);
625
1254
  const server = await build_server(options, client);
626
1255
 
627
- if (options.service_worker_entry_file) {
628
- if (config.kit.paths.assets) {
629
- throw new Error('Cannot use service worker alongside config.kit.paths.assets');
630
- }
631
-
632
- await build_service_worker(options, client.vite_manifest);
633
- }
634
-
1256
+ /** @type {import('types').BuildData} */
635
1257
  const build_data = {
636
1258
  app_dir: config.kit.appDir,
637
1259
  manifest_data: options.manifest_data,
638
1260
  service_worker: options.service_worker_entry_file ? 'service-worker.js' : null, // TODO make file configurable?
639
1261
  client,
640
- server,
641
- static: options.manifest_data.assets.map((asset) => posixify(asset.file)),
642
- entries: options.manifest_data.routes
643
- .map((route) => (route.type === 'page' ? route.path : ''))
644
- .filter(Boolean)
1262
+ server
645
1263
  };
646
1264
 
647
- const manifest = `export const manifest = ${generate_manifest(build_data, '.')};\n`;
1265
+ const manifest = `export const manifest = ${generate_manifest({
1266
+ build_data,
1267
+ relative_path: '.',
1268
+ routes: options.manifest_data.routes
1269
+ })};\n`;
648
1270
  fs__default.writeFileSync(`${output_dir}/server/manifest.js`, manifest);
649
1271
 
650
- return build_data;
1272
+ const static_files = options.manifest_data.assets.map((asset) => posixify(asset.file));
1273
+
1274
+ const files = new Set([
1275
+ ...static_files,
1276
+ ...client.chunks.map((chunk) => `${config.kit.appDir}/${chunk.fileName}`),
1277
+ ...client.assets.map((chunk) => `${config.kit.appDir}/${chunk.fileName}`)
1278
+ ]);
1279
+
1280
+ // TODO is this right?
1281
+ static_files.forEach((file) => {
1282
+ if (file.endsWith('/index.html')) {
1283
+ files.add(file.slice(0, -11));
1284
+ }
1285
+ });
1286
+
1287
+ const prerendered = await prerender({
1288
+ config,
1289
+ entries: options.manifest_data.routes
1290
+ .map((route) => (route.type === 'page' ? route.path : ''))
1291
+ .filter(Boolean),
1292
+ files,
1293
+ log
1294
+ });
1295
+
1296
+ if (options.service_worker_entry_file) {
1297
+ if (config.kit.paths.assets) {
1298
+ throw new Error('Cannot use service worker alongside config.kit.paths.assets');
1299
+ }
1300
+
1301
+ await build_service_worker(options, prerendered, client.vite_manifest);
1302
+ }
1303
+
1304
+ return { build_data, prerendered };
651
1305
  }
652
1306
 
653
1307
  export { build };