@sveltejs/adapter-netlify 1.0.2 → 1.0.3

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.
Files changed (2) hide show
  1. package/index.js +304 -0
  2. package/package.json +2 -1
package/index.js ADDED
@@ -0,0 +1,304 @@
1
+ import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'fs';
2
+ import { dirname, join, resolve, posix } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import esbuild from 'esbuild';
5
+ import toml from '@iarna/toml';
6
+
7
+ /**
8
+ * @typedef {{
9
+ * build?: { publish?: string }
10
+ * functions?: { node_bundler?: 'zisi' | 'esbuild' }
11
+ * } & toml.JsonMap} NetlifyConfig
12
+ */
13
+
14
+ /**
15
+ * @typedef {{
16
+ * functions: Array<
17
+ * | {
18
+ * function: string;
19
+ * path: string;
20
+ * }
21
+ * | {
22
+ * function: string;
23
+ * pattern: string;
24
+ * }
25
+ * >;
26
+ * version: 1;
27
+ * }} HandlerManifest
28
+ */
29
+
30
+ const files = fileURLToPath(new URL('./files', import.meta.url).href);
31
+
32
+ const edge_set_in_env_var =
33
+ process.env.NETLIFY_SVELTEKIT_USE_EDGE === 'true' ||
34
+ process.env.NETLIFY_SVELTEKIT_USE_EDGE === '1';
35
+
36
+ /** @type {import('.').default} */
37
+ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
38
+ return {
39
+ name: '@sveltejs/adapter-netlify',
40
+
41
+ async adapt(builder) {
42
+ const netlify_config = get_netlify_config();
43
+
44
+ // "build" is the default publish directory when Netlify detects SvelteKit
45
+ const publish = get_publish_directory(netlify_config, builder) || 'build';
46
+
47
+ // empty out existing build directories
48
+ builder.rimraf(publish);
49
+ builder.rimraf('.netlify/edge-functions');
50
+ builder.rimraf('.netlify/functions-internal');
51
+ builder.rimraf('.netlify/server');
52
+ builder.rimraf('.netlify/package.json');
53
+ builder.rimraf('.netlify/serverless.js');
54
+
55
+ builder.log.minor(`Publishing to "${publish}"`);
56
+
57
+ builder.log.minor('Copying assets...');
58
+ const publish_dir = `${publish}${builder.config.kit.paths.base}`;
59
+ builder.writeClient(publish_dir);
60
+ builder.writePrerendered(publish_dir);
61
+
62
+ builder.log.minor('Writing custom headers...');
63
+ const headers_file = join(publish, '_headers');
64
+ builder.copy('_headers', headers_file);
65
+ appendFileSync(
66
+ headers_file,
67
+ `\n\n/${builder.getAppPath()}/immutable/*\n cache-control: public\n cache-control: immutable\n cache-control: max-age=31536000\n`
68
+ );
69
+
70
+ if (edge) {
71
+ if (split) {
72
+ throw new Error('Cannot use `split: true` alongside `edge: true`');
73
+ }
74
+
75
+ await generate_edge_functions({ builder });
76
+ } else {
77
+ await generate_lambda_functions({ builder, split, publish });
78
+ }
79
+ }
80
+ };
81
+ }
82
+ /**
83
+ * @param { object } params
84
+ * @param {import('@sveltejs/kit').Builder} params.builder
85
+ */
86
+ async function generate_edge_functions({ builder }) {
87
+ const tmp = builder.getBuildDirectory('netlify-tmp');
88
+ builder.rimraf(tmp);
89
+ builder.mkdirp(tmp);
90
+
91
+ builder.mkdirp('.netlify/edge-functions');
92
+
93
+ // Don't match the static directory
94
+ const pattern = '^/.*$';
95
+
96
+ // Go doesn't support lookarounds, so we can't do this
97
+ // const pattern = appDir ? `^/(?!${escapeStringRegexp(appDir)}).*$` : '^/.*$';
98
+
99
+ /** @type {HandlerManifest} */
100
+ const edge_manifest = {
101
+ functions: [
102
+ {
103
+ function: 'render',
104
+ pattern
105
+ }
106
+ ],
107
+ version: 1
108
+ };
109
+
110
+ builder.log.minor('Generating Edge Function...');
111
+ const relativePath = posix.relative(tmp, builder.getServerDirectory());
112
+
113
+ builder.copy(`${files}/edge.js`, `${tmp}/entry.js`, {
114
+ replace: {
115
+ '0SERVER': `${relativePath}/index.js`,
116
+ MANIFEST: './manifest.js'
117
+ }
118
+ });
119
+
120
+ const manifest = builder.generateManifest({
121
+ relativePath
122
+ });
123
+
124
+ writeFileSync(
125
+ `${tmp}/manifest.js`,
126
+ `export const manifest = ${manifest};\n\nexport const prerendered = new Set(${JSON.stringify(
127
+ builder.prerendered.paths
128
+ )});\n`
129
+ );
130
+
131
+ await esbuild.build({
132
+ entryPoints: [`${tmp}/entry.js`],
133
+ outfile: '.netlify/edge-functions/render.js',
134
+ bundle: true,
135
+ format: 'esm',
136
+ platform: 'browser',
137
+ sourcemap: 'linked',
138
+ target: 'es2020'
139
+ });
140
+
141
+ writeFileSync('.netlify/edge-functions/manifest.json', JSON.stringify(edge_manifest));
142
+ }
143
+ /**
144
+ * @param { object } params
145
+ * @param {import('@sveltejs/kit').Builder} params.builder
146
+ * @param { string } params.publish
147
+ * @param { boolean } params.split
148
+ */
149
+ async function generate_lambda_functions({ builder, publish, split }) {
150
+ builder.mkdirp('.netlify/functions-internal');
151
+
152
+ /** @type {string[]} */
153
+ const redirects = [];
154
+ builder.writeServer('.netlify/server');
155
+
156
+ const replace = {
157
+ '0SERVER': './server/index.js' // digit prefix prevents CJS build from using this as a variable name, which would also get replaced
158
+ };
159
+
160
+ builder.copy(`${files}/esm`, '.netlify', { replace });
161
+
162
+ // Configuring the function to use ESM as the output format.
163
+ const fn_config = JSON.stringify({ config: { nodeModuleFormat: 'esm' }, version: 1 });
164
+
165
+ if (split) {
166
+ builder.log.minor('Generating serverless functions...');
167
+
168
+ await builder.createEntries((route) => {
169
+ const parts = [];
170
+ // Netlify's syntax uses '*' and ':param' as "splats" and "placeholders"
171
+ // https://docs.netlify.com/routing/redirects/redirect-options/#splats
172
+ for (const segment of route.segments) {
173
+ if (segment.rest) {
174
+ parts.push('*');
175
+ break; // Netlify redirects don't allow anything after a *
176
+ } else if (segment.dynamic) {
177
+ parts.push(`:${parts.length}`);
178
+ } else {
179
+ parts.push(segment.content);
180
+ }
181
+ }
182
+
183
+ const pattern = `/${parts.join('/')}`;
184
+ const name = parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index';
185
+
186
+ return {
187
+ id: pattern,
188
+ filter: (other) => matches(route.segments, other.segments),
189
+ complete: (entry) => {
190
+ const manifest = entry.generateManifest({
191
+ relativePath: '../server'
192
+ });
193
+
194
+ const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`;
195
+
196
+ writeFileSync(`.netlify/functions-internal/${name}.mjs`, fn);
197
+ writeFileSync(`.netlify/functions-internal/${name}.json`, fn_config);
198
+
199
+ redirects.push(`${pattern} /.netlify/functions/${name} 200`);
200
+ redirects.push(`${pattern}/__data.json /.netlify/functions/${name} 200`);
201
+ }
202
+ };
203
+ });
204
+ } else {
205
+ builder.log.minor('Generating serverless functions...');
206
+
207
+ const manifest = builder.generateManifest({
208
+ relativePath: '../server'
209
+ });
210
+
211
+ const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`;
212
+
213
+ writeFileSync(`.netlify/functions-internal/render.json`, fn_config);
214
+ writeFileSync('.netlify/functions-internal/render.mjs', fn);
215
+ redirects.push('* /.netlify/functions/render 200');
216
+ }
217
+
218
+ // this should happen at the end, after builder.writeClient(...),
219
+ // so that generated redirects are appended to custom redirects
220
+ // rather than replaced by them
221
+ builder.log.minor('Writing redirects...');
222
+ const redirect_file = join(publish, '_redirects');
223
+ if (existsSync('_redirects')) {
224
+ builder.copy('_redirects', redirect_file);
225
+ }
226
+ builder.mkdirp(dirname(redirect_file));
227
+ appendFileSync(redirect_file, `\n\n${redirects.join('\n')}`);
228
+ }
229
+
230
+ function get_netlify_config() {
231
+ if (!existsSync('netlify.toml')) return null;
232
+
233
+ try {
234
+ return /** @type {NetlifyConfig} */ (toml.parse(readFileSync('netlify.toml', 'utf-8')));
235
+ } catch (err) {
236
+ err.message = `Error parsing netlify.toml: ${err.message}`;
237
+ throw err;
238
+ }
239
+ }
240
+
241
+ /**
242
+ * @param {NetlifyConfig} netlify_config
243
+ * @param {import('@sveltejs/kit').Builder} builder
244
+ **/
245
+ function get_publish_directory(netlify_config, builder) {
246
+ if (netlify_config) {
247
+ if (!netlify_config.build?.publish) {
248
+ builder.log.minor('No publish directory specified in netlify.toml, using default');
249
+ return;
250
+ }
251
+
252
+ if (netlify_config.redirects) {
253
+ throw new Error(
254
+ "Redirects are not supported in netlify.toml. Use _redirects instead. For more details consult the readme's troubleshooting section."
255
+ );
256
+ }
257
+ if (resolve(netlify_config.build.publish) === process.cwd()) {
258
+ throw new Error(
259
+ 'The publish directory cannot be set to the site root. Please change it to another value such as "build" in netlify.toml.'
260
+ );
261
+ }
262
+ return netlify_config.build.publish;
263
+ }
264
+
265
+ builder.log.warn(
266
+ 'No netlify.toml found. Using default publish directory. Consult https://github.com/sveltejs/kit/tree/master/packages/adapter-netlify#configuration for more details '
267
+ );
268
+ }
269
+
270
+ /**
271
+ * @typedef {{ rest: boolean, dynamic: boolean, content: string }} RouteSegment
272
+ */
273
+
274
+ /**
275
+ * @param {RouteSegment[]} a
276
+ * @param {RouteSegment[]} b
277
+ * @returns {boolean}
278
+ */
279
+ function matches(a, b) {
280
+ if (a[0] && b[0]) {
281
+ if (b[0].rest) {
282
+ if (b.length === 1) return true;
283
+
284
+ const next_b = b.slice(1);
285
+
286
+ for (let i = 0; i < a.length; i += 1) {
287
+ if (matches(a.slice(i), next_b)) return true;
288
+ }
289
+
290
+ return false;
291
+ }
292
+
293
+ if (!b[0].dynamic) {
294
+ if (!a[0].dynamic && a[0].content !== b[0].content) return false;
295
+ }
296
+
297
+ if (a.length === 1 && b.length === 1) return true;
298
+ return matches(a.slice(1), b.slice(1));
299
+ } else if (a[0]) {
300
+ return a.length === 1 && a[0].rest;
301
+ } else {
302
+ return b.length === 1 && b[0].rest;
303
+ }
304
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/adapter-netlify",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -19,6 +19,7 @@
19
19
  "types": "index.d.ts",
20
20
  "files": [
21
21
  "files",
22
+ "index.js",
22
23
  "index.d.ts"
23
24
  ],
24
25
  "dependencies": {