@sveltejs/adapter-netlify 1.0.2 → 1.0.4

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 (3) hide show
  1. package/README.md +3 -105
  2. package/index.js +304 -0
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -2,113 +2,11 @@
2
2
 
3
3
  A SvelteKit adapter that creates a Netlify app.
4
4
 
5
- If you're using [adapter-auto](../adapter-auto), you don't need to install this unless you need to specify Netlify-specific options, since it's already included.
5
+ If you're using [adapter-auto](https://kit.svelte.dev/docs/adapter-auto), you don't need to install this unless you need to specify Netlify-specific options, since it's already included.
6
6
 
7
- ## Installation
7
+ ## Docs
8
8
 
9
- ```bash
10
- npm i -D @sveltejs/adapter-netlify
11
- ```
12
-
13
- You can then configure it inside of `svelte.config.js`:
14
-
15
- ```js
16
- import adapter from '@sveltejs/adapter-netlify';
17
-
18
- export default {
19
- kit: {
20
- // default options are shown
21
- adapter: adapter({
22
- // if true, will create a Netlify Edge Function rather
23
- // than using standard Node-based functions
24
- edge: false,
25
-
26
- // if true, will split your app into multiple functions
27
- // instead of creating a single one for the entire app.
28
- // if `edge` is true, this option cannot be used
29
- split: false
30
- })
31
- }
32
- };
33
- ```
34
-
35
- Then, make sure you have a [netlify.toml](https://docs.netlify.com/configure-builds/file-based-configuration) file in the project root. This will determine where to write static assets based on the `build.publish` settings, as per this sample configuration:
36
-
37
- ```toml
38
- [build]
39
- command = "npm run build"
40
- publish = "build"
41
- ```
42
-
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"`.
44
-
45
- ### Node version
46
-
47
- New projects will use Node 16 by default. However, if you're upgrading a project you created a while ago it may be stuck on an older version. See [the Netlify docs](https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript) for details on manually specifying Node 16 or newer.
48
-
49
- ## Netlify Edge Functions (beta)
50
-
51
- SvelteKit supports the beta release of [Netlify Edge Functions](https://docs.netlify.com/netlify-labs/experimental-features/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.
52
-
53
- ```js
54
- import adapter from '@sveltejs/adapter-netlify';
55
-
56
- export default {
57
- kit: {
58
- adapter: adapter({
59
- // will create a Netlify Edge Function using Deno-based
60
- // rather than using standard Node-based functions
61
- edge: true
62
- })
63
- }
64
- };
65
- ```
66
-
67
- ## Netlify alternatives to SvelteKit functionality
68
-
69
- 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.
70
-
71
- ### Using Netlify Redirect Rules
72
-
73
- During compilation, redirect rules are automatically appended to your `_redirects` file. (If it doesn't exist yet, it will be created.) That means:
74
-
75
- - `[[redirects]]` in `netlify.toml` will never match as `_redirects` has a [higher priority](https://docs.netlify.com/routing/redirects/#rule-processing-order). So always put your rules in the [`_redirects` file](https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file).
76
- - `_redirects` shouldn't have any custom "catch all" rules such as `/* /foobar/:splat`. Otherwise the automatically appended rule will never be applied as Netlify is only processing [the first matching rule](https://docs.netlify.com/routing/redirects/#rule-processing-order).
77
-
78
- ### Using Netlify Forms
79
-
80
- 1. Create your Netlify HTML form as described [here](https://docs.netlify.com/forms/setup/#html-forms), e.g. as `/routes/contact.svelte`. (Don't forget to add the hidden `form-name` input element!)
81
- 2. Netlify's build bot parses your HTML files at deploy time, which means your form must be [prerendered](https://kit.svelte.dev/docs/page-options#prerender) as HTML. You can either add `export const prerender = true` to your `contact.svelte` to prerender just that page or set the `kit.prerender.force: true` option to prerender all pages.
82
- 3. If your Netlify form has a [custom success message](https://docs.netlify.com/forms/setup/#success-messages) like `<form netlify ... action="/success">` then ensure the corresponding `/routes/success.svelte` exists and is prerendered.
83
-
84
- ### Using Netlify Functions
85
-
86
- With this adapter, SvelteKit endpoints are hosted as [Netlify Functions](https://docs.netlify.com/functions/overview/). Netlify function handlers have additional context, including [Netlify Identity](https://docs.netlify.com/visitor-access/identity/) information. You can access this context via the `event.platform.context` field inside your hooks and `+page.server` or `+layout.server` endpoints. These are [serverless functions](https://docs.netlify.com/functions/overview/) when the `edge` property is `false` in the adapter config or [edge functions](https://docs.netlify.com/edge-functions/overview/#app) when it is `true`.
87
-
88
- ```js
89
- // +page.server.js
90
- export const load = async (event) => {
91
- const context = event.platform.context;
92
- console.log(context); // shows up in your functions log in the Netlify app
93
- };
94
- ```
95
-
96
- Additionally, you can add your own Netlify functions by creating a directory for them and adding the configuration to your `netlify.toml` file. For example:
97
-
98
- ```toml
99
- [build]
100
- command = "npm run build"
101
- publish = "build"
102
-
103
- [functions]
104
- directory = "functions"
105
- ```
106
-
107
- ## Troubleshooting
108
-
109
- ### Accessing the file system
110
-
111
- You can't access the file system through methods like `fs.readFileSync` in Serverless/Edge environments. If you need to access files that way, do that during building the app through [prerendering](https://kit.svelte.dev/docs/page-options#prerender). If you have a blog for example and don't want to manage your content through a CMS, then you need to prerender the content (or prerender the endpoint from which you get it) and redeploy your blog everytime you add new content.
9
+ [Docs](https://kit.svelte.dev/docs/adapter-netlify)
112
10
 
113
11
  ## Changelog
114
12
 
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.4",
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": {
@@ -37,7 +38,7 @@
37
38
  "rollup": "^3.7.0",
38
39
  "typescript": "^4.9.4",
39
40
  "uvu": "^0.5.6",
40
- "@sveltejs/kit": "^1.1.1"
41
+ "@sveltejs/kit": "^1.1.3"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "@sveltejs/kit": "^1.0.0"