@sveltejs/adapter-netlify 1.0.0-next.50 → 1.0.0-next.53

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/README.md CHANGED
@@ -6,10 +6,8 @@ If you're using [adapter-auto](../adapter-auto), you don't need to install this
6
6
 
7
7
  ## Installation
8
8
 
9
- > ⚠️ For the time being, the latest version of adapter-netlify is at the @next tag. If you get the error `config.kit.adapter should be an object with an "adapt" method.`, this is a sign that you are using the wrong version (eg `1.0.0-next.0` instead of `1.0.0-next.9`).
10
-
11
9
  ```bash
12
- npm i -D @sveltejs/adapter-netlify@next
10
+ npm i -D @sveltejs/adapter-netlify
13
11
  ```
14
12
 
15
13
  You can then configure it inside of `svelte.config.js`:
@@ -19,9 +17,15 @@ import adapter from '@sveltejs/adapter-netlify';
19
17
 
20
18
  export default {
21
19
  kit: {
20
+ // default options are shown
22
21
  adapter: adapter({
22
+ // if true, will create a Netlify Edge Function rather
23
+ // than using standard Node-based functions
24
+ edge: false,
25
+
23
26
  // if true, will split your app into multiple functions
24
- // instead of creating a single one for the entire app
27
+ // instead of creating a single one for the entire app.
28
+ // if `edge` is true, this option cannot be used
25
29
  split: false
26
30
  })
27
31
  }
@@ -38,6 +42,10 @@ Then, make sure you have a [netlify.toml](https://docs.netlify.com/configure-bui
38
42
 
39
43
  If the `netlify.toml` file or the `build.publish` value is missing, a default value of `"build"` will be used. Note that if you have set the publish directory in the Netlify UI to something else then you will need to set it in `netlify.toml` too, or use the default value of `"build"`.
40
44
 
45
+ ## Netlify Edge Functions (beta)
46
+
47
+ SvelteKit supports the beta release of Netlify Edge Functions. If you pass the option `edge: true` to the `adapter` function, server-side rendering will happen in a Deno-based edge function that's deployed close to the site visitor. If set to `false` (the default), the site will deploy to standard Node-based Netlify Functions.
48
+
41
49
  ## Netlify alternatives to SvelteKit functionality
42
50
 
43
51
  You may build your app using functionality provided directly by SvelteKit without relying on any Netlify functionality. Using the SvelteKit versions of these features will allow them to be used in dev mode, tested with integration tests, and to work with other adapters should you ever decide to switch away from Netlify. However, in some scenarios you may find it beneficial to use the Netlify versions of these features. One example would be if you're migrating an app that's already hosted on Netlify to SvelteKit.
@@ -50,7 +50,12 @@ function init(manifest) {
50
50
  const server = new _0SERVER.Server(manifest);
51
51
 
52
52
  return async (event, context) => {
53
- const rendered = await server.respond(to_request(event), { platform: { context } });
53
+ const rendered = await server.respond(to_request(event), {
54
+ platform: { context },
55
+ getClientAddress() {
56
+ return event.headers['x-nf-client-connection-ip'];
57
+ }
58
+ });
54
59
 
55
60
  const partial_response = {
56
61
  statusCode: rendered.status,
@@ -46,7 +46,12 @@ function init(manifest) {
46
46
  const server = new Server(manifest);
47
47
 
48
48
  return async (event, context) => {
49
- const rendered = await server.respond(to_request(event), { platform: { context } });
49
+ const rendered = await server.respond(to_request(event), {
50
+ platform: { context },
51
+ getClientAddress() {
52
+ return event.headers['x-nf-client-connection-ip'];
53
+ }
54
+ });
50
55
 
51
56
  const partial_response = {
52
57
  statusCode: rendered.status,
package/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Adapter } from '@sveltejs/kit';
2
2
 
3
- declare function plugin(opts?: { split?: boolean }): Adapter;
3
+ declare function plugin(opts?: { split?: boolean; edge?: boolean }): Adapter;
4
+
4
5
  export = plugin;
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'fs';
2
- import { join, resolve } from 'path';
2
+ import { dirname, join, resolve, posix } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import glob from 'tiny-glob/sync.js';
5
5
  import esbuild from 'esbuild';
@@ -12,10 +12,30 @@ import toml from '@iarna/toml';
12
12
  * } & toml.JsonMap} NetlifyConfig
13
13
  */
14
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
+
15
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';
16
36
 
17
37
  /** @type {import('.')} */
18
- export default function ({ split = false } = {}) {
38
+ export default function ({ split = false, edge = edgeSetInEnvVar } = {}) {
19
39
  return {
20
40
  name: '@sveltejs/adapter-netlify',
21
41
 
@@ -26,97 +46,26 @@ export default function ({ split = false } = {}) {
26
46
  const publish = get_publish_directory(netlify_config, builder) || 'build';
27
47
 
28
48
  // empty out existing build directories
29
- builder.rimraf(publish);
49
+ builder.rimraf('.netlify/edge-functions');
30
50
  builder.rimraf('.netlify/functions-internal');
31
51
  builder.rimraf('.netlify/server');
32
52
  builder.rimraf('.netlify/package.json');
33
53
  builder.rimraf('.netlify/handler.js');
34
54
 
35
- builder.mkdirp('.netlify/functions-internal');
36
-
37
55
  builder.log.minor(`Publishing to "${publish}"`);
38
56
 
39
- builder.writeServer('.netlify/server');
40
-
41
57
  // for esbuild, use ESM
42
58
  // for zip-it-and-ship-it, use CJS until https://github.com/netlify/zip-it-and-ship-it/issues/750
43
59
  const esm = netlify_config?.functions?.node_bundler === 'esbuild';
44
60
 
45
- /** @type {string[]} */
46
- const redirects = [];
47
-
48
- const replace = {
49
- '0SERVER': './server/index.js' // digit prefix prevents CJS build from using this as a variable name, which would also get replaced
50
- };
61
+ if (edge) {
62
+ if (split) {
63
+ throw new Error('Cannot use `split: true` alongside `edge: true`');
64
+ }
51
65
 
52
- if (esm) {
53
- builder.copy(`${files}/esm`, '.netlify', { replace });
66
+ await generate_edge_functions({ builder });
54
67
  } else {
55
- glob('**/*.js', { cwd: '.netlify/server' }).forEach((file) => {
56
- const filepath = `.netlify/server/${file}`;
57
- const input = readFileSync(filepath, 'utf8');
58
- const output = esbuild.transformSync(input, { format: 'cjs', target: 'node12' }).code;
59
- writeFileSync(filepath, output);
60
- });
61
-
62
- builder.copy(`${files}/cjs`, '.netlify', { replace });
63
- writeFileSync(join('.netlify', 'package.json'), JSON.stringify({ type: 'commonjs' }));
64
- }
65
-
66
- if (split) {
67
- builder.log.minor('Generating serverless functions...');
68
-
69
- builder.createEntries((route) => {
70
- const parts = [];
71
-
72
- for (const segment of route.segments) {
73
- if (segment.rest) {
74
- parts.push('*');
75
- break; // Netlify redirects don't allow anything after a *
76
- } else if (segment.dynamic) {
77
- parts.push(`:${parts.length}`);
78
- } else {
79
- parts.push(segment.content);
80
- }
81
- }
82
-
83
- const pattern = `/${parts.join('/')}`;
84
- const name = parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index';
85
-
86
- return {
87
- id: pattern,
88
- filter: (other) => matches(route.segments, other.segments),
89
- complete: (entry) => {
90
- const manifest = entry.generateManifest({
91
- relativePath: '../server',
92
- format: esm ? 'esm' : 'cjs'
93
- });
94
-
95
- const fn = esm
96
- ? `import { init } from '../handler.js';\n\nexport const handler = init(${manifest});\n`
97
- : `const { init } = require('../handler.js');\n\nexports.handler = init(${manifest});\n`;
98
-
99
- writeFileSync(`.netlify/functions-internal/${name}.js`, fn);
100
-
101
- redirects.push(`${pattern} /.netlify/functions/${name} 200`);
102
- }
103
- };
104
- });
105
- } else {
106
- builder.log.minor('Generating serverless functions...');
107
-
108
- const manifest = builder.generateManifest({
109
- relativePath: '../server',
110
- format: esm ? 'esm' : 'cjs'
111
- });
112
-
113
- const fn = esm
114
- ? `import { init } from '../handler.js';\n\nexport const handler = init(${manifest});\n`
115
- : `const { init } = require('../handler.js');\n\nexports.handler = init(${manifest});\n`;
116
-
117
- writeFileSync('.netlify/functions-internal/render.js', fn);
118
-
119
- redirects.push('* /.netlify/functions/render 200');
68
+ await generate_lambda_functions({ builder, esm, split, publish });
120
69
  }
121
70
 
122
71
  builder.log.minor('Copying assets...');
@@ -124,11 +73,6 @@ export default function ({ split = false } = {}) {
124
73
  builder.writeClient(publish);
125
74
  builder.writePrerendered(publish);
126
75
 
127
- builder.log.minor('Writing redirects...');
128
- const redirect_file = join(publish, '_redirects');
129
- builder.copy('_redirects', redirect_file);
130
- appendFileSync(redirect_file, `\n\n${redirects.join('\n')}`);
131
-
132
76
  builder.log.minor('Writing custom headers...');
133
77
  const headers_file = join(publish, '_headers');
134
78
  builder.copy('_headers', headers_file);
@@ -139,6 +83,160 @@ export default function ({ split = false } = {}) {
139
83
  }
140
84
  };
141
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 = '^/.*$';
93
+
94
+ // Go doesn't support lookarounds, so we can't do this
95
+ // const pattern = appDir ? `^/(?!${escapeStringRegexp(appDir)}).*$` : '^/.*$';
96
+
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');
108
+
109
+ builder.rimraf(tmp);
110
+
111
+ builder.mkdirp('.netlify/edge-functions');
112
+
113
+ builder.log.minor('Generating Edge Function...');
114
+ const relativePath = posix.relative(tmp, builder.getServerDirectory());
115
+
116
+ builder.copy(`${src}/edge_function.js`, `${tmp}/entry.js`, {
117
+ replace: {
118
+ '0SERVER': `${relativePath}/index.js`,
119
+ MANIFEST: './manifest.js'
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
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
+ builder.mkdirp(dirname(redirect_file));
238
+ appendFileSync(redirect_file, `\n\n${redirects.join('\n')}`);
239
+ }
142
240
 
143
241
  function get_netlify_config() {
144
242
  if (!existsSync('netlify.toml')) return null;
@@ -157,8 +255,8 @@ function get_netlify_config() {
157
255
  **/
158
256
  function get_publish_directory(netlify_config, builder) {
159
257
  if (netlify_config) {
160
- if (!netlify_config.build || !netlify_config.build.publish) {
161
- builder.log.warn('No publish directory specified in netlify.toml, using default');
258
+ if (!netlify_config.build?.publish) {
259
+ builder.log.minor('No publish directory specified in netlify.toml, using default');
162
260
  return;
163
261
  }
164
262
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/adapter-netlify",
3
- "version": "1.0.0-next.50",
3
+ "version": "1.0.0-next.53",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/sveltejs/kit",
@@ -31,7 +31,7 @@
31
31
  "@rollup/plugin-commonjs": "^21.0.0",
32
32
  "@rollup/plugin-json": "^4.1.0",
33
33
  "@rollup/plugin-node-resolve": "^13.0.5",
34
- "@sveltejs/kit": "1.0.0-next.293",
34
+ "@sveltejs/kit": "1.0.0-next.318",
35
35
  "rimraf": "^3.0.2",
36
36
  "rollup": "^2.58.0",
37
37
  "typescript": "^4.6.2",