diff --git a/.changeset/popular-schools-guess.md b/.changeset/popular-schools-guess.md new file mode 100644 index 000000000000..9f2146745d2f --- /dev/null +++ b/.changeset/popular-schools-guess.md @@ -0,0 +1,6 @@ +--- +'@sveltejs/adapter-auto': patch +'create-svelte': patch +--- + +Add adapter-auto diff --git a/documentation/docs/10-adapters.md b/documentation/docs/10-adapters.md index bb522e39369f..b9386f98f737 100644 --- a/documentation/docs/10-adapters.md +++ b/documentation/docs/10-adapters.md @@ -2,45 +2,99 @@ title: Adapters --- -Before you can deploy your SvelteKit app, you need to _adapt_ it for your deployment target. Adapters are small plugins that take the built app as input and generate output for deployment. Many adapters are optimised for a specific hosting provider, and you can generally find information about deployment in your adapter's documentation. However, some adapters, like `adapter-static`, build output that can be hosted on numerous hosting providers, so it may also be helpful to reference the documentation of your hosting provider in these cases. +Before you can deploy your SvelteKit app, you need to _adapt_ it for your deployment target. Adapters are small plugins that take the built app as input and generate output for deployment. -For example, if you want to run your app as a simple Node server, you would use the `@sveltejs/adapter-node@next` package: +By default, projects are configured to use `@sveltejs/adapter-auto`, which detects your production environment and selects the appropriate adapter where possible. If your platform isn't (yet) supported, you may need to [install a custom adapter](#adapters-installing-custom-adapters) or [write one](#adapters-writing-custom-adapters). -```js +> See the [adapter-auto README](https://github.com/sveltejs/kit/tree/master/packages/adapter-auto) for information on adding support for new environments. + +### Supported environments + +The following platforms are officially supported and require no additional configuration: + +- [Cloudflare Pages](https://developers.cloudflare.com/pages/) via [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/master/packages/adapter-cloudflare) +- [Netlify](https://netlify.com) via [`adapter-netlify`](https://github.com/sveltejs/kit/tree/master/packages/adapter-netlify) +- [Vercel](https://vercel.com) via [`adapter-vercel`](https://github.com/sveltejs/kit/tree/master/packages/adapter-vercel) + +### Installing custom adapters + +Additional [community-provided adapters](https://sveltesociety.dev/components#adapters) exist for other platforms. After installing the relevant adapter with your package manager, update your `svelte.config.js`: + +```diff // svelte.config.js -import node from '@sveltejs/adapter-node'; +-import adapter from '@sveltejs/adapter-auto'; ++import adapter from 'svelte-adapter-[x]'; +``` -export default { - kit: { - adapter: node() - } -}; +### Building a Node app + +To create a simple Node server, install the `@sveltejs/adapter-node@next` package and update your `svelte.config.js`: + +```diff +// svelte.config.js +-import adapter from '@sveltejs/adapter-auto'; ++import adapter from '@sveltejs/adapter-node'; ``` -With this, [svelte-kit build](#command-line-interface-svelte-kit-build) will generate a self-contained Node app inside `build`. You can pass options to adapters, such as customising the output directory in `adapter-node`: +With this, [svelte-kit build](#command-line-interface-svelte-kit-build) will generate a self-contained Node app inside the `build` directory. You can pass options to adapters, such as customising the output directory: ```diff // svelte.config.js -import node from '@sveltejs/adapter-node'; +import adapter from '@sveltejs/adapter-node'; export default { kit: { -- adapter: node() -+ adapter: node({ out: 'my-output-directory' }) +- adapter: adapter() ++ adapter: adapter({ out: 'my-output-directory' }) } }; ``` -A variety of official adapters exist for serverless platforms... +### Creating a static site + +Most adapters will generate static HTML for any [prerenderable](#ssr-and-javascript-prerender) pages of your site. In some cases, your entire app might be prerenderable, in which case you can use `@sveltejs/adapter-static@next` to generate static HTML for _all_ your pages. A fully static site can be hosted on a wide variety of platforms, including static hosts like [GitHub Pages](https://pages.github.com/). + +```diff +// svelte.config.js +-import adapter from '@sveltejs/adapter-auto'; ++import adapter from '@sveltejs/adapter-static'; +``` + +You can also use `adapter-static` to generate single-page apps (SPAs) by specifying a [fallback page](https://github.com/sveltejs/kit/tree/master/packages/adapter-static#spa-mode). + +### Writing custom adapters + +We recommend [looking at the source for an adapter](https://github.com/sveltejs/kit/tree/master/packages) to a platform similar to yours and copying it as a starting point. + +Adapters packages must implement the following API, which creates an `Adapter`: + +```js +/** @param {AdapterSpecificOptions} options */ +export default function (options) { + /** @type {import('@sveltejs/kit').Adapter} */ + return { + name: 'adapter-package-name', + async adapt({ utils, config }) { + // adapter implementation + } + }; +} +``` + +The types for `Adapter` and its parameters are available in [types/config.d.ts](https://github.com/sveltejs/kit/blob/master/packages/kit/types/config.d.ts). -- [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/master/packages/adapter-cloudflare) — for [Cloudflare Pages](https://developers.cloudflare.com/pages/) -- [`adapter-cloudflare-workers`](https://github.com/sveltejs/kit/tree/master/packages/adapter-cloudflare-workers) — for [Cloudflare Workers](https://developers.cloudflare.com/workers/) -- [`adapter-netlify`](https://github.com/sveltejs/kit/tree/master/packages/adapter-netlify) — for [Netlify](https://netlify.com) -- [`adapter-vercel`](https://github.com/sveltejs/kit/tree/master/packages/adapter-vercel) — for [Vercel](https://vercel.com) +Within the `adapt` method, there are a number of things that an adapter should do: -...and traditional platforms: +- Clear out the build directory +- Output code that: + - Imports `init` and `render` from `.svelte-kit/output/server/app.js` + - Calls `init`, which configures the app + - Listens for requests from the platform, converts them to a a [SvelteKit request](#hooks-handle), calls the `render` function to generate a [SvelteKit response](#hooks-handle) and responds with it + - Globally shims `fetch` to work on the target platform, if necessary. SvelteKit provides a `@sveltejs/kit/install-fetch` helper for platforms that can use `node-fetch` +- Bundle the output to avoid needing to install dependencies on the target platform, if desired +- Call `utils.prerender` +- Put the user's static files and the generated JS/CSS in the correct location for the target platform -- [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) — for creating self-contained Node apps -- [`adapter-static`](https://github.com/sveltejs/kit/tree/master/packages/adapter-static) — for prerendering your entire site as a collection of static files +If possible, we recommend putting the adapter output under the `build/` directory with any intermediate output placed under `.svelte-kit/[adapter-name]`. -As well as [community-provided adapters](https://sveltesociety.dev/components#adapters). You may also [write your own adapter](#writing-an-adapter). +> The adapter API may change before 1.0. diff --git a/documentation/docs/80-adapter-api.md b/documentation/docs/80-adapter-api.md deleted file mode 100644 index 7094e1ff152c..000000000000 --- a/documentation/docs/80-adapter-api.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Writing an Adapter ---- - -We recommend [looking at the source for an adapter](https://github.com/sveltejs/kit/tree/master/packages) to a platform similar to yours and copying it as a starting point. - -Adapters packages must implement the following API, which creates an `Adapter`: - -```js -/** @param {AdapterSpecificOptions} options */ -export default function (options) { - /** @type {import('@sveltejs/kit').Adapter} */ - return { - name: 'adapter-package-name', - async adapt({ utils, config }) { - // adapter implementation - } - }; -} -``` - -The types for `Adapter` and its parameters are available in [types/config.d.ts](https://github.com/sveltejs/kit/blob/master/packages/kit/types/config.d.ts). - -Within the `adapt` method, there are a number of things that an adapter should do: - -- Clear out the build directory -- Output code that: - - Calls `init` - - Converts from the platform's request to a [SvelteKit request](#hooks-handle), calls `render`, and converts from a [SvelteKit response](#hooks-handle) to the platform's - - Globally shims `fetch` to work on the target platform. SvelteKit provides a `@sveltejs/kit/install-fetch` helper for platforms that can use `node-fetch` -- Bundle the output to avoid needing to install dependencies on the target platform, if desired -- Call `utils.prerender` -- Put the user's static files and the generated JS/CSS in the correct location for the target platform - -If possible, we recommend putting the adapter output under the `build/` directory with any intermediate output placed under `'.svelte-kit/' + adapterName`. - -> The adapter API may change before 1.0. diff --git a/packages/adapter-auto/.gitignore b/packages/adapter-auto/.gitignore new file mode 100644 index 000000000000..9daa8247da45 --- /dev/null +++ b/packages/adapter-auto/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +node_modules diff --git a/packages/adapter-auto/README.md b/packages/adapter-auto/README.md new file mode 100644 index 000000000000..b79d49a13f1f --- /dev/null +++ b/packages/adapter-auto/README.md @@ -0,0 +1,19 @@ +# adapter-auto + +Automatically chooses the adapter for your current environment, if possible. + +## Supported environments + +The following environments are supported out-of-the-box, meaning a newly created project can be deployed on one of these platforms without any additional configuration: + +- [Cloudflare Pages](https://developers.cloudflare.com/pages/) via [adapter-cloudflare](../adapter-cloudflare) +- [Netlify](https://netlify.com/) via [adapter-netlify](../adapter-netlify) +- [Vercel](https://vercel.com/) via [adapter-vercel](../adapter-vercel) + +## Community adapters + +Support for additional environments can be added in [adapters.js](adapters.js). To avoid this package ballooning in size, community-supported adapters should not be added as dependencies — adapter-auto will instead prompt users to install missing packages as needed. + +## Changelog + +[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-auto/CHANGELOG.md). diff --git a/packages/adapter-auto/adapters.js b/packages/adapter-auto/adapters.js new file mode 100644 index 000000000000..44cbf9f4f68a --- /dev/null +++ b/packages/adapter-auto/adapters.js @@ -0,0 +1,17 @@ +export const adapters = [ + { + name: 'Vercel', + test: () => !!process.env.VERCEL, + module: '@sveltejs/adapter-vercel' + }, + { + name: 'Netlify', + test: () => !!process.env.NETLIFY, + module: '@sveltejs/adapter-netlify' + }, + { + name: 'Cloudflare Pages', + test: () => !!process.env.CF_PAGES, + module: '@sveltejs/adapter-cloudflare' + } +]; diff --git a/packages/adapter-auto/index.d.ts b/packages/adapter-auto/index.d.ts new file mode 100644 index 000000000000..62aee0f1eaa9 --- /dev/null +++ b/packages/adapter-auto/index.d.ts @@ -0,0 +1,4 @@ +import { Adapter } from '@sveltejs/kit'; + +declare function plugin(): Adapter; +export = plugin; diff --git a/packages/adapter-auto/index.js b/packages/adapter-auto/index.js new file mode 100644 index 000000000000..e60b1ab4aa9e --- /dev/null +++ b/packages/adapter-auto/index.js @@ -0,0 +1,42 @@ +import { adapters } from './adapters.js'; + +/** @type {import('.')} **/ +export default function () { + return { + name: '@sveltejs/adapter-auto', + + async adapt(options) { + for (const candidate of adapters) { + if (candidate.test()) { + options.utils.log.info( + `Detected environment: \u001B[1m\u001B[92m${candidate.name}\u001B[39m\u001B[22m. Using ${candidate.module}` + ); + + let module; + + try { + module = await import(candidate.module); + } catch (error) { + if ( + error.code === 'ERR_MODULE_NOT_FOUND' && + error.message.startsWith(`Cannot find package '${candidate.module}'`) + ) { + throw new Error( + `It looks like ${candidate.module} is not installed. Please install it and try building your project again.` + ); + } + + throw error; + } + + const adapter = module.default(); + return adapter.adapt(options); + } + } + + options.utils.log.warn( + 'Could not detect a supported production environment. See https://kit.svelte.dev/docs#adapters to learn how to configure your app to run on the platform of your choosing' + ); + } + }; +} diff --git a/packages/adapter-auto/package.json b/packages/adapter-auto/package.json new file mode 100644 index 000000000000..d143b23b8c77 --- /dev/null +++ b/packages/adapter-auto/package.json @@ -0,0 +1,33 @@ +{ + "name": "@sveltejs/adapter-auto", + "version": "1.0.0-next", + "repository": { + "type": "git", + "url": "https://github.com/sveltejs/kit", + "directory": "packages/adapter-auto" + }, + "homepage": "https://kit.svelte.dev", + "type": "module", + "exports": { + ".": { + "import": "./index.js" + }, + "./package.json": "./package.json" + }, + "main": "index.js", + "types": "index.d.ts", + "files": [ + "files", + "index.d.ts" + ], + "scripts": { + "lint": "eslint --ignore-path .gitignore \"**/*.{ts,js,svelte}\" && npm run check-format", + "format": "npm run check-format -- --write", + "check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore" + }, + "dependencies": { + "@sveltejs/adapter-cloudflare": "workspace:*", + "@sveltejs/adapter-netlify": "workspace:*", + "@sveltejs/adapter-vercel": "workspace:*" + } +} diff --git a/packages/create-svelte/templates/default/.gitignore b/packages/create-svelte/templates/default/.gitignore index 75631d294e31..28426480040a 100644 --- a/packages/create-svelte/templates/default/.gitignore +++ b/packages/create-svelte/templates/default/.gitignore @@ -4,3 +4,5 @@ node_modules /.svelte-kit /package .env +.vercel +.output diff --git a/packages/create-svelte/templates/default/package.json b/packages/create-svelte/templates/default/package.json index aa1bcb0ff124..a83bc7d46b44 100644 --- a/packages/create-svelte/templates/default/package.json +++ b/packages/create-svelte/templates/default/package.json @@ -7,10 +7,8 @@ "start": "svelte-kit start" }, "devDependencies": { - "@sveltejs/adapter-cloudflare-workers": "next", - "@sveltejs/adapter-netlify": "next", - "@sveltejs/adapter-vercel": "next", - "@sveltejs/kit": "next", + "@sveltejs/adapter-auto": "workspace:*", + "@sveltejs/kit": "workspace:*", "svelte": "^3.44.0", "svelte-preprocess": "^4.9.8", "typescript": "^4.4.3" diff --git a/packages/create-svelte/templates/default/package.template.json b/packages/create-svelte/templates/default/package.template.json index eca8326dcaec..5c09811f43b9 100644 --- a/packages/create-svelte/templates/default/package.template.json +++ b/packages/create-svelte/templates/default/package.template.json @@ -7,6 +7,7 @@ "preview": "svelte-kit preview" }, "devDependencies": { + "@sveltejs/adapter-auto": "workspace:*", "@sveltejs/kit": "workspace:*", "svelte": "^3.34.0" }, diff --git a/packages/create-svelte/templates/default/svelte.config.js b/packages/create-svelte/templates/default/svelte.config.js index 3315cb538d64..aa85d10b3262 100644 --- a/packages/create-svelte/templates/default/svelte.config.js +++ b/packages/create-svelte/templates/default/svelte.config.js @@ -1,8 +1,6 @@ +import adapter from '@sveltejs/adapter-auto'; import preprocess from 'svelte-preprocess'; -const adapter = process.env.ADAPTER; -const options = JSON.parse(process.env.OPTIONS || '{}'); - /** @type {import('@sveltejs/kit').Config} */ const config = { // Consult https://github.com/sveltejs/svelte-preprocess @@ -10,13 +8,11 @@ const config = { preprocess: preprocess(), kit: { + adapter: adapter(), + // hydrate the