@sveltejs/adapter-netlify 1.0.0-next.7 → 1.0.0-next.70

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.
package/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { Adapter } from '@sveltejs/kit';
2
+ import './ambient.js';
3
+
4
+ export default function plugin(opts?: { split?: boolean; edge?: boolean }): Adapter;
package/index.js CHANGED
@@ -1,72 +1,320 @@
1
- const { existsSync, readFileSync, writeFileSync } = require('fs');
2
- const { join } = require('path');
3
- const esbuild = require('esbuild');
4
- const toml = require('toml');
5
-
6
- module.exports = function () {
7
- /** @type {import('@sveltejs/kit').Adapter} */
8
- const adapter = {
1
+ import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'fs';
2
+ import { dirname, join, resolve, posix } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import glob from 'tiny-glob/sync.js';
5
+ import esbuild from 'esbuild';
6
+ import toml from '@iarna/toml';
7
+
8
+ /**
9
+ * @typedef {{
10
+ * build?: { publish?: string }
11
+ * functions?: { node_bundler?: 'zisi' | 'esbuild' }
12
+ * } & toml.JsonMap} NetlifyConfig
13
+ */
14
+
15
+ /**
16
+ * @typedef {{
17
+ * functions: Array<
18
+ * | {
19
+ * function: string;
20
+ * path: string;
21
+ * }
22
+ * | {
23
+ * function: string;
24
+ * pattern: string;
25
+ * }
26
+ * >;
27
+ * version: 1;
28
+ * }} HandlerManifest
29
+ */
30
+
31
+ const files = fileURLToPath(new URL('./files', import.meta.url).href);
32
+
33
+ const edge_set_in_env_var =
34
+ process.env.NETLIFY_SVELTEKIT_USE_EDGE === 'true' ||
35
+ process.env.NETLIFY_SVELTEKIT_USE_EDGE === '1';
36
+
37
+ /** @type {import('.').default} */
38
+ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
39
+ return {
9
40
  name: '@sveltejs/adapter-netlify',
10
41
 
11
- async adapt(utils) {
12
- const { publish, functions } = validate_config().build;
42
+ async adapt(builder) {
43
+ const netlify_config = get_netlify_config();
13
44
 
14
- utils.rimraf(publish);
15
- utils.rimraf(functions);
45
+ // "build" is the default publish directory when Netlify detects SvelteKit
46
+ const publish = get_publish_directory(netlify_config, builder) || 'build';
16
47
 
17
- const files = join(__dirname, 'files');
48
+ // empty out existing build directories
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');
18
54
 
19
- utils.log.minor('Generating serverless function...');
20
- utils.copy(join(files, 'entry.js'), '.svelte/netlify/entry.js');
55
+ builder.log.minor(`Publishing to "${publish}"`);
21
56
 
22
- await esbuild.build({
23
- entryPoints: ['.svelte/netlify/entry.js'],
24
- outfile: join(functions, 'render/index.js'),
25
- bundle: true,
26
- platform: 'node'
27
- });
57
+ builder.log.minor('Copying assets...');
58
+ builder.writeClient(publish);
59
+ builder.writePrerendered(publish);
28
60
 
29
- writeFileSync(join(functions, 'package.json'), JSON.stringify({ type: 'commonjs' }));
61
+ builder.log.minor('Writing custom headers...');
62
+ const headers_file = join(publish, '_headers');
63
+ builder.copy('_headers', headers_file);
64
+ appendFileSync(
65
+ headers_file,
66
+ `\n\n/${builder.config.kit.appDir}/immutable/*\n cache-control: public\n cache-control: immutable\n cache-control: max-age=31536000\n`
67
+ );
30
68
 
31
- utils.log.info('Prerendering static pages...');
32
- await utils.prerender({
33
- dest: publish
34
- });
69
+ // for esbuild, use ESM
70
+ // for zip-it-and-ship-it, use CJS until https://github.com/netlify/zip-it-and-ship-it/issues/750
71
+ const esm = netlify_config?.functions?.node_bundler === 'esbuild';
35
72
 
36
- utils.log.minor('Copying assets...');
37
- utils.copy_static_files(publish);
38
- utils.copy_client_files(publish);
73
+ if (edge) {
74
+ if (split) {
75
+ throw new Error('Cannot use `split: true` alongside `edge: true`');
76
+ }
39
77
 
40
- utils.log.minor('Writing redirects...');
41
- utils.copy(`${files}/_redirects`, `${publish}/_redirects`);
78
+ await generate_edge_functions({ builder });
79
+ } else {
80
+ await generate_lambda_functions({ builder, esm, split, publish });
81
+ }
42
82
  }
43
83
  };
84
+ }
85
+ /**
86
+ * @param { object } params
87
+ * @param {import('@sveltejs/kit').Builder} params.builder
88
+ */
89
+ async function generate_edge_functions({ builder }) {
90
+ const tmp = builder.getBuildDirectory('netlify-tmp');
91
+ builder.rimraf(tmp);
92
+ builder.mkdirp(tmp);
93
+
94
+ builder.mkdirp('.netlify/edge-functions');
44
95
 
45
- return adapter;
46
- };
96
+ // Don't match the static directory
97
+ const pattern = '^/.*$';
47
98
 
48
- function validate_config() {
49
- if (existsSync('netlify.toml')) {
50
- let netlify_config;
99
+ // Go doesn't support lookarounds, so we can't do this
100
+ // const pattern = appDir ? `^/(?!${escapeStringRegexp(appDir)}).*$` : '^/.*$';
51
101
 
52
- try {
53
- netlify_config = toml.parse(readFileSync('netlify.toml', 'utf-8'));
54
- } catch (err) {
55
- err.message = `Error parsing netlify.toml: ${err.message}`;
56
- throw err;
102
+ /** @type {HandlerManifest} */
103
+ const edge_manifest = {
104
+ functions: [
105
+ {
106
+ function: 'render',
107
+ pattern
108
+ }
109
+ ],
110
+ version: 1
111
+ };
112
+
113
+ builder.log.minor('Generating Edge Function...');
114
+ const relativePath = posix.relative(tmp, builder.getServerDirectory());
115
+
116
+ builder.copy(`${files}/edge.js`, `${tmp}/entry.js`, {
117
+ replace: {
118
+ '0SERVER': `${relativePath}/index.js`,
119
+ MANIFEST: './manifest.js'
57
120
  }
121
+ });
122
+
123
+ const manifest = builder.generateManifest({
124
+ relativePath
125
+ });
126
+
127
+ writeFileSync(
128
+ `${tmp}/manifest.js`,
129
+ `export const manifest = ${manifest};\n\nexport const prerendered = new Set(${JSON.stringify(
130
+ builder.prerendered.paths
131
+ )});\n`
132
+ );
133
+
134
+ await esbuild.build({
135
+ entryPoints: [`${tmp}/entry.js`],
136
+ outfile: '.netlify/edge-functions/render.js',
137
+ bundle: true,
138
+ format: 'esm',
139
+ platform: 'browser',
140
+ sourcemap: 'linked',
141
+ target: 'es2020'
142
+ });
143
+
144
+ writeFileSync('.netlify/edge-functions/manifest.json', JSON.stringify(edge_manifest));
145
+ }
146
+ /**
147
+ * @param { object } params
148
+ * @param {import('@sveltejs/kit').Builder} params.builder
149
+ * @param { string } params.publish
150
+ * @param { boolean } params.split
151
+ * @param { boolean } params.esm
152
+ */
153
+ async function generate_lambda_functions({ builder, publish, split, esm }) {
154
+ builder.mkdirp('.netlify/functions-internal');
155
+
156
+ /** @type {string[]} */
157
+ const redirects = [];
158
+ builder.writeServer('.netlify/server');
159
+
160
+ const replace = {
161
+ '0SERVER': './server/index.js' // digit prefix prevents CJS build from using this as a variable name, which would also get replaced
162
+ };
163
+ if (esm) {
164
+ builder.copy(`${files}/esm`, '.netlify', { replace });
165
+ } else {
166
+ glob('**/*.js', { cwd: '.netlify/server' }).forEach((file) => {
167
+ const filepath = `.netlify/server/${file}`;
168
+ const input = readFileSync(filepath, 'utf8');
169
+ const output = esbuild.transformSync(input, { format: 'cjs', target: 'node12' }).code;
170
+ writeFileSync(filepath, output);
171
+ });
172
+
173
+ builder.copy(`${files}/cjs`, '.netlify', { replace });
174
+ writeFileSync(join('.netlify', 'package.json'), JSON.stringify({ type: 'commonjs' }));
175
+ }
176
+
177
+ if (split) {
178
+ builder.log.minor('Generating serverless functions...');
179
+
180
+ await builder.createEntries((route) => {
181
+ const parts = [];
182
+ // Netlify's syntax uses '*' and ':param' as "splats" and "placeholders"
183
+ // https://docs.netlify.com/routing/redirects/redirect-options/#splats
184
+ for (const segment of route.segments) {
185
+ if (segment.rest) {
186
+ parts.push('*');
187
+ break; // Netlify redirects don't allow anything after a *
188
+ } else if (segment.dynamic) {
189
+ parts.push(`:${parts.length}`);
190
+ } else {
191
+ parts.push(segment.content);
192
+ }
193
+ }
194
+
195
+ const pattern = `/${parts.join('/')}`;
196
+ const name = parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index';
197
+
198
+ return {
199
+ id: pattern,
200
+ filter: (other) => matches(route.segments, other.segments),
201
+ complete: (entry) => {
202
+ const manifest = entry.generateManifest({
203
+ relativePath: '../server',
204
+ format: esm ? 'esm' : 'cjs'
205
+ });
206
+
207
+ const fn = esm
208
+ ? `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`
209
+ : `const { init } = require('../serverless.js');\n\nexports.handler = init(${manifest});\n`;
210
+
211
+ writeFileSync(`.netlify/functions-internal/${name}.js`, fn);
212
+
213
+ redirects.push(`${pattern} /.netlify/functions/${name} 200`);
214
+ redirects.push(`${pattern}/__data.json /.netlify/functions/${name} 200`);
215
+ }
216
+ };
217
+ });
218
+ } else {
219
+ builder.log.minor('Generating serverless functions...');
58
220
 
59
- if (!netlify_config.build || !netlify_config.build.publish || !netlify_config.build.functions) {
221
+ const manifest = builder.generateManifest({
222
+ relativePath: '../server',
223
+ format: esm ? 'esm' : 'cjs'
224
+ });
225
+
226
+ const fn = esm
227
+ ? `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`
228
+ : `const { init } = require('../serverless.js');\n\nexports.handler = init(${manifest});\n`;
229
+
230
+ writeFileSync('.netlify/functions-internal/render.js', fn);
231
+ redirects.push('* /.netlify/functions/render 200');
232
+ }
233
+
234
+ // this should happen at the end, after builder.writeClient(...),
235
+ // so that generated redirects are appended to custom redirects
236
+ // rather than replaced by them
237
+ builder.log.minor('Writing redirects...');
238
+ const redirect_file = join(publish, '_redirects');
239
+ if (existsSync('_redirects')) {
240
+ builder.copy('_redirects', redirect_file);
241
+ }
242
+ builder.mkdirp(dirname(redirect_file));
243
+ appendFileSync(redirect_file, `\n\n${redirects.join('\n')}`);
244
+ }
245
+
246
+ function get_netlify_config() {
247
+ if (!existsSync('netlify.toml')) return null;
248
+
249
+ try {
250
+ return /** @type {NetlifyConfig} */ (toml.parse(readFileSync('netlify.toml', 'utf-8')));
251
+ } catch (err) {
252
+ err.message = `Error parsing netlify.toml: ${err.message}`;
253
+ throw err;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * @param {NetlifyConfig} netlify_config
259
+ * @param {import('@sveltejs/kit').Builder} builder
260
+ **/
261
+ function get_publish_directory(netlify_config, builder) {
262
+ if (netlify_config) {
263
+ if (!netlify_config.build?.publish) {
264
+ builder.log.minor('No publish directory specified in netlify.toml, using default');
265
+ return;
266
+ }
267
+
268
+ if (netlify_config.redirects) {
60
269
  throw new Error(
61
- 'You must specify build.publish and build.functions in netlify.toml. Consult https://github.com/sveltejs/kit/tree/master/packages/adapter-netlify#configuration'
270
+ "Redirects are not supported in netlify.toml. Use _redirects instead. For more details consult the readme's troubleshooting section."
62
271
  );
63
272
  }
64
-
65
- return netlify_config;
273
+ if (resolve(netlify_config.build.publish) === process.cwd()) {
274
+ throw new Error(
275
+ 'The publish directory cannot be set to the site root. Please change it to another value such as "build" in netlify.toml.'
276
+ );
277
+ }
278
+ return netlify_config.build.publish;
66
279
  }
67
280
 
68
- // TODO offer to create one?
69
- throw new Error(
70
- 'Missing a netlify.toml file. Consult https://github.com/sveltejs/kit/tree/master/packages/adapter-netlify#configuration'
281
+ builder.log.warn(
282
+ 'No netlify.toml found. Using default publish directory. Consult https://github.com/sveltejs/kit/tree/master/packages/adapter-netlify#configuration for more details '
71
283
  );
72
284
  }
285
+
286
+ /**
287
+ * @typedef {{ rest: boolean, dynamic: boolean, content: string }} RouteSegment
288
+ */
289
+
290
+ /**
291
+ * @param {RouteSegment[]} a
292
+ * @param {RouteSegment[]} b
293
+ * @returns {boolean}
294
+ */
295
+ function matches(a, b) {
296
+ if (a[0] && b[0]) {
297
+ if (b[0].rest) {
298
+ if (b.length === 1) return true;
299
+
300
+ const next_b = b.slice(1);
301
+
302
+ for (let i = 0; i < a.length; i += 1) {
303
+ if (matches(a.slice(i), next_b)) return true;
304
+ }
305
+
306
+ return false;
307
+ }
308
+
309
+ if (!b[0].dynamic) {
310
+ if (!a[0].dynamic && a[0].content !== b[0].content) return false;
311
+ }
312
+
313
+ if (a.length === 1 && b.length === 1) return true;
314
+ return matches(a.slice(1), b.slice(1));
315
+ } else if (a[0]) {
316
+ return a.length === 1 && a[0].rest;
317
+ } else {
318
+ return b.length === 1 && b[0].rest;
319
+ }
320
+ }
package/package.json CHANGED
@@ -1,20 +1,52 @@
1
1
  {
2
- "name": "@sveltejs/adapter-netlify",
3
- "version": "1.0.0-next.7",
4
- "main": "index.js",
5
- "files": [
6
- "files"
7
- ],
8
- "dependencies": {
9
- "toml": "^3.0.0"
10
- },
11
- "devDependencies": {
12
- "@sveltejs/kit": "1.0.0-next.80",
13
- "typescript": "^4.2.3"
14
- },
15
- "scripts": {
16
- "lint": "eslint --ignore-path .gitignore \"**/*.{ts,js,svelte}\" && npm run check-format",
17
- "format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore",
18
- "check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore"
19
- }
20
- }
2
+ "name": "@sveltejs/adapter-netlify",
3
+ "version": "1.0.0-next.70",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/sveltejs/kit",
7
+ "directory": "packages/adapter-netlify"
8
+ },
9
+ "license": "MIT",
10
+ "homepage": "https://kit.svelte.dev",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./index.d.ts",
15
+ "import": "./index.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "main": "index.js",
20
+ "types": "index.d.ts",
21
+ "files": [
22
+ "files",
23
+ "index.d.ts"
24
+ ],
25
+ "dependencies": {
26
+ "@iarna/toml": "^2.2.5",
27
+ "esbuild": "^0.14.48",
28
+ "set-cookie-parser": "^2.4.8",
29
+ "tiny-glob": "^0.2.9"
30
+ },
31
+ "devDependencies": {
32
+ "@netlify/functions": "^1.0.0",
33
+ "@rollup/plugin-commonjs": "^22.0.1",
34
+ "@rollup/plugin-json": "^4.1.0",
35
+ "@rollup/plugin-node-resolve": "^13.3.0",
36
+ "@sveltejs/kit": "1.0.0-next.380",
37
+ "@types/node": "^16.11.36",
38
+ "@types/set-cookie-parser": "^2.4.2",
39
+ "rimraf": "^3.0.2",
40
+ "rollup": "^2.75.7",
41
+ "typescript": "^4.7.4",
42
+ "uvu": "^0.5.3"
43
+ },
44
+ "scripts": {
45
+ "dev": "rimraf files && rollup -cw",
46
+ "build": "rimraf files && rollup -c && cp src/edge.js files/edge.js",
47
+ "test": "uvu src \"(spec\\.js|test[\\\\/]index\\.js)\"",
48
+ "check": "tsc",
49
+ "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore",
50
+ "format": "npm run check-format -- --write"
51
+ }
52
+ }
package/CHANGELOG.md DELETED
@@ -1,80 +0,0 @@
1
- # @sveltejs/adapter-netlify
2
-
3
- ## 1.0.0-next.7
4
-
5
- ### Patch Changes
6
-
7
- - 0db2cf7: Fix serverless function
8
-
9
- ## 1.0.0-next.6
10
-
11
- ### Patch Changes
12
-
13
- - 7a4b351: Bundle serverless functions with esbuild
14
-
15
- ## 1.0.0-next.5
16
-
17
- ### Patch Changes
18
-
19
- - 6e27880: Move server-side fetch to adapters instead of build step
20
-
21
- ## 1.0.0-next.4
22
-
23
- ### Patch Changes
24
-
25
- - 8805c6d: Pass adapters directly to svelte.config.cjs
26
-
27
- ## 1.0.0-next.3
28
-
29
- ### Patch Changes
30
-
31
- - f35a5cd: Change adapter signature
32
-
33
- ## 1.0.0-next.2
34
-
35
- ### Patch Changes
36
-
37
- - 512b8c9: adapter-netlify: Use CJS entrypoint
38
-
39
- ## 1.0.0-next.1
40
-
41
- ### Patch Changes
42
-
43
- - Fix adapters and convert to ES modules
44
-
45
- ## 0.0.13
46
-
47
- ### Patch Changes
48
-
49
- - Add svelte-kit start command
50
-
51
- ## 0.0.12
52
-
53
- ### Patch Changes
54
-
55
- - b475ed4: Overhaul adapter API - fixes #166
56
-
57
- ## 0.0.11
58
-
59
- ### Patch Changes
60
-
61
- - 67eaeea: Move app-utils stuff into subpackages
62
-
63
- ## 0.0.10
64
-
65
- ### Patch Changes
66
-
67
- - Use setup
68
-
69
- ## 0.0.9
70
-
71
- ### Patch Changes
72
-
73
- - 0320208: Rename 'server route' to 'endpoint'
74
- - 5ca907c: Use shared mkdirp helper
75
-
76
- ## 0.0.8
77
-
78
- ### Patch Changes
79
-
80
- - various
package/files/_redirects DELETED
@@ -1 +0,0 @@
1
- /* /.netlify/functions/render 200
package/files/entry.js DELETED
@@ -1,44 +0,0 @@
1
- import '@sveltejs/kit/install-fetch'; // eslint-disable-line import/no-unresolved
2
-
3
- // TODO hardcoding the relative location makes this brittle
4
- import { render } from '../output/server/app.js'; // eslint-disable-line import/no-unresolved
5
-
6
- export async function handler(event) {
7
- const {
8
- path,
9
- httpMethod,
10
- headers,
11
- queryStringParameters
12
- // body, // TODO pass this to renderer
13
- // isBase64Encoded // TODO is this useful?
14
- } = event;
15
-
16
- const query = new URLSearchParams();
17
- for (const k in queryStringParameters) {
18
- const value = queryStringParameters[k];
19
- value.split(', ').forEach((v) => {
20
- query.append(k, v);
21
- });
22
- }
23
-
24
- const rendered = await render({
25
- method: httpMethod,
26
- headers,
27
- path,
28
- query
29
- });
30
-
31
- if (rendered) {
32
- return {
33
- isBase64Encoded: false,
34
- statusCode: rendered.status,
35
- headers: rendered.headers,
36
- body: rendered.body
37
- };
38
- }
39
-
40
- return {
41
- statusCode: 404,
42
- body: 'Not found'
43
- };
44
- }