@sveltejs/adapter-netlify 1.0.0-next.5 → 1.0.0-next.52

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