diff --git a/.changeset/config.json b/.changeset/config.json index 58e5f01c7d97..b5a4060d1a52 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -4,7 +4,7 @@ "commit": false, "linked": [], "access": "public", - "baseBranch": "master", + "baseBranch": "main", "bumpVersionsWithWorkspaceProtocolOnly": true, - "ignore": ["!(@sveltejs/*|create-svelte|svelte-migrate)"] + "ignore": ["!(@sveltejs/*)"] } diff --git a/.changeset/funny-pets-melt.md b/.changeset/funny-pets-melt.md new file mode 100644 index 000000000000..1a8a5a94605f --- /dev/null +++ b/.changeset/funny-pets-melt.md @@ -0,0 +1,5 @@ +--- +"@sveltejs/adapter-cloudflare": patch +--- + +fix: await `init` on every request to prevent race condition diff --git a/.changeset/gold-wolves-fix.md b/.changeset/gold-wolves-fix.md new file mode 100644 index 000000000000..e5c2c4115012 --- /dev/null +++ b/.changeset/gold-wolves-fix.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: avoid overriding Vite default `base` when running Vitest 4 diff --git a/.eslint/no-runtime-to-exports-imports.js b/.eslint/no-runtime-to-exports-imports.js new file mode 100644 index 000000000000..d605b0658b41 --- /dev/null +++ b/.eslint/no-runtime-to-exports-imports.js @@ -0,0 +1,55 @@ +// @ts-expect-error no types here +import path from 'node:path'; + +/** + * @type {import('eslint').Rule.RuleModule} + */ +export default { + meta: { + type: 'problem', + docs: { + description: 'disallow relative imports from src/runtime to src/exports', + category: 'Possible Errors', + recommended: true + }, + schema: [], + messages: { + noRuntimeToExportsImport: + 'Relative imports from src/runtime to src/exports are not allowed because they can cause Vite to resolve the same module both via regular Node and internal Vite. Use internal import maps or `@sveltejs/kit/internal` instead.' + } + }, + + create(context) { + const filename = context.filename; + + // Only apply this rule to files in packages/kit/src/runtime + const in_runtime = filename.includes(path.join('packages', 'kit', 'src', 'runtime')); + + if (!in_runtime) { + return {}; + } + + return { + ImportDeclaration(node) { + const import_path = node.source.value; + + // Check if this is a relative import + if (typeof import_path === 'string' && import_path.startsWith('.')) { + // Resolve the import path relative to the current file + const current_dir = path.dirname(filename); + const resolved_path = path.resolve(current_dir, import_path); + + // Check if the resolved path points to src/exports + const exports_path = path.join('packages', 'kit', 'src', 'exports'); + + if (resolved_path.includes(exports_path)) { + context.report({ + node: node.source, + messageId: 'noRuntimeToExportsImport' + }); + } + } + } + }; + } +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 9501511a710b..000000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "root": true, - "extends": "@sveltejs", - "env": { - "es2022": true - }, - "ignorePatterns": [ - "packages/create-svelte/shared/", - "packages/kit/test/prerendering/*/build", - "packages/adapter-static/test/apps/*/build", - "packages/adapter-cloudflare/files", - "packages/adapter-netlify/files", - "packages/adapter-node/files" - ], - "rules": { - "no-undef": "off" - } -} diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b077e088fb94..3eaabfa5d71e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,7 @@ + + +--- + ### Please don't delete this checklist! Before submitting the PR, please make sure you do the following: - [ ] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs - [ ] This message body should clearly illustrate what problems it solves. @@ -8,3 +12,7 @@ ### Changesets - [ ] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. Changesets that add features should be `minor` and those that fix bugs should be `patch`. Please prefix changeset messages with `feat:`, `fix:`, or `chore:`. + +### Edits + +- [ ] Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 120000 index 000000000000..be77ac83a189 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1 @@ +../AGENTS.md \ No newline at end of file diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 049ebc7508c9..80b2aef63cbd 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -13,13 +13,15 @@ permissions: jobs: Audit: + # prevents this action from running on forks + if: github.repository == 'sveltejs/kit' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v2.4.0 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6 with: - node-version: '20.x' + node-version: '24.x' cache: pnpm - run: pnpm install --frozen-lockfile # check prod dependencies as these would affect users diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c6bd814714b..84075c46cf51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: push: branches: - - master + - main pull_request: env: @@ -19,87 +19,107 @@ permissions: contents: read # to fetch code (actions/checkout) jobs: - Lint: + pkg-pr-new: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v2.4.0 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6 with: - node-version: '16.x' + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm build + - run: pnpx pkg-pr-new publish --comment=off ./packages/* + lint-all: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6 + with: + node-version: 24 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm run lint - - run: cd packages/kit && pnpm prepublishOnly + - run: cd packages/kit && pnpm prepublishOnly && { [ "`git status --porcelain=v1`" == "" ] || (echo "Generated types have changed — please run prepublishOnly locally and commit the changes after you have reviewed them"; git diff; exit 1); } - run: pnpm run check - Tests: + test-kit: runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: fail-fast: false matrix: include: - - node-version: 16 - os: ubuntu-latest - e2e-browser: 'chromium' - node-version: 18 os: ubuntu-latest e2e-browser: 'chromium' - node-version: 20 os: ubuntu-latest e2e-browser: 'chromium' + - node-version: 22 + os: ubuntu-latest + e2e-browser: 'chromium' + - node-version: 24 + os: ubuntu-latest + e2e-browser: 'chromium' env: KIT_E2E_BROWSER: ${{matrix.e2e-browser}} steps: - run: git config --global core.autocrlf false - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v2.4.0 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm playwright install ${{ matrix.e2e-browser }} - - run: pnpm test + - run: pnpm run sync-all + - run: pnpm test:kit + env: + NODE_OPTIONS: ${{matrix.node-version == 22 && '--experimental-strip-types' || ''}} # allows loading svelte.config.ts + - name: Print flaky test report + run: node scripts/print-flaky-test-report.js - name: Archive test results if: failure() shell: bash run: find packages -type d -name test-results -not -empty | tar -czf test-results.tar.gz --files-from=- - name: Upload test results if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v6 with: retention-days: 3 name: test-failure-${{ github.run_id }}-${{ matrix.os }}-${{ matrix.node-version }}-${{ matrix.e2e-browser }} path: test-results.tar.gz - Cross-browser-test: + test-kit-cross-browser: runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: fail-fast: false matrix: include: - - node-version: 16 - os: windows-2019 # slowness reported on newer versions https://github.com/actions/runner-images/issues/5166 + - node-version: 18 + os: windows-latest e2e-browser: 'chromium' mode: 'dev' - - node-version: 16 + - node-version: 18 os: ubuntu-latest e2e-browser: 'firefox' mode: 'dev' - - node-version: 16 + - node-version: 18 os: macOS-latest e2e-browser: 'webkit' mode: 'dev' - - node-version: 16 - os: windows-2019 # slowness reported on newer versions https://github.com/actions/runner-images/issues/5166 + - node-version: 18 + os: windows-latest e2e-browser: 'chromium' mode: 'build' - - node-version: 16 + - node-version: 18 os: ubuntu-latest e2e-browser: 'firefox' mode: 'build' - - node-version: 16 + - node-version: 18 os: macOS-latest e2e-browser: 'webkit' mode: 'build' @@ -107,35 +127,112 @@ jobs: KIT_E2E_BROWSER: ${{matrix.e2e-browser}} steps: - run: git config --global core.autocrlf false - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v2.4.0 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm playwright install ${{ matrix.e2e-browser }} + - run: pnpm run sync-all - run: pnpm test:cross-platform:${{ matrix.mode }} + - name: Print flaky test report + run: node scripts/print-flaky-test-report.js - name: Archive test results if: failure() shell: bash run: find packages -type d -name test-results -not -empty | tar -czf test-results-cross-platform-${{ matrix.mode }}.tar.gz --files-from=- - name: Upload test results if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v6 with: retention-days: 3 name: test-failure-cross-platform-${{ matrix.mode }}-${{ github.run_id }}-${{ matrix.os }}-${{ matrix.node-version }}-${{ matrix.e2e-browser }} path: test-results-cross-platform-${{ matrix.mode }}.tar.gz - Test-create-svelte: + test-kit-server-side-route-resolution: + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - mode: 'dev' + - mode: 'build' + steps: + - run: git config --global core.autocrlf false + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm playwright install chromium + - run: pnpm run sync-all + - run: pnpm test:server-side-route-resolution:${{ matrix.mode }} + - name: Print flaky test report + run: node scripts/print-flaky-test-report.js + - name: Archive test results + if: failure() + shell: bash + run: find packages -type d -name test-results -not -empty | tar -czf test-results-server-side-route-resolution-${{ matrix.mode }}.tar.gz --files-from=- + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v6 + with: + retention-days: 3 + name: test-failure-server-side-route-resolution-${{ matrix.mode }}-${{ github.run_id }} + path: test-results-server-side-route-resolution-${{ matrix.mode }}.tar.gz + test-kit-svelte-async: + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - mode: 'dev' + - mode: 'build' + steps: + - run: git config --global core.autocrlf false + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm playwright install chromium + - run: pnpm run sync-all + - run: pnpm test:svelte-async:${{ matrix.mode }} + - name: Print flaky test report + run: node scripts/print-flaky-test-report.js + - name: Archive test results + if: failure() + shell: bash + run: find packages -type d -name test-results -not -empty | tar -czf test-results-svelte-async-${{ matrix.mode }}.tar.gz --files-from=- + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v6 + with: + retention-days: 3 + name: test-failure-svelte-async-${{ matrix.mode }}-${{ github.run_id }} + path: test-results-svelte-async-${{ matrix.mode }}.tar.gz + test-others: runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18, 20, 22, 24] steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v2.4.0 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4.2.0 + - uses: actions/setup-node@v6 with: - node-version: 16 + node-version: ${{matrix.node-version}} cache: pnpm - run: pnpm install --frozen-lockfile + - run: pnpm playwright install chromium - run: cd packages/kit && pnpm prepublishOnly - - run: pnpm run test:create-svelte + - run: pnpm run test:others + env: + NODE_OPTIONS: ${{matrix.node-version == 22 && '--experimental-strip-types' || ''}} # allows loading svelte.config.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7c8960ce40a9..7461807d1192 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release on: push: branches: - - master + - main permissions: {} jobs: @@ -12,20 +12,21 @@ jobs: if: github.repository == 'sveltejs/kit' permissions: contents: write # to create release (changesets/action) + id-token: write # OpenID Connect token needed for provenance pull-requests: write # to create pull request (changesets/action) name: Release runs-on: ubuntu-latest steps: - name: Checkout Repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - - uses: pnpm/action-setup@v2.4.0 + - uses: pnpm/action-setup@v4.2.0 - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: - node-version: 16.x + node-version: 24.x cache: pnpm - run: pnpm install --frozen-lockfile @@ -39,8 +40,7 @@ jobs: version: pnpm changeset:version env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - UPDATE_TEMPLATE_SSH_KEY: ${{ secrets.UPDATE_TEMPLATE_SSH_KEY }} + NPM_CONFIG_PROVENANCE: true # TODO alert discord # - name: Send a Slack notification if a publish happens diff --git a/.gitignore b/.gitignore index 679d1c89e690..e105f8313444 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ test-results/ package-lock.json yarn.lock vite.config.js.timestamp-* -/packages/create-svelte/template/CHANGELOG.md /packages/package/test/**/package /documentation/types.js .env @@ -19,3 +18,4 @@ vite.config.js.timestamp-* .test-tmp symlink-from .idea/ +_tmp_flaky_test_output.txt diff --git a/.npmrc b/.npmrc index d7445a115f87..8af70e3f1fb9 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,2 @@ link-workspace-packages = true -engine-strict = true +shell-emulator = true diff --git a/.prettierrc b/.prettierrc index 72264f51e616..9f806b7df2d4 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,6 +3,7 @@ "singleQuote": true, "trailingComma": "none", "printWidth": 100, + "plugins": ["prettier-plugin-svelte"], "overrides": [ { "files": ["*.svelte"], @@ -20,16 +21,21 @@ { "files": [ "**/CHANGELOG.md", + "**/vite.config.js.timestamp-*", "**/.svelte-kit/**", + "**/.custom-out-dir/**", + "**/test-results/**", + "**/.wrangler/**", "documentation/**/*.md", "packages/package/test/fixtures/**/expected/**/*", "packages/package/test/watch/expected/**/*", "packages/package/test/watch/package/**/*", "packages/kit/src/core/postbuild/fixtures/**/*", - "packages/migrate/migrations/routes/*/samples.md" + "packages/adapter-cloudflare/test/apps/workers/dist/**/*", + "packages/*/test/**/build" ], "options": { - "requirePragma": true + "rangeEnd": 0 } } ] diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..13e958fe7be2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,159 @@ +# SvelteKit Coding Agent Guide + +This guide is for AI coding agents working in the SvelteKit monorepo. + +**Important:** Read and follow `CONTRIBUTING.md` as well - it contains essential information about testing, code structure, and contribution guidelines that applies here. + +## Quick Reference + +### Essential Commands + +```bash +# Initial setup (takes 3-4 minutes, set 10+ min timeout) +pnpm install --frozen-lockfile + +# Build all packages (~1-2 seconds) +pnpm build + +# Format code (~15 seconds) +pnpm run format + +# Lint (takes 2-3 minutes, set 5+ min timeout) +pnpm run lint + +# Type checking (takes 3-4 minutes, set 8+ min timeout) +pnpm run check +``` + +### Testing Commands + +```bash +# Unit tests only (fastest - ~6 seconds) +pnpm -F @sveltejs/kit test:unit + +# Run a single unit test file +pnpm -F @sveltejs/kit test:unit:dev path/to/test.spec.js + +# Integration tests (10-30 minutes, set 60+ min timeout) +pnpm test:kit + +# A single integration test suite (name of suite found in packages/kit/test/apps/*/package.json) +pnpm -F {name-of-suite} test + +# Run single Playwright test (must use workdir - no pnpm -F shorthand) +cd packages/kit/test/apps/basics && npx playwright test --grep "test name" + +# Other package tests (5-15 minutes, set 30+ min timeout) +pnpm test:others +``` + +### Pre-submission Checklist + +1. `pnpm run format` - Auto-format code +2. `pnpm run lint` - Check code style (don't cancel early) +3. `pnpm run check` - Type checking (don't cancel early) +4. `pnpm -F @sveltejs/kit test:unit` - Run unit tests +5. For @sveltejs/kit changes: `pnpm -F @sveltejs/kit prepublishOnly` - Generate types +6. Run `pnpm changeset` to document changes (prefix with `fix`, `feat`, `breaking`, or `chore`) + +## Code Style Examples + +The coding style guidelines are in `CONTRIBUTING.md`. Here are additional examples: + +### Imports + +```javascript +// JSDoc type imports at the top +/** @import { Handle, RequestEvent } from '@sveltejs/kit' */ + +// Named imports (no default exports) +import { HttpError, SvelteKitError } from '@sveltejs/kit/internal'; +``` + +### Functions + +```javascript +// Exported named functions (no default exports) +export function coalesce_to_error(err) { + // Implementation +} + +// JSDoc for all parameters and return types +/** + * @param {unknown} error + * @returns {Error} + */ +export function coalesce_to_error(error) { + // Implementation +} + +// Use arrow functions for callbacks +const handler = (event) => { + /* ... */ +}; +``` + +### Error Handling + +```javascript +// Type checking with instanceof +if (error instanceof HttpError || error instanceof SvelteKitError) { + // Handle +} + +// Graceful fallbacks +const status = error?.status ?? 500; + +// Optional chaining and nullish coalescing +const content_type = request.headers.get('content-type')?.split(';', 1)[0]; +``` + +### TypeScript/JSDoc + +- Use JSDoc annotations for all function parameters and return types +- Complex types: `/** @type {Array<{ type: string, subtype: string }>} */` +- Type casting when needed: `/** @type {Error} */ (err)` +- Enable strict mode: `checkJs: true`, `strict: true` in tsconfig.json + +### Formatting (via Prettier) + +- **Tabs for indentation** (not spaces) +- **Single quotes** for strings +- **No trailing commas** +- **100 character line width** +- Files are auto-formatted by `pnpm run format` + +### Comments + +````javascript +// JSDoc with usage examples for public APIs +/** + * Sequence multiple handle functions + * + * @example + * ```js + * export const handle = sequence(first, second); + * ``` + * + * @param {...Handle} handlers + * @returns {Handle} + */ + +// Inline comments for clarifications +// no match equals invalid header — ignore +```` + +## Key Packages + +- `@sveltejs/kit` - Main framework (`packages/kit/`) +- `adapter-*` - Platform adapters (node, cloudflare, netlify, vercel, static, auto) +- `@sveltejs/package` - Package building utilities +- `@sveltejs/enhanced-img` - Enhanced image component +- `@sveltejs/amp` - AMP support + +## Troubleshooting + +- **Browser tests fail**: `pnpm playwright install chromium` +- **Build failures**: Ensure `pnpm install --frozen-lockfile` completed +- **Type errors**: Run `pnpm -F @sveltejs/kit prepublishOnly` +- **Lint issues**: Run `pnpm run format` first diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 000000000000..47dc3e3d863c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5fc83158606b..7e81bf303dad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,13 +4,13 @@ This is a monorepo, meaning the repo holds multiple packages. It requires the use of [pnpm](https://pnpm.io/). You can [install pnpm](https://pnpm.io/installation) with: -```bash +```sh npm i -g pnpm ``` `pnpm` commands run in the project's root directory will run on all sub-projects. You can checkout the code and install the dependencies with: -```bash +```sh git clone git@github.com:sveltejs/kit.git cd kit pnpm install @@ -22,34 +22,41 @@ pnpm install You can use the playground at [`playgrounds/basic`](./playgrounds/basic/) to experiment with your changes to SvelteKit locally. -### Linking +### Linking local changes If you want to test against an existing project, you can use [pnpm `overrides`](https://pnpm.io/package_json#pnpmoverrides) in that project: ```jsonc { - // ... - "pnpm": { - "overrides": { - "@sveltejs/kit": "link:../path/to/svelte-kit/packages/kit", - // additionally/optional the adapter you're using - "@sveltejs/adapter-auto": "link:../path/to/svelte-kit/packages/adapter-auto" - } - } + // ... + "pnpm": { + "overrides": { + "@sveltejs/kit": "link:../path/to/svelte-kit/packages/kit", + // additionally/optional the adapter you're using + "@sveltejs/adapter-auto": "link:../path/to/svelte-kit/packages/adapter-auto" + } + } } ``` +### Testing PR changes + +Each pull request will be built and published via [pkg.pr.new/](https://pkg.pr.new/). You can test the change by installing the package with your PR number: + +``` +npm add https://pkg.pr.new/sveltejs/kit/@sveltejs/kit@YOUR_PR_NUMBER_GOES_HERE +``` + ## Code structure Entry points to be aware of are: -- [`packages/create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte) - code that's run when you create a new project with `npm create svelte@latest` -- [`packages/package`](https://github.com/sveltejs/kit/tree/master/packages/package) - for the `svelte-package` command -- [`packages/kit/src/core`](https://github.com/sveltejs/kit/tree/master/packages/kit/src/core) - code that's called at dev/build-time -- [`packages/kit/src/core/sync`](https://github.com/sveltejs/kit/tree/master/packages/kit/src/core/sync) - for `svelte-kit sync`, which regenerates routing info and type definitions -- [`packages/kit/src/runtime`](https://github.com/sveltejs/kit/tree/master/packages/kit/src/runtime) - code that's called at runtime -- [`packages/kit/src/exports/vite`](https://github.com/sveltejs/kit/tree/master/packages/kit/src/exports/vite) - for all the Vite plugin related stuff -- [`packages/adapter-[platform]`](https://github.com/sveltejs/kit/tree/master/packages) - for the various SvelteKit-provided adapters +- [`packages/package`](https://github.com/sveltejs/kit/tree/main/packages/package) - for the `svelte-package` command +- [`packages/kit/src/core`](https://github.com/sveltejs/kit/tree/main/packages/kit/src/core) - code that's called at dev/build-time +- [`packages/kit/src/core/sync`](https://github.com/sveltejs/kit/tree/main/packages/kit/src/core/sync) - for `svelte-kit sync`, which regenerates routing info and type definitions +- [`packages/kit/src/runtime`](https://github.com/sveltejs/kit/tree/main/packages/kit/src/runtime) - code that's called at runtime +- [`packages/kit/src/exports/vite`](https://github.com/sveltejs/kit/tree/main/packages/kit/src/exports/vite) - for all the Vite plugin related stuff +- [`packages/adapter-[platform]`](https://github.com/sveltejs/kit/tree/main/packages) - for the various SvelteKit-provided adapters ## Good first issues @@ -61,7 +68,7 @@ Issues with the [**soon**](https://github.com/sveltejs/kit/issues?q=is%3Aissue+i ## Testing -Run `pnpm test` to run the tests from all subpackages. Browser tests live in subdirectories of `packages/kit/test` such as `packages/kit/test/apps/basics`. +Run `pnpm test:kit` to run the tests from the `packages/kit` directory. You can also run `pnpm test:others` to run tests from all packages __except__ the `packages/kit` directory. Browser tests live in subdirectories of `packages/kit/test` such as `packages/kit/test/apps/basics`. You can run the tests for only a single package by first moving to that directory. E.g. `cd packages/kit`. @@ -87,21 +94,21 @@ If you would like to test local changes to Vite or another dependency, you can b ```jsonc { - // ... - "dependencies": { - "vite": "^4.0.0" - }, - "pnpm": { - "overrides": { - "vite": "link:../path/to/vite/packages/vite" - } - } + // ... + "dependencies": { + "vite": "^4.0.0" + }, + "pnpm": { + "overrides": { + "vite": "link:../path/to/vite/packages/vite" + } + } } ``` ## Documentation changes -All documentation for SvelteKit is in the [`documentation` directory](https://github.com/sveltejs/kit/tree/master/documentation), and any improvements should be made as a Pull Request to this repository. The site itself is located in the [`sites/kit.svelte.dev` directory](https://github.com/sveltejs/kit/tree/master/sites/kit.svelte.dev) and can be run locally to preview changes. +All documentation for SvelteKit is in the [`documentation` directory](https://github.com/sveltejs/kit/tree/main/documentation), and any improvements should be made as a Pull Request to this repository. The site itself is located in the [`sveltejs/svelte.dev` repo](https://github.com/sveltejs/svelte.dev) and can be run locally to preview changes. ## Sending PRs @@ -116,7 +123,7 @@ There are a few guidelines we follow: To use the git hooks in the repo, which will save you from waiting for CI to tell you that you forgot to lint, run this: -```bash +```sh git config core.hookspath .githooks ``` @@ -124,12 +131,16 @@ git config core.hookspath .githooks For changes to be reflected in package changelogs, run `pnpm changeset` and follow the prompts. +### Type changes + +If your PR changes the generated types of SvelteKit, run `pnpm generate:types` inside `packages/kit` and commit the new output (don't format it with Prettier!). Review the changes carefully to ensure there are no unwanted changes. If you don't commit type changes, CI will fail. + ## Releases The [Changesets GitHub action](https://github.com/changesets/action#with-publishing) will create and update a PR that applies changesets and publishes new versions of changed packages to npm. New packages will need to be published manually the first time if they are scoped to the `@sveltejs` organisation, by running this from the package directory: -```bash +```sh npm publish --access=public ``` diff --git a/FUNDING.json b/FUNDING.json new file mode 100644 index 000000000000..7ed51db691e6 --- /dev/null +++ b/FUNDING.json @@ -0,0 +1,7 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0xCE08E02c37d90d75C2bf7D9e55f7606C8DB80E70" + } + } +} diff --git a/README.md b/README.md index 65ff09b6b9b1..025b8acab1c1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # SvelteKit -Web development, streamlined. Read the [documentation](https://kit.svelte.dev/docs) to get started. +Web development, streamlined. Read the [documentation](https://svelte.dev/docs/kit) to get started. ### Packages @@ -11,17 +11,15 @@ Web development, streamlined. Read the [documentation](https://kit.svelte.dev/do | [@sveltejs/kit](packages/kit) | [Changelog](packages/kit/CHANGELOG.md) | | [@sveltejs/adapter-auto](packages/adapter-auto) | [Changelog](packages/adapter-auto/CHANGELOG.md) | | [@sveltejs/adapter-cloudflare](packages/adapter-cloudflare) | [Changelog](packages/adapter-cloudflare/CHANGELOG.md) | -| [@sveltejs/adapter-cloudflare-workers](packages/adapter-cloudflare-workers) | [Changelog](packages/adapter-cloudflare-workers/CHANGELOG.md) | | [@sveltejs/adapter-netlify](packages/adapter-netlify) | [Changelog](packages/adapter-netlify/CHANGELOG.md) | | [@sveltejs/adapter-node](packages/adapter-node) | [Changelog](packages/adapter-node/CHANGELOG.md) | | [@sveltejs/adapter-static](packages/adapter-static) | [Changelog](packages/adapter-static/CHANGELOG.md) | | [@sveltejs/adapter-vercel](packages/adapter-vercel) | [Changelog](packages/adapter-vercel/CHANGELOG.md) | | [@sveltejs/amp](packages/amp) | [Changelog](packages/amp/CHANGELOG.md) | +| [@sveltejs/enhanced-img](packages/enhanced-img) | [Changelog](packages/enhanced-img/CHANGELOG.md) | | [@sveltejs/package](packages/package) | [Changelog](packages/package/CHANGELOG.md) | -| [create-svelte](packages/create-svelte) | [Changelog](packages/create-svelte/CHANGELOG.md) | -| [svelte-migrate](packages/migrate) | [Changelog](packages/migrate/CHANGELOG.md) | -[Additional adapters](<(https://sveltesociety.dev/components#adapters)>) are maintained by the community. +[Additional adapters](https://sveltesociety.dev/packages?category=sveltekit-adapters) are maintained by the community. ## Bug reporting @@ -43,4 +41,4 @@ Funds donated via Open Collective will be used for compensating expenses related ## License -[MIT](https://github.com/sveltejs/kit/blob/master/LICENSE) +[MIT](https://github.com/sveltejs/kit/blob/main/LICENSE) diff --git a/documentation/docs/10-getting-started/10-introduction.md b/documentation/docs/10-getting-started/10-introduction.md index 189fcb3bcc7d..dd093ff5abea 100644 --- a/documentation/docs/10-getting-started/10-introduction.md +++ b/documentation/docs/10-getting-started/10-introduction.md @@ -4,24 +4,24 @@ title: Introduction ## Before we begin -> If you're new to Svelte or SvelteKit we recommend checking out the [interactive tutorial](https://learn.svelte.dev). +> [!NOTE] If you're new to Svelte or SvelteKit we recommend checking out the [interactive tutorial](/tutorial/kit). > -> If you get stuck, reach out for help in the [Discord chatroom](https://svelte.dev/chat). +> If you get stuck, reach out for help in the [Discord chatroom](/chat). ## What is SvelteKit? -SvelteKit is a framework for rapidly developing robust, performant web applications using [Svelte](https://svelte.dev/). If you're coming from React, SvelteKit is similar to Next. If you're coming from Vue, SvelteKit is similar to Nuxt. +SvelteKit is a framework for rapidly developing robust, performant web applications using [Svelte](../svelte). If you're coming from React, SvelteKit is similar to Next. If you're coming from Vue, SvelteKit is similar to Nuxt. -To learn more about the kinds of applications you can build with SvelteKit, see the [FAQ](/docs/faq#what-can-i-make-with-sveltekit). +To learn more about the kinds of applications you can build with SvelteKit, see the [documentation regarding project types](project-types). ## What is Svelte? -In short, Svelte is a way of writing user interface components — like a navigation bar, comment section, or contact form — that users see and interact with in their browsers. The Svelte compiler converts your components to JavaScript that can be run to render the HTML for the page and to CSS that styles the page. You don't need to know Svelte to understand the rest of this guide, but it will help. If you'd like to learn more, check out [the Svelte tutorial](https://svelte.dev/tutorial). +In short, Svelte is a way of writing user interface components — like a navigation bar, comment section, or contact form — that users see and interact with in their browsers. The Svelte compiler converts your components to JavaScript that can be run to render the HTML for the page and to CSS that styles the page. You don't need to know Svelte to understand the rest of this guide, but it will help. If you'd like to learn more, check out [the Svelte tutorial](/tutorial). ## SvelteKit vs Svelte Svelte renders UI components. You can compose these components and render an entire page with just Svelte, but you need more than just Svelte to write an entire app. -SvelteKit helps you build web apps while following modern best practices and providing solutions to common development challenges. It offers everything from basic functionalities — like a [router](glossary#routing) that updates your UI when a link is clicked — to more advanced capabilities. Its extensive list of features includes [build optimizations](https://vitejs.dev/guide/features.html#build-optimizations) to load only the minimal required code; [offline support](service-workers); [preloading](link-options#data-sveltekit-preload-data) pages before user navigation; [configurable rendering](page-options) to handle different parts of your app on the server via [SSR](glossary#ssr), in the browser through [client-side rendering](glossary#csr), or at build-time with [prerendering](glossary#prerendering); and much more. Building an app with all the modern best practices is fiendishly complicated, but SvelteKit does all the boring stuff for you so that you can get on with the creative part. +SvelteKit helps you build web apps while following modern best practices and providing solutions to common development challenges. It offers everything from basic functionalities — like a [router](glossary#Routing) that updates your UI when a link is clicked — to more advanced capabilities. Its extensive list of features includes [build optimizations](https://vitejs.dev/guide/features.html#build-optimizations) to load only the minimal required code; [offline support](service-workers); [preloading](link-options#data-sveltekit-preload-data) pages before user navigation; [configurable rendering](page-options) to handle different parts of your app on the server via [SSR](glossary#SSR), in the browser through [client-side rendering](glossary#CSR), or at build-time with [prerendering](glossary#Prerendering); [image optimization](images); and much more. Building an app with all the modern best practices is fiendishly complicated, but SvelteKit does all the boring stuff for you so that you can get on with the creative part. It reflects changes to your code in the browser instantly to provide a lightning-fast and feature-rich development experience by leveraging [Vite](https://vitejs.dev/) with a [Svelte plugin](https://github.com/sveltejs/vite-plugin-svelte) to do [Hot Module Replacement (HMR)](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#hot). diff --git a/documentation/docs/10-getting-started/20-creating-a-project.md b/documentation/docs/10-getting-started/20-creating-a-project.md index a65814e9a50f..c1d125ec0bd8 100644 --- a/documentation/docs/10-getting-started/20-creating-a-project.md +++ b/documentation/docs/10-getting-started/20-creating-a-project.md @@ -2,24 +2,23 @@ title: Creating a project --- -The easiest way to start building a SvelteKit app is to run `npm create`: +The easiest way to start building a SvelteKit app is to run `npx sv create`: -```bash -npm create svelte@latest my-app +```sh +npx sv create my-app cd my-app -npm install npm run dev ``` -The first command will scaffold a new project in the `my-app` directory asking you if you'd like to set up some basic tooling such as TypeScript. See [integrations](./integrations) for pointers on setting up additional tooling. The subsequent commands will then install its dependencies and start a server on [localhost:5173](http://localhost:5173). +The first command will scaffold a new project in the `my-app` directory asking if you'd like to set up some basic tooling such as TypeScript. See [the CLI docs](/docs/cli/overview) for information about these options and [the integrations page](./integrations) for pointers on setting up additional tooling. `npm run dev` will then start the development server on [localhost:5173](http://localhost:5173) - make sure you install dependencies before running this if you didn't do so during project creation. There are two basic concepts: -- Each page of your app is a [Svelte](https://svelte.dev) component +- Each page of your app is a [Svelte](../svelte) component - You create pages by adding files to the `src/routes` directory of your project. These will be server-rendered so that a user's first visit to your app is as fast as possible, then a client-side app takes over Try editing the files to get a feel for how everything works. ## Editor setup -We recommend using [Visual Studio Code (aka VS Code)](https://code.visualstudio.com/download) with [the Svelte extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), but [support also exists for numerous other editors](https://sveltesociety.dev/tools#editor-support). +We recommend using [Visual Studio Code (aka VS Code)](https://code.visualstudio.com/download) with [the Svelte extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), but [support also exists for numerous other editors](https://sveltesociety.dev/collection/editor-support-c85c080efc292a34). diff --git a/documentation/docs/10-getting-started/25-project-types.md b/documentation/docs/10-getting-started/25-project-types.md new file mode 100644 index 000000000000..8ac78cd9d6b8 --- /dev/null +++ b/documentation/docs/10-getting-started/25-project-types.md @@ -0,0 +1,69 @@ +--- +title: Project types +--- + +SvelteKit offers configurable rendering, which allows you to build and deploy your project in several different ways. You can build all of the below types of applications and more with SvelteKit. Rendering settings are not mutually exclusive and you may choose the optimal manner with which to render different parts of your application. + +If you don't have a particular way you'd like to build your application in mind, don't worry! The way your application is built, deployed, and rendered is controlled by which adapter you've chosen and a small amount of configuration and these can always be changed later. The [project structure](project-structure) and [routing](glossary#Routing) will be the same regardless of the project type that you choose. + +## Default rendering + +By default, when a user visits a site, SvelteKit will render the first page with [server-side rendering (SSR)](glossary#SSR) and subsequent pages with [client-side rendering (CSR)](glossary#CSR). Using SSR for the initial render improves SEO and perceived performance of the initial page load. Client-side rendering then takes over and updates the page without having to rerender common components, which is typically faster and eliminates a flash when navigating between pages. Apps built with this hybrid rendering approach have also been called [transitional apps](https://www.youtube.com/watch?v=860d8usGC0o). + +## Static site generation + +You can use SvelteKit as a [static site generator (SSG)](glossary#SSG) that fully [prerenders](glossary#Prerendering) your site with static rendering using [`adapter-static`](adapter-static). You may also use [the prerender option](page-options#prerender) to prerender only some pages and then choose a different adapter with which to dynamically server-render other pages. + +Tools built solely to do static site generation may scale the prerendering process more efficiently during build when rendering a very large number of pages. When working with very large statically generated sites, you can avoid long build times with [Incremental Static Regeneration (ISR) if using `adapter-vercel`](adapter-vercel#Incremental-Static-Regeneration). And in contrast to purpose-built SSGs, SvelteKit allows for nicely mixing and matching different rendering types on different pages. + +## Single-page app + +[Single-page apps (SPAs)](glossary#SPA) exclusively use [client-side rendering (CSR)](glossary#CSR). You can [build single-page apps (SPAs)](single-page-apps) with SvelteKit. As with all types of SvelteKit applications, you can write your backend in SvelteKit or [another language or framework](#Separate-backend). If you are building an application with no backend or a [separate backend](#Separate-backend), you can simply skip over and ignore the parts of the docs talking about `server` files. + +## Multi-page app + +SvelteKit isn't typically used to build [traditional multi-page apps](glossary#MPA). However, in SvelteKit you can remove all JavaScript on a page with [`csr = false`](page-options#csr), which will render subsequent links on the server, or you can use [`data-sveltekit-reload`](link-options#data-sveltekit-reload) to render specific links on the server. + +## Separate backend + +If your backend is written in another language such as Go, Java, PHP, Ruby, Rust, or C#, there are a couple of ways that you can deploy your application. The most recommended way would be to deploy your SvelteKit frontend separately from your backend utilizing `adapter-node` or a serverless adapter. Some users prefer not to have a separate process to manage and decide to deploy their application as a [single-page app (SPA)](single-page-apps) served by their backend server, but note that single-page apps have worse SEO and performance characteristics. + +If you are using an external backend, you can simply skip over and ignore the parts of the docs talking about `server` files. You may also want to reference [the FAQ about how to make calls to a separate backend](faq#How-do-I-use-a-different-backend-API-server). + +## Serverless app + +SvelteKit apps are simple to run on serverless platforms. [The default zero config adapter](adapter-auto) will automatically run your app on a number of supported platforms or you can use [`adapter-vercel`](adapter-vercel), [`adapter-netlify`](adapter-netlify), or [`adapter-cloudflare`](adapter-cloudflare) to provide platform-specific configuration. And [community adapters](/packages#sveltekit-adapters) allow you to deploy your application to almost any serverless environment. Some of these adapters such as [`adapter-vercel`](adapter-vercel) and [`adapter-netlify`](adapter-netlify) offer an `edge` option, to support [edge rendering](glossary#Edge) for improved latency. + +## Your own server + +You can deploy to your own server or VPS using [`adapter-node`](adapter-node). + +## Container + +You can use [`adapter-node`](adapter-node) to run a SvelteKit app within a container such as Docker or LXC. + +## Library + +You can create a library to be used by other Svelte apps with the [`@sveltejs/package`](packaging) add-on to SvelteKit by choosing the library option when running [`sv create`](/docs/cli/sv-create). + +## Offline app + +SvelteKit has full support for [service workers](service-workers) allowing you to build many types of applications such as offline apps and [progressive web apps](glossary#PWA). + +## Mobile app + +You can turn a [SvelteKit SPA](single-page-apps) into a mobile app with [Tauri](https://v2.tauri.app/start/frontend/sveltekit/) or [Capacitor](https://capacitorjs.com/solution/svelte). Mobile features like the camera, geolocation, and push notifications are available via plugins for both platforms. + +These mobile development platforms work by starting a local web server and then serving your application like a static host on your phone. You may find [`bundleStrategy: 'single'`](configuration#output) to be a helpful option to limit the number of requests made. E.g. at the time of writing, the Capacitor local server uses HTTP/1, which limits the number of concurrent connections. + +## Desktop app + +You can turn a [SvelteKit SPA](single-page-apps) into a desktop app with [Tauri](https://v2.tauri.app/start/frontend/sveltekit/), [Wails](https://wails.io/docs/guides/sveltekit/), or [Electron](https://www.electronjs.org/). + +## Browser extension + +You can build browser extensions using either [`adapter-static`](adapter-static) or [community adapters](/packages#sveltekit-adapters) specifically tailored towards browser extensions. + +## Embedded device + +Because of its efficient rendering, Svelte can be run on low power devices. Embedded devices like microcontrollers and TVs may limit the number of concurrent connections. In order to reduce the number of concurrent requests, you may find [`bundleStrategy: 'single'`](configuration#output) to be a helpful option in this deployment configuration. diff --git a/documentation/docs/10-getting-started/30-project-structure.md b/documentation/docs/10-getting-started/30-project-structure.md index e9a63473b0d9..d1db3547ca34 100644 --- a/documentation/docs/10-getting-started/30-project-structure.md +++ b/documentation/docs/10-getting-started/30-project-structure.md @@ -4,7 +4,7 @@ title: Project structure A typical SvelteKit project looks like this: -```bash +```tree my-project/ ├ src/ │ ├ lib/ @@ -19,7 +19,8 @@ my-project/ │ ├ error.html │ ├ hooks.client.js │ ├ hooks.server.js -│ └ service-worker.js +│ ├ service-worker.js +│ └ tracing.server.js ├ static/ │ └ [your static assets] ├ tests/ @@ -30,7 +31,7 @@ my-project/ └ vite.config.js ``` -You'll also find common files like `.gitignore` and `.npmrc` (and `.prettierrc` and `.eslintrc.cjs` and so on, if you chose those options when running `npm create svelte@latest`). +You'll also find common files like `.gitignore` and `.npmrc` (and `.prettierrc` and `eslint.config.js` and so on, if you chose those options when running `npx sv create`). ## Project files @@ -38,9 +39,9 @@ You'll also find common files like `.gitignore` and `.npmrc` (and `.prettierrc` The `src` directory contains the meat of your project. Everything except `src/routes` and `src/app.html` is optional. -- `lib` contains your library code (utilities and components), which can be imported via the [`$lib`](modules#$lib) alias, or packaged up for distribution using [`svelte-package`](packaging) +- `lib` contains your library code (utilities and components), which can be imported via the [`$lib`]($lib) alias, or packaged up for distribution using [`svelte-package`](packaging) - `server` contains your server-only library code. It can be imported by using the [`$lib/server`](server-only-modules) alias. SvelteKit will prevent you from importing these in client code. -- `params` contains any [param matchers](advanced-routing#matching) your app needs +- `params` contains any [param matchers](advanced-routing#Matching) your app needs - `routes` contains the [routes](routing) of your application. You can also colocate other components that are only used within a single route here - `app.html` is your page template — an HTML document containing the following placeholders: - `%sveltekit.head%` — `` and `

{data.title}

{@html data.content}
``` -> Note that SvelteKit uses `` elements to navigate between routes, rather than a framework-specific `` component. +> [!LEGACY] +> `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` property manually with `PageData` instead, see [$types](#\$types). +> +> In Svelte 4, you'd use `export let data` instead. ### +page.js @@ -61,7 +74,7 @@ export function load({ params }) { }; } - throw error(404, 'Not found'); + error(404, 'Not found'); } ``` @@ -77,7 +90,7 @@ You can find more information about these in [page options](page-options). ### +page.server.js -If your `load` function can only run on the server — for example, if it needs to fetch data from a database or you need to access private [environment variables](modules#$env-static-private) like API keys — then you can rename `+page.js` to `+page.server.js` and change the `PageLoad` type to `PageServerLoad`. +If your `load` function can only run on the server — for example, if it needs to fetch data from a database or you need to access private [environment variables]($env-static-private) like API keys — then you can rename `+page.js` to `+page.server.js` and change the `PageLoad` type to `PageServerLoad`. ```js /// file: src/routes/blog/[slug]/+page.server.js @@ -104,7 +117,7 @@ export async function load({ params }) { return post; } - throw error(404, 'Not found'); + error(404, 'Not found'); } ``` @@ -121,19 +134,22 @@ If an error occurs during `load`, SvelteKit will render a default error page. Yo ```svelte -

{$page.status}: {$page.error.message}

+

{page.status}: {page.error.message}

``` +> [!LEGACY] +> `$app/state` was added in SvelteKit 2.12. If you're using an earlier version or are using Svelte 4, use `$app/stores` instead. + SvelteKit will 'walk up the tree' looking for the closest error boundary — if the file above didn't exist it would try `src/routes/blog/+error.svelte` and then `src/routes/+error.svelte` before rendering the default error page. If _that_ fails (or if the error was thrown from the `load` function of the root `+layout`, which sits 'above' the root `+error`), SvelteKit will bail out and render a static fallback error page, which you can customise by creating a `src/error.html` file. If the error occurs inside a `load` function in `+layout(.server).js`, the closest error boundary in the tree is an `+error.svelte` file _above_ that layout (not next to it). If no route can be found (404), `src/routes/+error.svelte` (or the default error page, if that file does not exist) will be used. -> `+error.svelte` is _not_ used when an error occurs inside [`handle`](hooks#server-hooks-handle) or a [+server.js](#server) request handler. +> [!NOTE] `+error.svelte` is _not_ used when an error occurs inside [`handle`](hooks#Server-hooks-handle) or a [+server.js](#server) request handler. You can read more about error handling [here](errors). @@ -147,21 +163,29 @@ But in many apps, there are elements that should be visible on _every_ page, suc To create a layout that applies to every page, make a file called `src/routes/+layout.svelte`. The default layout (the one that SvelteKit uses if you don't bring your own) looks like this... -```html - +```svelte + + +{@render children()} ``` -...but we can add whatever markup, styles and behaviour we want. The only requirement is that the component includes a `` for the page content. For example, let's add a nav bar: +...but we can add whatever markup, styles and behaviour we want. The only requirement is that the component includes a `@render` tag for the page content. For example, let's add a nav bar: + +```svelte + + -```html -/// file: src/routes/+layout.svelte
- +{@render children()} ``` If we create pages for `/`, `/about` and `/settings`... @@ -190,8 +214,8 @@ We can create a layout that only applies to pages below `/settings` (while inher ```svelte

Settings

@@ -202,10 +226,15 @@ We can create a layout that only applies to pages below `/settings` (while inher {/each} - +{@render children()} ``` -By default, each layout inherits the layout above it. Sometimes that isn't what you want - in this case, [advanced layouts](advanced-routing#advanced-layouts) can help you. +> [!LEGACY] +> `LayoutProps` was added in 2.16.0. In earlier versions, you had to [type the properties manually instead](#\$types). + +You can see how `data` is populated by looking at the `+layout.js` example in the next section just below. + +By default, each layout inherits the layout above it. Sometimes that isn't what you want - in this case, [advanced layouts](advanced-routing#Advanced-layouts) can help you. ### +layout.js @@ -231,14 +260,14 @@ Data returned from a layout's `load` function is also available to all its child ```svelte ``` -> Often, layout data is unchanged when navigating between pages. SvelteKit will intelligently rerun [`load`](load) functions when necessary. +> [!NOTE] Often, layout data is unchanged when navigating between pages. SvelteKit will intelligently rerun [`load`](load) functions when necessary. ### +layout.server.js @@ -248,7 +277,7 @@ Like `+layout.js`, `+layout.server.js` can export [page options](page-options) ## +server -As well as pages, you can define routes with a `+server.js` file (sometimes referred to as an 'API route' or an 'endpoint'), which gives you full control over the response. Your `+server.js` file exports functions corresponding to HTTP verbs like `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `OPTIONS`, and `HEAD` that take a `RequestEvent` argument and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object. +As well as pages, you can define routes with a `+server.js` file (sometimes referred to as an 'API route' or an 'endpoint'), which gives you full control over the response. Your `+server.js` file exports functions corresponding to HTTP verbs like `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `OPTIONS`, and `HEAD` that take a [`RequestEvent`](@sveltejs-kit#RequestEvent) argument and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object. For example we could create an `/api/random-number` route with a `GET` handler: @@ -264,7 +293,7 @@ export function GET({ url }) { const d = max - min; if (isNaN(d) || d < 0) { - throw error(400, 'min and max must be numbers, and min must be less than max'); + error(400, 'min and max must be numbers, and min must be less than max'); } const random = min + Math.random() * d; @@ -275,11 +304,13 @@ export function GET({ url }) { The first argument to `Response` can be a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream), making it possible to stream large amounts of data or create server-sent events (unless deploying to platforms that buffer responses, like AWS Lambda). -You can use the [`error`](modules#sveltejs-kit-error), [`redirect`](modules#sveltejs-kit-redirect) and [`json`](modules#sveltejs-kit-json) methods from `@sveltejs/kit` for convenience (but you don't have to). +You can use the [`error`](@sveltejs-kit#error), [`redirect`](@sveltejs-kit#redirect) and [`json`](@sveltejs-kit#json) methods from `@sveltejs/kit` for convenience (but you don't have to). + +If an error is thrown (either `error(...)` or an unexpected error), the response will be a JSON representation of the error or a fallback error page — which can be customised via `src/error.html` — depending on the `Accept` header. The [`+error.svelte`](#error) component will _not_ be rendered in this case. You can read more about error handling [here](errors). -If an error is thrown (either `throw error(...)` or an unexpected error), the response will be a JSON representation of the error or a fallback error page — which can be customised via `src/error.html` — depending on the `Accept` header. The [`+error.svelte`](#error) component will _not_ be rendered in this case. You can read more about error handling [here](errors). +> [!NOTE] When creating an `OPTIONS` handler, note that Vite will inject `Access-Control-Allow-Origin` and `Access-Control-Allow-Methods` headers — these will not be present in production unless you add them. -> When creating an `OPTIONS` handler, note that Vite will inject `Access-Control-Allow-Origin` and `Access-Control-Allow-Methods` headers — these will not be present in production unless you add them. +> [!NOTE] `+layout` files have no effect on `+server.js` files. If you want to run some logic before each request, add it to the server [`handle`](hooks#Server-hooks-handle) hook. ### Receiving data @@ -288,9 +319,9 @@ By exporting `POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`/`HEAD` handlers, `+server.j ```svelte ``` +> [!NOTE] +> The `PageProps` and `LayoutProps` types, added in 2.16.0, are a shortcut for typing the `data` prop as `PageData` or `LayoutData`, as well as other props, such as `form` for pages, or `children` for layouts. In earlier versions, you had to type these properties manually. For example, for a page: +> +> ```js +> /// file: +page.svelte +> /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ +> let { data, form } = $props(); +> ``` +> +> Or, for a layout: +> +> ```js +> /// file: +layout.svelte +> /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */ +> let { data, children } = $props(); +> ``` + In turn, annotating the `load` function with `PageLoad`, `PageServerLoad`, `LayoutLoad` or `LayoutServerLoad` (for `+page.js`, `+page.server.js`, `+layout.js` and `+layout.server.js` respectively) ensures that `params` and the return value are correctly typed. If you're using VS Code or any IDE that supports the language server protocol and TypeScript plugins then you can omit these types _entirely_! Svelte's IDE tooling will insert the correct types for you, so you'll get type checking without writing them yourself. It also works with our command line tool `svelte-check`. -You can read more about omitting `$types` in our [blog post](https://svelte.dev/blog/zero-config-type-safety) about it. +You can read more about omitting `$types` in our [blog post](/blog/zero-config-type-safety) about it. ## Other files Any other files inside a route directory are ignored by SvelteKit. This means you can colocate components and utility modules with the routes that need them. -If components and modules are needed by multiple routes, it's a good idea to put them in [`$lib`](modules#$lib). +If components and modules are needed by multiple routes, it's a good idea to put them in [`$lib`]($lib). ## Further reading -- [Tutorial: Routing](https://learn.svelte.dev/tutorial/pages) -- [Tutorial: API routes](https://learn.svelte.dev/tutorial/get-handlers) +- [Tutorial: Routing](/tutorial/kit/pages) +- [Tutorial: API routes](/tutorial/kit/get-handlers) - [Docs: Advanced routing](advanced-routing) diff --git a/documentation/docs/20-core-concepts/20-load.md b/documentation/docs/20-core-concepts/20-load.md index d582e9987904..d6a1ccc21825 100644 --- a/documentation/docs/20-core-concepts/20-load.md +++ b/documentation/docs/20-core-concepts/20-load.md @@ -2,7 +2,7 @@ title: Loading data --- -Before a [`+page.svelte`](routing#page-page-svelte) component (and its containing [`+layout.svelte`](routing#layout-layout-svelte) components) can be rendered, we often need to get some data. This is done by defining `load` functions. +Before a [`+page.svelte`](routing#page-page.svelte) component (and its containing [`+layout.svelte`](routing#layout-layout.svelte) components) can be rendered, we often need to get some data. This is done by defining `load` functions. ## Page data @@ -24,17 +24,27 @@ export function load({ params }) { ```svelte

{data.post.title}

{@html data.post.content}
``` +> [!LEGACY] +> Before version 2.16.0, the props of a page and layout had to be typed individually: +> ```js +> /// file: +page.svelte +> /** @type {{ data: import('./$types').PageData }} */ +> let { data } = $props(); +> ``` +> +> In Svelte 4, you'd use `export let data` instead. + Thanks to the generated `$types` module, we get full type safety. -A `load` function in a `+page.js` file runs both on the server and in the browser (unless combined with `export const ssr = false`, in which case it will [only run in the browser](https://kit.svelte.dev/docs/page-options#ssr)). If your `load` function should _always_ run on the server (because it uses private environment variables, for example, or accesses a database) then it would go in a `+page.server.js` instead. +A `load` function in a `+page.js` file runs both on the server and in the browser (unless combined with `export const ssr = false`, in which case it will [only run in the browser](page-options#ssr)). If your `load` function should _always_ run on the server (because it uses private environment variables, for example, or accesses a database) then it would go in a `+page.server.js` instead. A more realistic version of your blog post's `load` function, that only runs on the server and pulls data from a database, might look like this: @@ -57,7 +67,7 @@ export async function load({ params }) { } ``` -Notice that the type changed from `PageLoad` to `PageServerLoad`, because server `load` functions can access additional arguments. To understand when to use `+page.js` and when to use `+page.server.js`, see [Universal vs server](load#universal-vs-server). +Notice that the type changed from `PageLoad` to `PageServerLoad`, because server `load` functions can access additional arguments. To understand when to use `+page.js` and when to use `+page.server.js`, see [Universal vs server](load#Universal-vs-server). ## Layout data @@ -85,13 +95,13 @@ export async function load() { ```svelte
- - + + {@render children()}
``` +> [!LEGACY] +> `LayoutProps` was added in 2.16.0. In earlier versions, properties had to be typed individually: +> ```js +> /// file: +layout.svelte +> /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */ +> let { data, children } = $props(); +> ``` + Data returned from layout `load` functions is available to child `+layout.svelte` components and the `+page.svelte` component as well as the layout that it 'belongs' to. -```diff +```svelte /// file: src/routes/blog/[slug]/+page.svelte

{data.post.title}

{@html data.post.content}
-+{#if next} -+

Next post: {next.title}

-+{/if} ++++{#if next} +

Next post: {next.title}

+{/if}+++ ``` -> If multiple `load` functions return data with the same key, the last one 'wins' — the result of a layout `load` returning `{ a: 1, b: 2 }` and a page `load` returning `{ b: 3, c: 4 }` would be `{ a: 1, b: 3, c: 4 }`. +> [!NOTE] If multiple `load` functions return data with the same key, the last one 'wins' — the result of a layout `load` returning `{ a: 1, b: 2 }` and a page `load` returning `{ b: 3, c: 4 }` would be `{ a: 1, b: 3, c: 4 }`. -## $page.data +## page.data The `+page.svelte` component, and each `+layout.svelte` component above it, has access to its own data plus all the data from its parents. -In some cases, we might need the opposite — a parent layout might need to access page data or data from a child layout. For example, the root layout might want to access a `title` property returned from a `load` function in `+page.js` or `+page.server.js`. This can be done with `$page.data`: +In some cases, we might need the opposite — a parent layout might need to access page data or data from a child layout. For example, the root layout might want to access a `title` property returned from a `load` function in `+page.js` or `+page.server.js`. This can be done with `page.data`: ```svelte - {$page.data.title} + {page.data.title} ``` -Type information for `$page.data` is provided by `App.PageData`. +Type information for `page.data` is provided by `App.PageData`. + +> [!LEGACY] +> `$app/state` was added in SvelteKit 2.12. If you're using an earlier version or are using Svelte 4, use `$app/stores` instead. +> It provides a `page` store with the same interface that you can subscribe to, e.g. `$page.data.title`. ## Universal vs server @@ -166,13 +188,15 @@ Conceptually, they're the same thing, but there are some important differences t Server `load` functions _always_ run on the server. -By default, universal `load` functions run on the server during SSR when the user first visits your page. They will then run again during hydration, reusing any responses from [fetch requests](#making-fetch-requests). All subsequent invocations of universal `load` functions happen in the browser. You can customize the behavior through [page options](page-options). If you disable [server side rendering](page-options#ssr), you'll get an SPA and universal `load` functions _always_ run on the client. +By default, universal `load` functions run on the server during SSR when the user first visits your page. They will then run again during hydration, reusing any responses from [fetch requests](#Making-fetch-requests). All subsequent invocations of universal `load` functions happen in the browser. You can customize the behavior through [page options](page-options). If you disable [server-side rendering](page-options#ssr), you'll get an SPA and universal `load` functions _always_ run on the client. + +If a route contains both universal and server `load` functions, the server `load` runs first. A `load` function is invoked at runtime, unless you [prerender](page-options#prerender) the page — in that case, it's invoked at build time. ### Input -Both universal and server `load` functions have access to properties describing the request (`params`, `route` and `url`) and various functions (`fetch`, `setHeaders`, `parent` and `depends`). These are described in the following sections. +Both universal and server `load` functions have access to properties describing the request (`params`, `route` and `url`) and various functions (`fetch`, `setHeaders`, `parent`, `depends` and `untrack`). These are described in the following sections. Server `load` functions are called with a `ServerLoadEvent`, which inherits `clientAddress`, `cookies`, `locals`, `platform` and `request` from `RequestEvent`. @@ -182,7 +206,7 @@ Universal `load` functions are called with a `LoadEvent`, which has a `data` pro A universal `load` function can return an object containing any values, including things like custom classes and component constructors. -A server `load` function must return data that can be serialized with [devalue](https://github.com/rich-harris/devalue) — anything that can be represented as JSON plus things like `BigInt`, `Date`, `Map`, `Set` and `RegExp`, or repeated/cyclical references — so that it can be transported over the network. Your data can include [promises](#streaming-with-promises), in which case it will be streamed to browsers. +A server `load` function must return data that can be serialized with [devalue](https://github.com/rich-harris/devalue) — anything that can be represented as JSON plus things like `BigInt`, `Date`, `Map`, `Set` and `RegExp`, or repeated/cyclical references — so that it can be transported over the network. Your data can include [promises](#Streaming-with-promises), in which case it will be streamed to browsers. If you need to serialize/deserialize custom types, use [transport hooks](hooks#Universal-hooks-transport). ### When to use which @@ -190,7 +214,29 @@ Server `load` functions are convenient when you need to access data directly fro Universal `load` functions are useful when you need to `fetch` data from an external API and don't need private credentials, since SvelteKit can get the data directly from the API rather than going via your server. They are also useful when you need to return something that can't be serialized, such as a Svelte component constructor. -In rare cases, you might need to use both together — for example, you might need to return an instance of a custom class that was initialised with data from your server. +In rare cases, you might need to use both together — for example, you might need to return an instance of a custom class that was initialised with data from your server. When using both, the server `load` return value is _not_ passed directly to the page, but to the universal `load` function (as the `data` property): + +```js +/// file: src/routes/+page.server.js +/** @type {import('./$types').PageServerLoad} */ +export async function load() { + return { + serverMessage: 'hello from server load function' + }; +} +``` + +```js +/// file: src/routes/+page.js +// @errors: 18047 +/** @type {import('./$types').PageLoad} */ +export async function load({ data }) { + return { + serverMessage: data.serverMessage, + universalMessage: 'hello from universal load function' + }; +} +``` ## Using URL data @@ -200,7 +246,7 @@ Often the `load` function depends on the URL in one way or another. For this, th An instance of [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), containing properties like the `origin`, `hostname`, `pathname` and `searchParams` (which contains the parsed query string as a [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object). `url.hash` cannot be accessed during `load`, since it is unavailable on the server. -> In some environments this is derived from request headers during server-side rendering. If you're using [adapter-node](adapter-node), for example, you may need to configure the adapter in order for the URL to be correct. +> [!NOTE] In some environments this is derived from request headers during server-side rendering. If you're using [adapter-node](adapter-node), for example, you may need to configure the adapter in order for the URL to be correct. ### route @@ -234,7 +280,7 @@ To get data from an external API or a `+server.js` handler, you can use the prov - It can be used to make credentialed requests on the server, as it inherits the `cookie` and `authorization` headers for the page request. - It can make relative requests on the server (ordinarily, `fetch` requires a URL with an origin when used in a server context). - Internal requests (e.g. for `+server.js` routes) go directly to the handler function when running on the server, without the overhead of an HTTP call. -- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the `text` and `json` methods of the `Response` object. Note that headers will _not_ be serialized, unless explicitly included via [`filterSerializedResponseHeaders`](hooks#server-hooks-handle). +- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the `text`, `json` and `arrayBuffer` methods of the `Response` object. Note that headers will _not_ be serialized, unless explicitly included via [`filterSerializedResponseHeaders`](hooks#Server-hooks-handle). - During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request - if you received a warning in your browser console when using the browser `fetch` instead of the `load` `fetch`, this is why. ```js @@ -250,7 +296,7 @@ export async function load({ fetch, params }) { ## Cookies -A server `load` function can get and set [`cookies`](types#public-types-cookies). +A server `load` function can get and set [`cookies`](@sveltejs-kit#Cookies). ```js /// file: src/routes/+layout.server.js @@ -278,12 +324,10 @@ Cookies will only be passed through the provided `fetch` function if the target For example, if SvelteKit is serving my.domain.com: - domain.com WILL NOT receive cookies - my.domain.com WILL receive cookies -- api.domain.dom WILL NOT receive cookies +- api.domain.com WILL NOT receive cookies - sub.my.domain.com WILL receive cookies -Other cookies will not be passed when `credentials: 'include'` is set, because SvelteKit does not know which domain which cookie belongs to (the browser does not pass this information along), so it's not safe to forward any of them. Use the [handleFetch hook](hooks#server-hooks-handlefetch) to work around it. - -> When setting cookies, be aware of the `path` property. By default, the `path` of a cookie is the current pathname. If you for example set a cookie at page `admin/user`, the cookie will only be available within the `admin` pages by default. In most cases you likely want to set `path` to `'/'` to make the cookie available throughout your app. +Other cookies will not be passed when `credentials: 'include'` is set, because SvelteKit does not know which domain which cookie belongs to (the browser does not pass this information along), so it's not safe to forward any of them. Use the [handleFetch hook](hooks#Server-hooks-handleFetch) to work around it. ## Headers @@ -297,8 +341,8 @@ export async function load({ fetch, setHeaders }) { const url = `https://cms.example.com/products.json`; const response = await fetch(url); - // cache the page for the same length of time - // as the underlying data + // Headers are only set during SSR, caching the page's HTML + // for the same length of time as the underlying data. setHeaders({ age: response.headers.get('age'), 'cache-control': response.headers.get('cache-control') @@ -308,7 +352,7 @@ export async function load({ fetch, setHeaders }) { } ``` -Setting the same header multiple times (even in separate `load` functions) is an error — you can only set a given header once. You cannot add a `set-cookie` header with `setHeaders` — use `cookies.set(name, value, options)` instead. +Setting the same header multiple times (even in separate `load` functions) is an error. You can only set a given header once using the `setHeaders` function. You cannot add a `set-cookie` header with `setHeaders` — use `cookies.set(name, value, options)` instead. ## Using parent data @@ -343,15 +387,15 @@ export async function load({ parent }) { ```svelte

{data.a} + {data.b} = {data.c}

``` -> Notice that the `load` function in `+page.js` receives the merged data from both layout `load` functions, not just the immediate parent. +> [!NOTE] Notice that the `load` function in `+page.js` receives the merged data from both layout `load` functions, not just the immediate parent. Inside `+page.server.js` and `+layout.server.js`, `parent` returns data from parent `+layout.server.js` files. @@ -359,16 +403,21 @@ In `+page.js` or `+layout.js` it will return data from parent `+layout.js` files Take care not to introduce waterfalls when using `await parent()`. Here, for example, `getData(params)` does not depend on the result of calling `parent()`, so we should call it first to avoid a delayed render. -```diff +```js /// file: +page.js +// @filename: ambient.d.ts +declare function getData(params: Record): Promise<{ meta: any }> + +// @filename: index.js +// ---cut--- /** @type {import('./$types').PageLoad} */ export async function load({ params, parent }) { -- const parentData = await parent(); + ---const parentData = await parent();--- const data = await getData(params); -+ const parentData = await parent(); + +++const parentData = await parent();+++ return { - ...data + ...data, meta: { ...parentData.meta, ...data.meta } }; } @@ -376,7 +425,7 @@ export async function load({ params, parent }) { ## Errors -If an error is thrown during `load`, the nearest [`+error.svelte`](routing#error) will be rendered. For _expected_ errors, use the `error` helper from `@sveltejs/kit` to specify the HTTP status code and an optional message: +If an error is thrown during `load`, the nearest [`+error.svelte`](routing#error) will be rendered. For [_expected_](errors#Expected-errors) errors, use the `error` helper from `@sveltejs/kit` to specify the HTTP status code and an optional message: ```js /// file: src/routes/admin/+layout.server.js @@ -397,20 +446,24 @@ import { error } from '@sveltejs/kit'; /** @type {import('./$types').LayoutServerLoad} */ export function load({ locals }) { if (!locals.user) { - throw error(401, 'not logged in'); + error(401, 'not logged in'); } if (!locals.user.isAdmin) { - throw error(403, 'not an admin'); + error(403, 'not an admin'); } } ``` -If an _unexpected_ error is thrown, SvelteKit will invoke [`handleError`](hooks#shared-hooks-handleerror) and treat it as a 500 Internal Error. +Calling `error(...)` will throw an exception, making it easy to stop execution from inside helper functions. + +If an [_unexpected_](errors#Unexpected-errors) error is thrown, SvelteKit will invoke [`handleError`](hooks#Shared-hooks-handleError) and treat it as a 500 Internal Error. + +> [!NOTE] [In SvelteKit 1.x](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you) you had to `throw` the error yourself ## Redirects -To redirect users, use the `redirect` helper from `@sveltejs/kit` to specify the location to which they should be redirected alongside a `3xx` status code. +To redirect users, use the `redirect` helper from `@sveltejs/kit` to specify the location to which they should be redirected alongside a `3xx` status code. Like `error(...)`, calling `redirect(...)` will throw an exception, making it easy to stop execution from inside helper functions. ```js /// file: src/routes/user/+layout.server.js @@ -430,33 +483,40 @@ import { redirect } from '@sveltejs/kit'; /** @type {import('./$types').LayoutServerLoad} */ export function load({ locals }) { if (!locals.user) { - throw redirect(307, '/login'); + redirect(307, '/login'); } } ``` -> Don't use `throw redirect()` from within a try-catch block, as the redirect will immediately trigger the catch statement. +> [!NOTE] Don't use `redirect()` inside a `try {...}` block, as the redirect will immediately trigger the catch statement. + +In the browser, you can also navigate programmatically outside of a `load` function using [`goto`]($app-navigation#goto) from [`$app.navigation`]($app-navigation). -In the browser, you can also navigate programmatically outside of a `load` function using [`goto`](modules#$app-navigation-goto) from [`$app.navigation`](modules#$app-navigation). +> [!NOTE] [In SvelteKit 1.x](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you) you had to `throw` the `redirect` yourself ## Streaming with promises -Promises at the _top level_ of the returned object will be awaited, making it easy to return multiple promises without creating a waterfall. When using a server `load`, _nested_ promises will be streamed to the browser as they resolve. This is useful if you have slow, non-essential data, since you can start rendering the page before all the data is available: +When using a server `load`, promises will be streamed to the browser as they resolve. This is useful if you have slow, non-essential data, since you can start rendering the page before all the data is available: ```js -/// file: src/routes/+page.server.js +/// file: src/routes/blog/[slug]/+page.server.js +// @filename: ambient.d.ts +declare global { + const loadPost: (slug: string) => Promise<{ title: string, content: string }>; + const loadComments: (slug: string) => Promise<{ content: string }>; +} + +export {}; + +// @filename: index.js +// ---cut--- /** @type {import('./$types').PageServerLoad} */ -export function load() { +export async function load({ params }) { return { - one: Promise.resolve(1), - two: Promise.resolve(2), - streamed: { - three: new Promise((fulfil) => { - setTimeout(() => { - fulfil(3) - }, 1000); - }) - } + // make sure the `await` happens at the end, otherwise we + // can't start loading comments until we've loaded the post + comments: loadComments(params.slug), + post: await loadPost(params.slug) }; } ``` @@ -464,35 +524,50 @@ export function load() { This is useful for creating skeleton loading states, for example: ```svelte - + -

- one: {data.one} -

-

- two: {data.two} -

-

- three: - {#await data.streamed.three} - Loading... - {:then value} - {value} - {:catch error} - {error.message} - {/await} -

+

{data.post.title}

+
{@html data.post.content}
+ +{#await data.comments} + Loading comments... +{:then comments} + {#each comments as comment} +

{comment.content}

+ {/each} +{:catch error} +

error loading comments: {error.message}

+{/await} +``` + +When streaming data, be careful to handle promise rejections correctly. More specifically, the server could crash with an "unhandled promise rejection" error if a lazy-loaded promise fails before rendering starts (at which point it's caught) and isn't handling the error in some way. When using SvelteKit's `fetch` directly in the `load` function, SvelteKit will handle this case for you. For other promises, it is enough to attach a noop-`catch` to the promise to mark it as handled. + +```js +/// file: src/routes/+page.server.js +/** @type {import('./$types').PageServerLoad} */ +export function load({ fetch }) { + const ok_manual = Promise.reject(); + ok_manual.catch(() => {}); + + return { + ok_manual, + ok_fetch: fetch('/fetch/that/could/fail'), + dangerous_unhandled: Promise.reject() + }; +} ``` -> On platforms that do not support streaming, such as AWS Lambda, responses will be buffered. This means the page will only render once all promises resolve. If you are using a proxy (e.g. NGINX), make sure it does not buffer responses from the proxied server. +> [!NOTE] On platforms that do not support streaming, such as AWS Lambda or Firebase, responses will be buffered. This means the page will only render once all promises resolve. If you are using a proxy (e.g. NGINX), make sure it does not buffer responses from the proxied server. -> Streaming data will only work when JavaScript is enabled. You should avoid returning nested promises from a universal `load` function if the page is server rendered, as these are _not_ streamed — instead, the promise is recreated when the function reruns in the browser. +> [!NOTE] Streaming data will only work when JavaScript is enabled. You should avoid returning promises from a universal `load` function if the page is server rendered, as these are _not_ streamed — instead, the promise is recreated when the function reruns in the browser. -> The headers and status code of a response cannot be changed once the response has started streaming, therefore you cannot `setHeaders` or throw redirects inside a streamed promise. +> [!NOTE] The headers and status code of a response cannot be changed once the response has started streaming, therefore you cannot `setHeaders` or throw redirects inside a streamed promise. + +> [!NOTE] [In SvelteKit 1.x](migrating-to-sveltekit-2#Top-level-promises-are-no-longer-awaited) top-level promises were automatically awaited, only nested promises were streamed. ## Parallel loading @@ -546,11 +621,28 @@ export async function load() { A `load` function that calls `await parent()` will also rerun if a parent `load` function is rerun. -Dependency tracking does not apply _after_ the `load` function has returned — for example, accessing `params.x` inside a nested [promise](#streaming-with-promises) will not cause the function to rerun when `params.x` changes. (Don't worry, you'll get a warning in development if you accidentally do this.) Instead, access the parameter in the main body of your `load` function. +Dependency tracking does not apply _after_ the `load` function has returned — for example, accessing `params.x` inside a nested [promise](#Streaming-with-promises) will not cause the function to rerun when `params.x` changes. (Don't worry, you'll get a warning in development if you accidentally do this.) Instead, access the parameter in the main body of your `load` function. + +Search parameters are tracked independently from the rest of the url. For example, accessing `event.url.searchParams.get("x")` inside a `load` function will make that `load` function re-run when navigating from `?x=1` to `?x=2`, but not when navigating from `?x=1&y=1` to `?x=1&y=2`. + +### Untracking dependencies + +In rare cases, you may wish to exclude something from the dependency tracking mechanism. You can do this with the provided `untrack` function: + +```js +/// file: src/routes/+page.js +/** @type {import('./$types').PageLoad} */ +export async function load({ untrack, url }) { + // Untrack url.pathname so that path changes don't trigger a rerun + if (untrack(() => url.pathname === '/')) { + return { message: 'Welcome!' }; + } +} +``` ### Manual invalidation -You can also rerun `load` functions that apply to the current page using [`invalidate(url)`](modules#$app-navigation-invalidate), which reruns all `load` functions that depend on `url`, and [`invalidateAll()`](modules#$app-navigation-invalidateall), which reruns every `load` function. Server load functions will never automatically depend on a fetched `url` to avoid leaking secrets to the client. +You can also rerun `load` functions that apply to the current page using [`invalidate(url)`]($app-navigation#invalidate), which reruns all `load` functions that depend on `url`, and [`invalidateAll()`]($app-navigation#invalidateAll), which reruns every `load` function. Server load functions will never automatically depend on a fetched `url` to avoid leaking secrets to the client. A `load` function depends on `url` if it calls `fetch(url)` or `depends(url)`. Note that `url` can be a custom identifier that starts with `[a-z]:`: @@ -575,8 +667,8 @@ export async function load({ fetch, depends }) {

random number: {data.number}

- + ``` ### When do load functions rerun? @@ -597,16 +689,100 @@ To summarize, a `load` function will rerun in the following situations: - It references a property of `params` whose value has changed - It references a property of `url` (such as `url.pathname` or `url.search`) whose value has changed. Properties in `request.url` are _not_ tracked +- It calls `url.searchParams.get(...)`, `url.searchParams.getAll(...)` or `url.searchParams.has(...)` and the parameter in question changes. Accessing other properties of `url.searchParams` will have the same effect as accessing `url.search`. - It calls `await parent()` and a parent `load` function reran -- It declared a dependency on a specific URL via [`fetch`](#making-fetch-requests) (universal load only) or [`depends`](types#public-types-loadevent), and that URL was marked invalid with [`invalidate(url)`](modules#$app-navigation-invalidate) -- All active `load` functions were forcibly rerun with [`invalidateAll()`](modules#$app-navigation-invalidateall) +- A child `load` function calls `await parent()` and is rerunning, and the parent is a server load function +- It declared a dependency on a specific URL via [`fetch`](#Making-fetch-requests) (universal load only) or [`depends`](@sveltejs-kit#LoadEvent), and that URL was marked invalid with [`invalidate(url)`]($app-navigation#invalidate) +- All active `load` functions were forcibly rerun with [`invalidateAll()`]($app-navigation#invalidateAll) + +`params` and `url` can change in response to a `` link click, a [`
` interaction](form-actions#GET-vs-POST), a [`goto`]($app-navigation#goto) invocation, or a [`redirect`](@sveltejs-kit#redirect). + +Note that rerunning a `load` function will update the `data` prop inside the corresponding `+layout.svelte` or `+page.svelte`; it does _not_ cause the component to be recreated. As a result, internal state is preserved. If this isn't what you want, you can reset whatever you need to reset inside an [`afterNavigate`]($app-navigation#afterNavigate) callback, and/or wrap your component in a [`{#key ...}`](../svelte/key) block. + +## Implications for authentication + +A couple features of loading data have important implications for auth checks: +- Layout `load` functions do not run on every request, such as during client side navigation between child routes. [(When do load functions rerun?)](load#Rerunning-load-functions-When-do-load-functions-rerun) +- Layout and page `load` functions run concurrently unless `await parent()` is called. If a layout `load` throws, the page `load` function runs, but the client will not receive the returned data. -`params` and `url` can change in response to a `` link click, a [`` interaction](form-actions#get-vs-post), a [`goto`](modules#$app-navigation-goto) invocation, or a [`redirect`](modules#sveltejs-kit-redirect). +There are a few possible strategies to ensure an auth check occurs before protected code. + +To prevent data waterfalls and preserve layout `load` caches: +- Use [hooks](hooks) to protect multiple routes before any `load` functions run +- Use auth guards directly in `+page.server.js` `load` functions for route specific protection + +Putting an auth guard in `+layout.server.js` requires all child pages to call `await parent()` before protected code. Unless every child page depends on returned data from `await parent()`, the other options will be more performant. + +## Using `getRequestEvent` + +When running server `load` functions, the `event` object passed to the function as an argument can also be retrieved with [`getRequestEvent`]($app-server#getRequestEvent). This allows shared logic (such as authentication guards) to access information about the current request without it needing to be passed around. + +For example, you might have a function that requires users to be logged in, and redirects them to `/login` if not: + +```js +/// file: src/lib/server/auth.js +// @filename: ambient.d.ts +interface User { + name: string; +} + +declare namespace App { + interface Locals { + user?: User; + } +} -Note that rerunning a `load` function will update the `data` prop inside the corresponding `+layout.svelte` or `+page.svelte`; it does _not_ cause the component to be recreated. As a result, internal state is preserved. If this isn't what you want, you can reset whatever you need to reset inside an [`afterNavigate`](modules#$app-navigation-afternavigate) callback, and/or wrap your component in a [`{#key ...}`](https://svelte.dev/docs#template-syntax-key) block. +// @filename: index.ts +// ---cut--- +import { redirect } from '@sveltejs/kit'; +import { getRequestEvent } from '$app/server'; + +export function requireLogin() { + const { locals, url } = getRequestEvent(); + + // assume `locals.user` is populated in `handle` + if (!locals.user) { + const redirectTo = url.pathname + url.search; + const params = new URLSearchParams({ redirectTo }); + + redirect(307, `/login?${params}`); + } + + return locals.user; +} +``` + +Now, you can call `requireLogin` in any `load` function (or [form action](form-actions), for example) to guarantee that the user is logged in: + +```js +/// file: +page.server.js +// @filename: ambient.d.ts + +declare module '$lib/server/auth' { + interface User { + name: string; + } + + export function requireLogin(): User; +} + +// @filename: index.ts +// ---cut--- +import { requireLogin } from '$lib/server/auth'; + +export function load() { + const user = requireLogin(); + + // `user` is guaranteed to be a user object here, because otherwise + // `requireLogin` would throw a redirect and we wouldn't get here + return { + message: `hello ${user.name}!` + }; +} +``` ## Further reading -- [Tutorial: Loading data](https://learn.svelte.dev/tutorial/page-data) -- [Tutorial: Errors and redirects](https://learn.svelte.dev/tutorial/error-basics) -- [Tutorial: Advanced loading](https://learn.svelte.dev/tutorial/await-parent) +- [Tutorial: Loading data](/tutorial/kit/page-data) +- [Tutorial: Errors and redirects](/tutorial/kit/error-basics) +- [Tutorial: Advanced loading](/tutorial/kit/await-parent) diff --git a/documentation/docs/20-core-concepts/30-form-actions.md b/documentation/docs/20-core-concepts/30-form-actions.md index d25e141eadc7..be0af6c990d3 100644 --- a/documentation/docs/20-core-concepts/30-form-actions.md +++ b/documentation/docs/20-core-concepts/30-form-actions.md @@ -12,7 +12,7 @@ In the simplest case, a page declares a `default` action: ```js /// file: src/routes/login/+page.server.js -/** @type {import('./$types').Actions} */ +/** @satisfies {import('./$types').Actions} */ export const actions = { default: async (event) => { // TODO log the user in @@ -39,7 +39,7 @@ To invoke this action from the `/login` page, just add a `` — no JavaScr If someone were to click the button, the browser would send the form data via `POST` request to the server, running the default action. -> Actions always use `POST` requests, since `GET` requests should never have side-effects. +> [!NOTE] Actions always use `POST` requests, since `GET` requests should never have side-effects. We can also invoke the action from other pages (for example if there's a login widget in the nav in the root layout) by adding the `action` attribute, pointing to the page: @@ -54,17 +54,17 @@ We can also invoke the action from other pages (for example if there's a login w Instead of one `default` action, a page can have as many named actions as it needs: -```diff +```js /// file: src/routes/login/+page.server.js -/** @type {import('./$types').Actions} */ +/** @satisfies {import('./$types').Actions} */ export const actions = { -- default: async (event) => { -+ login: async (event) => { +--- default: async (event) => {--- ++++ login: async (event) => {+++ // TODO log the user in }, -+ register: async (event) => { -+ // TODO register the user -+ } ++++ register: async (event) => { + // TODO register the user + }+++ }; ``` @@ -82,10 +82,9 @@ To invoke a named action, add a query parameter with the name prefixed by a `/` As well as the `action` attribute, we can use the `formaction` attribute on a button to `POST` the same form data to a different action than the parent ``: -```diff +```svelte /// file: src/routes/login/+page.svelte -- -+ + -+ + ++++++ ``` -> We can't have default actions next to named actions, because if you POST to a named action without a redirect, the query parameter is persisted in the URL, which means the next default POST would go through the named action from before. +> [!NOTE] We can't have default actions next to named actions, because if you POST to a named action without a redirect, the query parameter is persisted in the URL, which means the next default POST would go through the named action from before. ## Anatomy of an action -Each action receives a `RequestEvent` object, allowing you to read the data with `request.formData()`. After processing the request (for example, logging the user in by setting a cookie), the action can respond with data that will be available through the `form` property on the corresponding page and through `$page.form` app-wide until the next update. +Each action receives a `RequestEvent` object, allowing you to read the data with `request.formData()`. After processing the request (for example, logging the user in by setting a cookie), the action can respond with data that will be available through the `form` property on the corresponding page and through `page.form` app-wide until the next update. ```js -// @errors: 2304 /// file: src/routes/login/+page.server.js +// @filename: ambient.d.ts +declare module '$lib/server/db'; + +// @filename: index.js +// ---cut--- +import * as db from '$lib/server/db'; + /** @type {import('./$types').PageServerLoad} */ export async function load({ cookies }) { const user = await db.getUserFromSession(cookies.get('sessionid')); return { user }; } -/** @type {import('./$types').Actions} */ +/** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); @@ -122,7 +127,7 @@ export const actions = { const password = data.get('password'); const user = await db.getUser(email); - cookies.set('sessionid', await db.createSession(user)); + cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; }, @@ -135,11 +140,8 @@ export const actions = { ```svelte {#if form?.success} @@ -149,32 +151,48 @@ export const actions = { {/if} ``` +> [!LEGACY] +> `PageProps` was added in 2.16.0. In earlier versions, you had to type the `data` and `form` properties individually: +> ```js +> /// file: +page.svelte +> /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ +> let { data, form } = $props(); +> ``` +> +> In Svelte 4, you'd use `export let data` and `export let form` instead to declare properties. + ### Validation errors -If the request couldn't be processed because of invalid data, you can return validation errors — along with the previously submitted form values — back to the user so that they can try again. The `fail` function lets you return an HTTP status code (typically 400 or 422, in the case of validation errors) along with the data. The status code is available through `$page.status` and the data through `form`: +If the request couldn't be processed because of invalid data, you can return validation errors — along with the previously submitted form values — back to the user so that they can try again. The `fail` function lets you return an HTTP status code (typically 400 or 422, in the case of validation errors) along with the data. The status code is available through `page.status` and the data through `form`: -```diff +```js /// file: src/routes/login/+page.server.js -+import { fail } from '@sveltejs/kit'; +// @filename: ambient.d.ts +declare module '$lib/server/db'; + +// @filename: index.js +// ---cut--- ++++import { fail } from '@sveltejs/kit';+++ +import * as db from '$lib/server/db'; -/** @type {import('./$types').Actions} */ +/** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); -+ if (!email) { -+ return fail(400, { email, missing: true }); -+ } ++++ if (!email) { + return fail(400, { email, missing: true }); + }+++ const user = await db.getUser(email); -+ if (!user || user.password !== hash(password)) { -+ return fail(400, { email, incorrect: true }); -+ } ++++ if (!user || user.password !== db.hash(password)) { + return fail(400, { email, incorrect: true }); + }+++ - cookies.set('sessionid', await db.createSession(user)); + cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; }, @@ -184,17 +202,16 @@ export const actions = { }; ``` -> Note that as a precaution, we only return the email back to the page — not the password. +> [!NOTE] Note that as a precaution, we only return the email back to the page — not the password. -```diff +```svelte /// file: src/routes/login/+page.svelte
-+ {#if form?.missing}

The email field is required

{/if} -+ {#if form?.incorrect}

Invalid credentials!

{/if} ++++ {#if form?.missing}

The email field is required

{/if} + {#if form?.incorrect}

Invalid credentials!

{/if}+++
` elements that point to other pages that are candidates for prerendering — because of this, you generally don't need to specify which pages should be accessed. If you _do_ need to specify which pages should be accessed by the prerenderer, you can do so with [`config.kit.prerender.entries`](configuration#prerender), or by exporting an [`entries`](#entries) function from your dynamic route. -While prerendering, the value of `building` imported from [`$app/environment`](modules#$app-environment) will be `true`. +While prerendering, the value of `building` imported from [`$app/environment`]($app-environment) will be `true`. ### Prerendering server routes -Unlike the other page options, `prerender` also applies to `+server.js` files. These files are _not_ affected from layouts, but will inherit default values from the pages that fetch data from them, if any. For example if a `+page.js` contains this `load` function... +Unlike the other page options, `prerender` also applies to `+server.js` files. These files are _not_ affected by layouts, but will inherit default values from the pages that fetch data from them, if any. For example if a `+page.js` contains this `load` function... ```js /// file: +page.js @@ -58,11 +58,11 @@ export async function load({ fetch }) { The basic rule is this: for a page to be prerenderable, any two users hitting it directly must get the same content from the server. -> Not all pages are suitable for prerendering. Any content that is prerendered will be seen by all users. You can of course fetch personalized data in `onMount` in a prerendered page, but this may result in a poorer user experience since it will involve blank initial content or loading indicators. +> [!NOTE] Not all pages are suitable for prerendering. Any content that is prerendered will be seen by all users. You can of course fetch personalized data in `onMount` in a prerendered page, but this may result in a poorer user experience since it will involve blank initial content or loading indicators. Note that you can still prerender pages that load data based on the page's parameters, such as a `src/routes/blog/[slug]/+page.svelte` route. -Accessing [`url.searchParams`](load#using-url-data-url) during prerendering is forbidden. If you need to use it, ensure you are only doing so in the browser (for example in `onMount`). +Accessing [`url.searchParams`](load#Using-URL-data-url) during prerendering is forbidden. If you need to use it, ensure you are only doing so in the browser (for example in `onMount`). Pages with [actions](form-actions) cannot be prerendered, because a server must be able to handle the action `POST` requests. @@ -76,18 +76,19 @@ For _pages_, we skirt around this problem by writing `foo/index.html` instead of ### Troubleshooting -If you encounter an error like 'The following routes were marked as prerenderable, but were not prerendered' it's because the route in question (or a parent layout, if it's a page) has `export const prerender = true` but the page wasn't actually prerendered, because it wasn't reached by the prerendering crawler. +If you encounter an error like 'The following routes were marked as prerenderable, but were not prerendered' it's because the route in question (or a parent layout, if it's a page) has `export const prerender = true` but the page wasn't reached by the prerendering crawler and thus wasn't prerendered. -Since these routes cannot be dynamically server-rendered, this will cause errors when people try to access the route in question. There are two ways to fix it: +Since these routes cannot be dynamically server-rendered, this will cause errors when people try to access the route in question. There are a few ways to fix it: * Ensure that SvelteKit can find the route by following links from [`config.kit.prerender.entries`](configuration#prerender) or the [`entries`](#entries) page option. Add links to dynamic routes (i.e. pages with `[parameters]` ) to this option if they are not found through crawling the other entry points, else they are not prerendered because SvelteKit doesn't know what value the parameters should have. Pages not marked as prerenderable will be ignored and their links to other pages will not be crawled, even if some of them would be prerenderable. +* Ensure that SvelteKit can find the route by discovering a link to it from one of your other prerendered pages that have server-side rendering enabled. * Change `export const prerender = true` to `export const prerender = 'auto'`. Routes with `'auto'` can be dynamically server rendered ## entries SvelteKit will discover pages to prerender automatically, by starting at _entry points_ and crawling them. By default, all your non-dynamic routes are considered entry points — for example, if you have these routes... -```bash +```sh / # non-dynamic /blog # non-dynamic /blog/[slug] # dynamic, because of `[slug]` @@ -116,25 +117,44 @@ export const prerender = true; ## ssr -Normally, SvelteKit renders your page on the server first and sends that HTML to the client where it's [hydrated](glossary#hydration). If you set `ssr` to `false`, it renders an empty 'shell' page instead. This is useful if your page is unable to be rendered on the server (because you use browser-only globals like `document` for example), but in most situations it's not recommended ([see appendix](glossary#ssr)). +Normally, SvelteKit renders your page on the server first and sends that HTML to the client where it's [hydrated](glossary#Hydration). If you set `ssr` to `false`, it renders an empty 'shell' page instead. This is useful if your page is unable to be rendered on the server (because you use browser-only globals like `document` for example), but in most situations it's not recommended ([see appendix](glossary#SSR)). ```js /// file: +page.js export const ssr = false; +// If both `ssr` and `csr` are `false`, nothing will be rendered! ``` If you add `export const ssr = false` to your root `+layout.js`, your entire app will only be rendered on the client — which essentially means you turn your app into an SPA. +> [!NOTE] If all your page options are boolean or string literal values, SvelteKit will evaluate them statically. If not, it will import your `+page.js` or `+layout.js` file on the server (both at build time, and at runtime if your app isn't fully static) so it can evaluate the options. In the second case, browser-only code must not run when the module is loaded. In practice, this means you should import browser-only code in your `+page.svelte` or `+layout.svelte` file instead. + ## csr -Ordinarily, SvelteKit [hydrates](glossary#hydration) your server-rendered HTML into an interactive client-side-rendered (CSR) page. Some pages don't require JavaScript at all — many blog posts and 'about' pages fall into this category. In these cases you can disable CSR: +Ordinarily, SvelteKit [hydrates](glossary#Hydration) your server-rendered HTML into an interactive client-side-rendered (CSR) page. Some pages don't require JavaScript at all — many blog posts and 'about' pages fall into this category. In these cases you can disable CSR: ```js /// file: +page.js export const csr = false; +// If both `csr` and `ssr` are `false`, nothing will be rendered! ``` -> If both `ssr` and `csr` are `false`, nothing will be rendered! +Disabling CSR does not ship any JavaScript to the client. This means: + +* The webpage should work with HTML and CSS only. +* ` ``` @@ -110,22 +107,27 @@ You might wonder how we're able to use `$page.data` and other [app stores](modul const user = getContext('user'); -

Welcome {$user.name}

+

Welcome {user().name}

``` -Updating the context-based store value in deeper-level pages or components will not affect the value in the parent component when the page is rendered via SSR: The parent component has already been rendered by the time the store value is updated. To avoid values 'flashing' during state updates during hydration, it is generally recommended to pass state down into components rather than up. +> [!NOTE] We're passing a function into `setContext` to keep reactivity across boundaries. Read more about it [here](/docs/svelte/$state#Passing-state-into-functions) + +> [!LEGACY] +> You also use stores from `svelte/store` for this, but when using Svelte 5 it is recommended to make use of universal reactivity instead. + +Updating the value of context-based state in deeper-level pages or components while the page is being rendered via SSR will not affect the value in the parent component because it has already been rendered by the time the state value is updated. In contrast, on the client (when CSR is enabled, which is the default) the value will be propagated and components, pages, and layouts higher in the hierarchy will react to the new value. Therefore, to avoid values 'flashing' during state updates during hydration, it is generally recommended to pass state down into components rather than up. If you're not using SSR (and can guarantee that you won't need to use SSR in future) then you can safely keep state in a shared module, without using the context API. -## Component state is preserved +## Component and page state is preserved When you navigate around your application, SvelteKit reuses existing layout and page components. For example, if you have a route like this... ```svelte ``` -Reusing components like this means that things like sidebar scroll state are preserved, and you can easily animate between changing values. However, if you do need to completely destroy and remount a component on navigation, you can use this pattern: +> [!NOTE] If your code in `onMount` and `onDestroy` has to run again after navigation you can use [afterNavigate]($app-navigation#afterNavigate) and [beforeNavigate]($app-navigation#beforeNavigate) respectively. + +Reusing components like this means that things like sidebar scroll state are preserved, and you can easily animate between changing values. In the case that you do need to completely destroy and remount a component on navigation, you can use this pattern: ```svelte -{#key $page.url.pathname} + + +{#key page.url.pathname} {/key} ``` ## Storing state in the URL -If you have state that should survive a reload and/or affect SSR, such as filters or sorting rules on a table, URL search parameters (like `?sort=price&order=ascending`) are a good place to put them. You can put them in `
` or `
` attributes, or set them programmatically via `goto('?key=value')`. They can be accessed inside `load` functions via the `url` parameter, and inside components via `$page.url.searchParams`. +If you have state that should survive a reload and/or affect SSR, such as filters or sorting rules on a table, URL search parameters (like `?sort=price&order=ascending`) are a good place to put them. You can put them in `` or `` attributes, or set them programmatically via `goto('?key=value')`. They can be accessed inside `load` functions via the `url` parameter, and inside components via `page.url.searchParams`. ## Storing ephemeral state in snapshots diff --git a/documentation/docs/20-core-concepts/60-remote-functions.md b/documentation/docs/20-core-concepts/60-remote-functions.md new file mode 100644 index 000000000000..b821bd1dfabf --- /dev/null +++ b/documentation/docs/20-core-concepts/60-remote-functions.md @@ -0,0 +1,1162 @@ +--- +title: Remote functions +--- + +
+

Available since 2.27

+
+ +Remote functions are a tool for type-safe communication between client and server. They can be _called_ anywhere in your app, but always _run_ on the server, meaning they can safely access [server-only modules](server-only-modules) containing things like environment variables and database clients. + +Combined with Svelte's experimental support for [`await`](/docs/svelte/await-expressions), it allows you to load and manipulate data directly inside your components. + +This feature is currently experimental, meaning it is likely to contain bugs and is subject to change without notice. You must opt in by adding the `kit.experimental.remoteFunctions` option in your `svelte.config.js` and optionally, the `compilerOptions.experimental.async` option to use `await` in components: + +```js +/// file: svelte.config.js +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + experimental: { + +++remoteFunctions: true+++ + } + }, + compilerOptions: { + experimental: { + +++async: true+++ + } + } +}; + +export default config; +``` + +## Overview + +Remote functions are exported from a `.remote.js` or `.remote.ts` file, and come in four flavours: `query`, `form`, `command` and `prerender`. On the client, the exported functions are transformed to `fetch` wrappers that invoke their counterparts on the server via a generated HTTP endpoint. Remote files can be placed anywhere in your `src` directory (except inside the `src/lib/server` directory), and third party libraries can provide them, too. + +## query + +The `query` function allows you to read dynamic data from the server (for _static_ data, consider using [`prerender`](#prerender) instead): + +```js +/// file: src/routes/blog/data.remote.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; +} +// @filename: index.js +// ---cut--- +import { query } from '$app/server'; +import * as db from '$lib/server/database'; + +export const getPosts = query(async () => { + const posts = await db.sql` + SELECT title, slug + FROM post + ORDER BY published_at + DESC + `; + + return posts; +}); +``` + +> [!NOTE] Throughout this page, you'll see imports from fictional modules like `$lib/server/database` and `$lib/server/auth`. These are purely for illustrative purposes — you can use whatever database client and auth setup you like. +> +> The `db.sql` function above is a [tagged template function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) that escapes any interpolated values. + +The query returned from `getPosts` works as a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves to `posts`: + +```svelte + + + +

Recent posts

+ +
+``` + +Until the promise resolves — and if it errors — the nearest [``](../svelte/svelte-boundary) will be invoked. + +While using `await` is recommended, as an alternative the query also has `loading`, `error` and `current` properties: + +```svelte + + + +

Recent posts

+ +{#if query.error} +

oops!

+{:else if query.loading} +

loading...

+{:else} +
    + {#each query.current as { title, slug }} +
  • {title}
  • + {/each} +
+{/if} +``` + +> [!NOTE] For the rest of this document, we'll use the `await` form. + +### Query arguments + +Query functions can accept an argument, such as the `slug` of an individual post: + +```svelte + + + +

{post.title}

+
{@html post.content}
+``` + +Since `getPost` exposes an HTTP endpoint, it's important to validate this argument to be sure that it's the correct type. For this, we can use any [Standard Schema](https://standardschema.dev/) validation library such as [Zod](https://zod.dev/) or [Valibot](https://valibot.dev/): + +```js +/// file: src/routes/blog/data.remote.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; +} +// @filename: index.js +// ---cut--- +import * as v from 'valibot'; +import { error } from '@sveltejs/kit'; +import { query } from '$app/server'; +import * as db from '$lib/server/database'; + +export const getPosts = query(async () => { /* ... */ }); + +export const getPost = query(v.string(), async (slug) => { + const [post] = await db.sql` + SELECT * FROM post + WHERE slug = ${slug} + `; + + if (!post) error(404, 'Not found'); + return post; +}); +``` + +Both the argument and the return value are serialized with [devalue](https://github.com/sveltejs/devalue), which handles types like `Date` and `Map` (and custom types defined in your [transport hook](hooks#Universal-hooks-transport)) in addition to JSON. + +### Refreshing queries + +Any query can be re-fetched via its `refresh` method, which retrieves the latest value from the server: + +```svelte + +``` + +> [!NOTE] Queries are cached while they're on the page, meaning `getPosts() === getPosts()`. This means you don't need a reference like `const posts = getPosts()` in order to update the query. + +## query.batch + +`query.batch` works like `query` except that it batches requests that happen within the same macrotask. This solves the so-called n+1 problem: rather than each query resulting in a separate database call (for example), simultaneous queries are grouped together. + +On the server, the callback receives an array of the arguments the function was called with. It must return a function of the form `(input: Input, index: number) => Output`. SvelteKit will then call this with each of the input arguments to resolve the individual calls with their results. + +```js +/// file: weather.remote.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; +} +// @filename: index.js +// ---cut--- +import * as v from 'valibot'; +import { query } from '$app/server'; +import * as db from '$lib/server/database'; + +export const getWeather = query.batch(v.string(), async (cities) => { + const weather = await db.sql` + SELECT * FROM weather + WHERE city = ANY(${cities}) + `; + const lookup = new Map(weather.map(w => [w.city, w])); + + return (city) => lookup.get(city); +}); +``` + +```svelte + + + +

Weather

+ +{#each cities.slice(0, limit) as city} +

{city.name}

+ +{/each} + +{#if cities.length > limit} + +{/if} +``` + +## form + +The `form` function makes it easy to write data to the server. It takes a callback that receives `data` constructed from the submitted [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData)... + +```ts +/// file: src/routes/blog/data.remote.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; +} + +declare module '$lib/server/auth' { + interface User { + name: string; + } + + /** + * Gets a user's info from their cookies, using `getRequestEvent` + */ + export function getUser(): Promise; +} +// @filename: index.js +// ---cut--- +import * as v from 'valibot'; +import { error, redirect } from '@sveltejs/kit'; +import { query, form } from '$app/server'; +import * as db from '$lib/server/database'; +import * as auth from '$lib/server/auth'; + +export const getPosts = query(async () => { /* ... */ }); + +export const getPost = query(v.string(), async (slug) => { /* ... */ }); + +export const createPost = form( + v.object({ + title: v.pipe(v.string(), v.nonEmpty()), + content:v.pipe(v.string(), v.nonEmpty()) + }), + async ({ title, content }) => { + // Check the user is logged in + const user = await auth.getUser(); + if (!user) error(401, 'Unauthorized'); + + const slug = title.toLowerCase().replace(/ /g, '-'); + + // Insert into the database + await db.sql` + INSERT INTO post (slug, title, content) + VALUES (${slug}, ${title}, ${content}) + `; + + // Redirect to the newly created page + redirect(303, `/blog/${slug}`); + } +); +``` + +...and returns an object that can be spread onto a `` element. The callback is called whenever the form is submitted. + +```svelte + + + +

Create a new post

+ + + + + + +``` + +The form object contains `method` and `action` properties that allow it to work without JavaScript (i.e. it submits data and reloads the page). It also has an [attachment](/docs/svelte/@attach) that progressively enhances the form when JavaScript is available, submitting data *without* reloading the entire page. + +As with `query`, if the callback uses the submitted `data`, it should be [validated](#query-Query-arguments) by passing a [Standard Schema](https://standardschema.dev) as the first argument to `form`. + +### Fields + +A form is composed of a set of _fields_, which are defined by the schema. In the case of `createPost`, we have two fields, `title` and `content`, which are both strings. To get the attributes for a field, call its `.as(...)` method, specifying which [input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#input_types) to use: + +```svelte +
+ + + + + +
+``` + +These attributes allow SvelteKit to set the correct input type, set a `name` that is used to construct the `data` passed to the handler, populate the `value` of the form (for example following a failed submission, to save the user having to re-enter everything), and set the [`aria-invalid`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-invalid) state. + +> [!NOTE] The generated `name` attribute uses JS object notation (e.g. `nested.array[0].value`). String keys that require quotes such as `object['nested-array'][0].value` are not supported. Under the hood, boolean checkbox and number field names are prefixed with `b:` and `n:`, respectively, to signal SvelteKit to coerce the values from strings prior to validation. + +Fields can be nested in objects and arrays, and their values can be strings, numbers, booleans or `File` objects. For example, if your schema looked like this... + +```js +/// file: data.remote.js +import * as v from 'valibot'; +import { form } from '$app/server'; +// ---cut--- +const datingProfile = v.object({ + name: v.string(), + photo: v.file(), + info: v.object({ + height: v.number(), + likesDogs: v.optional(v.boolean(), false) + }), + attributes: v.array(v.string()) +}); + +export const createProfile = form(datingProfile, (data) => { /* ... */ }); +``` + +...your form could look like this: + +```svelte + + +
+ + + + + + + + +

My best attributes

+ + + + + +
+``` + +Because our form contains a `file` input, we've added an `enctype="multipart/form-data"` attribute. The values for `info.height` and `info.likesDogs` are coerced to a number and a boolean respectively. + +> [!NOTE] If a `checkbox` input is unchecked, the value is not included in the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object that SvelteKit constructs the data from. As such, we have to make the value optional in our schema. In Valibot that means using `v.optional(v.boolean(), false)` instead of just `v.boolean()`, whereas in Zod it would mean using `z.coerce.boolean()`. + +In the case of `radio` and `checkbox` inputs that all belong to the same field, the `value` must be specified as a second argument to `.as(...)`: + +```js +/// file: data.remote.js +import * as v from 'valibot'; +import { form } from '$app/server'; +// ---cut--- +export const survey = form( + v.object({ + operatingSystem: v.picklist(['windows', 'mac', 'linux']), + languages: v.optional(v.array(v.picklist(['html', 'css', 'js'])), []) + }), + (data) => { /* ... */ } +); +``` + +```svelte +
+

Which operating system do you use?

+ + {#each ['windows', 'mac', 'linux'] as os} + + {/each} + +

Which languages do you write code in?

+ + {#each ['html', 'css', 'js'] as language} + + {/each} + + +
+``` + +Alternatively, you could use `select` and `select multiple`: + +```svelte +
+

Which operating system do you use?

+ + + +

Which languages do you write code in?

+ + + + +
+``` + +> [!NOTE] As with unchecked `checkbox` inputs, if no selections are made then the data will be `undefined`. For this reason, the `languages` field uses `v.optional(v.array(...), [])` rather than just `v.array(...)`. + +### Programmatic validation + +In addition to declarative schema validation, you can programmatically mark fields as invalid inside the form handler using the `invalid` helper from `@sveltejs/kit`. This is useful for cases where you can't know if something is valid until you try to perform some action. + +- It throws just like `redirect` or `error` +- It accepts multiple arguments that can be strings (for issues relating to the form as a whole — these will only show up in `fields.allIssues()`) or standard-schema-compliant issues (for those relating to a specific field). Use the `issue` parameter for type-safe creation of such issues: + +```js +/// file: src/routes/shop/data.remote.js +import * as v from 'valibot'; +import { invalid } from '@sveltejs/kit'; +import { form } from '$app/server'; +import * as db from '$lib/server/database'; + +export const buyHotcakes = form( + v.object({ + qty: v.pipe( + v.number(), + v.minValue(1, 'you must buy at least one hotcake') + ) + }), + async (data, issue) => { + try { + await db.buy(data.qty); + } catch (e) { + if (e.code === 'OUT_OF_STOCK') { + invalid( + issue.qty(`we don't have enough hotcakes`) + ); + } + } + } +); +``` + +### Validation + +If the submitted data doesn't pass the schema, the callback will not run. Instead, each invalid field's `issues()` method will return an array of `{ message: string }` objects, and the `aria-invalid` attribute (returned from `as(...)`) will be set to `true`: + +```svelte +
+ + + + + +
+``` + +You don't need to wait until the form is submitted to validate the data — you can call `validate()` programmatically, for example in an `oninput` callback (which will validate the data on every keystroke) or an `onchange` callback: + +```svelte +
createPost.validate()}> + +
+``` + +By default, issues will be ignored if they belong to form controls that haven't yet been interacted with. To validate _all_ inputs, call `validate({ includeUntouched: true })`. + +For client-side validation, you can specify a _preflight_ schema which will populate `issues()` and prevent data being sent to the server if the data doesn't validate: + +```svelte + + +

Create a new post

+ +
+ +
+``` + +> [!NOTE] The preflight schema can be the same object as your server-side schema, if appropriate, though it won't be able to do server-side checks like 'this value already exists in the database'. Note that you cannot export a schema from a `.remote.ts` or `.remote.js` file, so the schema must either be exported from a shared module, or from a ` +``` + +### Handling sensitive data + +In the case of a non-progressively-enhanced form submission (i.e. where JavaScript is unavailable, for whatever reason) `value()` is also populated if the submitted data is invalid, so that the user does not need to fill the entire form out from scratch. + +You can prevent sensitive data (such as passwords and credit card numbers) from being sent back to the user by using a name with a leading underscore: + +```svelte +
+ + + + + +
+``` + +In this example, if the data does not validate, only the first `` will be populated when the page reloads. + +### Single-flight mutations + +By default, all queries used on the page (along with any `load` functions) are automatically refreshed following a successful form submission. This ensures that everything is up-to-date, but it's also inefficient: many queries will be unchanged, and it requires a second trip to the server to get the updated data. + +Instead, we can specify which queries should be refreshed in response to a particular form submission. This is called a _single-flight mutation_, and there are two ways to achieve it. The first is to refresh the query on the server, inside the form handler: + +```js +import * as v from 'valibot'; +import { error, redirect } from '@sveltejs/kit'; +import { query, form } from '$app/server'; +const slug = ''; +const post = { id: '' }; +/** @type {any} */ +const externalApi = ''; +// ---cut--- +export const getPosts = query(async () => { /* ... */ }); + +export const getPost = query(v.string(), async (slug) => { /* ... */ }); + +export const createPost = form( + v.object({/* ... */}), + async (data) => { + // form logic goes here... + + // Refresh `getPosts()` on the server, and send + // the data back with the result of `createPost` + +++await getPosts().refresh();+++ + + // Redirect to the newly created page + redirect(303, `/blog/${slug}`); + } +); + +export const updatePost = form( + v.object({/* ... */}), + async (data) => { + // form logic goes here... + const result = externalApi.update(post); + + // The API already gives us the updated post, + // no need to refresh it, we can set it directly + +++await getPost(post.id).set(result);+++ + } +); +``` + +The second is to drive the single-flight mutation from the client, which we'll see in the section on [`enhance`](#form-enhance). + +### Returns and redirects + +The example above uses [`redirect(...)`](@sveltejs-kit#redirect), which sends the user to the newly created page. Alternatively, the callback could return data, in which case it would be available as `createPost.result`: + +```ts +/// file: src/routes/blog/data.remote.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; +} + +declare module '$lib/server/auth' { + interface User { + name: string; + } + + /** + * Gets a user's info from their cookies, using `getRequestEvent` + */ + export function getUser(): Promise; +} +// @filename: index.js +import * as v from 'valibot'; +import { error, redirect } from '@sveltejs/kit'; +import { query, form } from '$app/server'; +import * as db from '$lib/server/database'; +import * as auth from '$lib/server/auth'; + +export const getPosts = query(async () => { /* ... */ }); + +export const getPost = query(v.string(), async (slug) => { /* ... */ }); + +// ---cut--- +export const createPost = form( + v.object({/* ... */}), + async (data) => { + // ... + + return { success: true }; + } +); +``` + +```svelte + + + +

Create a new post

+ +
+ +
+ +{#if createPost.result?.success} +

Successfully published!

+{/if} +``` + +This value is _ephemeral_ — it will vanish if you resubmit, navigate away, or reload the page. + +> [!NOTE] The `result` value need not indicate success — it can also contain validation errors, along with any data that should repopulate the form on page reload. + +If an error occurs during submission, the nearest `+error.svelte` page will be rendered. + +### enhance + +We can customize what happens when the form is submitted with the `enhance` method: + +```svelte + + + +

Create a new post

+ +
{ + try { + await submit(); + form.reset(); + + showToast('Successfully published!'); + } catch (error) { + showToast('Oh no! Something went wrong'); + } +})}> + +
+``` + +> When using `enhance`, the `
` is not automatically reset — you must call `form.reset()` if you want to clear the inputs. + +The callback receives the `form` element, the `data` it contains, and a `submit` function. + +To enable client-driven [single-flight mutations](#form-Single-flight-mutations), use `submit().updates(...)`. For example, if the `getPosts()` query was used on this page, we could refresh it like so: + +```ts +import type { RemoteQuery, RemoteQueryOverride } from '@sveltejs/kit'; +interface Post {} +declare function submit(): Promise & { + updates(...queries: Array | RemoteQueryOverride>): Promise; +} + +declare function getPosts(): RemoteQuery; +// ---cut--- +await submit().updates(getPosts()); +``` + +We can also _override_ the current data while the submission is ongoing: + +```ts +import type { RemoteQuery, RemoteQueryOverride } from '@sveltejs/kit'; +interface Post {} +declare function submit(): Promise & { + updates(...queries: Array | RemoteQueryOverride>): Promise; +} + +declare function getPosts(): RemoteQuery; +declare const newPost: Post; +// ---cut--- +await submit().updates( + getPosts().withOverride((posts) => [newPost, ...posts]) +); +``` + +The override will be applied immediately, and released when the submission completes (or fails). + +### Multiple instances of a form + +Some forms may be repeated as part of a list. In this case you can create separate instances of a form function via `for(id)` to achieve isolation. + +```svelte + + + +

Todos

+ +{#each await getTodos() as todo} + {@const modify = modifyTodo.for(todo.id)} + + + + +{/each} +``` + +### buttonProps + +By default, submitting a form will send a request to the URL indicated by the `
` element's [`action`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form#attributes_for_form_submission) attribute, which in the case of a remote function is a property on the form object generated by SvelteKit. + +It's possible for a ` + +
+``` + +Like the form object itself, `buttonProps` has an `enhance` method for customizing submission behaviour. + +## command + +The `command` function, like `form`, allows you to write data to the server. Unlike `form`, it's not specific to an element and can be called from anywhere. + +> [!NOTE] Prefer `form` where possible, since it gracefully degrades if JavaScript is disabled or fails to load. + +As with `query` and `form`, if the function accepts an argument, it should be [validated](#query-Query-arguments) by passing a [Standard Schema](https://standardschema.dev) as the first argument to `command`. + +```ts +/// file: likes.remote.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; +} +// @filename: index.js +// ---cut--- +import * as v from 'valibot'; +import { query, command } from '$app/server'; +import * as db from '$lib/server/database'; + +export const getLikes = query(v.string(), async (id) => { + const [row] = await db.sql` + SELECT likes + FROM item + WHERE id = ${id} + `; + + return row.likes; +}); + +export const addLike = command(v.string(), async (id) => { + await db.sql` + UPDATE item + SET likes = likes + 1 + WHERE id = ${id} + `; +}); +``` + +Now simply call `addLike`, from (for example) an event handler: + +```svelte + + + + + +

likes: {await getLikes(item.id)}

+``` + +> [!NOTE] Commands cannot be called during render. + +### Updating queries + +To update `getLikes(item.id)`, or any other query, we need to tell SvelteKit _which_ queries need to be refreshed (unlike `form`, which by default invalidates everything, to approximate the behaviour of a native form submission). + +We either do that inside the command itself... + +```js +/// file: likes.remote.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; +} +// @filename: index.js +// ---cut--- +import * as v from 'valibot'; +import { query, command } from '$app/server'; +import * as db from '$lib/server/database'; +// ---cut--- +export const getLikes = query(v.string(), async (id) => { /* ... */ }); + +export const addLike = command(v.string(), async (id) => { + await db.sql` + UPDATE item + SET likes = likes + 1 + WHERE id = ${id} + `; + + +++getLikes(id).refresh();+++ + // Just like within form functions you can also do + // getLikes(id).set(...) + // in case you have the result already +}); +``` + +...or when we call it: + +```ts +import { RemoteCommand, RemoteQueryFunction } from '@sveltejs/kit'; + +interface Item { id: string } + +declare const addLike: RemoteCommand; +declare const getLikes: RemoteQueryFunction; +declare function showToast(message: string): void; +declare const item: Item; +// ---cut--- +try { + await addLike(item.id).+++updates(getLikes(item.id))+++; +} catch (error) { + showToast('Something went wrong!'); +} +``` + +As before, we can use `withOverride` for optimistic updates: + +```ts +import { RemoteCommand, RemoteQueryFunction } from '@sveltejs/kit'; + +interface Item { id: string } + +declare const addLike: RemoteCommand; +declare const getLikes: RemoteQueryFunction; +declare function showToast(message: string): void; +declare const item: Item; +// ---cut--- +try { + await addLike(item.id).updates( + getLikes(item.id).+++withOverride((n) => n + 1)+++ + ); +} catch (error) { + showToast('Something went wrong!'); +} +``` + +## prerender + +The `prerender` function is similar to `query`, except that it will be invoked at build time to prerender the result. Use this for data that changes at most once per redeployment. + +```js +/// file: src/routes/blog/data.remote.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; +} +// @filename: index.js +// ---cut--- +import { prerender } from '$app/server'; +import * as db from '$lib/server/database'; + +export const getPosts = prerender(async () => { + const posts = await db.sql` + SELECT title, slug + FROM post + ORDER BY published_at + DESC + `; + + return posts; +}); +``` + +You can use `prerender` functions on pages that are otherwise dynamic, allowing for partial prerendering of your data. This results in very fast navigation, since prerendered data can live on a CDN along with your other static assets. + +In the browser, prerendered data is saved using the [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache) API. This cache survives page reloads, and will be cleared when the user first visits a new deployment of your app. + +> [!NOTE] When the entire page has `export const prerender = true`, you cannot use queries, as they are dynamic. + +### Prerender arguments + +As with queries, prerender functions can accept an argument, which should be [validated](#query-Query-arguments) with a [Standard Schema](https://standardschema.dev/): + +```js +/// file: src/routes/blog/data.remote.js +// @filename: ambient.d.ts +declare module '$lib/server/database' { + export function sql(strings: TemplateStringsArray, ...values: any[]): Promise; +} +// @filename: index.js +// ---cut--- +import * as v from 'valibot'; +import { error } from '@sveltejs/kit'; +import { prerender } from '$app/server'; +import * as db from '$lib/server/database'; + +export const getPosts = prerender(async () => { /* ... */ }); + +export const getPost = prerender(v.string(), async (slug) => { + const [post] = await db.sql` + SELECT * FROM post + WHERE slug = ${slug} + `; + + if (!post) error(404, 'Not found'); + return post; +}); +``` + +Any calls to `getPost(...)` found by SvelteKit's crawler while [prerendering pages](page-options#prerender) will be saved automatically, but you can also specify which values it should be called with using the `inputs` option: + +```js +/// file: src/routes/blog/data.remote.js +import * as v from 'valibot'; +import { prerender } from '$app/server'; +// ---cut--- + +export const getPost = prerender( + v.string(), + async (slug) => { /* ... */ }, + { + inputs: () => [ + 'first-post', + 'second-post', + 'third-post' + ] + } +); +``` + +By default, prerender functions are excluded from your server bundle, which means that you cannot call them with any arguments that were _not_ prerendered. You can set `dynamic: true` to change this behaviour: + +```js +/// file: src/routes/blog/data.remote.js +import * as v from 'valibot'; +import { prerender } from '$app/server'; +// ---cut--- + +export const getPost = prerender( + v.string(), + async (slug) => { /* ... */ }, + { + +++dynamic: true+++, + inputs: () => [ + 'first-post', + 'second-post', + 'third-post' + ] + } +); +``` + +## Handling validation errors + +As long as _you're_ not passing invalid data to your remote functions, there are only two reasons why the argument passed to a `command`, `query` or `prerender` function would fail validation: + +- the function signature changed between deployments, and some users are currently on an older version of your app +- someone is trying to attack your site by poking your exposed endpoints with bad data + +In the second case, we don't want to give the attacker any help, so SvelteKit will generate a generic [400 Bad Request](https://http.dog/400) response. You can control the message by implementing the [`handleValidationError`](hooks#Server-hooks-handleValidationError) server hook, which, like [`handleError`](hooks#Shared-hooks-handleError), must return an [`App.Error`](errors#Type-safety) (which defaults to `{ message: string }`): + +```js +/// file: src/hooks.server.ts +/** @type {import('@sveltejs/kit').HandleValidationError} */ +export function handleValidationError({ event, issues }) { + return { + message: 'Nice try, hacker!' + }; +} +``` + +If you know what you're doing and want to opt out of validation, you can pass the string `'unchecked'` in place of a schema: + +```ts +/// file: data.remote.ts +import { query } from '$app/server'; + +export const getStuff = query('unchecked', async ({ id }: { id: string }) => { + // the shape might not actually be what TypeScript thinks + // since bad actors might call this function with other arguments +}); +``` + +## Using `getRequestEvent` + +Inside `query`, `form` and `command` you can use [`getRequestEvent`]($app-server#getRequestEvent) to get the current [`RequestEvent`](@sveltejs-kit#RequestEvent) object. This makes it easy to build abstractions for interacting with cookies, for example: + +```ts +/// file: user.remote.ts +import { getRequestEvent, query } from '$app/server'; +import { findUser } from '$lib/server/database'; + +export const getProfile = query(async () => { + const user = await getUser(); + + return { + name: user.name, + avatar: user.avatar + }; +}); + +// this query could be called from multiple places, but +// the function will only run once per request +const getUser = query(async () => { + const { cookies } = getRequestEvent(); + + return await findUser(cookies.get('session_id')); +}); +``` + +Note that some properties of `RequestEvent` are different inside remote functions: + +- you cannot set headers (other than writing cookies, and then only inside `form` and `command` functions) +- `route`, `params` and `url` relate to the page the remote function was called from, _not_ the URL of the endpoint SvelteKit creates for the remote function. Queries are not re-run when the user navigates (unless the argument to the query changes as a result of navigation), and so you should be mindful of how you use these values. In particular, never use them to determine whether or not a user is authorized to access certain data. + +## Redirects + +Inside `query`, `form` and `prerender` functions it is possible to use the [`redirect(...)`](@sveltejs-kit#redirect) function. It is *not* possible inside `command` functions, as you should avoid redirecting here. (If you absolutely have to, you can return a `{ redirect: location }` object and deal with it in the client.) diff --git a/documentation/docs/20-core-concepts/index.md b/documentation/docs/20-core-concepts/index.md new file mode 100644 index 000000000000..46f1e3708cf8 --- /dev/null +++ b/documentation/docs/20-core-concepts/index.md @@ -0,0 +1,3 @@ +--- +title: Core concepts +--- diff --git a/documentation/docs/20-core-concepts/meta.json b/documentation/docs/20-core-concepts/meta.json deleted file mode 100644 index ec4007c842a5..000000000000 --- a/documentation/docs/20-core-concepts/meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Core concepts" -} diff --git a/documentation/docs/25-build-and-deploy/10-building-your-app.md b/documentation/docs/25-build-and-deploy/10-building-your-app.md index d7c4cb71b50c..e641b4cfeaad 100644 --- a/documentation/docs/25-build-and-deploy/10-building-your-app.md +++ b/documentation/docs/25-build-and-deploy/10-building-your-app.md @@ -10,15 +10,15 @@ Secondly, an _adapter_ takes this production build and tunes it for your target ## During the build -SvelteKit will load your `+page/layout(.server).js` files (and all files they import) for analysis during the build. Any code that should _not_ be executed at this stage must check that `building` from [`$app/environment`](modules#$app-environment) is `false`: +SvelteKit will load your `+page/layout(.server).js` files (and all files they import) for analysis during the build. Any code that should _not_ be executed at this stage must check that `building` from [`$app/environment`]($app-environment) is `false`: -```diff -+import { building } from '$app/environment'; +```js ++++import { building } from '$app/environment';+++ import { setupMyDatabase } from '$lib/server/database'; -+if (!building) { ++++if (!building) {+++ setupMyDatabase(); -+} ++++}+++ export function load() { // ... @@ -27,4 +27,4 @@ export function load() { ## Preview your app -After building, you can view your production build locally with `vite preview` (via `npm run preview`). Note that this will run the app in Node, and so is not a perfect reproduction of your deployed app — adapter-specific adjustments like the [`platform` object](adapters#platform-specific-context) do not apply to previews. +After building, you can view your production build locally with `vite preview` (via `npm run preview`). Note that this will run the app in Node, and so is not a perfect reproduction of your deployed app — adapter-specific adjustments like the [`platform` object](adapters#Platform-specific-context) do not apply to previews. diff --git a/documentation/docs/25-build-and-deploy/20-adapters.md b/documentation/docs/25-build-and-deploy/20-adapters.md index 15acd012fd06..0e6062a5101a 100644 --- a/documentation/docs/25-build-and-deploy/20-adapters.md +++ b/documentation/docs/25-build-and-deploy/20-adapters.md @@ -6,14 +6,13 @@ Before you can deploy your SvelteKit app, you need to _adapt_ it for your deploy Official adapters exist for a variety of platforms — these are documented on the following pages: -- [`@sveltejs/adapter-cloudflare`](adapter-cloudflare) for Cloudflare Pages -- [`@sveltejs/adapter-cloudflare-workers`](adapter-cloudflare-workers) for Cloudflare Workers +- [`@sveltejs/adapter-cloudflare`](adapter-cloudflare) for Cloudflare Workers and Cloudflare Pages - [`@sveltejs/adapter-netlify`](adapter-netlify) for Netlify - [`@sveltejs/adapter-node`](adapter-node) for Node servers - [`@sveltejs/adapter-static`](adapter-static) for static site generation (SSG) - [`@sveltejs/adapter-vercel`](adapter-vercel) for Vercel -Additional [community-provided adapters](https://sveltesociety.dev/components#adapters) exist for other platforms. +Additional [community-provided adapters](/packages#sveltekit-adapters) exist for other platforms. ## Using adapters diff --git a/documentation/docs/25-build-and-deploy/30-adapter-auto.md b/documentation/docs/25-build-and-deploy/30-adapter-auto.md index e52f5967bd3b..0e60deae69e1 100644 --- a/documentation/docs/25-build-and-deploy/30-adapter-auto.md +++ b/documentation/docs/25-build-and-deploy/30-adapter-auto.md @@ -2,13 +2,14 @@ title: Zero-config deployments --- -When you create a new SvelteKit project with `npm create svelte@latest`, it installs [`adapter-auto`](https://github.com/sveltejs/kit/tree/master/packages/adapter-auto) by default. This adapter automatically installs and uses the correct adapter for supported environments when you deploy: +When you create a new SvelteKit project with `npx sv create`, it installs [`adapter-auto`](https://github.com/sveltejs/kit/tree/main/packages/adapter-auto) by default. This adapter automatically installs and uses the correct adapter for supported environments when you deploy: - [`@sveltejs/adapter-cloudflare`](adapter-cloudflare) for [Cloudflare Pages](https://developers.cloudflare.com/pages/) - [`@sveltejs/adapter-netlify`](adapter-netlify) for [Netlify](https://netlify.com/) - [`@sveltejs/adapter-vercel`](adapter-vercel) for [Vercel](https://vercel.com/) - [`svelte-adapter-azure-swa`](https://github.com/geoffrich/svelte-adapter-azure-swa) for [Azure Static Web Apps](https://docs.microsoft.com/en-us/azure/static-web-apps/) -- [`svelte-kit-sst`](https://github.com/serverless-stack/sst/tree/master/packages/svelte-kit-sst) for [AWS via SST](https://docs.sst.dev/start/svelte) +- [`svelte-kit-sst`](https://github.com/sst/v2/tree/master/packages/svelte-kit-sst) for [AWS via SST](https://sst.dev/docs/start/aws/svelte) +- [`@sveltejs/adapter-node`](adapter-node) for [Google Cloud Run](https://cloud.google.com/run) It's recommended to install the appropriate adapter to your `devDependencies` once you've settled on a target environment, since this will add the adapter to your lockfile and slightly improve install times on CI. @@ -18,4 +19,4 @@ To add configuration options, such as `{ edge: true }` in [`adapter-vercel`](ada ## Adding community adapters -You can add zero-config support for additional adapters by editing [adapters.js](https://github.com/sveltejs/kit/blob/master/packages/adapter-auto/adapters.js) and opening a pull request. \ No newline at end of file +You can add zero-config support for additional adapters by editing [adapters.js](https://github.com/sveltejs/kit/blob/main/packages/adapter-auto/adapters.js) and opening a pull request. diff --git a/documentation/docs/25-build-and-deploy/40-adapter-node.md b/documentation/docs/25-build-and-deploy/40-adapter-node.md index f3c04bfa85c3..347396904790 100644 --- a/documentation/docs/25-build-and-deploy/40-adapter-node.md +++ b/documentation/docs/25-build-and-deploy/40-adapter-node.md @@ -2,7 +2,7 @@ title: Node servers --- -To generate a standalone Node server, use [`adapter-node`](https://github.com/sveltejs/kit/tree/master/packages/adapter-node). +To generate a standalone Node server, use [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node). ## Usage @@ -13,11 +13,14 @@ Install with `npm i -D @sveltejs/adapter-node`, then add the adapter to your `sv /// file: svelte.config.js import adapter from '@sveltejs/adapter-node'; -export default { +/** @type {import('@sveltejs/kit').Config} */ +const config = { kit: { adapter: adapter() } }; + +export default config; ``` ## Deploying @@ -26,48 +29,59 @@ First, build your app with `npm run build`. This will create the production serv You will need the output directory, the project's `package.json`, and the production dependencies in `node_modules` to run the application. Production dependencies can be generated by copying the `package.json` and `package-lock.json` and then running `npm ci --omit dev` (you can skip this step if your app doesn't have any dependencies). You can then start your app with this command: -```bash +```sh node build ``` Development dependencies will be bundled into your app using [Rollup](https://rollupjs.org). To control whether a given package is bundled or externalised, place it in `devDependencies` or `dependencies` respectively in your `package.json`. +### Compressing responses + +You will typically want to compress responses coming from the server. If you're already deploying your server behind a reverse proxy for SSL or load balancing, it typically results in better performance to also handle compression at that layer since Node.js is single-threaded. + +However, if you're building a [custom server](#Custom-server) and do want to add a compression middleware there, note that we would recommend using [`@polka/compression`](https://www.npmjs.com/package/@polka/compression) since SvelteKit streams responses and the more popular `compression` package does not support streaming and may cause errors when used. + ## Environment variables In `dev` and `preview`, SvelteKit will read environment variables from your `.env` file (or `.env.local`, or `.env.[mode]`, [as determined by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files).) In production, `.env` files are _not_ automatically loaded. To do so, install `dotenv` in your project... -```bash +```sh npm install dotenv ``` ...and invoke it before running the built app: -```diff --node build -+node -r dotenv/config build +```sh +node +++-r dotenv/config+++ build +``` + +If you use Node.js v20.6+, you can use the [`--env-file`](https://nodejs.org/en/learn/command-line/how-to-read-environment-variables-from-nodejs) flag instead: + +```sh +node +++--env-file=.env+++ build ``` ### `PORT`, `HOST` and `SOCKET_PATH` By default, the server will accept connections on `0.0.0.0` using port 3000. These can be customised with the `PORT` and `HOST` environment variables: -``` +```sh HOST=127.0.0.1 PORT=4000 node build ``` Alternatively, the server can be configured to accept connections on a specified socket path. When this is done using the `SOCKET_PATH` environment variable, the `HOST` and `PORT` environment variables will be disregarded. -``` +```sh SOCKET_PATH=/tmp/socket node build ``` -### `ORIGIN`, `PROTOCOL_HEADER` and `HOST_HEADER` +### `ORIGIN`, `PROTOCOL_HEADER`, `HOST_HEADER`, and `PORT_HEADER` HTTP doesn't give SvelteKit a reliable way to know the URL that is currently being requested. The simplest way to tell SvelteKit where the app is being served is to set the `ORIGIN` environment variable: -``` +```sh ORIGIN=https://my.site node build # or e.g. for local previewing and testing @@ -76,25 +90,27 @@ ORIGIN=http://localhost:3000 node build With this, a request for the `/stuff` pathname will correctly resolve to `https://my.site/stuff`. Alternatively, you can specify headers that tell SvelteKit about the request protocol and host, from which it can construct the origin URL: -``` +```sh PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host node build ``` -> [`x-forwarded-proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) and [`x-forwarded-host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host) are de facto standard headers that forward the original protocol and host if you're using a reverse proxy (think load balancers and CDNs). You should only set these variables if your server is behind a trusted reverse proxy; otherwise, it'd be possible for clients to spoof these headers. +> [!NOTE] [`x-forwarded-proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) and [`x-forwarded-host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host) are de facto standard headers that forward the original protocol and host if you're using a reverse proxy (think load balancers and CDNs). You should only set these variables if your server is behind a trusted reverse proxy; otherwise, it'd be possible for clients to spoof these headers. +> +> If you're hosting your proxy on a non-standard port and your reverse proxy supports `x-forwarded-port`, you can also set `PORT_HEADER=x-forwarded-port`. If `adapter-node` can't correctly determine the URL of your deployment, you may experience this error when using [form actions](form-actions): -> Cross-site POST form submissions are forbidden +> [!NOTE] Cross-site POST form submissions are forbidden ### `ADDRESS_HEADER` and `XFF_DEPTH` -The [RequestEvent](types#public-types-requestevent) object passed to hooks and endpoints includes an `event.getClientAddress()` function that returns the client's IP address. By default this is the connecting `remoteAddress`. If your server is behind one or more proxies (such as a load balancer), this value will contain the innermost proxy's IP address rather than the client's, so we need to specify an `ADDRESS_HEADER` to read the address from: +The [`RequestEvent`](@sveltejs-kit#RequestEvent) object passed to hooks and endpoints includes an `event.getClientAddress()` function that returns the client's IP address. By default this is the connecting `remoteAddress`. If your server is behind one or more proxies (such as a load balancer), this value will contain the innermost proxy's IP address rather than the client's, so we need to specify an `ADDRESS_HEADER` to read the address from: -``` +```sh ADDRESS_HEADER=True-Client-IP node build ``` -> Headers can easily be spoofed. As with `PROTOCOL_HEADER` and `HOST_HEADER`, you should [know what you're doing](https://adam-p.ca/blog/2022/03/x-forwarded-for/) before setting these. +> [!NOTE] Headers can easily be spoofed. As with `PROTOCOL_HEADER` and `HOST_HEADER`, you should [know what you're doing](https://adam-p.ca/blog/2022/03/x-forwarded-for/) before setting these. If the `ADDRESS_HEADER` is `X-Forwarded-For`, the header value will contain a comma-separated list of IP addresses. The `XFF_DEPTH` environment variable should specify how many trusted proxies sit in front of your server. E.g. if there are three trusted proxies, proxy 3 will forward the addresses of the original connection and the first two proxies: @@ -110,11 +126,23 @@ Some guides will tell you to read the left-most address, but this leaves you [vu We instead read from the _right_, accounting for the number of trusted proxies. In this case, we would use `XFF_DEPTH=3`. -> If you need to read the left-most address instead (and don't care about spoofing) — for example, to offer a geolocation service, where it's more important for the IP address to be _real_ than _trusted_, you can do so by inspecting the `x-forwarded-for` header within your app. +> [!NOTE] If you need to read the left-most address instead (and don't care about spoofing) — for example, to offer a geolocation service, where it's more important for the IP address to be _real_ than _trusted_, you can do so by inspecting the `x-forwarded-for` header within your app. ### `BODY_SIZE_LIMIT` -The maximum request body size to accept in bytes including while streaming. Defaults to 512kb. You can disable this option with a value of 0 and implement a custom check in [`handle`](hooks#server-hooks-handle) if you need something more advanced. +The maximum request body size to accept in bytes including while streaming. The body size can also be specified with a unit suffix for kilobytes (`K`), megabytes (`M`), or gigabytes (`G`). For example, `512K` or `1M`. Defaults to 512kb. You can disable this option with a value of `Infinity` (0 in older versions of the adapter) and implement a custom check in [`handle`](hooks#Server-hooks-handle) if you need something more advanced. + +### `SHUTDOWN_TIMEOUT` + +The number of seconds to wait before forcefully closing any remaining connections after receiving a `SIGTERM` or `SIGINT` signal. Defaults to `30`. Internally the adapter calls [`closeAllConnections`](https://nodejs.org/api/http.html#servercloseallconnections). See [Graceful shutdown](#Graceful-shutdown) for more details. + +### `IDLE_TIMEOUT` + +When using systemd socket activation, `IDLE_TIMEOUT` specifies the number of seconds after which the app is automatically put to sleep when receiving no requests. If not set, the app runs continuously. See [Socket activation](#Socket-activation) for more details. + +### `KEEP_ALIVE_TIMEOUT` and `HEADERS_TIMEOUT` + +The number of seconds for [`keepAliveTimeout`](https://nodejs.org/api/http.html#serverkeepalivetimeout) and [`headersTimeout`](https://nodejs.org/api/http.html#serverheaderstimeout). ## Options @@ -125,17 +153,19 @@ The adapter can be configured with various options: /// file: svelte.config.js import adapter from '@sveltejs/adapter-node'; -export default { +/** @type {import('@sveltejs/kit').Config} */ +const config = { kit: { adapter: adapter({ // default options are shown out: 'build', - precompress: false, - envPrefix: '', - polyfill: true + precompress: true, + envPrefix: '' }) } }; + +export default config; ``` ### out @@ -144,7 +174,7 @@ The directory to build the server to. It defaults to `build` — i.e. `node buil ### precompress -Enables precompressing using gzip and brotli for assets and prerendered pages. It defaults to `false`. +Enables precompressing using gzip and brotli for assets and prerendered pages. It defaults to `true`. ### envPrefix @@ -161,11 +191,61 @@ MY_CUSTOM_ORIGIN=https://my.site \ node build ``` -### polyfill +## Graceful shutdown + +By default `adapter-node` gracefully shuts down the HTTP server when a `SIGTERM` or `SIGINT` signal is received. It will: + +1. reject new requests ([`server.close`](https://nodejs.org/api/http.html#serverclosecallback)) +2. wait for requests that have already been made but not received a response yet to finish and close connections once they become idle ([`server.closeIdleConnections`](https://nodejs.org/api/http.html#servercloseidleconnections)) +3. and finally, close any remaining connections that are still active after [`SHUTDOWN_TIMEOUT`](#Environment-variables-SHUTDOWN_TIMEOUT) seconds. ([`server.closeAllConnections`](https://nodejs.org/api/http.html#servercloseallconnections)) + +> [!NOTE] If you want to customize this behaviour you can use a [custom server](#Custom-server). + +You can listen to the `sveltekit:shutdown` event which is emitted after the HTTP server has closed all connections. Unlike Node's `exit` event, the `sveltekit:shutdown` event supports asynchronous operations and is always emitted when all connections are closed even if the server has dangling work such as open database connections. + +```js +// @errors: 2304 +process.on('sveltekit:shutdown', async (reason) => { + await jobs.stop(); + await db.close(); +}); +``` + +The parameter `reason` has one of the following values: + +- `SIGINT` - shutdown was triggered by a `SIGINT` signal +- `SIGTERM` - shutdown was triggered by a `SIGTERM` signal +- `IDLE` - shutdown was triggered by [`IDLE_TIMEOUT`](#Environment-variables-IDLE_TIMEOUT) + +## Socket activation + +Most Linux operating systems today use a modern process manager called systemd to start the server and run and manage services. You can configure your server to allocate a socket and start and scale your app on demand. This is called [socket activation](https://0pointer.de/blog/projects/socket-activated-containers.html). In this case, the OS will pass two environment variables to your app — `LISTEN_PID` and `LISTEN_FDS`. The adapter will then listen on file descriptor 3 which refers to a systemd socket unit that you will have to create. + +> [!NOTE] You can still use [`envPrefix`](#Options-envPrefix) with systemd socket activation. `LISTEN_PID` and `LISTEN_FDS` are always read without a prefix. -Controls whether your build will load polyfills for missing modules. It defaults to `true`, and should only be disabled when using Node 18.11 or greater. +To take advantage of socket activation follow these steps. -Note: to use Node's built-in `crypto` global with Node 18 you will need to use the `--experimental-global-webcrypto` flag. This flag is not required with Node 20. +1. Run your app as a [systemd service](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html). It can either run directly on the host system or inside a container (using Docker or a systemd portable service for example). If you additionally pass an [`IDLE_TIMEOUT`](#Environment-variables-IDLE_TIMEOUT) environment variable to your app it will gracefully shutdown if there are no requests for `IDLE_TIMEOUT` seconds. systemd will automatically start your app again when new requests are coming in. + +```ini +/// file: /etc/systemd/system/myapp.service +[Service] +Environment=NODE_ENV=production IDLE_TIMEOUT=60 +ExecStart=/usr/bin/node /usr/bin/myapp/build +``` + +2. Create an accompanying [socket unit](https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html). The adapter only accepts a single socket. + +```ini +/// file: /etc/systemd/system/myapp.socket +[Socket] +ListenStream=3000 + +[Install] +WantedBy=sockets.target +``` + +3. Make sure systemd has recognised both units by running `sudo systemctl daemon-reload`. Then enable the socket on boot and start it immediately using `sudo systemctl enable --now myapp.socket`. The app will then automatically start once the first request is made to `localhost:3000`. ## Custom server @@ -193,20 +273,3 @@ app.listen(3000, () => { console.log('listening on port 3000'); }); ``` - -## Troubleshooting - -### Is there a hook for cleaning up before the server exits? - -There's nothing built-in to SvelteKit for this, because such a cleanup hook depends highly on the execution environment you're on. For Node, you can use its built-in `process.on(..)` to implement a callback that runs before the server exits: - -```js -// @errors: 2304 2580 -function shutdownGracefully() { - // anything you need to clean up manually goes in here - db.shutdown(); -} - -process.on('SIGINT', shutdownGracefully); -process.on('SIGTERM', shutdownGracefully); -``` diff --git a/documentation/docs/25-build-and-deploy/50-adapter-static.md b/documentation/docs/25-build-and-deploy/50-adapter-static.md index ad2a2ce63be8..2b50d91515e3 100644 --- a/documentation/docs/25-build-and-deploy/50-adapter-static.md +++ b/documentation/docs/25-build-and-deploy/50-adapter-static.md @@ -2,7 +2,7 @@ title: Static site generation --- -To use SvelteKit as a static site generator (SSG), use [`adapter-static`](https://github.com/sveltejs/kit/tree/master/packages/adapter-static). +To use SvelteKit as a static site generator (SSG), use [`adapter-static`](https://github.com/sveltejs/kit/tree/main/packages/adapter-static). This will prerender your entire site as a collection of static files. If you'd like to prerender only some pages and dynamically server-render others, you will need to use a different adapter together with [the `prerender` option](page-options#prerender). @@ -11,11 +11,11 @@ This will prerender your entire site as a collection of static files. If you'd l Install with `npm i -D @sveltejs/adapter-static`, then add the adapter to your `svelte.config.js`: ```js -// @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-static'; -export default { +/** @type {import('@sveltejs/kit').Config} */ +const config = { kit: { adapter: adapter({ // default options are shown. On some platforms @@ -28,17 +28,21 @@ export default { }) } }; + +export default config; ``` ...and add the [`prerender`](page-options#prerender) option to your root layout: ```js /// file: src/routes/+layout.js -// This can be false if you're using a fallback (i.e. SPA mode) +// If you're using a fallback (i.e. SPA mode) you don't need to prerender all +// pages by setting this here, but should prerender as many as possible to +// avoid large performance and SEO impacts export const prerender = true; ``` -> You must ensure SvelteKit's [`trailingSlash`](page-options#trailingslash) option is set appropriately for your environment. If your host does not render `/a.html` upon receiving a request for `/a` then you will need to set `trailingSlash: 'always'` in your root layout to create `/a/index.html` instead. +> [!NOTE] You must ensure SvelteKit's [`trailingSlash`](page-options#trailingSlash) option is set appropriately for your environment. If your host does not render `/a.html` upon receiving a request for `/a` then you will need to set `trailingSlash: 'always'` in your root layout to create `/a/index.html` instead. ## Zero-config support @@ -48,14 +52,18 @@ Some platforms have zero-config support (more to come in future): On these platforms, you should omit the adapter options so that `adapter-static` can provide the optimal configuration: -```diff +```js /// file: svelte.config.js -export default { +import adapter from '@sveltejs/adapter-static'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { kit: { -- adapter: adapter({...}) -+ adapter: adapter() + adapter: adapter(---{...}---) } }; + +export default config; ``` ## Options @@ -70,7 +78,9 @@ The directory to write static assets (the contents of `static`, plus client-side ### fallback -Specify a fallback page for [SPA mode](single-page-apps), e.g. `index.html` or `200.html` or `404.html`. +To create a [single page app (SPA)](single-page-apps) you must specify the name of the fallback page to be generated by SvelteKit, which is used as the entry point for URLs that have not been prerendered. This is commonly `200.html`, but can vary depending on your deployment platform. You should avoid `index.html` where possible to avoid conflicting with a prerendered homepage. + +> This option has large negative performance and SEO impacts. It is only recommended in certain circumstances such as wrapping the site in a mobile app. See the [single page apps](single-page-apps) documentation for more details and alternatives. ### precompress @@ -82,24 +92,29 @@ By default, `adapter-static` checks that either all pages and endpoints (if any) ## GitHub Pages -When building for GitHub Pages, make sure to update [`paths.base`](configuration#paths) to match your repo name, since the site will be served from rather than from the root. +When building for [GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages), if your repo name is not equivalent to `your-username.github.io`, make sure to update [`config.kit.paths.base`](configuration#paths) to match your repo name. This is because the site will be served from `https://your-username.github.io/your-repo-name` rather than from the root. + +You'll also want to generate a fallback `404.html` page to replace the default 404 page shown by GitHub Pages. A config for GitHub Pages might look like the following: ```js -// @errors: 2307 2322 +// @errors: 2322 /// file: svelte.config.js import adapter from '@sveltejs/adapter-static'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { - adapter: adapter() + adapter: adapter({ + fallback: '404.html' + }), + paths: { + base: process.argv.includes('dev') ? '' : process.env.BASE_PATH + } } }; -config.paths = { base: process.argv.includes('dev') ? '' : process.env.BASE_PATH } - export default config; ``` @@ -118,22 +133,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # If you're using pnpm, add this step then change the commands and cache key below to use `pnpm` # - name: Install pnpm - # uses: pnpm/action-setup@v2 + # uses: pnpm/action-setup@v3 # with: # version: 8 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: npm - name: Install dependencies - run: npm install + run: npm i - name: build env: @@ -142,7 +157,7 @@ jobs: npm run build - name: Upload Artifacts - uses: actions/upload-pages-artifact@v2 + uses: actions/upload-pages-artifact@v3 with: # this should match the `pages` option in your adapter-static options path: 'build/' @@ -162,5 +177,7 @@ jobs: steps: - name: Deploy id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 ``` + +If you're not using GitHub actions to deploy your site (for example, you're pushing the built site to its own repo), add an empty `.nojekyll` file in your `static` directory to prevent Jekyll from interfering. diff --git a/documentation/docs/25-build-and-deploy/55-single-page-apps.md b/documentation/docs/25-build-and-deploy/55-single-page-apps.md index c830470cbaae..2826c28f8d2c 100644 --- a/documentation/docs/25-build-and-deploy/55-single-page-apps.md +++ b/documentation/docs/25-build-and-deploy/55-single-page-apps.md @@ -2,38 +2,55 @@ title: Single-page apps --- -You can turn any SvelteKit app, using any adapter, into a fully client-rendered single-page app (SPA) by disabling SSR at the root layout: +You can turn a SvelteKit app into a fully client-rendered single-page app (SPA) by specifying a _fallback page_. This page will be served for any URLs that can't be served by other means such as returning a prerendered page. +> [!NOTE] SPA mode has a large negative performance impact by forcing multiple network round trips (for the blank HTML document, then for the JavaScript, and then for any data needed for the page) before content can be shown. Unless you are serving the app from a local network (e.g. a mobile app that wraps a locally-served SPA) this will delay startup, especially when considering the latency of mobile devices. It also harms SEO by often causing sites to be downranked for performance (SPAs are much more likely to fail [Core Web Vitals](https://web.dev/explore/learn-core-web-vitals)), excluding search engines that don't render JS, and causing your site to receive less frequent updates from those that do. And finally, it makes your app inaccessible to users if JavaScript fails or is disabled (which happens [more often than you probably think](https://kryogenix.org/code/browser/everyonehasjs.html)). +> +> You can avoid these drawbacks by [prerendering](#Prerendering-individual-pages) as many pages as possible when using SPA mode (especially your homepage). If you can prerender all pages, you can simply use [static site generation](adapter-static) rather than a SPA. Otherwise, you should strongly consider using an adapter which supports server side rendering. SvelteKit has officially supported adapters for various providers with generous free tiers. + +## Usage + +First, disable SSR for the pages you don't want to prerender. These pages will be served via the fallback page; for example, to serve all pages via the fallback by default, you can update the root layout as shown below. You should [opt back into prerendering individual pages and directories](#Prerendering-individual-pages) where possible. ```js /// file: src/routes/+layout.js export const ssr = false; ``` -> In most situations this is not recommended: it harms SEO, tends to slow down perceived performance, and makes your app inaccessible to users if JavaScript fails or is disabled (which happens [more often than you probably think](https://kryogenix.org/code/browser/everyonehasjs.html)). - -If you don't have any server-side logic (i.e. `+page.server.js`, `+layout.server.js` or `+server.js` files) you can use [`adapter-static`](adapter-static) to create your SPA by adding a _fallback page_. - -## Usage - -Install with `npm i -D @sveltejs/adapter-static`, then add the adapter to your `svelte.config.js` with the following options: +If you don't have any server-side logic (i.e. `+page.server.js`, `+layout.server.js` or `+server.js` files) you can use [`adapter-static`](adapter-static) to create your SPA. Install `adapter-static` with `npm i -D @sveltejs/adapter-static` and add it to your `svelte.config.js` with the `fallback` option: ```js -// @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-static'; -export default { +/** @type {import('@sveltejs/kit').Config} */ +const config = { kit: { adapter: adapter({ fallback: '200.html' // may differ from host to host }) } }; + +export default config; ``` The `fallback` page is an HTML page created by SvelteKit from your page template (e.g. `app.html`) that loads your app and navigates to the correct route. For example [Surge](https://surge.sh/help/adding-a-200-page-for-client-side-routing), a static web host, lets you add a `200.html` file that will handle any requests that don't correspond to static assets or prerendered pages. -On some hosts it may be `index.html` or something else entirely — consult your platform's documentation. +On some hosts it may be something else entirely — consult your platform's documentation. We recommend avoiding `index.html` if possible as it may conflict with prerendering. + +> [!NOTE] Note that the fallback page will always contain absolute asset paths (i.e. beginning with `/` rather than `.`) regardless of the value of [`paths.relative`](configuration#paths), since it is used to respond to requests for arbitrary paths. + +## Prerendering individual pages + +If you want certain pages to be prerendered, you can re-enable `ssr` alongside `prerender` for just those parts of your app: + +```js +/// file: src/routes/my-prerendered-page/+page.js +export const prerender = true; +export const ssr = true; +``` + +You won't need a Node server or server capable of running JavaScript to deploy this page. It will only server render your page while building your project for the purposes of outputting an `.html` page that can be served from any static web host. ## Apache @@ -49,13 +66,3 @@ To run an SPA on [Apache](https://httpd.apache.org/), you should add a `static/. RewriteRule . /200.html [L] ``` - -## Prerendering individual pages - -If you want certain pages to be prerendered, you can re-enable `ssr` alongside `prerender` for just those parts of your app: - -```js -/// file: src/routes/my-prerendered-page/+page.js -export const prerender = true; -export const ssr = true; -``` \ No newline at end of file diff --git a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md index 92ceff556129..a681488daad8 100644 --- a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md +++ b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md @@ -1,30 +1,37 @@ --- -title: Cloudflare Pages +title: Cloudflare --- -To deploy to [Cloudflare Pages](https://developers.cloudflare.com/pages/), use [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/master/packages/adapter-cloudflare). +To deploy to [Cloudflare Workers](https://workers.cloudflare.com/) or [Cloudflare Pages](https://pages.cloudflare.com/), use [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare). -This adapter will be installed by default when you use [`adapter-auto`](adapter-auto). If you plan on staying with Cloudflare Pages you can switch from [`adapter-auto`](adapter-auto) to using this adapter directly so that type declarations will be automatically applied and you can set Cloudflare-specific options. +This adapter will be installed by default when you use [`adapter-auto`](adapter-auto). If you plan on staying with Cloudflare, you can switch from [`adapter-auto`](adapter-auto) to using this adapter directly so that `event.platform` is emulated during local development, type declarations are automatically applied, and the ability to set Cloudflare-specific options is provided. ## Comparisons -- `adapter-cloudflare` – supports all SvelteKit features; builds for [Cloudflare Pages](https://blog.cloudflare.com/cloudflare-pages-goes-full-stack/) -- `adapter-cloudflare-workers` – supports all SvelteKit features; builds for Cloudflare Workers -- `adapter-static` – only produces client-side static assets; compatible with Cloudflare Pages +- `adapter-cloudflare` – supports all SvelteKit features; builds for Cloudflare Workers Static Assets and Cloudflare Pages +- `adapter-cloudflare-workers` – deprecated. Supports all SvelteKit features; builds for Cloudflare Workers Sites +- `adapter-static` – only produces client-side static assets; compatible with Cloudflare Workers Static Assets and Cloudflare Pages ## Usage Install with `npm i -D @sveltejs/adapter-cloudflare`, then add the adapter to your `svelte.config.js`: ```js -// @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-cloudflare'; -export default { +/** @type {import('@sveltejs/kit').Config} */ +const config = { kit: { adapter: adapter({ // See below for an explanation of these options + config: undefined, + platformProxy: { + configPath: undefined, + environment: undefined, + persist: undefined + }, + fallback: 'plaintext', routes: { include: ['/*'], exclude: [''] @@ -32,11 +39,37 @@ export default { }) } }; + +export default config; ``` ## Options -The `routes` option allows you to customise the [`_routes.json`](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) file generated by `adapter-cloudflare`. +### config + +Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). If you would like to use a Wrangler configuration filename other than `wrangler.jsonc`, `wrangler.json`, or `wrangler.toml` you can specify it using this option. + +### platformProxy + +Preferences for the emulated `platform.env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API documentation for a full list of options. + +### fallback + +Whether to render a plaintext 404.html page or a rendered SPA fallback page for non-matching asset requests. + +For Cloudflare Workers, the default behaviour is to return a null-body 404-status response for non-matching assets requests. However, if the [`assets.not_found_handling`](https://developers.cloudflare.com/workers/static-assets/routing/#2-not_found_handling) Wrangler configuration setting is set to `"404-page"`, this page will be served if a request fails to match an asset. If `assets.not_found_handling` is set to `"single-page-application"`, the adapter will render a SPA fallback index.html page regardless of the `fallback` option specified. + +For Cloudflare Pages, this page will only be served when a request that matches an entry in `routes.exclude` fails to match an asset. + +Most of the time `plaintext` is sufficient, but if you are using `routes.exclude` to manually +exclude a set of prerendered pages without exceeding the 100 route limit, you may wish to +use `spa` instead to avoid showing an unstyled 404 page to users. + +See Cloudflare Pages' [Not Found behaviour](https://developers.cloudflare.com/pages/configuration/serving-pages/#not-found-behavior) for more info. + +### routes + +Only for Cloudflare Pages. Allows you to customise the [`_routes.json`](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file) file generated by `adapter-cloudflare`. - `include` defines routes that will invoke a function, and defaults to `['/*']` - `exclude` defines routes that will _not_ invoke a function — this is a faster and cheaper way to serve your app's static assets. This array can include the following special values: @@ -47,40 +80,91 @@ The `routes` option allows you to customise the [`_routes.json`](https://develop You can have up to 100 `include` and `exclude` rules combined. Generally you can omit the `routes` options, but if (for example) your `` paths exceed that limit, you may find it helpful to manually create an `exclude` list that includes `'/articles/*'` instead of the auto-generated `['/articles/foo', '/articles/bar', '/articles/baz', ...]`. -## Deployment +## Cloudflare Workers + +### Basic configuration + +When building for Cloudflare Workers, this adapter expects to find a [Wrangler configuration file](https://developers.cloudflare.com/workers/configuration/sites/configuration/) in the project root. It should look something like this: + +```jsonc +/// file: wrangler.jsonc +{ + "name": "", + "main": ".svelte-kit/cloudflare/_worker.js", + "compatibility_date": "2025-01-01", + "assets": { + "binding": "ASSETS", + "directory": ".svelte-kit/cloudflare", + } +} +``` + +### Deployment + +Please follow the [framework guide](https://developers.cloudflare.com/workers/frameworks/framework-guides/svelte/) for Cloudflare Workers to begin. + +## Cloudflare Pages + +### Deployment + +Please follow the [Get Started Guide](https://developers.cloudflare.com/pages/get-started/) for Cloudflare Pages to begin. -Please follow the [Get Started Guide](https://developers.cloudflare.com/pages/get-started) for Cloudflare Pages to begin. +If you're using the [Git integration](https://developers.cloudflare.com/pages/get-started/git-integration/), your build settings should look like this: -When configuring your project settings, you must use the following settings: +- Framework preset – SvelteKit +- Build command – `npm run build` or `vite build` +- Build output directory – `.svelte-kit/cloudflare` -- **Framework preset** – SvelteKit -- **Build command** – `npm run build` or `vite build` -- **Build output directory** – `.svelte-kit/cloudflare` +### Further reading + +You may wish to refer to [Cloudflare's documentation for deploying a SvelteKit site on Cloudflare Pages](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-kit-site/). + +### Notes + +Functions contained in the [`/functions` directory](https://developers.cloudflare.com/pages/functions/routing/) at the project's root will _not_ be included in the deployment. Instead, functions should be implemented as [server endpoints](routing#server) in your SvelteKit app, which is compiled to a [single `_worker.js` file](https://developers.cloudflare.com/pages/functions/advanced-mode/). -## Bindings +## Runtime APIs -The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/platform/environment-variables/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with `context` and `caches`, meaning that you can access it in hooks and endpoints: +The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with [`ctx`](https://developers.cloudflare.com/workers/runtime-apis/context/), [`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/), and [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties), meaning that you can access it in hooks and endpoints: ```js -// @errors: 7031 +// @filename: ambient.d.ts +import { DurableObjectNamespace } from '@cloudflare/workers-types'; + +declare global { + namespace App { + interface Platform { + env: { + YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; + }; + } + } +} +// @filename: +server.js +// ---cut--- +// @errors: 2355 2322 +/// file: +server.js +/** @type {import('./$types').RequestHandler} */ export async function POST({ request, platform }) { - const x = platform.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); + const x = platform?.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); } ``` -> SvelteKit's built-in `$env` module should be preferred for environment variables. +> [!NOTE] SvelteKit's built-in [`$env` module]($env-static-private) should be preferred for environment variables. -To make these types available to your app, reference them in your `src/app.d.ts`: +To make these types available to your app, install [`@cloudflare/workers-types`](https://www.npmjs.com/package/@cloudflare/workers-types) and reference them in your `src/app.d.ts`: -```diff +```ts /// file: src/app.d.ts ++++import { KVNamespace, DurableObjectNamespace } from '@cloudflare/workers-types';+++ + declare global { namespace App { interface Platform { -+ env?: { -+ YOUR_KV_NAMESPACE: KVNamespace; -+ YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; -+ }; ++++ env: { + YOUR_KV_NAMESPACE: KVNamespace; + YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; + };+++ } } } @@ -88,24 +172,85 @@ declare global { export {}; ``` -### Testing Locally +### Testing locally -`platform.env` is only available in the final build and not in dev mode. For testing the build, you can use [wrangler](https://developers.cloudflare.com/workers/cli-wrangler) **version 3**. Once you have built your site, run `wrangler pages dev .svelte-kit/cloudflare`. Ensure you have your [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) in your `wrangler.toml`. +Cloudflare specific values in the `platform` property are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `platform.env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. -## Notes +For testing the build, you should use [Wrangler](https://developers.cloudflare.com/workers/wrangler/) version 4. Once you have built your site, run `wrangler dev .svelte-kit/cloudflare/_worker.js` if you're testing for Cloudflare Workers or `wrangler pages dev .svelte-kit/cloudflare` for Cloudflare Pages. -Functions contained in the `/functions` directory at the project's root will _not_ be included in the deployment, which is compiled to a [single `_worker.js` file](https://developers.cloudflare.com/pages/platform/functions/#advanced-mode). Functions should be implemented as [server endpoints](https://kit.svelte.dev/docs/routing#server) in your SvelteKit app. +## Headers and redirects -The `_headers` and `_redirects` files specific to Cloudflare Pages can be used for static asset responses (like images) by putting them into the `/static` folder. +The [`_headers`](https://developers.cloudflare.com/pages/configuration/headers/) and [`_redirects`](https://developers.cloudflare.com/pages/configuration/redirects/) files, specific to Cloudflare, can be used for static asset responses (like images) by putting them into the project root folder. -However, they will have no effect on responses dynamically rendered by SvelteKit, which should return custom headers or redirect responses from [server endpoints](https://kit.svelte.dev/docs/routing#server) or with the [`handle`](https://kit.svelte.dev/docs/hooks#server-hooks-handle) hook. +However, they will have no effect on responses dynamically rendered by SvelteKit, which should return custom headers or redirect responses from [server endpoints](routing#server) or with the [`handle`](hooks#Server-hooks-handle) hook. ## Troubleshooting -### Further reading +### Node.js compatibility + +If you would like to enable [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/), you can add the `nodejs_compat` compatibility flag to your Wrangler configuration file: + +```jsonc +/// file: wrangler.jsonc +{ + "compatibility_flags": ["nodejs_compat"] +} +``` + +### Worker size limits -You may wish to refer to [Cloudflare's documentation for deploying a SvelteKit site](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-site). +When deploying your application, the server generated by SvelteKit is bundled into a single file. Wrangler will fail to publish your worker if it exceeds [the size limits](https://developers.cloudflare.com/workers/platform/limits/#worker-size) after minification. You're unlikely to hit this limit usually, but some large libraries can cause this to happen. In that case, you can try to reduce the size of your worker by only importing such libraries on the client side. See [the FAQ](./faq#How-do-I-use-a-client-side-library-accessing-document-or-window) for more information. ### Accessing the file system -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. +You can't use `fs` in Cloudflare Workers. + +Instead, use the [`read`]($app-server#read) function from `$app/server` to access your files. It works by fetching the file from the deployed public assets location. + +Alternatively, you can [prerender](page-options#prerender) the routes in question. + +## Migrating from Workers Sites + +Cloudflare no longer recommends using [Workers Sites](https://developers.cloudflare.com/workers/configuration/sites/configuration/) and instead recommends using [Workers Static Assets](https://developers.cloudflare.com/workers/static-assets/). To migrate, replace `@sveltejs/adapter-cloudflare-workers` with `@sveltejs/adapter-cloudflare` and remove all `site` configuration settings from your Wrangler configuration file, then add the `assets.directory` and `assets.binding` configuration settings: + +### svelte.config.js + +```js +// @errors: 2307 +/// file: svelte.config.js +---import adapter from '@sveltejs/adapter-cloudflare-workers';--- ++++import adapter from '@sveltejs/adapter-cloudflare';+++ + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter() + } +}; + +export default config; +``` + +### wrangler.toml + +```toml +/// file: wrangler.toml +---site.bucket = ".cloudflare/public"--- ++++assets.directory = ".cloudflare/public" +assets.binding = "ASSETS" # Exclude this if you don't have a `main` key configured.+++ +``` + +### wrangler.jsonc + +```jsonc +/// file: wrangler.jsonc +{ +--- "site": { + "bucket": ".cloudflare/public" + },--- ++++ "assets": { + "directory": ".cloudflare/public", + "binding": "ASSETS" // Exclude this if you don't have a `main` key configured. + }+++ +} +``` diff --git a/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md b/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md index 48d1335c206e..418a7e198adf 100644 --- a/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md +++ b/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md @@ -2,9 +2,9 @@ title: Cloudflare Workers --- -To deploy to [Cloudflare Workers](https://workers.cloudflare.com/), use [`adapter-cloudflare-workers`](https://github.com/sveltejs/kit/tree/master/packages/adapter-cloudflare-workers). +> [!NOTE] `adapter-cloudflare-workers` has been deprecated in favour of [`adapter-cloudflare`](adapter-cloudflare). We recommend using `adapter-cloudflare` to deploy to Cloudflare Workers with [Static Assets](https://developers.cloudflare.com/workers/static-assets/) since Cloudflare Workers Sites will be deprecated in favour of it. -> Unless you have a specific reason to use `adapter-cloudflare-workers`, it's recommended that you use `adapter-cloudflare` instead. Both adapters have equivalent functionality, but Cloudflare Pages offers features like GitHub integration with automatic builds and deploys, preview deployments, instant rollback and so on. +To deploy to [Cloudflare Workers](https://workers.cloudflare.com/) with [Workers Sites](https://developers.cloudflare.com/workers/configuration/sites/), use `adapter-cloudflare-workers`. ## Usage @@ -15,92 +15,111 @@ Install with `npm i -D @sveltejs/adapter-cloudflare-workers`, then add the adapt /// file: svelte.config.js import adapter from '@sveltejs/adapter-cloudflare-workers'; -export default { +/** @type {import('@sveltejs/kit').Config} */ +const config = { kit: { - adapter: adapter() + adapter: adapter({ + // see below for options that can be set here + }) } }; + +export default config; ``` -## Basic Configuration +## Options -This adapter expects to find a [wrangler.toml](https://developers.cloudflare.com/workers/platform/sites/configuration) file in the project root. It should look something like this: +### config -```toml -/// file: wrangler.toml -name = "" -account_id = "" +Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). If you would like to use a Wrangler configuration filename other than `wrangler.jsonc`, `wrangler.json`, or `wrangler.toml` you can specify it using this option. -main = "./.cloudflare/worker.js" -site.bucket = "./.cloudflare/public" +### platformProxy -build.command = "npm run build" +Preferences for the emulated `platform.env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API documentation for a full list of options. + +## Basic Configuration -compatibility_date = "2021-11-12" -workers_dev = true +This adapter expects to find a [Wrangler configuration file](https://developers.cloudflare.com/workers/configuration/sites/configuration/) in the project root. It should look something like this: + +```jsonc +/// file: wrangler.jsonc +{ + "name": "", + "account_id": "", + "main": "./.cloudflare/worker.js", + "site": { + "bucket": "./.cloudflare/public" + }, + "build": { + "command": "npm run build" + }, + "compatibility_date": "2021-11-12" +} ``` -`` can be anything. `` can be found by logging into your [Cloudflare dashboard](https://dash.cloudflare.com) and grabbing it from the end of the URL: +`` can be anything. `` can be found by running `wrangler whoami` using the Wrangler CLI tool or by logging into your [Cloudflare dashboard](https://dash.cloudflare.com) and grabbing it from the end of the URL: ``` -https://dash.cloudflare.com/ +https://dash.cloudflare.com//home ``` -> You should add the `.cloudflare` directory (or whichever directories you specified for `main` and `site.bucket`) to your `.gitignore`. +> [!NOTE] You should add the `.cloudflare` directory (or whichever directories you specified for `main` and `site.bucket`) and the `.wrangler` directory to your `.gitignore`. -You will need to install [wrangler](https://developers.cloudflare.com/workers/wrangler/get-started/) and log in, if you haven't already: +You will need to install [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/) and log in, if you haven't already: -``` -npm i -g wrangler +```sh +npm i -D wrangler wrangler login ``` Then, you can build your app and deploy it: ```sh -wrangler publish +wrangler deploy ``` -## Custom config +## Runtime APIs -If you would like to use a config file other than `wrangler.toml`, you can do like so: +The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with [`ctx`](https://developers.cloudflare.com/workers/runtime-apis/context/), [`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/), and [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties), meaning that you can access it in hooks and endpoints: ```js -// @errors: 2307 -/// file: svelte.config.js -import adapter from '@sveltejs/adapter-cloudflare-workers'; +// @filename: ambient.d.ts +import { DurableObjectNamespace } from '@cloudflare/workers-types'; -export default { - kit: { - adapter: adapter({ config: '.toml' }) +declare global { + namespace App { + interface Platform { + env: { + YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; + }; + } } -}; -``` - -## Bindings - -The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/platform/environment-variables/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with `context` and `caches`, meaning that you can access it in hooks and endpoints: - -```js -// @errors: 7031 +} +// @filename: +server.js +// ---cut--- +// @errors: 2355 2322 +/// file: +server.js +/** @type {import('./$types').RequestHandler} */ export async function POST({ request, platform }) { - const x = platform.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); + const x = platform?.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); } ``` -> SvelteKit's built-in `$env` module should be preferred for environment variables. +> [!NOTE] SvelteKit's built-in [`$env` module]($env-static-private) should be preferred for environment variables. -To make these types available to your app, reference them in your `src/app.d.ts`: +To make these types available to your app, install [`@cloudflare/workers-types`](https://www.npmjs.com/package/@cloudflare/workers-types) and reference them in your `src/app.d.ts`: -```diff +```ts /// file: src/app.d.ts ++++import { KVNamespace, DurableObjectNamespace } from '@cloudflare/workers-types';+++ + declare global { namespace App { interface Platform { -+ env?: { -+ YOUR_KV_NAMESPACE: KVNamespace; -+ YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; -+ }; ++++ env?: { + YOUR_KV_NAMESPACE: KVNamespace; + YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; + };+++ } } } @@ -110,14 +129,27 @@ export {}; ### Testing Locally -`platform.env` is only available in the final build and not in dev mode. For testing the build, you can use [wrangler](https://developers.cloudflare.com/workers/cli-wrangler). Once you have built your site, run `wrangler dev`. Ensure you have your [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) in your `wrangler.toml`. Wrangler version 3 is recommended. +Cloudflare Workers specific values in the `platform` property are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `platform.env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. + +For testing the build, you should use [Wrangler](https://developers.cloudflare.com/workers/wrangler/) version 4. Once you have built your site, run `wrangler dev`. ## Troubleshooting +### Node.js compatibility + +If you would like to enable [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/), you can add the `nodejs_compat` compatibility flag to your Wrangler configuration file: + +```jsonc +/// file: wrangler.jsonc +{ + "compatibility_flags": ["nodejs_compat"] +} +``` + ### Worker size limits -When deploying to workers, the server generated by SvelteKit is bundled into a single file. Wrangler will fail to publish your worker if it exceeds [the size limits](https://developers.cloudflare.com/workers/platform/limits/#worker-size) after minification. You're unlikely to hit this limit usually, but some large libraries can cause this to happen. In that case, you can try to reduce the size of your worker by only importing such libraries on the client side. See [the FAQ](./faq#how-do-i-use-x-with-sveltekit-how-do-i-use-a-client-side-only-library-that-depends-on-document-or-window) for more information. +When deploying your application, the server generated by SvelteKit is bundled into a single file. Wrangler will fail to publish your worker if it exceeds [the size limits](https://developers.cloudflare.com/workers/platform/limits/#worker-size) after minification. You're unlikely to hit this limit usually, but some large libraries can cause this to happen. In that case, you can try to reduce the size of your worker by only importing such libraries on the client side. See [the FAQ](./faq#How-do-I-use-a-client-side-library-accessing-document-or-window) for more information. ### Accessing the file system -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. +You can't use `fs` in Cloudflare Workers — you must [prerender](page-options#prerender) the routes in question. diff --git a/documentation/docs/25-build-and-deploy/80-adapter-netlify.md b/documentation/docs/25-build-and-deploy/80-adapter-netlify.md index 168f1bdc073d..78bdeaefa1fb 100644 --- a/documentation/docs/25-build-and-deploy/80-adapter-netlify.md +++ b/documentation/docs/25-build-and-deploy/80-adapter-netlify.md @@ -2,7 +2,7 @@ title: Netlify --- -To deploy to Netlify, use [`adapter-netlify`](https://github.com/sveltejs/kit/tree/master/packages/adapter-netlify). +To deploy to Netlify, use [`adapter-netlify`](https://github.com/sveltejs/kit/tree/main/packages/adapter-netlify). This adapter will be installed by default when you use [`adapter-auto`](adapter-auto), but adding it to your project allows you to specify Netlify-specific options. @@ -11,11 +11,11 @@ This adapter will be installed by default when you use [`adapter-auto`](adapter- Install with `npm i -D @sveltejs/adapter-netlify`, then add the adapter to your `svelte.config.js`: ```js -// @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-netlify'; -export default { +/** @type {import('@sveltejs/kit').Config} */ +const config = { kit: { // default options are shown adapter: adapter({ @@ -30,6 +30,8 @@ export default { }) } }; + +export default config; ``` 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: @@ -44,18 +46,18 @@ If the `netlify.toml` file or the `build.publish` value is missing, a default va ### Node version -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. +New projects will use the current Node LTS version 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 a current Node version. ## Netlify Edge Functions -SvelteKit supports [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 Node-based Netlify Functions. +SvelteKit supports [Netlify Edge Functions](https://docs.netlify.com/build/edge-functions/overview/). 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 Node-based Netlify Functions. ```js -// @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-netlify'; -export default { +/** @type {import('@sveltejs/kit').Config} */ +const config = { kit: { adapter: adapter({ // will create a Netlify Edge Function using Deno-based @@ -64,12 +66,18 @@ export default { }) } }; + +export default config; ``` ## Netlify alternatives to SvelteKit functionality 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. +### `_headers` and `_redirects` + +The [`_headers`](https://docs.netlify.com/routing/headers/#syntax-for-the-headers-file) and [`_redirects`](https://docs.netlify.com/routing/redirects/redirect-options/) files specific to Netlify can be used for static asset responses (like images) by putting them into the project root folder. + ### Redirect rules During compilation, redirect rules are automatically appended to your `_redirects` file. (If it doesn't exist yet, it will be created.) That means: @@ -80,7 +88,7 @@ During compilation, redirect rules are automatically appended to your `_redirect ### Netlify Forms 1. Create your Netlify HTML form as described [here](https://docs.netlify.com/forms/setup/#html-forms), e.g. as `/routes/contact/+page.svelte`. (Don't forget to add the hidden `form-name` input element!) -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. +2. Netlify's build bot parses your HTML files at deploy time, which means your form must be [prerendered](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. 3. If your Netlify form has a [custom success message](https://docs.netlify.com/forms/setup/#success-messages) like `
` then ensure the corresponding `/routes/success/+page.svelte` exists and is prerendered. ### Netlify Functions @@ -88,10 +96,15 @@ During compilation, redirect rules are automatically appended to your `_redirect 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`. ```js -// @errors: 2705 7006 +// @errors: 2339 +// @filename: ambient.d.ts +/// +// @filename: +page.server.js +// ---cut--- /// file: +page.server.js +/** @type {import('./$types').PageServerLoad} */ export const load = async (event) => { - const context = event.platform.context; + const context = event.platform?.context; console.log(context); // shows up in your functions log in the Netlify app }; ``` @@ -111,4 +124,8 @@ Additionally, you can add your own Netlify functions by creating a directory for ### Accessing the file system -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. +You can't use `fs` in edge deployments. + +You _can_ use it in serverless deployments, but it won't work as expected, since files are not copied from your project into your deployment. Instead, use the [`read`]($app-server#read) function from `$app/server` to access your files. It also works inside edge deployments by fetching the file from the deployed public assets location. + +Alternatively, you can [prerender](page-options#prerender) the routes in question. diff --git a/documentation/docs/25-build-and-deploy/90-adapter-vercel.md b/documentation/docs/25-build-and-deploy/90-adapter-vercel.md index 6f72025b9565..50f25f4a9299 100644 --- a/documentation/docs/25-build-and-deploy/90-adapter-vercel.md +++ b/documentation/docs/25-build-and-deploy/90-adapter-vercel.md @@ -2,7 +2,7 @@ title: Vercel --- -To deploy to Vercel, use [`adapter-vercel`](https://github.com/sveltejs/kit/tree/master/packages/adapter-vercel). +To deploy to Vercel, use [`adapter-vercel`](https://github.com/sveltejs/kit/tree/main/packages/adapter-vercel). This adapter will be installed by default when you use [`adapter-auto`](adapter-auto), but adding it to your project allows you to specify Vercel-specific options. @@ -11,46 +11,39 @@ This adapter will be installed by default when you use [`adapter-auto`](adapter- Install with `npm i -D @sveltejs/adapter-vercel`, then add the adapter to your `svelte.config.js`: ```js -// @errors: 2307 2345 /// file: svelte.config.js import adapter from '@sveltejs/adapter-vercel'; -export default { +/** @type {import('@sveltejs/kit').Config} */ +const config = { kit: { adapter: adapter({ - // see the 'Deployment configuration' section below + // see below for options that can be set here }) } }; + +export default config; ``` ## Deployment configuration To control how your routes are deployed to Vercel as functions, you can specify deployment configuration, either through the option shown above or with [`export const config`](page-options#config) inside `+server.js`, `+page(.server).js` and `+layout(.server).js` files. -For example you could deploy some parts of your app as [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions)... +For example you could deploy one specific route as an individual serverless function, separate from the rest of your app: ```js /// file: about/+page.js /** @type {import('@sveltejs/adapter-vercel').Config} */ export const config = { - runtime: 'edge' -}; -``` - -...and others as [Serverless Functions](https://vercel.com/docs/concepts/functions/serverless-functions) (note that by specifying `config` inside a layout, it applies to all child pages): - -```js -/// file: admin/+layout.js -/** @type {import('@sveltejs/adapter-vercel').Config} */ -export const config = { - runtime: 'nodejs18.x' + split: true }; ``` The following options apply to all functions: -- `runtime`: `'edge'`, `'nodejs16.x'` or `'nodejs18.x'`. By default, the adapter will select `'nodejs16.x'` or `'nodejs18.x'` depending on the Node version your project is configured to use on the Vercel dashboard +- `runtime`: `'edge'`, `'nodejs20.x'` or `'nodejs22.x'`. By default, the adapter will select the `'nodejs.x'` corresponding to the Node version your project is configured to use on the Vercel dashboard + > [!NOTE] This option is deprecated and will be removed in a future version, at which point all your functions will use whichever Node version is specified in the project configuration on Vercel - `regions`: an array of [edge network regions](https://vercel.com/docs/concepts/edge-network/regions) (defaulting to `["iad1"]` for serverless functions) or `'all'` if `runtime` is `edge` (its default). Note that multiple regions for serverless functions are only supported on Enterprise plans - `split`: if `true`, causes a route to be deployed as an individual function. If `split` is set to `true` at the adapter level, all routes will be deployed as individual functions @@ -59,55 +52,98 @@ Additionally, the following option applies to edge functions: And the following option apply to serverless functions: - `memory`: the amount of memory available to the function. Defaults to `1024` Mb, and can be decreased to `128` Mb or [increased](https://vercel.com/docs/concepts/limits/overview#serverless-function-memory) in 64Mb increments up to `3008` Mb on Pro or Enterprise accounts -- `maxDuration`: maximum execution duration of the function. Defaults to `10` seconds for Hobby accounts, `15` for Pro and `900` for Enterprise +- `maxDuration`: [maximum execution duration](https://vercel.com/docs/functions/runtimes#max-duration) of the function. Defaults to `10` seconds for Hobby accounts, `15` for Pro and `900` for Enterprise - `isr`: configuration Incremental Static Regeneration, described below +Configuration set in a layout applies to all the routes beneath that layout, unless overridden at a more granular level. + If your functions need to access data in a specific region, it's recommended that they be deployed in the same region (or close to it) for optimal performance. +## Image Optimization + +You may set the `images` config to control how Vercel builds your images. See the [image configuration reference](https://vercel.com/docs/build-output-api/v3/configuration#images) for full details. As an example, you may set: + +```js +/// file: svelte.config.js +import adapter from '@sveltejs/adapter-vercel'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter({ + images: { + sizes: [640, 828, 1200, 1920, 3840], + formats: ['image/avif', 'image/webp'], + minimumCacheTTL: 300, + domains: ['example-app.vercel.app'], + } + }) + } +}; + +export default config; +``` + ## Incremental Static Regeneration -Vercel supports [Incremental Static Regeneration](https://vercel.com/docs/concepts/incremental-static-regeneration/overview) (ISR), which provides the performance and cost advantages of prerendered content with the flexibility of dynamically rendered content. +Vercel supports [Incremental Static Regeneration](https://vercel.com/docs/incremental-static-regeneration) (ISR), which provides the performance and cost advantages of prerendered content with the flexibility of dynamically rendered content. + +> [!NOTE] Use ISR only on routes where every visitor should see the same content (much like when you prerender). If there's anything user-specific happening (like session cookies), they should happen on the client via JavaScript only to not leak sensitive information across visits To add ISR to a route, include the `isr` property in your `config` object: ```js -/// file: blog/[slug]/+page.server.js -// @filename: ambient.d.ts -declare module '$env/static/private' { - export const BYPASS_TOKEN: string; -} - -// @filename: index.js -// ---cut--- import { BYPASS_TOKEN } from '$env/static/private'; +/** @type {import('@sveltejs/adapter-vercel').Config} */ export const config = { isr: { - // Expiration time (in seconds) before the cached asset will be re-generated by invoking the Serverless Function. - // Setting the value to `false` means it will never expire. expiration: 60, - - // Random token that can be provided in the URL to bypass the cached version of the asset, by requesting the asset - // with a __prerender_bypass= cookie. - // - // Making a `GET` or `HEAD` request with `x-prerender-revalidate: ` will force the asset to be re-validated. bypassToken: BYPASS_TOKEN, - - // List of valid query parameters. Other parameters (such as utm tracking codes) will be ignored, - // ensuring that they do not result in content being regenerated unnecessarily allowQuery: ['search'] } }; ``` -The `expiration` property is required; all others are optional. +> [!NOTE] Using ISR on a route with `export const prerender = true` will have no effect, since the route is prerendered at build time + +The `expiration` property is required; all others are optional. The properties are discussed in more detail below. + +### expiration + +The expiration time (in seconds) before the cached asset will be re-generated by invoking the Serverless Function. Setting the value to `false` means it will never expire. In that case, you likely want to define a bypass token to re-generate on demand. + +### bypassToken + +A random token that can be provided in the URL to bypass the cached version of the asset, by requesting the asset with a `__prerender_bypass=` cookie. + +Making a `GET` or `HEAD` request with `x-prerender-revalidate: ` will force the asset to be re-validated. + +Note that the `BYPASS_TOKEN` string must be at least 32 characters long. You could generate one using the JavaScript console like so: + +```js +crypto.randomUUID(); +``` + +Set this string as an environment variable on Vercel by logging in and going to your project then Settings > Environment Variables. For "Key" put `BYPASS_TOKEN` and for "value" use the string generated above, then hit "Save". + +To get this key known about for local development, you can use the [Vercel CLI](https://vercel.com/docs/cli/env) by running the `vercel env pull` command locally like so: + +```sh +vercel env pull .env.development.local +``` + +### allowQuery + +A list of valid query parameters that contribute to the cache key. Other parameters (such as utm tracking codes) will be ignored, ensuring that they do not result in content being re-generated unnecessarily. By default, query parameters are ignored. + +> [!NOTE] Pages that are [prerendered](page-options#prerender) will ignore ISR configuration. ## Environment variables Vercel makes a set of [deployment-specific environment variables](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) available. Like other environment variables, these are accessible from `$env/static/private` and `$env/dynamic/private` (sometimes — more on that later), and inaccessible from their public counterparts. To access one of these variables from the client: ```js -// @errors: 2305 /// file: +layout.server.js import { VERCEL_COMMIT_REF } from '$env/static/private'; @@ -122,8 +158,8 @@ export function load() { ```svelte

This staging environment was deployed from {data.deploymentGitBranch}.

@@ -131,18 +167,38 @@ export function load() { Since all of these variables are unchanged between build time and run time when building on Vercel, we recommend using `$env/static/private` — which will statically replace the variables, enabling optimisations like dead code elimination — rather than `$env/dynamic/private`. +## Skew protection + +When a new version of your app is deployed, assets belonging to the previous version may no longer be accessible. If a user is actively using your app when this happens, it can cause errors when they navigate — this is known as _version skew_. SvelteKit mitigates this by detecting errors resulting from version skew and causing a hard reload to get the latest version of the app, but this will cause any client-side state to be lost. (You can also proactively mitigate it by observing [`updated.current`]($app-state#updated) from `$app/state`, which tells clients when a new version has been deployed.) + +[Skew protection](https://vercel.com/docs/deployments/skew-protection) is a Vercel feature that routes client requests to their original deployment. When a user visits your app, a cookie is set with the deployment ID, and any subsequent requests will be routed to that deployment for as long as skew protection is active. When they reload the page, they will get the newest deployment. (`updated.current` is exempted from this behaviour, and so will continue to report new deployments.) To enable it, visit the Advanced section of your project settings on Vercel. + +Cookie-based skew protection comes with one caveat: if a user has multiple versions of your app open in multiple tabs, requests from older versions will be routed to the newer one, meaning they will fall back to SvelteKit's built-in skew protection. + ## Notes +### Vercel utilities + +If you need Vercel-specific utilities like `waitUntil`, use the package [`@vercel/functions`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package). + ### Vercel functions -If you have Vercel functions contained in the `api` directory at the project's root, any requests for `/api/*` will _not_ be handled by SvelteKit. You should implement these as [API routes](https://kit.svelte.dev/docs/routing#server) in your SvelteKit app instead, unless you need to use a non-JavaScript language in which case you will need to ensure that you don't have any `/api/*` routes in your SvelteKit app. +If you have Vercel functions contained in the `api` directory at the project's root, any requests for `/api/*` will _not_ be handled by SvelteKit. You should implement these as [API routes](routing#server) in your SvelteKit app instead, unless you need to use a non-JavaScript language in which case you will need to ensure that you don't have any `/api/*` routes in your SvelteKit app. ### Node version -Projects created before a certain date will default to using Node 14, while SvelteKit requires Node 16 or later. You can [change the Node version in your project settings](https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js#node.js-version). +Projects created before a certain date may default to using an older Node version than what SvelteKit currently requires. You can [change the Node version in your project settings](https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js#node.js-version). ## Troubleshooting ### Accessing the file system -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. +You can't use `fs` in edge functions. + +You _can_ use it in serverless functions, but it won't work as expected, since files are not copied from your project into your deployment. Instead, use the [`read`]($app-server#read) function from `$app/server` to access your files. It also works inside routes deployed as edge functions by fetching the file from the deployed public assets location. + +Alternatively, you can [prerender](page-options#prerender) the routes in question. + +### Deployment protection + +If using [`read`]($app-server#read) in an edge function, SvelteKit will `fetch` the file in question from your deployment. If you are using [Deployment Protection](https://vercel.com/docs/deployment-protection), you must also enable [Protection Bypass for Automation](https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation) so that the request does not result in a [401 Unauthorized](https://http.dog/401) response. diff --git a/documentation/docs/25-build-and-deploy/99-writing-adapters.md b/documentation/docs/25-build-and-deploy/99-writing-adapters.md index 25b22f4037fc..56723f7de3f5 100644 --- a/documentation/docs/25-build-and-deploy/99-writing-adapters.md +++ b/documentation/docs/25-build-and-deploy/99-writing-adapters.md @@ -2,11 +2,12 @@ title: Writing adapters --- -If an adapter for your preferred environment doesn't yet exist, you can build your own. 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. +If an adapter for your preferred environment doesn't yet exist, you can build your own. We recommend [looking at the source for an adapter](https://github.com/sveltejs/kit/tree/main/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`: +Adapter packages implement the following API, which creates an `Adapter`: ```js +// @errors: 2322 // @filename: ambient.d.ts type AdapterSpecificOptions = any; @@ -19,6 +20,25 @@ export default function (options) { name: 'adapter-package-name', async adapt(builder) { // adapter implementation + }, + async emulate() { + return { + async platform({ config, prerender }) { + // the returned object becomes `event.platform` during dev, build and + // preview. Its shape is that of `App.Platform` + } + } + }, + supports: { + read: ({ config, route }) => { + // Return `true` if the route with the given `config` can use `read` + // from `$app/server` in production, return `false` if it can't. + // Or throw a descriptive error describing how to configure the deployment + }, + tracing: () => { + // Return `true` if this adapter supports loading `tracing.server.js`. + // Return `false if it can't, or throw a descriptive error. + } } }; @@ -26,6 +46,8 @@ export default function (options) { } ``` +Of these, `name` and `adapt` are required. `emulate` and `supports` are optional. + Within the `adapt` method, there are a number of things that an adapter should do: - Clear out the build directory @@ -33,7 +55,7 @@ Within the `adapt` method, there are a number of things that an adapter should d - Output code that: - Imports `Server` from `${builder.getServerDirectory()}/index.js` - Instantiates the app with a manifest generated with `builder.generateManifest({ relativePath })` - - Listens for requests from the platform, converts them to a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) if necessary, calls the `server.respond(request, { getClientAddress })` function to generate a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) and responds with it + - Listens for requests from the platform, converts them to a standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) if necessary, calls the `server.respond(request, { getClientAddress })` function to generate a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) and responds with it - expose any platform-specific information to SvelteKit via the `platform` option passed to `server.respond` - Globally shims `fetch` to work on the target platform, if necessary. SvelteKit provides a `@sveltejs/kit/node/polyfills` helper for platforms that can use `undici` - Bundle the output to avoid needing to install dependencies on the target platform, if necessary diff --git a/documentation/docs/25-build-and-deploy/index.md b/documentation/docs/25-build-and-deploy/index.md new file mode 100644 index 000000000000..2c342fe66060 --- /dev/null +++ b/documentation/docs/25-build-and-deploy/index.md @@ -0,0 +1,3 @@ +--- +title: Build and deploy +--- diff --git a/documentation/docs/25-build-and-deploy/meta.json b/documentation/docs/25-build-and-deploy/meta.json deleted file mode 100644 index 528879bbebda..000000000000 --- a/documentation/docs/25-build-and-deploy/meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Build and deploy" -} diff --git a/documentation/docs/30-advanced/10-advanced-routing.md b/documentation/docs/30-advanced/10-advanced-routing.md index ca84a27df22b..dd1f5b7b5420 100644 --- a/documentation/docs/30-advanced/10-advanced-routing.md +++ b/documentation/docs/30-advanced/10-advanced-routing.md @@ -6,29 +6,29 @@ title: Advanced routing If the number of route segments is unknown, you can use rest syntax — for example you might implement GitHub's file viewer like so... -```bash +```sh /[org]/[repo]/tree/[branch]/[...file] ``` -...in which case a request for `/sveltejs/kit/tree/master/documentation/docs/04-advanced-routing.md` would result in the following parameters being available to the page: +...in which case a request for `/sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md` would result in the following parameters being available to the page: ```js // @noErrors { org: 'sveltejs', repo: 'kit', - branch: 'master', + branch: 'main', file: 'documentation/docs/04-advanced-routing.md' } ``` -> `src/routes/a/[...rest]/z/+page.svelte` will match `/a/z` (i.e. there's no parameter at all) as well as `/a/b/z` and `/a/b/c/z` and so on. Make sure you check that the value of the rest parameter is valid, for example using a [matcher](#matching). +> [!NOTE] `src/routes/a/[...rest]/z/+page.svelte` will match `/a/z` (i.e. there's no parameter at all) as well as `/a/b/z` and `/a/b/c/z` and so on. Make sure you check that the value of the rest parameter is valid, for example using a [matcher](#Matching). ### 404 pages Rest parameters also allow you to render custom 404s. Given these routes... -``` +```tree src/routes/ ├ marx-brothers/ │ ├ chico/ @@ -40,10 +40,10 @@ src/routes/ ...the `marx-brothers/+error.svelte` file will _not_ be rendered if you visit `/marx-brothers/karl`, because no route was matched. If you want to render the nested error page, you should create a route that matches any `/marx-brothers/*` request, and return a 404 from it: -```diff +```tree src/routes/ ├ marx-brothers/ -+| ├ [...path]/ ++++| ├ [...path]/+++ │ ├ chico/ │ ├ harpo/ │ ├ groucho/ @@ -57,11 +57,11 @@ import { error } from '@sveltejs/kit'; /** @type {import('./$types').PageLoad} */ export function load(event) { - throw error(404, 'Not Found'); + error(404, 'Not Found'); } ``` -> If you don't handle 404 cases, they will appear in [`handleError`](hooks#shared-hooks-handleerror) +> [!NOTE] If you don't handle 404 cases, they will appear in [`handleError`](hooks#Shared-hooks-handleError) ## Optional parameters @@ -71,34 +71,37 @@ Note that an optional route parameter cannot follow a rest parameter (`[...rest] ## Matching -A route like `src/routes/archive/[page]` would match `/archive/3`, but it would also match `/archive/potato`. We don't want that. You can ensure that route parameters are well-formed by adding a _matcher_ — which takes the parameter string (`"3"` or `"potato"`) and returns `true` if it is valid — to your [`params`](configuration#files) directory... +A route like `src/routes/fruits/[page]` would match `/fruits/apple`, but it would also match `/fruits/rocketship`. We don't want that. You can ensure that route parameters are well-formed by adding a _matcher_ — which takes the parameter string (`"apple"` or `"rocketship"`) and returns `true` if it is valid — to your [`params`](configuration#files) directory... ```js -/// file: src/params/integer.js -/** @type {import('@sveltejs/kit').ParamMatcher} */ +/// file: src/params/fruit.js +/** + * @param {string} param + * @return {param is ('apple' | 'orange')} + * @satisfies {import('@sveltejs/kit').ParamMatcher} + */ export function match(param) { - return /^\d+$/.test(param); + return param === 'apple' || param === 'orange'; } ``` ...and augmenting your routes: -```diff --src/routes/archive/[page] -+src/routes/archive/[page=integer] +``` +src/routes/fruits/[page+++=fruit+++] ``` If the pathname doesn't match, SvelteKit will try to match other routes (using the sort order specified below), before eventually returning a 404. Each module in the `params` directory corresponds to a matcher, with the exception of `*.test.js` and `*.spec.js` files which may be used to unit test your matchers. -> Matchers run both on the server and in the browser. +> [!NOTE] Matchers run both on the server and in the browser. ## Sorting It's possible for multiple routes to match a given path. For example each of these routes would match `/foo-abc`: -```bash +```sh src/routes/[...catchall]/+page.svelte src/routes/[[a=x]]/+page.svelte src/routes/[b]/+page.svelte @@ -109,13 +112,13 @@ src/routes/foo-abc/+page.svelte SvelteKit needs to know which route is being requested. To do so, it sorts them according to the following rules... - More specific routes are higher priority (e.g. a route with no parameters is more specific than a route with one dynamic parameter, and so on) -- Parameters with [matchers](#matching) (`[name=type]`) are higher priority than those without (`[name]`) +- Parameters with [matchers](#Matching) (`[name=type]`) are higher priority than those without (`[name]`) - `[[optional]]` and `[...rest]` parameters are ignored unless they are the final part of the route, in which case they are treated with lowest priority. In other words `x/[[y]]/z` is treated equivalently to `x/z` for the purposes of sorting - Ties are resolved alphabetically ...resulting in this ordering, meaning that `/foo-abc` will invoke `src/routes/foo-abc/+page.svelte`, and `/foo-def` will invoke `src/routes/foo-[c]/+page.svelte` rather than less specific routes: -```bash +```sh src/routes/foo-abc/+page.svelte src/routes/foo-[c]/+page.svelte src/routes/[[a=x]]/+page.svelte @@ -162,7 +165,7 @@ src/routes/🤪/+page.svelte The format for a Unicode escape sequence is `[u+nnnn]` where `nnnn` is a valid value between `0000` and `10ffff`. (Unlike JavaScript string escaping, there's no need to use surrogate pairs to represent code points above `ffff`.) To learn more about Unicode encodings, consult [Programming with Unicode](https://unicodebook.readthedocs.io/unicode_encodings.html). -> Since TypeScript [struggles](https://github.com/microsoft/TypeScript/issues/13399) with directories with a leading `.` character, you may find it useful to encode these characters when creating e.g. [`.well-known`](https://en.wikipedia.org/wiki/Well-known_URI) routes: `src/routes/[x+2e]well-known/...` +> [!NOTE] Since TypeScript [struggles](https://github.com/microsoft/TypeScript/issues/13399) with directories with a leading `.` character, you may find it useful to encode these characters when creating e.g. [`.well-known`](https://en.wikipedia.org/wiki/Well-known_URI) routes: `src/routes/[x+2e]well-known/...` ## Advanced layouts @@ -172,13 +175,13 @@ By default, the _layout hierarchy_ mirrors the _route hierarchy_. In some cases, Perhaps you have some routes that are 'app' routes that should have one layout (e.g. `/dashboard` or `/item`), and others that are 'marketing' routes that should have a different layout (`/about` or `/testimonials`). We can group these routes with a directory whose name is wrapped in parentheses — unlike normal directories, `(app)` and `(marketing)` do not affect the URL pathname of the routes inside them: -```diff +```tree src/routes/ -+│ (app)/ ++++│ (app)/+++ │ ├ dashboard/ │ ├ item/ │ └ +layout.svelte -+│ (marketing)/ ++++│ (marketing)/+++ │ ├ about/ │ ├ testimonials/ │ └ +layout.svelte @@ -190,7 +193,7 @@ You can also put a `+page` directly inside a `(group)`, for example if `/` shoul ### Breaking out of layouts -The root layout applies to every page of your app — if omitted, it defaults to ``. If you want some pages to have a different layout hierarchy than the rest, then you can put your entire app inside one or more groups _except_ the routes that should not inherit the common layouts. +The root layout applies to every page of your app — if omitted, it defaults to `{@render children()}`. If you want some pages to have a different layout hierarchy than the rest, then you can put your entire app inside one or more groups _except_ the routes that should not inherit the common layouts. In the example above, the `/admin` route does not inherit either the `(app)` or `(marketing)` layouts. @@ -198,13 +201,13 @@ In the example above, the `/admin` route does not inherit either the `(app)` or Pages can break out of the current layout hierarchy on a route-by-route basis. Suppose we have an `/item/[id]/embed` route inside the `(app)` group from the previous example: -```diff +```tree src/routes/ ├ (app)/ │ ├ item/ │ │ ├ [id]/ │ │ │ ├ embed/ -+│ │ │ │ └ +page.svelte ++++│ │ │ │ └ +page.svelte+++ │ │ │ └ +layout.svelte │ │ └ +layout.svelte │ └ +layout.svelte @@ -218,13 +221,13 @@ Ordinarily, this would inherit the root layout, the `(app)` layout, the `item` l - `+page@(app).svelte` - inherits from `src/routes/(app)/+layout.svelte` - `+page@.svelte` - inherits from `src/routes/+layout.svelte` -```diff +```tree src/routes/ ├ (app)/ │ ├ item/ │ │ ├ [id]/ │ │ │ ├ embed/ -+│ │ │ │ └ +page@(app).svelte ++++│ │ │ │ └ +page@(app).svelte+++ │ │ │ └ +layout.svelte │ │ └ +layout.svelte │ └ +layout.svelte @@ -257,11 +260,11 @@ Not all use cases are suited for layout grouping, nor should you feel compelled - + {@render children()} ``` @@ -284,4 +287,4 @@ export function load(event) { ## Further reading -- [Tutorial: Advanced Routing](https://learn.svelte.dev/tutorial/optional-params) +- [Tutorial: Advanced Routing](/tutorial/kit/optional-params) diff --git a/documentation/docs/30-advanced/20-hooks.md b/documentation/docs/30-advanced/20-hooks.md index 02496849b69b..2091e49794c7 100644 --- a/documentation/docs/30-advanced/20-hooks.md +++ b/documentation/docs/30-advanced/20-hooks.md @@ -4,14 +4,15 @@ title: Hooks 'Hooks' are app-wide functions you declare that SvelteKit will call in response to specific events, giving you fine-grained control over the framework's behaviour. -There are two hooks files, both optional: +There are three hooks files, all optional: - `src/hooks.server.js` — your app's server hooks - `src/hooks.client.js` — your app's client hooks +- `src/hooks.js` — your app's hooks that run on both the client and server Code in these modules will run when the application starts up, making them useful for initializing database clients and so on. -> You can configure the location of these files with [`config.kit.files.hooks`](configuration#files). +> [!NOTE] You can configure the location of these files with [`config.kit.files.hooks`](configuration#files). ## Server hooks @@ -19,7 +20,7 @@ The following hooks can be added to `src/hooks.server.js`: ### handle -This function runs every time the SvelteKit server receives a [request](web-standards#fetch-apis-request) — whether that happens while the app is running, or during [prerendering](page-options#prerender) — and determines the [response](web-standards#fetch-apis-response). It receives an `event` object representing the request and a function called `resolve`, which renders the route and generates a `Response`. This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example). +This function runs every time the SvelteKit server receives a [request](web-standards#Fetch-APIs-Request) — whether that happens while the app is running, or during [prerendering](page-options#prerender) — and determines the [response](web-standards#Fetch-APIs-Response). It receives an `event` object representing the request and a function called `resolve`, which renders the route and generates a `Response`. This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example). ```js /// file: src/hooks.server.js @@ -34,9 +35,15 @@ export async function handle({ event, resolve }) { } ``` -> Requests for static assets — which includes pages that were already prerendered — are _not_ handled by SvelteKit. +> [!NOTE] Requests for static assets — which includes pages that were already prerendered — are _not_ handled by SvelteKit. -If unimplemented, defaults to `({ event, resolve }) => resolve(event)`. To add custom data to the request, which is passed to handlers in `+server.js` and server `load` functions, populate the `event.locals` object, as shown below. +If unimplemented, defaults to `({ event, resolve }) => resolve(event)`. + +During prerendering, SvelteKit crawls your pages for links and renders each route it finds. Rendering the route invokes the `handle` function (and all other route dependencies, like `load`). If you need to exclude some code from running during this phase, check that the app is not [`building`]($app-environment#building) beforehand. + +### locals + +To add custom data to the request, which is passed to handlers in `+server.js` and server `load` functions, populate the `event.locals` object, as shown below. ```js /// file: src/hooks.server.js @@ -60,13 +67,20 @@ export async function handle({ event, resolve }) { event.locals.user = await getUserInformation(event.cookies.get('sessionid')); const response = await resolve(event); + + // Note that modifying response headers isn't always safe. + // Response objects can have immutable headers + // (e.g. Response.redirect() returned from an endpoint). + // Modifying immutable headers throws a TypeError. + // In that case, clone the response or avoid creating a + // response object with immutable headers. response.headers.set('x-custom-header', 'potato'); return response; } ``` -You can define multiple `handle` functions and execute them with [the `sequence` helper function](modules#sveltejs-kit-hooks). +You can define multiple `handle` functions and execute them with [the `sequence` helper function](@sveltejs-kit-hooks). `resolve` also supports a second, optional parameter that gives you more control over how the response will be rendered. That parameter is an object that can have the following fields: @@ -92,7 +106,7 @@ Note that `resolve(...)` will never throw an error, it will always return a `Pro ### handleFetch -This function allows you to modify (or replace) a `fetch` request that happens inside a `load` or `action` function that runs on the server (or during pre-rendering). +This function allows you to modify (or replace) the result of an [`event.fetch`](load#Making-fetch-requests) call that runs on the server (or during prerendering) inside an endpoint, `load`, `action`, `handle`, `handleError` or `reroute`. For example, your `load` function might make a request to a public URL like `https://api.yourapp.com` when the user performs a client-side navigation to the respective page, but during SSR it might make sense to hit the API directly (bypassing whatever proxies and load balancers sit between it and the public internet). @@ -112,13 +126,9 @@ export async function handleFetch({ request, fetch }) { } ``` -**Credentials** - -For same-origin requests, SvelteKit's `fetch` implementation will forward `cookie` and `authorization` headers unless the `credentials` option is set to `"omit"`. +Requests made with `event.fetch` follow the browser's credentials model — for same-origin requests, `cookie` and `authorization` headers are forwarded unless the `credentials` option is set to `"omit"`. For cross-origin requests, `cookie` will be included if the request URL belongs to a subdomain of the app — for example if your app is on `my-domain.com`, and your API is on `api.my-domain.com`, cookies will be included in the request. -For cross-origin requests, `cookie` will be included if the request URL belongs to a subdomain of the app — for example if your app is on `my-domain.com`, and your API is on `api.my-domain.com`, cookies will be included in the request. - -If your app and your API are on sibling subdomains — `www.my-domain.com` and `api.my-domain.com` for example — then a cookie belonging to a common parent domain like `my-domain.com` will _not_ be included, because SvelteKit has no way to know which domain the cookie belongs to. In these cases you will need to manually include the cookie using `handleFetch`: +There is one caveat: if your app and your API are on sibling subdomains — `www.my-domain.com` and `api.my-domain.com` for example — then a cookie belonging to a common parent domain like `my-domain.com` will _not_ be included, because SvelteKit has no way to know which domain the cookie belongs to. In these cases you will need to manually include the cookie using `handleFetch`: ```js /// file: src/hooks.server.js @@ -133,18 +143,52 @@ export async function handleFetch({ event, request, fetch }) { } ``` +### handleValidationError + +This hook is called when a remote function is called with an argument that does not match the provided [Standard Schema](https://standardschema.dev/). It must return an object matching the shape of [`App.Error`](types#Error). + +Say you have a remote function that expects a string as its argument ... + +```js +/// file: todos.remote.js +import * as v from 'valibot'; +import { query } from '$app/server'; + +export const getTodo = query(v.string(), (id) => { + // implementation... +}); +``` + +...but it is called with something that doesn't match the schema — such as a number (e.g `await getTodos(1)`) — then validation will fail, the server will respond with a [400 status code](https://http.dog/400), and the function will throw with the message 'Bad Request'. + +To customise this message and add additional properties to the error object, implement `handleValidationError`: + +```js +/// file: src/hooks.server.js +/** @type {import('@sveltejs/kit').HandleValidationError} */ +export function handleValidationError({ issues }) { + return { + message: 'No thank you' + }; +} +``` + +Be thoughtful about what information you expose here, as the most likely reason for validation to fail is that someone is sending malicious requests to your server. + ## Shared hooks The following can be added to `src/hooks.server.js` _and_ `src/hooks.client.js`: ### handleError -If an unexpected error is thrown during loading or rendering, this function will be called with the `error` and the `event`. This allows for two things: +If an [unexpected error](errors#Unexpected-errors) is thrown during loading, rendering, or from an endpoint, this function will be called with the `error`, `event`, `status` code and `message`. This allows for two things: - you can log the error -- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value becomes the value of `$page.error`. It defaults to `{ message: 'Not Found' }` in case of a 404 (you can detect them through `event.route.id` being `null`) and to `{ message: 'Internal Error' }` for everything else. To make this type-safe, you can customize the expected shape by declaring an `App.Error` interface (which must include `message: string`, to guarantee sensible fallback behavior). +- you can generate a custom representation of the error that is safe to show to users, omitting sensitive details like messages and stack traces. The returned value, which defaults to `{ message }`, becomes the value of `$page.error`. + +For errors thrown from your code (or library code called by your code) the status will be 500 and the message will be "Internal Error". While `error.message` may contain sensitive information that should not be exposed to users, `message` is safe (albeit meaningless to the average user). -The following code shows an example of typing the error shape as `{ message: string; errorId: string }` and returning it accordingly from the `handleError` functions: +To add more information to the `$page.error` object in a type-safe way, you can customize the expected shape by declaring an `App.Error` interface (which must include `message: string`, to guarantee sensible fallback behavior). This allows you to — for example — append a tracking ID for users to quote in correspondence with your technical support staff: ```ts /// file: src/app.d.ts @@ -162,25 +206,27 @@ export {}; ```js /// file: src/hooks.server.js -// @errors: 2322 +// @errors: 2322 2353 // @filename: ambient.d.ts -declare module '@sentry/node' { +declare module '@sentry/sveltekit' { export const init: (opts: any) => void; export const captureException: (error: any, opts: any) => void; } // @filename: index.js // ---cut--- -import * as Sentry from '@sentry/node'; -import crypto from 'crypto'; +import * as Sentry from '@sentry/sveltekit'; Sentry.init({/*...*/}) /** @type {import('@sveltejs/kit').HandleServerError} */ -export async function handleError({ error, event }) { +export async function handleError({ error, event, status, message }) { const errorId = crypto.randomUUID(); + // example integration with https://sentry.io/ - Sentry.captureException(error, { extra: { event, errorId } }); + Sentry.captureException(error, { + extra: { event, errorId, status } + }); return { message: 'Whoops!', @@ -191,24 +237,27 @@ export async function handleError({ error, event }) { ```js /// file: src/hooks.client.js -// @errors: 2322 +// @errors: 2322 2353 // @filename: ambient.d.ts -declare module '@sentry/svelte' { +declare module '@sentry/sveltekit' { export const init: (opts: any) => void; export const captureException: (error: any, opts: any) => void; } // @filename: index.js // ---cut--- -import * as Sentry from '@sentry/svelte'; +import * as Sentry from '@sentry/sveltekit'; Sentry.init({/*...*/}) /** @type {import('@sveltejs/kit').HandleClientError} */ -export async function handleError({ error, event }) { +export async function handleError({ error, event, status, message }) { const errorId = crypto.randomUUID(); + // example integration with https://sentry.io/ - Sentry.captureException(error, { extra: { event, errorId } }); + Sentry.captureException(error, { + extra: { event, errorId, status } + }); return { message: 'Whoops!', @@ -217,14 +266,108 @@ export async function handleError({ error, event }) { } ``` -> In `src/hooks.client.js`, the type of `handleError` is `HandleClientError` instead of `HandleServerError`, and `event` is a `NavigationEvent` rather than a `RequestEvent`. +> [!NOTE] In `src/hooks.client.js`, the type of `handleError` is `HandleClientError` instead of `HandleServerError`, and `event` is a `NavigationEvent` rather than a `RequestEvent`. -This function is not called for _expected_ errors (those thrown with the [`error`](modules#sveltejs-kit-error) function imported from `@sveltejs/kit`). +This function is not called for _expected_ errors (those thrown with the [`error`](@sveltejs-kit#error) function imported from `@sveltejs/kit`). During development, if an error occurs because of a syntax error in your Svelte code, the passed in error has a `frame` property appended highlighting the location of the error. -> Make sure that `handleError` _never_ throws an error +> [!NOTE] Make sure that `handleError` _never_ throws an error + +### init + +This function runs once, when the server is created or the app starts in the browser, and is a useful place to do asynchronous work such as initializing a database connection. + +> [!NOTE] If your environment supports top-level await, the `init` function is really no different from writing your initialisation logic at the top level of the module, but some environments — most notably, Safari — don't. + +```js +// @errors: 2307 +/// file: src/hooks.server.js +import * as db from '$lib/server/database'; + +/** @type {import('@sveltejs/kit').ServerInit} */ +export async function init() { + await db.connect(); +} +``` + +> [!NOTE] +> In the browser, asynchronous work in `init` will delay hydration, so be mindful of what you put in there. + +## Universal hooks + +The following can be added to `src/hooks.js`. Universal hooks run on both server and client (not to be confused with shared hooks, which are environment-specific). + +### reroute + +This function runs before `handle` and allows you to change how URLs are translated into routes. The returned pathname (which defaults to `url.pathname`) is used to select the route and its parameters. + +For example, you might have a `src/routes/[[lang]]/about/+page.svelte` page, which should be accessible as `/en/about` or `/de/ueber-uns` or `/fr/a-propos`. You could implement this with `reroute`: + +```js +// @errors: 2345 2304 +/// file: src/hooks.js + +/** @type {Record} */ +const translated = { + '/en/about': '/en/about', + '/de/ueber-uns': '/de/about', + '/fr/a-propos': '/fr/about', +}; + +/** @type {import('@sveltejs/kit').Reroute} */ +export function reroute({ url }) { + if (url.pathname in translated) { + return translated[url.pathname]; + } +} +``` + +The `lang` parameter will be correctly derived from the returned pathname. + +Using `reroute` will _not_ change the contents of the browser's address bar, or the value of `event.url`. + +Since version 2.18, the `reroute` hook can be asynchronous, allowing it to (for example) fetch data from your backend to decide where to reroute to. Use this carefully and make sure it's fast, as it will delay navigation otherwise. If you need to fetch data, use the `fetch` provided as an argument. It has the [same benefits](load#Making-fetch-requests) as the `fetch` provided to `load` functions, with the caveat that `params` and `id` are unavailable to [`handleFetch`](#Server-hooks-handleFetch) because the route is not yet known. + +```js +// @errors: 2345 2304 +/// file: src/hooks.js + +/** @type {import('@sveltejs/kit').Reroute} */ +export async function reroute({ url, fetch }) { + // Ask a special endpoint within your app about the destination + if (url.pathname === '/api/reroute') return; + + const api = new URL('/api/reroute', url); + api.searchParams.set('pathname', url.pathname); + + const result = await fetch(api).then(r => r.json()); + return result.pathname; +} +``` + + +> [!NOTE] `reroute` is considered a pure, idempotent function. As such, it must always return the same output for the same input and not have side effects. Under these assumptions, SvelteKit caches the result of `reroute` on the client so it is only called once per unique URL. + +### transport + +This is a collection of _transporters_, which allow you to pass custom types — returned from `load` and form actions — across the server/client boundary. Each transporter contains an `encode` function, which encodes values on the server (or returns a falsy value for anything that isn't an instance of the type) and a corresponding `decode` function: + +```js +// @errors: 2307 +/// file: src/hooks.js +import { Vector } from '$lib/math'; + +/** @type {import('@sveltejs/kit').Transport} */ +export const transport = { + Vector: { + encode: (value) => value instanceof Vector && [value.x, value.y], + decode: ([x, y]) => new Vector(x, y) + } +}; +``` + ## Further reading -- [Tutorial: Hooks](https://learn.svelte.dev/tutorial/handle) +- [Tutorial: Hooks](/tutorial/kit/handle) diff --git a/documentation/docs/30-advanced/25-errors.md b/documentation/docs/30-advanced/25-errors.md index 7a3ca93d70c0..17d7eabc8268 100644 --- a/documentation/docs/30-advanced/25-errors.md +++ b/documentation/docs/30-advanced/25-errors.md @@ -8,11 +8,11 @@ Errors are an inevitable fact of software development. SvelteKit handles errors SvelteKit distinguishes between expected and unexpected errors, both of which are represented as simple `{ message: string }` objects by default. -You can add additional properties, like a `code` or a tracking `id`, as shown in the examples below. (When using TypeScript this requires you to redefine the `Error` type as described in [type safety](errors#type-safety)). +You can add additional properties, like a `code` or a tracking `id`, as shown in the examples below. (When using TypeScript this requires you to redefine the `Error` type as described in [type safety](errors#Type-safety)). ## Expected errors -An _expected_ error is one created with the [`error`](modules#sveltejs-kit-error) helper imported from `@sveltejs/kit`: +An _expected_ error is one created with the [`error`](@sveltejs-kit#error) helper imported from `@sveltejs/kit`: ```js /// file: src/routes/blog/[slug]/+page.server.js @@ -31,7 +31,7 @@ export async function load({ params }) { const post = await db.getPost(params.slug); if (!post) { - throw error(404, { + error(404, { message: 'Not found' }); } @@ -40,33 +40,54 @@ export async function load({ params }) { } ``` -This tells SvelteKit to set the response status code to 404 and render an [`+error.svelte`](routing#error) component, where `$page.error` is the object provided as the second argument to `error(...)`. +This throws an exception that SvelteKit catches, causing it to set the response status code to 404 and render an [`+error.svelte`](routing#error) component, where `page.error` is the object provided as the second argument to `error(...)`. ```svelte -

{$page.error.message}

+

{page.error.message}

``` +> [!LEGACY] +> `$app/state` was added in SvelteKit 2.12. If you're using an earlier version or are using Svelte 4, use `$app/stores` instead. + You can add extra properties to the error object if needed... -```diff -throw error(404, { +```js +// @filename: ambient.d.ts +declare global { + namespace App { + interface Error { + message: string; + code: string; + } + } +} +export {} + +// @filename: index.js +import { error } from '@sveltejs/kit'; +// ---cut--- +error(404, { message: 'Not found', -+ code: 'NOT_FOUND' + +++code: 'NOT_FOUND'+++ }); ``` ...otherwise, for convenience, you can pass a string as the second argument: -```diff --throw error(404, { message: 'Not found' }); -+throw error(404, 'Not found'); +```js +import { error } from '@sveltejs/kit'; +// ---cut--- +---error(404, { message: 'Not found' });--- ++++error(404, 'Not found');+++ ``` +> [!NOTE] [In SvelteKit 1.x](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you) you had to `throw` the `error` yourself + ## Unexpected errors An _unexpected_ error is any other exception that occurs while handling a request. Since these can contain sensitive information, unexpected error messages and stack traces are not exposed to users. @@ -77,36 +98,7 @@ By default, unexpected errors are printed to the console (or, in production, you { "message": "Internal Error" } ``` -Unexpected errors will go through the [`handleError`](hooks#shared-hooks-handleerror) hook, where you can add your own error handling — for example, sending errors to a reporting service, or returning a custom error object. - -```js -/// file: src/hooks.server.js -// @errors: 2322 1360 2571 2339 -// @filename: ambient.d.ts -declare module '@sentry/node' { - export const init: (opts: any) => void; - export const captureException: (error: any, opts: any) => void; -} - -// @filename: index.js -// ---cut--- -import * as Sentry from '@sentry/node'; - -Sentry.init({/*...*/}) - -/** @type {import('@sveltejs/kit').HandleServerError} */ -export function handleError({ error, event }) { - // example integration with https://sentry.io/ - Sentry.captureException(error, { extra: { event } }); - - return { - message: 'Whoops!', - code: error?.code ?? 'UNKNOWN' - }; -} -``` - -> Make sure that `handleError` _never_ throws an error +Unexpected errors will go through the [`handleError`](hooks#Shared-hooks-handleError) hook, where you can add your own error handling — for example, sending errors to a reporting service, or returning a custom error object which becomes `$page.error`. ## Responses @@ -139,13 +131,13 @@ The exception is when the error occurs inside the root `+layout.js` or `+layout. If you're using TypeScript and need to customize the shape of errors, you can do so by declaring an `App.Error` interface in your app (by convention, in `src/app.d.ts`, though it can live anywhere that TypeScript can 'see'): -```diff +```ts /// file: src/app.d.ts declare global { namespace App { interface Error { -+ code: string; -+ id: string; ++++ code: string; + id: string;+++ } } } @@ -157,5 +149,5 @@ This interface always includes a `message: string` property. ## Further reading -- [Tutorial: Errors and redirects](https://learn.svelte.dev/tutorial/error-basics) -- [Tutorial: Hooks](https://learn.svelte.dev/tutorial/handle) +- [Tutorial: Errors and redirects](/tutorial/kit/error-basics) +- [Tutorial: Hooks](/tutorial/kit/handle) diff --git a/documentation/docs/30-advanced/30-link-options.md b/documentation/docs/30-advanced/30-link-options.md index 37d3c3a4c35f..bf368876ffa9 100644 --- a/documentation/docs/30-advanced/30-link-options.md +++ b/documentation/docs/30-advanced/30-link-options.md @@ -6,7 +6,7 @@ In SvelteKit, `` elements (rather than framework-specific `` components You can customise the behaviour of links with `data-sveltekit-*` attributes. These can be applied to the `` itself, or to a parent element. -These options also apply to `` elements with [`method="GET"`](form-actions#get-vs-post). +These options also apply to `` elements with [`method="GET"`](form-actions#GET-vs-POST). ## data-sveltekit-preload-data @@ -37,7 +37,7 @@ In these cases, you can specify the `"tap"` value, which causes SvelteKit to cal ``` -> You can also programmatically invoke `preloadData` from `$app/navigation`. +> [!NOTE] You can also programmatically invoke `preloadData` from `$app/navigation`. Data will never be preloaded if the user has chosen reduced data usage, meaning [`navigator.connection.saveData`](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/saveData) is `true`. @@ -52,7 +52,7 @@ Even in cases where you don't want to preload _data_ for a link, it can be benef Note that `viewport` and `eager` only apply to links that are present in the DOM immediately following navigation — if a link is added later (in an `{#if ...}` block, for example) it will not be preloaded until triggered by `hover` or `tap`. This is to avoid performance pitfalls resulting from aggressively observing the DOM for changes. -> Since preloading code is a prerequisite for preloading data, this attribute will only have an effect if it specifies a more eager value than any `data-sveltekit-preload-data` attribute that is present. +> [!NOTE] Since preloading code is a prerequisite for preloading data, this attribute will only have an effect if it specifies a more eager value than any `data-sveltekit-preload-data` attribute that is present. As with `data-sveltekit-preload-data`, this attribute will be ignored if the user has chosen reduced data usage. @@ -80,7 +80,7 @@ Sometimes you don't want navigation to create a new entry in the browser's sessi ## data-sveltekit-keepfocus -Sometimes you don't want [focus to be reset](accessibility#focus-management) after navigation. For example, maybe you have a search form that submits as the user is typing, and you want to keep focus on the text input. Adding a `data-sveltekit-keepfocus` attribute to it... +Sometimes you don't want [focus to be reset](accessibility#Focus-management) after navigation. For example, maybe you have a search form that submits as the user is typing, and you want to keep focus on the text input. Adding a `data-sveltekit-keepfocus` attribute to it... ```html @@ -122,8 +122,8 @@ To disable any of these options inside an element where they have been enabled, ``` -To apply an attribute to an element conditionally, do this (`"true"` and `"false"` are both accepted values): +To apply an attribute to an element conditionally, do this: -```html -
-``` \ No newline at end of file +```svelte +
+``` diff --git a/documentation/docs/30-advanced/40-service-workers.md b/documentation/docs/30-advanced/40-service-workers.md index 33c90f663289..45cf6da08aa7 100644 --- a/documentation/docs/30-advanced/40-service-workers.md +++ b/documentation/docs/30-advanced/40-service-workers.md @@ -6,7 +6,7 @@ Service workers act as proxy servers that handle network requests inside your ap In SvelteKit, if you have a `src/service-worker.js` file (or `src/service-worker/index.js`) it will be bundled and automatically registered. You can change the [location of your service worker](configuration#files) if you need to. -You can [disable automatic registration](configuration#serviceworker) if you need to register the service worker with your own logic or use another solution. The default registration looks something like this: +You can [disable automatic registration](configuration#serviceWorker) if you need to register the service worker with your own logic or use another solution. The default registration looks something like this: ```js if ('serviceWorker' in navigator) { @@ -18,15 +18,29 @@ if ('serviceWorker' in navigator) { ## Inside the service worker -Inside the service worker you have access to the [`$service-worker` module](modules#$service-worker), which provides you with the paths to all static assets, build files and prerendered pages. You're also provided with an app version string, which you can use for creating a unique cache name, and the deployment's `base` path. If your Vite config specifies `define` (used for global variable replacements), this will be applied to service workers as well as your server/client builds. +Inside the service worker you have access to the [`$service-worker` module]($service-worker), which provides you with the paths to all static assets, build files and prerendered pages. You're also provided with an app version string, which you can use for creating a unique cache name, and the deployment's `base` path. If your Vite config specifies `define` (used for global variable replacements), this will be applied to service workers as well as your server/client builds. The following example caches the built app and any files in `static` eagerly, and caches all other requests as they happen. This would make each page work offline once visited. ```js -// @errors: 2339 +/// file: src/service-worker.js +// Disables access to DOM typings like `HTMLElement` which are not available +// inside a service worker and instantiates the correct globals +/// +/// +/// + +// Ensures that the `$service-worker` import has proper type definitions /// + +// Only necessary if you have an import from `$env/static/public` +/// + import { build, files, version } from '$service-worker'; +// This gives `self` the correct types +const self = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (globalThis.self)); + // Create a unique cache name for this deployment const CACHE = `cache-${version}`; @@ -66,7 +80,11 @@ self.addEventListener('fetch', (event) => { // `build`/`files` can always be served from the cache if (ASSETS.includes(url.pathname)) { - return cache.match(url.pathname); + const response = await cache.match(url.pathname); + + if (response) { + return response; + } } // for everything else, try the network first, but @@ -74,13 +92,27 @@ self.addEventListener('fetch', (event) => { try { const response = await fetch(event.request); + // if we're offline, fetch can return a value that is not a Response + // instead of throwing - and we can't pass this non-Response to respondWith + if (!(response instanceof Response)) { + throw new Error('invalid response from fetch'); + } + if (response.status === 200) { cache.put(event.request, response.clone()); } return response; - } catch { - return cache.match(event.request); + } catch (err) { + const response = await cache.match(event.request); + + if (response) { + return response; + } + + // if there's no cache, then just error out + // as there is nothing we can do to respond to this request + throw err; } } @@ -88,7 +120,7 @@ self.addEventListener('fetch', (event) => { }); ``` -> Be careful when caching! In some cases, stale data might be worse than data that's unavailable while offline. Since browsers will empty caches if they get too full, you should also be careful about caching large assets like video files. +> [!NOTE] Be careful when caching! In some cases, stale data might be worse than data that's unavailable while offline. Since browsers will empty caches if they get too full, you should also be careful about caching large assets like video files. ## During development @@ -102,31 +134,12 @@ navigator.serviceWorker.register('/service-worker.js', { }); ``` -> `build` and `prerendered` are empty arrays during development - -## Type safety +> [!NOTE] `build` and `prerendered` are empty arrays during development -Setting up proper types for service workers requires some manual setup. Inside your `service-worker.js`, add the following to the top of your file: - -```original-js -/// -/// -/// -/// - -const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self)); -``` -```generated-ts -/// -/// -/// -/// - -const sw = self as unknown as ServiceWorkerGlobalScope; -``` +## Other solutions -This disables access to DOM typings like `HTMLElement` which are not available inside a service worker and instantiates the correct globals. The reassignment of `self` to `sw` allows you to type cast it in the process (there are a couple of ways to do this, but the easiest that requires no additional files). Use `sw` instead of `self` in the rest of the file. The reference to the SvelteKit types ensures that the `$service-worker` import has proper type definitions. +SvelteKit's service worker implementation is designed to be easy to work with and is probably a good solution for most users. However, outside of SvelteKit, many PWA applications leverage the [Workbox](https://web.dev/learn/pwa/workbox) library. If you're used to using Workbox you may prefer [Vite PWA plugin](https://vite-pwa-org.netlify.app/frameworks/sveltekit.html). -## Other solutions +## References -SvelteKit's service worker implementation is deliberately low-level. If you need a more full-flegded but also more opinionated solution, we recommend looking at solutions like [Vite PWA plugin](https://vite-pwa-org.netlify.app/frameworks/sveltekit.html), which uses [Workbox](https://web.dev/learn/pwa/workbox). For more general information on service workers, we recommend [the MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers). +For more general information on service workers, we recommend [the MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers). diff --git a/documentation/docs/30-advanced/50-server-only-modules.md b/documentation/docs/30-advanced/50-server-only-modules.md index 78f3a75da261..5bb98f340b3f 100644 --- a/documentation/docs/30-advanced/50-server-only-modules.md +++ b/documentation/docs/30-advanced/50-server-only-modules.md @@ -6,7 +6,11 @@ Like a good friend, SvelteKit keeps your secrets. When writing your backend and ## Private environment variables -The `$env/static/private` and `$env/dynamic/private` modules, which are covered in the [modules](modules) section, can only be imported into modules that only run on the server, such as [`hooks.server.js`](hooks#server-hooks) or [`+page.server.js`](routing#page-page-server-js). +The [`$env/static/private`]($env-static-private) and [`$env/dynamic/private`]($env-dynamic-private) modules can only be imported into modules that only run on the server, such as [`hooks.server.js`](hooks#Server-hooks) or [`+page.server.js`](routing#page-page.server.js). + +## Server-only utilities + +The [`$app/server`]($app-server) module, which contains a [`read`]($app-server#read) function for reading assets from the filesystem, can likewise only be imported by code that runs on the server. ## Your modules @@ -43,18 +47,21 @@ export const add = (a, b) => a + b; ...SvelteKit will error: ``` -Cannot import $lib/server/secrets.js into public-facing code: -- src/routes/+page.svelte - - src/routes/utils.js - - $lib/server/secrets.js +Cannot import $lib/server/secrets.ts into code that runs in the browser, as this could leak sensitive information. + + src/routes/+page.svelte imports + src/routes/utils.js imports + $lib/server/secrets.ts + +If you're only using the import as a type, change it to `import type`. ``` Even though the public-facing code — `src/routes/+page.svelte` — only uses the `add` export and not the secret `atlantisCoordinates` export, the secret code could end up in JavaScript that the browser downloads, and so the import chain is considered unsafe. -This feature also works with dynamic imports, even interpolated ones like ``await import(`./${foo}.js`)``, with one small caveat: during development, if there are two or more dynamic imports between the public-facing code and the server-only module, the illegal import will not be detected the first time the code is loaded. +This feature also works with dynamic imports, even interpolated ones like ``await import(`./${foo}.js`)``. -> Unit testing frameworks like Vitest do not distinguish between server-only and public-facing code. For this reason, illegal import detection is disabled when running tests, as determined by `process.env.TEST === 'true'`. +> [!NOTE] Unit testing frameworks like Vitest do not distinguish between server-only and public-facing code. For this reason, illegal import detection is disabled when running tests, as determined by `process.env.TEST === 'true'`. ## Further reading -- [Tutorial: Environment variables](https://learn.svelte.dev/tutorial/env-static-private) +- [Tutorial: Environment variables](/tutorial/kit/env-static-private) diff --git a/documentation/docs/30-advanced/60-assets.md b/documentation/docs/30-advanced/60-assets.md deleted file mode 100644 index 98edc45dd91b..000000000000 --- a/documentation/docs/30-advanced/60-assets.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Asset handling ---- - -## Caching and inlining - -[Vite will automatically process imported assets](https://vitejs.dev/guide/assets.html) for improved performance. Hashes will be added to the filenames so that they can be cached and assets smaller than `assetsInlineLimit` will be inlined. - -```html - - -The project logo -``` - -If you prefer to reference assets directly in the markup, you can use a preprocessor such as [svelte-preprocess-import-assets](https://github.com/bluwy/svelte-preprocess-import-assets). - -For assets included via the CSS `url()` function, you may find [`vitePreprocess`](https://kit.svelte.dev/docs/integrations#preprocessors-vitepreprocess) useful. - -## Transforming - -You may wish to transform your images to output compressed image formats such as `.webp` or `.avif`, responsive images with different sizes for different devices, or images with the EXIF data stripped for privacy. For images that are included statically, you may use a Vite plugin such as [vite-imagetools](https://github.com/JonasKruckenberg/imagetools). You may also consider a CDN, which can serve the appropriate transformed image based on the `Accept` HTTP header and query string parameters. diff --git a/documentation/docs/30-advanced/65-snapshots.md b/documentation/docs/30-advanced/65-snapshots.md index 2ea91be8b357..cc7477f0fe66 100644 --- a/documentation/docs/30-advanced/65-snapshots.md +++ b/documentation/docs/30-advanced/65-snapshots.md @@ -4,14 +4,14 @@ title: Snapshots Ephemeral DOM state — like scroll positions on sidebars, the content of `` elements and so on — is discarded when you navigate from one page to another. -For example, if the user fills out a form but clicks a link before submitting, then hits the browser's back button, the values they filled in will be lost. In cases where it's valuable to preserve that input, you can take a _snapshot_ of DOM state, which can then be restored if the user navigates back. +For example, if the user fills out a form but navigates away and then back before submitting, or if the user refreshes the page, the values they filled in will be lost. In cases where it's valuable to preserve that input, you can take a _snapshot_ of DOM state, which can then be restored if the user navigates back. To do this, export a `snapshot` object with `capture` and `restore` methods from a `+page.svelte` or `+layout.svelte`: ```svelte + +{#if page.state.showModal} + history.back()} /> +{/if} +``` + +The modal can be dismissed by navigating back (unsetting `page.state.showModal`) or by interacting with it in a way that causes the `close` callback to run, which will navigate back programmatically. + +## API + +The first argument to `pushState` is the URL, relative to the current URL. To stay on the current URL, use `''`. + +The second argument is the new page state, which can be accessed via the [page object]($app-state#page) as `page.state`. You can make page state type-safe by declaring an [`App.PageState`](types#PageState) interface (usually in `src/app.d.ts`). + +To set page state without creating a new history entry, use `replaceState` instead of `pushState`. + +> [!LEGACY] +> `page.state` from `$app/state` was added in SvelteKit 2.12. If you're using an earlier version or are using Svelte 4, use `$page.state` from `$app/stores` instead. + +## Loading data for a route + +When shallow routing, you may want to render another `+page.svelte` inside the current page. For example, clicking on a photo thumbnail could pop up the detail view without navigating to the photo page. + +For this to work, you need to load the data that the `+page.svelte` expects. A convenient way to do this is to use [`preloadData`]($app-navigation#preloadData) inside the `click` handler of an `` element. If the element (or a parent) uses [`data-sveltekit-preload-data`](link-options#data-sveltekit-preload-data), the data will have already been requested, and `preloadData` will reuse that request. + +```svelte + + + +{#each data.thumbnails as thumbnail} + { + if (innerWidth < 640 // bail if the screen is too small + || e.shiftKey // or the link is opened in a new window + || e.metaKey || e.ctrlKey // or a new tab (mac: metaKey, win/linux: ctrlKey) + // should also consider clicking with a mouse scroll wheel + ) return; + + // prevent navigation + e.preventDefault(); + + const { href } = e.currentTarget; + + // run `load` functions (or rather, get the result of the `load` functions + // that are already running because of `data-sveltekit-preload-data`) + const result = await preloadData(href); + + if (result.type === 'loaded' && result.status === 200) { + pushState(href, { selected: result.data }); + } else { + // something bad happened! try navigating + goto(href); + } + }} + > + {thumbnail.alt} + +{/each} + +{#if page.state.selected} + history.back()}> + + + +{/if} +``` + +## Caveats + +During server-side rendering, `page.state` is always an empty object. The same is true for the first page the user lands on — if the user reloads the page (or returns from another document), state will _not_ be applied until they navigate. + +Shallow routing is a feature that requires JavaScript to work. Be mindful when using it and try to think of sensible fallback behavior in case JavaScript isn't available. diff --git a/documentation/docs/30-advanced/68-observability.md b/documentation/docs/30-advanced/68-observability.md new file mode 100644 index 000000000000..cd58178a6063 --- /dev/null +++ b/documentation/docs/30-advanced/68-observability.md @@ -0,0 +1,100 @@ +--- +title: Observability +--- + +
+

Available since 2.31

+
+ +Sometimes, you may need to observe how your application is behaving in order to improve performance or find the root cause of a pesky bug. To help with this, SvelteKit can emit server-side [OpenTelemetry](https://opentelemetry.io) spans for the following: + +- The [`handle`](hooks#Server-hooks-handle) hook and `handle` functions running in a [`sequence`](@sveltejs-kit-hooks#sequence) (these will show up as children of each other and the root `handle` hook) +- Server [`load`](load) functions and universal `load` functions when they're run on the server +- [Form actions](form-actions) +- [Remote functions](remote-functions) + +Just telling SvelteKit to emit spans won't get you far, though — you need to actually collect them somewhere to be able to view them. SvelteKit provides `src/instrumentation.server.ts` as a place to write your tracing setup and instrumentation code. It's guaranteed to be run prior to your application code being imported, providing your deployment platform supports it and your adapter is aware of it. + +Both of these features are currently experimental, meaning they are likely to contain bugs and are subject to change without notice. You must opt in by adding the `kit.experimental.tracing.server` and `kit.experimental.instrumentation.server` option in your `svelte.config.js`: + +```js +/// file: svelte.config.js +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + experimental: { + +++tracing: { + server: true + }, + instrumentation: { + server: true + }+++ + } + } +}; + +export default config; +``` + +> [!NOTE] Tracing — and more significantly, observability instrumentation — can have a nontrivial overhead. Before you go all-in on tracing, consider whether or not you really need it, or if it might be more appropriate to turn it on in development and preview environments only. + +## Augmenting the built-in tracing + +SvelteKit provides access to the `root` span and the `current` span on the request event. The root span is the one associated with your root `handle` function, and the current span could be associated with `handle`, `load`, a form action, or a remote function, depending on the context. You can annotate these spans with any attributes you wish to record: + +```js +/// file: $lib/authenticate.ts + +// @filename: ambient.d.ts +declare module '$lib/auth-core' { + export function getAuthenticatedUser(): Promise<{ id: string }> +} + +// @filename: index.js +// ---cut--- +import { getRequestEvent } from '$app/server'; +import { getAuthenticatedUser } from '$lib/auth-core'; + +async function authenticate() { + const user = await getAuthenticatedUser(); + const event = getRequestEvent(); + event.tracing.root.setAttribute('userId', user.id); +} +``` + +## Development quickstart + +To view your first trace, you'll need to set up a local collector. We'll use [Jaeger](https://www.jaegertracing.io/docs/getting-started/) in this example, as they provide an easy-to-use quickstart command. Once your collector is running locally: + +- Turn on the experimental flags mentioned earlier in your `svelte.config.js` file +- Use your package manager to install the dependencies you'll need: + ```sh + npm i @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-proto import-in-the-middle + ``` +- Create `src/instrumentation.server.js` with the following: + +```js +/// file: src/instrumentation.server.js +import { NodeSDK } from '@opentelemetry/sdk-node'; +import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'; +import { createAddHookMessageChannel } from 'import-in-the-middle'; +import { register } from 'node:module'; + +const { registerOptions } = createAddHookMessageChannel(); +register('import-in-the-middle/hook.mjs', import.meta.url, registerOptions); + +const sdk = new NodeSDK({ + serviceName: 'test-sveltekit-tracing', + traceExporter: new OTLPTraceExporter(), + instrumentations: [getNodeAutoInstrumentations()] +}); + +sdk.start(); +``` + +Now, server-side requests will begin generating traces, which you can view in Jaeger's web console at [localhost:16686](http://localhost:16686). + +## `@opentelemetry/api` + +SvelteKit uses `@opentelemetry/api` to generate its spans. This is declared as an optional peer dependency so that users not needing traces see no impact on install size or runtime performance. In most cases, if you're configuring your application to collect SvelteKit's spans, you'll end up installing a library like `@opentelemetry/sdk-node` or `@vercel/otel`, which in turn depend on `@opentelemetry/api`, which will satisfy SvelteKit's dependency as well. If you see an error from SvelteKit telling you it can't find `@opentelemetry/api`, it may just be because you haven't set up your trace collection yet. If you _have_ done that and are still seeing the error, you can install `@opentelemetry/api` yourself. diff --git a/documentation/docs/30-advanced/70-packaging.md b/documentation/docs/30-advanced/70-packaging.md index 602bce96860b..a806f9795691 100644 --- a/documentation/docs/30-advanced/70-packaging.md +++ b/documentation/docs/30-advanced/70-packaging.md @@ -2,18 +2,18 @@ title: Packaging --- -You can use SvelteKit to build apps as well as component libraries, using the `@sveltejs/package` package (`npm create svelte` has an option to set this up for you). +You can use SvelteKit to build apps as well as component libraries, using the `@sveltejs/package` package (`npx sv create` has an option to set this up for you). -When you're creating an app, the contents of `src/routes` is the public-facing stuff; [`src/lib`](modules#$lib) contains your app's internal library. +When you're creating an app, the contents of `src/routes` is the public-facing stuff; [`src/lib`]($lib) contains your app's internal library. A component library has the exact same structure as a SvelteKit app, except that `src/lib` is the public-facing bit, and your root `package.json` is used to publish the package. `src/routes` might be a documentation or demo site that accompanies the library, or it might just be a sandbox you use during development. -Running the `svelte-package` command from `@sveltejs/package` will take the contents of `src/lib` and generate a `dist` directory (which can be [configured](#options)) containing the following: +Running the `svelte-package` command from `@sveltejs/package` will take the contents of `src/lib` and generate a `dist` directory (which can be [configured](#Options)) containing the following: - All the files in `src/lib`. Svelte components will be preprocessed, TypeScript files will be transpiled to JavaScript. -- Type definitions (`d.ts` files) which are generated for Svelte, JavaScript and TypeScript files. You need to install `typescript >= 4.0.0` for this. Type definitions are placed next to their implementation, hand-written `d.ts` files are copied over as is. You can [disable generation](#options), but we strongly recommend against it — people using your library might use TypeScript, for which they require these type definition files. +- Type definitions (`d.ts` files) which are generated for Svelte, JavaScript and TypeScript files. You need to install `typescript >= 4.0.0` for this. Type definitions are placed next to their implementation, hand-written `d.ts` files are copied over as is. You can [disable generation](#Options), but we strongly recommend against it — people using your library might use TypeScript, for which they require these type definition files. -> `@sveltejs/package` version 1 generated a `package.json`. This is no longer the case and it will now use the `package.json` from your project and validate that it is correct instead. If you're still on version 1, see [this PR](https://github.com/sveltejs/kit/pull/8922) for migration instructions. +> [!NOTE] `@sveltejs/package` version 1 generated a `package.json`. This is no longer the case and it will now use the `package.json` from your project and validate that it is correct instead. If you're still on version 1, see [this PR](https://github.com/sveltejs/kit/pull/8922) for migration instructions. ## Anatomy of a package.json @@ -59,7 +59,7 @@ Read more about it [here](https://docs.npmjs.com/cli/v9/configuring-npm/package- ### exports -The `"exports"` field contains the package's entry points. If you set up a new library project through `npm create svelte@latest`, it's set to a single export, the package root: +The `"exports"` field contains the package's entry points. If you set up a new library project through `npx sv create`, it's set to a single export, the package root: ```json { @@ -84,7 +84,7 @@ The `types` and `svelte` keys are [export conditions](https://nodejs.org/api/pac - TypeScript sees the `types` condition and looks up the type definition file. If you don't publish type definitions, omit this condition. - Svelte-aware tooling sees the `svelte` condition and knows this is a Svelte component library. If you publish a library that does not export any Svelte components and that could also work in non-Svelte projects (for example a Svelte store library), you can replace this condition with `default`. -> Previous versions of `@sveltejs/package` also added a `package.json` export. This is no longer part of the template because all tooling can now deal with a `package.json` not being explicitly exported. +> [!NOTE] Previous versions of `@sveltejs/package` also added a `package.json` export. This is no longer part of the template because all tooling can now deal with a `package.json` not being explicitly exported. You can adjust `exports` to your liking and provide more entry points. For example, if instead of a `src/lib/index.js` file that re-exported components you wanted to expose a `src/lib/Foo.svelte` component directly, you could create the following export map... @@ -110,7 +110,7 @@ declare module 'your-library/Foo.svelte'; import Foo from 'your-library/Foo.svelte'; ``` -> Beware that doing this will need additional care if you provide type definitions. Read more about the caveat [here](#typescript) +> [!NOTE] Beware that doing this will need additional care if you provide type definitions. Read more about the caveat [here](#TypeScript) In general, each key of the exports map is the path the user will have to use to import something from your package, and the value is the path to the file that will be imported or a map of export conditions which in turn contains these file paths. @@ -118,7 +118,7 @@ Read more about `exports` [here](https://nodejs.org/docs/latest-v18.x/api/packag ### svelte -This is a legacy field that enabled tooling to recognise Svelte component libraries. It's no longer necessary when using the `svelte` [export condition](#anatomy-of-a-package-json-exports), but for backwards compatibility with outdated tooling that doesn't yet know about export conditions it's good to keep it around. It should point towards your root entry point. +This is a legacy field that enabled tooling to recognise Svelte component libraries. It's no longer necessary when using the `svelte` [export condition](#Anatomy-of-a-package.json-exports), but for backwards compatibility with outdated tooling that doesn't yet know about export conditions it's good to keep it around. It should point towards your root entry point. ```json { @@ -126,9 +126,38 @@ This is a legacy field that enabled tooling to recognise Svelte component librar } ``` +### sideEffects + +The `sideEffects` field in `package.json` is used by bundlers to determine if a module may contain code that has side effects. A module is considered to have side effects if it makes changes that are observable from other scripts outside the module when it's imported. For example, side effects include modifying global variables or the prototype of built-in JavaScript objects. Because a side effect could potentially affect the behavior of other parts of the application, these files/modules will be included in the final bundle regardless of whether their exports are used in the application. It is a best practice to avoid side effects in your code. + +Setting the `sideEffects` field in `package.json` can help the bundler to be more aggressive in eliminating unused exports from the final bundle, a process known as tree-shaking. This results in smaller and more efficient bundles. Different bundlers handle `sideEffects` in various manners. While not necessary for Vite, we recommend that libraries state that all CSS files have side effects so that your library will be [compatible with webpack](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free). This is the configuration that comes with newly created projects: + +```json +/// file: package.json +{ + "sideEffects": ["**/*.css"] +} +``` + +> [!NOTE] If the scripts in your library have side effects, ensure that you update the `sideEffects` field. All scripts are marked as side effect free by default in newly created projects. If a file with side effects is incorrectly marked as having no side effects, it can result in broken functionality. + +If your package has files with side effects, you can specify them in an array: + +```json +/// file: package.json +{ + "sideEffects": [ + "**/*.css", + "./dist/sideEffectfulFile.js" + ] +} +``` + +This will treat only the specified files as having side effects. + ## TypeScript -You should ship type definitions for your library even if you don't use TypeScript yourself so that people who do get proper intellisense when using your library. `@sveltejs/package` makes the process of generating types mostly opaque to you. By default, when packaging your library, type definitions are auto-generated for JavaScript, TypeScript and Svelte files. All you need to ensure is that the `types` condition in the [exports](#anatomy-of-a-package-json-exports) map points to the correct files. When initialising a library project through `npm create svelte@latest`, this is automatically setup for the root export. +You should ship type definitions for your library even if you don't use TypeScript yourself so that people who do get proper intellisense when using your library. `@sveltejs/package` makes the process of generating types mostly opaque to you. By default, when packaging your library, type definitions are auto-generated for JavaScript, TypeScript and Svelte files. All you need to ensure is that the `types` condition in the [exports](#Anatomy-of-a-package.json-exports) map points to the correct files. When initialising a library project through `npx sv create`, this is automatically setup for the root export. If you have something else than a root export however — for example providing a `your-library/foo` import — you need to take additional care for providing type definitions. Unfortunately, TypeScript by default will _not_ resolve the `types` condition for an export like `{ "./foo": { "types": "./dist/foo.d.ts", ... }}`. Instead, it will search for a `foo.d.ts` relative to the root of your library (i.e. `your-library/foo.d.ts` instead of `your-library/dist/foo.d.ts`). To fix this, you have two options: @@ -158,45 +187,64 @@ You can read more about that feature [here](https://www.typescriptlang.org/docs/ ## Best practices -You should avoid using [SvelteKit-specific modules](modules) like `$app` in your packages unless you intend for them to only be consumable by other SvelteKit projects. E.g. rather than using `import { browser } from '$app/environment'` you could use `import { BROWSER } from 'esm-env'` ([see esm-env docs](https://github.com/benmccann/esm-env)). You may also wish to pass in things like the current URL or a navigation action as a prop rather than relying directly on `$app/stores`, `$app/navigation`, etc. Writing your app in this more generic fashion will also make it easier to setup tools for testing, UI demos and so on. +You should avoid using SvelteKit-specific modules like `$app/environment` in your packages unless you intend for them to only be consumable by other SvelteKit projects. E.g. rather than using `import { browser } from '$app/environment'` you could use `import { BROWSER } from 'esm-env'` ([see esm-env docs](https://github.com/benmccann/esm-env)). You may also wish to pass in things like the current URL or a navigation action as a prop rather than relying directly on `$app/state`, `$app/navigation`, etc. Writing your app in this more generic fashion will also make it easier to setup tools for testing, UI demos and so on. Ensure that you add [aliases](configuration#alias) via `svelte.config.js` (not `vite.config.js` or `tsconfig.json`), so that they are processed by `svelte-package`. You should think carefully about whether or not the changes you make to your package are a bug fix, a new feature, or a breaking change, and update the package version accordingly. Note that if you remove any paths from `exports` or any `export` conditions inside them from your existing library, that should be regarded as a breaking change. -```diff +```json { "exports": { ".": { "types": "./dist/index.d.ts", // changing `svelte` to `default` is a breaking change: -- "svelte": "./dist/index.js" -+ "default": "./dist/index.js" +--- "svelte": "./dist/index.js"--- ++++ "default": "./dist/index.js"+++ }, // removing this is a breaking change: -- "./foo": { -- "types": "./dist/foo.d.ts", -- "svelte": "./dist/foo.js", -- "default": "./dist/foo.js" -- }, +--- "./foo": { + "types": "./dist/foo.d.ts", + "svelte": "./dist/foo.js", + "default": "./dist/foo.js" + },--- // adding this is ok: -+ "./bar": { -+ "types": "./dist/bar.d.ts", -+ "svelte": "./dist/bar.js", -+ "default": "./dist/bar.js" -+ } ++++ "./bar": { + "types": "./dist/bar.d.ts", + "svelte": "./dist/bar.js", + "default": "./dist/bar.js" + }+++ } } ``` +## Source maps + +You can create so-called declaration maps (`d.ts.map` files) by setting `"declarationMap": true` in your `tsconfig.json`. This will allow editors such as VS Code to go to the original `.ts` or `.svelte` file when using features like _Go to Definition_. This means you also need to publish your source files alongside your dist folder in a way that the relative path inside the declaration files leads to a file on disk. Assuming that you have all your library code inside `src/lib` as suggested by Svelte's CLI, this is as simple as adding `src/lib` to `files` in your `package.json`: + +```json +{ + "files": [ + "dist", + "!dist/**/*.test.*", + "!dist/**/*.spec.*", + +++"src/lib", + "!src/lib/**/*.test.*", + "!src/lib/**/*.spec.*"+++ + ] +} +``` + ## Options `svelte-package` accepts the following options: - `-w`/`--watch` — watch files in `src/lib` for changes and rebuild the package - `-i`/`--input` — the input directory which contains all the files of the package. Defaults to `src/lib` -- `-o`/`--o` — the output directory where the processed files are written to. Your `package.json`'s `exports` should point to files inside there, and the `files` array should include that folder. Defaults to `dist` +- `-o`/`--output` — the output directory where the processed files are written to. Your `package.json`'s `exports` should point to files inside there, and the `files` array should include that folder. Defaults to `dist` +- `-p`/`--preserve-output` — prevent deletion of the output directory before packaging. Defaults to `false`, which means that the output directory will be emptied first - `-t`/`--types` — whether or not to create type definitions (`d.ts` files). We strongly recommend doing this as it fosters ecosystem library quality. Defaults to `true` +- `--tsconfig` - the path to a tsconfig or jsconfig. When not provided, searches for the next upper tsconfig/jsconfig in the workspace path. ## Publishing @@ -210,9 +258,9 @@ npm publish All relative file imports need to be fully specified, adhering to Node's ESM algorithm. This means that for a file like `src/lib/something/index.js`, you must include the filename with the extension: -```diff --import { something } from './something'; -+import { something } from './something/index.js'; +```js +// @errors: 2307 +import { something } from './something+++/index.js+++'; ``` If you are using TypeScript, you need to import `.ts` files the same way, but using a `.js` file ending, _not_ a `.ts` file ending. (This is a TypeScript design decision outside our control.) Setting `"moduleResolution": "NodeNext"` in your `tsconfig.json` or `jsconfig.json` will help you with this. diff --git a/documentation/docs/30-advanced/index.md b/documentation/docs/30-advanced/index.md new file mode 100644 index 000000000000..f8c158a040eb --- /dev/null +++ b/documentation/docs/30-advanced/index.md @@ -0,0 +1,3 @@ +--- +title: Advanced +--- diff --git a/documentation/docs/30-advanced/meta.json b/documentation/docs/30-advanced/meta.json deleted file mode 100644 index 59e6001d3dd5..000000000000 --- a/documentation/docs/30-advanced/meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Advanced" -} diff --git a/documentation/docs/40-best-practices/03-auth.md b/documentation/docs/40-best-practices/03-auth.md new file mode 100644 index 000000000000..349548fdf1e3 --- /dev/null +++ b/documentation/docs/40-best-practices/03-auth.md @@ -0,0 +1,23 @@ +--- +title: Auth +--- + +Auth refers to authentication and authorization, which are common needs when building a web application. Authentication means verifying that the user is who they say they are based on their provided credentials. Authorization means determining which actions they are allowed to take. + +## Sessions vs tokens + +After the user has provided their credentials such as a username and password, we want to allow them to use the application without needing to provide their credentials again for future requests. Users are commonly authenticated on subsequent requests with either a session identifier or signed token such as a JSON Web Token (JWT). + +Session IDs are most commonly stored in a database. They can be immediately revoked, but require a database query to be made on each request. + +In contrast, JWT generally are not checked against a datastore, which means they cannot be immediately revoked. The advantage of this method is improved latency and reduced load on your datastore. + +## Integration points + +Auth [cookies](@sveltejs-kit#Cookies) can be checked inside [server hooks](hooks#Server-hooks). If a user is found matching the provided credentials, the user information can be stored in [`locals`](hooks#Server-hooks-locals). + +## Guides + +[Lucia](https://lucia-auth.com/) is a good reference for session-based web app auth. It contains example code snippets and projects for implementing session-based auth within SvelteKit and other JS projects. You can add code which follows the Lucia guide to your project with `npx sv create` when creating a new project or `npx sv add lucia` for an existing project. + +An auth system is tightly coupled to a web framework because most of the code lies in validating user input, handling errors, and directing users to the appropriate next page. As a result, many of the generic JS auth libraries include one or more web frameworks within them. For this reason, many users will find it preferrable to follow a SvelteKit-specific guide such as the examples found in [Lucia](https://lucia-auth.com/) rather than having multiple web frameworks inside their project. diff --git a/documentation/docs/40-best-practices/05-performance.md b/documentation/docs/40-best-practices/05-performance.md new file mode 100644 index 000000000000..9ecd0080ef9a --- /dev/null +++ b/documentation/docs/40-best-practices/05-performance.md @@ -0,0 +1,105 @@ +--- +title: Performance +--- + +Out of the box, SvelteKit does a lot of work to make your applications as performant as possible: + +- Code-splitting, so that only the code you need for the current page is loaded +- Asset preloading, so that 'waterfalls' (of files requesting other files) are prevented +- File hashing, so that your assets can be cached forever +- Request coalescing, so that data fetched from separate server `load` functions is grouped into a single HTTP request +- Parallel loading, so that separate universal `load` functions fetch data simultaneously +- Data inlining, so that requests made with `fetch` during server rendering can be replayed in the browser without issuing a new request +- Conservative invalidation, so that `load` functions are only re-run when necessary +- Prerendering (configurable on a per-route basis, if necessary) so that pages without dynamic data can be served instantaneously +- Link preloading, so that data and code requirements for a client-side navigation are eagerly anticipated + +Nevertheless, we can't (yet) eliminate all sources of slowness. To eke out maximum performance, you should be mindful of the following tips. + +## Diagnosing issues + +Google's [PageSpeed Insights](https://pagespeed.web.dev/) and (for more advanced analysis) [WebPageTest](https://www.webpagetest.org/) are excellent ways to understand the performance characteristics of a site that is already deployed to the internet. + +Your browser also includes useful developer tools for analysing your site, whether deployed or running locally: + +* Chrome - [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview#devtools), [Network](https://developer.chrome.com/docs/devtools/network), and [Performance](https://developer.chrome.com/docs/devtools/performance) devtools +* Edge - [Lighthouse](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/lighthouse/lighthouse-tool), [Network](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/network/), and [Performance](https://learn.microsoft.com/en-us/microsoft-edge/devtools-guide-chromium/evaluate-performance/) devtools +* Firefox - [Network](https://firefox-source-docs.mozilla.org/devtools-user/network_monitor/) and [Performance](https://hacks.mozilla.org/2022/03/performance-tool-in-firefox-devtools-reloaded/) devtools +* Safari - [enhancing the performance of your webpage](https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/Web_Inspector_Tutorial/EnhancingyourWebpagesPerformance/EnhancingyourWebpagesPerformance.html) + +Note that your site running locally in `dev` mode will exhibit different behaviour than your production app, so you should do performance testing in [preview](building-your-app#Preview-your-app) mode after building. + +### Instrumenting + +If you see in the network tab of your browser that an API call is taking a long time and you'd like to understand why, you may consider instrumenting your backend with a tool like [OpenTelemetry](https://opentelemetry.io/) or [Server-Timing headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing). + +## Optimizing assets + +### Images + +Reducing the size of image files is often one of the most impactful changes you can make to a site's performance. Svelte provides the `@sveltejs/enhanced-img` package, detailed on the [images](images) page, for making this easier. Additionally, Lighthouse is useful for identifying the worst offenders. + +### Videos + +Video files can be very large, so extra care should be taken to ensure that they're optimized: + +- Compress videos with tools such as [Handbrake](https://handbrake.fr/). Consider converting the videos to web-friendly formats such as `.webm` or `.mp4`. +- You can [lazy-load videos](https://web.dev/articles/lazy-loading-video) located below the fold with `preload="none"` (though note that this will slow down playback when the user _does_ initiate it). +- Strip the audio track out of muted videos using a tool like [FFmpeg](https://ffmpeg.org/). + +### Fonts + +SvelteKit automatically preloads critical `.js` and `.css` files when the user visits a page, but it does _not_ preload fonts by default, since this may cause unnecessary files (such as font weights that are referenced by your CSS but not actually used on the current page) to be downloaded. Having said that, preloading fonts correctly can make a big difference to how fast your site feels. In your [`handle`](hooks#Server-hooks-handle) hook, you can call `resolve` with a `preload` filter that includes your fonts. + +You can reduce the size of font files by [subsetting](https://web.dev/learn/performance/optimize-web-fonts#subset_your_web_fonts) your fonts. + +## Reducing code size + +### Svelte version + +We recommend running the latest version of Svelte. Svelte 5 is smaller and faster than Svelte 4, which is smaller and faster than Svelte 3. + +### Packages + +[`rollup-plugin-visualizer`](https://www.npmjs.com/package/rollup-plugin-visualizer) can be helpful for identifying which packages are contributing the most to the size of your site. You may also find opportunities to remove code by manually inspecting the build output (use `build: { minify: false }` in your [Vite config](https://vitejs.dev/config/build-options.html#build-minify) to make the output readable, but remember to undo that before deploying your app), or via the network tab of your browser's devtools. + +### External scripts + +Try to minimize the number of third-party scripts running in the browser. For example, instead of using JavaScript-based analytics consider using server-side implementations, such as those offered by many platforms with SvelteKit adapters including [Cloudflare](https://www.cloudflare.com/web-analytics/), [Netlify](https://docs.netlify.com/monitor-sites/site-analytics/), and [Vercel](https://vercel.com/docs/analytics). + +To run third party scripts in a web worker (which avoids blocking the main thread), use [Partytown's SvelteKit integration](https://partytown.builder.io/sveltekit). + +### Selective loading + +Code imported with static `import` declarations will be automatically bundled with the rest of your page. If there is a piece of code you need only when some condition is met, use the dynamic `import(...)` form to selectively lazy-load the component. + +## Navigation + +### Preloading + +You can speed up client-side navigations by eagerly preloading the necessary code and data, using [link options](link-options). This is configured by default on the `` element when you create a new SvelteKit app. + +### Non-essential data + +For slow-loading data that isn't needed immediately, the object returned from your `load` function can contain promises rather than the data itself. For server `load` functions, this will cause the data to [stream](load#Streaming-with-promises) in after the navigation (or initial page load). + +### Preventing waterfalls + +One of the biggest performance killers is what is referred to as a _waterfall_, which is a series of requests that is made sequentially. This can happen on the server or in the browser, but is especially costly when dealing with data that has to travel further or across slower networks, such as a mobile user making a call to a distant server. + +In the browser, waterfalls can occur when your HTML kicks off request chains such as requesting JS which requests CSS which requests a background image and web font. SvelteKit will largely solve this class of problems for you by adding [`modulepreload`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/modulepreload) tags or headers, but you should view [the network tab in your devtools](#Diagnosing-issues) to check whether additional resources need to be preloaded. +- Pay special attention to this if you use [web fonts](#Optimizing-assets-Fonts) since they need to be handled manually. +- Enabling [single page app (SPA) mode](single-page-apps) will cause such waterfalls. With SPA mode, an empty page is generated, which fetches JavaScript, which ultimately loads and renders the page. This results in extra network round trips before a single pixel can be displayed. + +Waterfalls can also occur on calls to the backend whether made from the browser or server. E.g. if a universal `load` function makes an API call to fetch the current user, then uses the details from that response to fetch a list of saved items, and then uses _that_ response to fetch the details for each item, the browser will end up making multiple sequential requests. This is deadly for performance, especially for users that are physically located far from your backend. +- Avoid this issue by using [server `load` functions](load#Universal-vs-server) to make requests to backend services that are dependencies from the server rather than from the browser. Note, however, that server `load` functions are also not immune to waterfalls (though they are much less costly since they rarely involve round trips with high latency). For example, if you query a database to get the current user and then use that data to make a second query for a list of saved items, it will typically be more performant to issue a single query with a database join. + +## Hosting + +Your frontend should be located in the same data center as your backend to minimize latency. For sites with no central backend, many SvelteKit adapters support deploying to the _edge_, which means handling each user's requests from a nearby server. This can reduce load times significantly. Some adapters even support [configuring deployment on a per-route basis](page-options#config). You should also consider serving images from a CDN (which are typically edge networks) — the hosts for many SvelteKit adapters will do this automatically. + +Ensure your host uses HTTP/2 or newer. Vite's code splitting creates numerous small files for improved cacheability, which results in excellent performance, but this does assume that your files can be loaded in parallel with HTTP/2. + +## Further reading + +For the most part, building a performant SvelteKit app is the same as building any performant web app. You should be able to apply information from general performance resources such as [Core Web Vitals](https://web.dev/explore/learn-core-web-vitals) to any web experience you build. diff --git a/documentation/docs/40-best-practices/06-icons.md b/documentation/docs/40-best-practices/06-icons.md new file mode 100644 index 000000000000..623b62e02d60 --- /dev/null +++ b/documentation/docs/40-best-practices/06-icons.md @@ -0,0 +1,11 @@ +--- +title: Icons +--- + +## CSS + +A great way to use icons is to define them purely via CSS. Iconify offers support for [many popular icon sets](https://icon-sets.iconify.design/) that [can be included via CSS](https://iconify.design/docs/usage/css/). This method can also be used with popular CSS frameworks by leveraging the Iconify [Tailwind CSS plugin](https://iconify.design/docs/usage/css/tailwind/) or [UnoCSS plugin](https://iconify.design/docs/usage/css/unocss/). As opposed to libraries based on Svelte components, it doesn't require each icon to be imported into your `.svelte` file. + +## Svelte + +There are many [icon libraries for Svelte](/packages#icons). When choosing an icon library, it is recommended to avoid those that provide a `.svelte` file per icon, as these libraries can have thousands of `.svelte` files which really slow down [Vite's dependency optimization](https://vite.dev/guide/dep-pre-bundling.html). This can become especially pathological if the icons are imported both via an umbrella import and subpath import [as described in the `vite-plugin-svelte` FAQ](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#what-is-going-on-with-vite-and-pre-bundling-dependencies). diff --git a/documentation/docs/40-best-practices/07-images.md b/documentation/docs/40-best-practices/07-images.md new file mode 100644 index 000000000000..4ba8d2420493 --- /dev/null +++ b/documentation/docs/40-best-practices/07-images.md @@ -0,0 +1,164 @@ +--- +title: Images +--- + +Images can have a big impact on your app's performance. For best results, you should optimize them by doing the following: + +- generate optimal formats like `.avif` and `.webp` +- create different sizes for different screens +- ensure that assets can be cached effectively + +Doing this manually is tedious. There are a variety of techniques you can use, depending on your needs and preferences. + +## Vite's built-in handling + +[Vite will automatically process imported assets](https://vitejs.dev/guide/assets.html) for improved performance. This includes assets referenced via the CSS `url()` function. Hashes will be added to the filenames so that they can be cached, and assets smaller than `assetsInlineLimit` will be inlined. Vite's asset handling is most often used for images, but is also useful for video, audio, etc. + +```svelte + + +The project logo +``` + +## @sveltejs/enhanced-img + +`@sveltejs/enhanced-img` is a plugin offered on top of Vite's built-in asset handling. It provides plug and play image processing that serves smaller file formats like `avif` or `webp`, automatically sets the intrinsic `width` and `height` of the image to avoid layout shift, creates images of multiple sizes for various devices, and strips EXIF data for privacy. It will work in any Vite-based project including, but not limited to, SvelteKit projects. + +> [!NOTE] As a build plugin, `@sveltejs/enhanced-img` can only optimize files located on your machine during the build process. If you have an image located elsewhere (such as a path served from your database, CMS, or backend), please read about [loading images dynamically from a CDN](#Loading-images-dynamically-from-a-CDN). + +### Setup + +Install: + +```sh +npm i -D @sveltejs/enhanced-img +``` + +Adjust `vite.config.js`: + +```js +import { sveltekit } from '@sveltejs/kit/vite'; ++++import { enhancedImages } from '@sveltejs/enhanced-img';+++ +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ + +++enhancedImages(), // must come before the SvelteKit plugin+++ + sveltekit() + ] +}); +``` + +Building will take longer on the first build due to the computational expense of transforming images. However, the build output will be cached in `./node_modules/.cache/imagetools` so that subsequent builds will be fast. + +### Basic usage + +Use in your `.svelte` components by using `` rather than `` and referencing the image file with a [Vite asset import](https://vitejs.dev/guide/assets.html#static-asset-handling) path: + +```svelte + +``` + +At build time, your `` tag will be replaced with an `` wrapped by a `` providing multiple image types and sizes. It's only possible to downscale images without losing quality, which means that you should provide the highest resolution image that you need — smaller versions will be generated for the various device types that may request an image. + +You should provide your image at 2x resolution for HiDPI displays (a.k.a. retina displays). `` will automatically take care of serving smaller versions to smaller devices. + +> [!NOTE] if you wish to use a [tag name CSS selector](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Basic_selectors#type_selectors) in your ` +``` + +### `srcset` and `sizes` + +If you have a large image, such as a hero image taking the width of the design, you should specify `sizes` so that smaller versions are requested on smaller devices. E.g. if you have a 1280px image you may want to specify something like: + +```svelte + +``` + +If `sizes` is specified, `` will generate small images for smaller devices and populate the `srcset` attribute. + +The smallest picture generated automatically will have a width of 540px. If you'd like smaller images or would otherwise like to specify custom widths, you can do that with the `w` query parameter: +```svelte + +``` + +If `sizes` is not provided, then a HiDPI/Retina image and a standard resolution image will be generated. The image you provide should be 2x the resolution you wish to display so that the browser can display that image on devices with a high [device pixel ratio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio). + +### Per-image transforms + +By default, enhanced images will be transformed to more efficient formats. However, you may wish to apply other transforms such as a blur, quality, flatten, or rotate operation. You can run per-image transforms by appending a query string: + +```svelte + +``` + +[See the imagetools repo for the full list of directives](https://github.com/JonasKruckenberg/imagetools/blob/main/docs/directives.md). + +## Loading images dynamically from a CDN + +In some cases, the images may not be accessible at build time — e.g. they may live inside a content management system or elsewhere. + +Using a content delivery network (CDN) can allow you to optimize these images dynamically, and provides more flexibility with regards to sizes, but it may involve some setup overhead and usage costs. Depending on caching strategy, the browser may not be able to use a cached copy of the asset until a [304 response](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304) is received from the CDN. Building HTML to target CDNs allows using an `` tag since the CDN can serve the appropriate format based on the `User-Agent` header, whereas build-time optimizations must produce `` tags with multiple sources. Finally, some CDNs may generate images lazily, which could have a negative performance impact for sites with low traffic and frequently changing images. + +CDNs can generally be used without any need for a library. However, there are a number of libraries with Svelte support that make it easier. [`@unpic/svelte`](https://unpic.pics/img/svelte/) is a CDN-agnostic library with support for a large number of providers. You may also find that specific CDNs like [Cloudinary](https://svelte.cloudinary.dev/) have Svelte support. Finally, some content management systems (CMS) which support Svelte (such as [Contentful](https://www.contentful.com/sveltekit-starter-guide/), [Storyblok](https://github.com/storyblok/storyblok-svelte), and [Contentstack](https://www.contentstack.com/docs/developers/sample-apps/build-a-starter-website-with-sveltekit-and-contentstack)) have built-in support for image handling. + +## Best practices + +- For each image type, use the appropriate solution from those discussed above. You can mix and match all three solutions in one project. For example, you may use Vite's built-in handling to provide images for `` tags, display images on your homepage with `@sveltejs/enhanced-img`, and display user-submitted content with a dynamic approach. +- Consider serving all images via CDN regardless of the image optimization types you use. CDNs reduce latency by distributing copies of static assets globally. +- Your original images should have a good quality/resolution and should have 2x the width it will be displayed at to serve HiDPI devices. Image processing can size images down to save bandwidth when serving smaller screens, but it would be a waste of bandwidth to invent pixels to size images up. +- For images which are much larger than the width of a mobile device (roughly 400px), such as a hero image taking the width of the page design, specify `sizes` so that smaller images can be served on smaller devices. +- For important images, such as the [largest contentful paint (LCP)](https://web.dev/articles/lcp) image, set `fetchpriority="high"` and avoid `loading="lazy"` to prioritize loading as early as possible. +- Give the image a container or styling so that it is constrained and does not jump around while the page is loading affecting your [cumulative layout shift (CLS)](https://web.dev/articles/cls). `width` and `height` help the browser to reserve space while the image is still loading, so `@sveltejs/enhanced-img` will add a `width` and `height` for you. +- Always provide a good `alt` text. The Svelte compiler will warn you if you don't do this. +- Do not use `em` or `rem` in `sizes` and change the default size of these measures. When used in `sizes` or `@media` queries, `em` and `rem` are both defined to mean the user's default `font-size`. For a `sizes` declaration like `sizes="(min-width: 768px) min(100vw, 108rem), 64rem"`, the actual `em` or `rem` that controls how the image is laid out on the page can be different if changed by CSS. For example, do not do something like `html { font-size: 62.5%; }` as the slot reserved by the browser preloader will now end up being larger than the actual slot of the CSS object model once it has been created. diff --git a/documentation/docs/40-best-practices/10-accessibility.md b/documentation/docs/40-best-practices/10-accessibility.md index af8264837004..a432acbe6687 100644 --- a/documentation/docs/40-best-practices/10-accessibility.md +++ b/documentation/docs/40-best-practices/10-accessibility.md @@ -2,9 +2,9 @@ title: Accessibility --- -SvelteKit strives to provide an accessible platform for your app by default. Svelte's [compile-time accessibility checks](https://svelte.dev/docs#accessibility-warnings) will also apply to any SvelteKit application you build. +SvelteKit strives to provide an accessible platform for your app by default. Svelte's [compile-time accessibility checks](../svelte/compiler-warnings) will also apply to any SvelteKit application you build. -Here's how SvelteKit's built-in accessibility features work and what you need to do to help these features to work as well as possible. Keep in mind that while SvelteKit provides an accessible foundation, you are still responsible for making sure your application code is accessible. If you're new to accessibility, see the ["further reading"](accessibility#further-reading) section of this guide for additional resources. +Here's how SvelteKit's built-in accessibility features work and what you need to do to help these features to work as well as possible. Keep in mind that while SvelteKit provides an accessible foundation, you are still responsible for making sure your application code is accessible. If you're new to accessibility, see the ["further reading"](accessibility#Further-reading) section of this guide for additional resources. We recognize that accessibility can be hard to get right. If you want to suggest improvements to how SvelteKit handles accessibility, please [open a GitHub issue](https://github.com/sveltejs/kit/issues). @@ -12,7 +12,7 @@ We recognize that accessibility can be hard to get right. If you want to suggest In traditional server-rendered applications, every navigation (e.g. clicking on an `` tag) triggers a full page reload. When this happens, screen readers and other assistive technology will read out the new page's title so that users understand that the page has changed. -Since navigation between pages in SvelteKit happens without reloading the page (known as [client-side routing](glossary#routing)), SvelteKit injects a [live region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) onto the page that will read out the new page name after each navigation. This determines the page name to announce by inspecting the `` element. +Since navigation between pages in SvelteKit happens without reloading the page (known as [client-side routing](glossary#Routing)), SvelteKit injects a [live region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) onto the page that will read out the new page name after each navigation. This determines the page name to announce by inspecting the `<title>` element. Because of this behavior, every page in your app should have a unique, descriptive title. In SvelteKit, you can do this by placing a `<svelte:head>` element on each page: @@ -23,13 +23,13 @@ Because of this behavior, every page in your app should have a unique, descripti </svelte:head> ``` -This will allow screen readers and other assistive technology to identify the new page after a navigation occurs. Providing a descriptive title is also important for [SEO](seo#manual-setup-title-and-meta). +This will allow screen readers and other assistive technology to identify the new page after a navigation occurs. Providing a descriptive title is also important for [SEO](seo#Manual-setup-title-and-meta). ## Focus management In traditional server-rendered applications, every navigation will reset focus to the top of the page. This ensures that people browsing the web with a keyboard or screen reader will start interacting with the page from the beginning. -To simulate this behavior during client-side routing, SvelteKit focuses the `<body>` element after each navigation and [enhanced form submission](https://kit.svelte.dev/docs/form-actions#progressive-enhancement). There is one exception - if an element with the [`autofocus`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) attribute is present, SvelteKit will focus that element instead. Make sure to [consider the implications for assistive technology](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus#accessibility_considerations) when using that attribute. +To simulate this behavior during client-side routing, SvelteKit focuses the `<body>` element after each navigation and [enhanced form submission](form-actions#Progressive-enhancement). There is one exception - if an element with the [`autofocus`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus) attribute is present, SvelteKit will focus that element instead. Make sure to [consider the implications for assistive technology](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus#accessibility_considerations) when using that attribute. If you want to customize SvelteKit's focus management, you can use the `afterNavigate` hook: @@ -45,7 +45,7 @@ afterNavigate(() => { }); ``` -You can also programmatically navigate to a different page using the [`goto`](modules#$app-navigation-goto) function. By default, this will have the same client-side routing behavior as clicking on a link. However, `goto` also accepts a `keepFocus` option that will preserve the currently-focused element instead of resetting focus. If you enable this option, make sure the currently-focused element still exists on the page after navigation. If the element no longer exists, the user's focus will be lost, making for a confusing experience for assistive technology users. +You can also programmatically navigate to a different page using the [`goto`]($app-navigation#goto) function. By default, this will have the same client-side routing behavior as clicking on a link. However, `goto` also accepts a `keepFocus` option that will preserve the currently-focused element instead of resetting focus. If you enable this option, make sure the currently-focused element still exists on the page after navigation. If the element no longer exists, the user's focus will be lost, making for a confusing experience for assistive technology users. ## The "lang" attribute @@ -56,7 +56,7 @@ By default, SvelteKit's page template sets the default language of the document <html lang="de"> ``` -If your content is available in multiple languages, you should set the `lang` attribute based on the language of the current page. You can do this with SvelteKit's [handle hook](hooks#server-hooks-handle): +If your content is available in multiple languages, you should set the `lang` attribute based on the language of the current page. You can do this with SvelteKit's [handle hook](hooks#Server-hooks-handle): ```html /// file: src/app.html diff --git a/documentation/docs/40-best-practices/20-seo.md b/documentation/docs/40-best-practices/20-seo.md index d26e5fbd0ab2..b3c50c1e38ef 100644 --- a/documentation/docs/40-best-practices/20-seo.md +++ b/documentation/docs/40-best-practices/20-seo.md @@ -8,49 +8,25 @@ The most important aspect of SEO is to create high-quality content that is widel ### SSR -While search engines have got better in recent years at indexing content that was rendered with client-side JavaScript, server-side rendered content is indexed more frequently and reliably. SvelteKit employs SSR by default, and while you can disable it in [`handle`](hooks#server-hooks-handle), you should leave it on unless you have a good reason not to. +While search engines have got better in recent years at indexing content that was rendered with client-side JavaScript, server-side rendered content is indexed more frequently and reliably. SvelteKit employs SSR by default, and while you can disable it in [`handle`](hooks#Server-hooks-handle), you should leave it on unless you have a good reason not to. -> SvelteKit's rendering is highly configurable and you can implement [dynamic rendering](https://developers.google.com/search/docs/advanced/javascript/dynamic-rendering) if necessary. It's not generally recommended, since SSR has other benefits beyond SEO. +> [!NOTE] SvelteKit's rendering is highly configurable and you can implement [dynamic rendering](https://developers.google.com/search/docs/advanced/javascript/dynamic-rendering) if necessary. It's not generally recommended, since SSR has other benefits beyond SEO. ### Performance -Signals such as [Core Web Vitals](https://web.dev/vitals/#core-web-vitals) impact search engine ranking. Because Svelte and SvelteKit introduce minimal overhead, it's easier to build high performance sites. You can test your site's performance using Google's [PageSpeed Insights](https://pagespeed.web.dev/) or [Lighthouse](https://developers.google.com/web/tools/lighthouse). +Signals such as [Core Web Vitals](https://web.dev/vitals/#core-web-vitals) impact search engine ranking. Because Svelte and SvelteKit introduce minimal overhead, they make it easier to build high performance sites. You can test your site's performance using Google's [PageSpeed Insights](https://pagespeed.web.dev/) or [Lighthouse](https://developers.google.com/web/tools/lighthouse). With just a few key actions like using SvelteKit's default [hybrid rendering](glossary#Hybrid-app) mode and [optimizing your images](images), you can greatly improve your site's speed. Read [the performance page](performance) for more details. ### Normalized URLs -SvelteKit redirects pathnames with trailing slashes to ones without (or vice versa depending on your [configuration](page-options#trailingslash)), as duplicate URLs are bad for SEO. +SvelteKit redirects pathnames with trailing slashes to ones without (or vice versa depending on your [configuration](page-options#trailingSlash)), as duplicate URLs are bad for SEO. ## Manual setup ### <title> and <meta> -Every page should have well-written and unique `<title>` and `<meta name="description">` elements inside a [`<svelte:head>`](https://svelte.dev/docs#template-syntax-svelte-head). Guidance on how to write descriptive titles and descriptions, along with other suggestions on making content understandable by search engines, can be found on Google's [Lighthouse SEO audits](https://web.dev/lighthouse-seo/) documentation. +Every page should have well-written and unique `<title>` and `<meta name="description">` elements inside a [`<svelte:head>`](../svelte/svelte-head). Guidance on how to write descriptive titles and descriptions, along with other suggestions on making content understandable by search engines, can be found on Google's [Lighthouse SEO audits](https://web.dev/lighthouse-seo/) documentation. -> A common pattern is to return SEO-related `data` from page [`load`](load) functions, then use it (as [`$page.data`](modules#$app-stores)) in a `<svelte:head>` in your root [layout](routing#layout). - -### Structured data - -[Structured data](https://developers.google.com/search/docs/advanced/structured-data/intro-structured-data) helps search engines understand the content of a page. If you're using structured data alongside [`svelte-preprocess`](https://github.com/sveltejs/svelte-preprocess), you will need to explicitly preserve `ld+json` data (this [may change in future](https://github.com/sveltejs/svelte-preprocess/issues/305)): - -```js -/// file: svelte.config.js -// @filename: ambient.d.ts -declare module 'svelte-preprocess'; - -// @filename: index.js -// ---cut--- -import preprocess from 'svelte-preprocess'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - preprocess: preprocess({ - preserve: ['ld+json'] - // ... - }) -}; - -export default config; -``` +> [!NOTE] A common pattern is to return SEO-related `data` from page [`load`](load) functions, then use it (as [`page.data`]($app-state)) in a `<svelte:head>` in your root [layout](routing#layout). ### Sitemaps @@ -63,12 +39,12 @@ export async function GET() { ` <?xml version="1.0" encoding="UTF-8" ?> <urlset - xmlns="https://www.sitemaps.org/schemas/sitemap/0.9" - xmlns:xhtml="https://www.w3.org/1999/xhtml" - xmlns:mobile="https://www.google.com/schemas/sitemap-mobile/1.0" - xmlns:news="https://www.google.com/schemas/sitemap-news/0.9" - xmlns:image="https://www.google.com/schemas/sitemap-image/1.1" - xmlns:video="https://www.google.com/schemas/sitemap-video/1.1" + xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" + xmlns:xhtml="http://www.w3.org/1999/xhtml" + xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" + xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" + xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" + xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" > <!-- <url> elements go here --> </urlset>`.trim(), @@ -83,7 +59,7 @@ export async function GET() { ### AMP -An unfortunate reality of modern web development is that it is sometimes necessary to create an [Accelerated Mobile Pages (AMP)](https://amp.dev/) version of your site. In SvelteKit this can be done by setting the [`inlineStyleThreshold`](configuration#inlinestylethreshold) option... +An unfortunate reality of modern web development is that it is sometimes necessary to create an [Accelerated Mobile Pages (AMP)](https://amp.dev/) version of your site. In SvelteKit this can be done by setting the [`inlineStyleThreshold`](configuration#inlineStyleThreshold) option... ```js /// file: svelte.config.js @@ -134,6 +110,11 @@ export async function handle({ event, resolve }) { To prevent shipping any unused CSS as a result of transforming the page to amp, we can use [`dropcss`](https://www.npmjs.com/package/dropcss): ```js +// @filename: ambient.d.ts +declare module 'dropcss'; + +// @filename: index.js +// ---cut--- /// file: src/hooks.server.js // @errors: 2307 import * as amp from '@sveltejs/amp'; @@ -166,4 +147,4 @@ export async function handle({ event, resolve }) { ``` -> It's a good idea to use the `handle` hook to validate the transformed HTML using `amphtml-validator`, but only if you're prerendering pages since it's very slow. +> [!NOTE] It's a good idea to use the `handle` hook to validate the transformed HTML using `amphtml-validator`, but only if you're prerendering pages since it's very slow. diff --git a/documentation/docs/40-best-practices/index.md b/documentation/docs/40-best-practices/index.md new file mode 100644 index 000000000000..182d497f4cce --- /dev/null +++ b/documentation/docs/40-best-practices/index.md @@ -0,0 +1,3 @@ +--- +title: Best practices +--- diff --git a/documentation/docs/40-best-practices/meta.json b/documentation/docs/40-best-practices/meta.json deleted file mode 100644 index 3b8b86368da7..000000000000 --- a/documentation/docs/40-best-practices/meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Best practices" -} diff --git a/documentation/docs/50-reference/10-configuration.md b/documentation/docs/50-reference/10-configuration.md deleted file mode 100644 index 5a4f2de14147..000000000000 --- a/documentation/docs/50-reference/10-configuration.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Configuration ---- - -Your project's configuration lives in a `svelte.config.js` file at the root of your project. As well as SvelteKit, this config object is used by other tooling that integrates with Svelte such as editor extensions. - -```js -/// file: svelte.config.js -// @filename: ambient.d.ts -declare module '@sveltejs/adapter-auto' { - const plugin: () => import('@sveltejs/kit').Adapter; - export default plugin; -} - -// @filename: index.js -// ---cut--- -import adapter from '@sveltejs/adapter-auto'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - kit: { - adapter: adapter() - } -}; - -export default config; -``` - -> TYPES: @sveltejs/kit#Config - -The `kit` property configures SvelteKit, and can have the following properties: - -> EXPANDED_TYPES: @sveltejs/kit#KitConfig \ No newline at end of file diff --git a/documentation/docs/50-reference/30-modules.md b/documentation/docs/50-reference/30-modules.md deleted file mode 100644 index 7140726eb783..000000000000 --- a/documentation/docs/50-reference/30-modules.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Modules ---- - -SvelteKit makes a number of modules available to your application. - -> MODULES diff --git a/documentation/docs/50-reference/40-types.md b/documentation/docs/50-reference/40-types.md deleted file mode 100644 index ba34e6543cb1..000000000000 --- a/documentation/docs/50-reference/40-types.md +++ /dev/null @@ -1,157 +0,0 @@ ---- -title: Types ---- - -## Public types - -The following types can be imported from `@sveltejs/kit`: - -> TYPES: @sveltejs/kit - -## Private types - -The following are referenced by the public types documented above, but cannot be imported directly: - -> TYPES: Private types - -## Generated types - -The `RequestHandler` and `Load` types both accept a `Params` argument allowing you to type the `params` object. For example this endpoint expects `foo`, `bar` and `baz` params: - -```js -/// file: src/routes/[foo]/[bar]/[baz]/+page.server.js -// @errors: 2355 2322 1360 -/** @type {import('@sveltejs/kit').RequestHandler<{ - foo: string; - bar: string; - baz: string - }>} */ -export async function GET({ params }) { - // ... -} -``` - -Needless to say, this is cumbersome to write out, and less portable (if you were to rename the `[foo]` directory to `[qux]`, the type would no longer reflect reality). - -To solve this problem, SvelteKit generates `.d.ts` files for each of your endpoints and pages: - -```ts -/// file: .svelte-kit/types/src/routes/[foo]/[bar]/[baz]/$types.d.ts -/// link: false -import type * as Kit from '@sveltejs/kit'; - -type RouteParams = { - foo: string; - bar: string; - baz: string; -} - -export type PageServerLoad = Kit.ServerLoad<RouteParams>; -export type PageLoad = Kit.Load<RouteParams>; -``` - -These files can be imported into your endpoints and pages as siblings, thanks to the [`rootDirs`](https://www.typescriptlang.org/tsconfig#rootDirs) option in your TypeScript configuration: - -```js -/// file: src/routes/[foo]/[bar]/[baz]/+page.server.js -// @filename: $types.d.ts -import type * as Kit from '@sveltejs/kit'; - -type RouteParams = { - foo: string; - bar: string; - baz: string; -} - -export type PageServerLoad = Kit.ServerLoad<RouteParams>; - -// @filename: index.js -// @errors: 2355 -// ---cut--- -/** @type {import('./$types').PageServerLoad} */ -export async function GET({ params }) { - // ... -} -``` - -```js -/// file: src/routes/[foo]/[bar]/[baz]/+page.js -// @filename: $types.d.ts -import type * as Kit from '@sveltejs/kit'; - -type RouteParams = { - foo: string; - bar: string; - baz: string; -} - -export type PageLoad = Kit.Load<RouteParams>; - -// @filename: index.js -// @errors: 2355 -// ---cut--- -/** @type {import('./$types').PageLoad} */ -export async function load({ params, fetch }) { - // ... -} -``` - -> For this to work, your own `tsconfig.json` or `jsconfig.json` should extend from the generated `.svelte-kit/tsconfig.json` (where `.svelte-kit` is your [`outDir`](configuration#outdir)): -> -> `{ "extends": "./.svelte-kit/tsconfig.json" }` - -### Default tsconfig.json - -The generated `.svelte-kit/tsconfig.json` file contains a mixture of options. Some are generated programmatically based on your project configuration, and should generally not be overridden without good reason: - -```json -/// file: .svelte-kit/tsconfig.json -{ - "compilerOptions": { - "baseUrl": "..", - "paths": { - "$lib": "src/lib", - "$lib/*": "src/lib/*" - }, - "rootDirs": ["..", "./types"] - }, - "include": ["../src/**/*.js", "../src/**/*.ts", "../src/**/*.svelte"], - "exclude": ["../node_modules/**", "./**"] -} -``` - -Others are required for SvelteKit to work properly, and should also be left untouched unless you know what you're doing: - -```json -/// file: .svelte-kit/tsconfig.json -{ - "compilerOptions": { - // this ensures that types are explicitly - // imported with `import type`, which is - // necessary as svelte-preprocess cannot - // otherwise compile components correctly - "importsNotUsedAsValues": "error", - - // Vite compiles one TypeScript module - // at a time, rather than compiling - // the entire module graph - "isolatedModules": true, - - // TypeScript cannot 'see' when you - // use an imported value in your - // markup, so we need this - "preserveValueImports": true, - - // This ensures both `vite build` - // and `svelte-package` work correctly - "lib": ["esnext", "DOM", "DOM.Iterable"], - "moduleResolution": "node", - "module": "esnext", - "target": "esnext" - } -} -``` - -## App - -> TYPES: App diff --git a/documentation/docs/50-reference/meta.json b/documentation/docs/50-reference/meta.json deleted file mode 100644 index aa111a0d6dd3..000000000000 --- a/documentation/docs/50-reference/meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Reference" -} diff --git a/documentation/docs/60-appendix/01-faq.md b/documentation/docs/60-appendix/01-faq.md deleted file mode 100644 index 1dfb031c76da..000000000000 --- a/documentation/docs/60-appendix/01-faq.md +++ /dev/null @@ -1,245 +0,0 @@ ---- -title: Frequently asked questions ---- - -## Other resources - -Please see [the Svelte FAQ](https://svelte.dev/faq) and [`vite-plugin-svelte` FAQ](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md) as well for the answers to questions deriving from those libraries. - -## What can I make with SvelteKit? - -SvelteKit can be used to create most kinds of applications. Out of the box, SvelteKit supports many features including: - -- Dynamic page content with [load](/docs/load) functions and [API routes](/docs/routing#server). -- SEO-friendly dynamic content with [server-side rendering (SSR)](/docs/glossary#ssr). -- User-friendly progressively-enhanced interactive pages with SSR and [Form Actions](/docs/form-actions). -- Static pages with [prerendering](/docs/page-options#prerender). - -SvelteKit can also be deployed to a wide spectrum of hosted architectures via [adapters](/docs/adapters). In cases where SSR is used (or server-side logic is added without prerendering), those functions will be adapted to the target backend. Some examples include: - -- Self-hosted dynamic web applications with a [Node.js backend](/docs/adapter-node). -- Serverless web applications with backend loaders and APIs deployed as remote functions. See [zero-config deployments](/docs/adapter-auto) for popular deployment options. -- [Static pre-rendered sites](/docs/adapter-static) such as a blog or multi-page site hosted on a CDN or static host. Statically-generated sites are shipped without a backend. -- [Single-page Applications (SPAs)](/docs/single-page-apps) with client-side routing and rendering for API-driven dynamic content. SPAs are shipped without a backend and are not server-rendered. This option is commonly chosen when bundling SvelteKit with an app written in PHP, .Net, Java, C, Golang, Rust, etc. -- A mix of the above; some routes can be static, and some routes can use backend functions to fetch dynamic information. This can be configured with [page options](/docs/page-options) that includes the option to opt out of SSR. - -In order to support SSR, a JS backend — such as Node.js or Deno-based server, serverless function, or edge function — is required. - -It is also possible to write custom adapters or leverage community adapters to deploy SvelteKit to more platforms such as specialized server environments, browser extensions, or native applications. See [integrations](./integrations) for more examples and integrations. - -## How do I use HMR with SvelteKit? - -SvelteKit has HMR enabled by default powered by [svelte-hmr](https://github.com/sveltejs/svelte-hmr). If you saw [Rich's presentation at the 2020 Svelte Summit](https://svelte.dev/blog/whats-the-deal-with-sveltekit), you may have seen a more powerful-looking version of HMR presented. This demo had `svelte-hmr`'s `preserveLocalState` flag on. This flag is now off by default because it may lead to unexpected behaviour and edge cases. But don't worry, you are still getting HMR with SvelteKit! If you'd like to preserve local state you can use the `@hmr:keep` or `@hmr:keep-all` directives as documented on the [svelte-hmr](https://github.com/sveltejs/svelte-hmr) page. - -## How do I include details from package.json in my application? - -You cannot directly require JSON files, since SvelteKit expects [`svelte.config.js`](./configuration) to be an ES module. If you'd like to include your application's version number or other information from `package.json` in your application, you can load JSON like so: - -```js -/// file: svelte.config.js -// @filename: index.js -/// <reference types="@types/node" /> -import { URL } from 'node:url'; -// ---cut--- -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; - -const path = fileURLToPath(new URL('package.json', import.meta.url)); -const pkg = JSON.parse(readFileSync(path, 'utf8')); -``` - -## How do I fix the error I'm getting trying to include a package? - -Most issues related to including a library are due to incorrect packaging. You can check if a library's packaging is compatible with Node.js by entering it into [the publint website](https://publint.dev/). - -Here are a few things to keep in mind when checking if a library is packaged correctly: - -- `exports` takes precedence over the other entry point fields such as `main` and `module`. Adding an `exports` field may not be backwards-compatible as it prevents deep imports. -- ESM files should end with `.mjs` unless `"type": "module"` is set in which any case CommonJS files should end with `.cjs`. -- `main` should be defined if `exports` is not. It should be either a CommonJS or ESM file and adhere to the previous bullet. If a `module` field is defined, it should refer to an ESM file. -- Svelte components should be distributed as uncompiled `.svelte` files with any JS in the package written as ESM only. Custom script and style languages, like TypeScript and SCSS, should be preprocessed as vanilla JS and CSS respectively. We recommend using [`svelte-package`](./packaging) for packaging Svelte libraries, which will do this for you. - -Libraries work best in the browser with Vite when they distribute an ESM version, especially if they are dependencies of a Svelte component library. You may wish to suggest to library authors that they provide an ESM version. However, CommonJS (CJS) dependencies should work as well since, by default, [`vite-plugin-svelte` will ask Vite to pre-bundle them](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#what-is-going-on-with-vite-and-pre-bundling-dependencies) using `esbuild` to convert them to ESM. - -If you are still encountering issues we recommend searching both [the Vite issue tracker](https://github.com/vitejs/vite/issues) and the issue tracker of the library in question. Sometimes issues can be worked around by fiddling with the [`optimizeDeps`](https://vitejs.dev/config/#dep-optimization-options) or [`ssr`](https://vitejs.dev/config/#ssr-options) config values though we recommend this as only a short-term workaround in favor of fixing the library in question. - -## How do I use the view transitions API with SvelteKit? - -While SvelteKit does not have any specific integration with [view transitions](https://developer.chrome.com/docs/web-platform/view-transitions/), you can call `document.startViewTransition` in [`onNavigate`](/docs/modules#$app-navigation-onnavigate) to trigger a view transition on every navigation. - -```js -// @errors: 2339 2810 -import { onNavigate } from '$app/navigation'; - -onNavigate((navigation) => { - if (!document.startViewTransition) return; - - return new Promise((resolve) => { - document.startViewTransition(async () => { - resolve(); - await navigation.complete; - }); - }); -}); -``` - -For more, see ["Unlocking view transitions"](https://svelte.dev/blog/view-transitions) on the Svelte blog. - -## How do I use X with SvelteKit? - -Make sure you've read the [documentation section on integrations](./integrations). If you're still having trouble, solutions to common issues are listed below. - -### How do I setup a database? - -Put the code to query your database in a [server route](./routing#server) - don't query the database in .svelte files. You can create a `db.js` or similar that sets up a connection immediately and makes the client accessible throughout the app as a singleton. You can execute any one-time setup code in `hooks.js` and import your database helpers into any endpoint that needs them. - -### How do I use a client-side only library that depends on `document` or `window`? - -If you need access to the `document` or `window` variables or otherwise need code to run only on the client-side you can wrap it in a `browser` check: - -```js -/// <reference types="@sveltejs/kit" /> -// ---cut--- -import { browser } from '$app/environment'; - -if (browser) { - // client-only code here -} -``` - -You can also run code in `onMount` if you'd like to run it after the component has been first rendered to the DOM: - -```js -// @filename: ambient.d.ts -// @lib: ES2015 -declare module 'some-browser-only-library'; - -// @filename: index.js -// ---cut--- -import { onMount } from 'svelte'; - -onMount(async () => { - const { method } = await import('some-browser-only-library'); - method('hello world'); -}); -``` - -If the library you'd like to use is side-effect free you can also statically import it and it will be tree-shaken out in the server-side build where `onMount` will be automatically replaced with a no-op: - -```js -// @filename: ambient.d.ts -// @lib: ES2015 -declare module 'some-browser-only-library'; - -// @filename: index.js -// ---cut--- -import { onMount } from 'svelte'; -import { method } from 'some-browser-only-library'; - -onMount(() => { - method('hello world'); -}); -``` - -Otherwise, if the library has side effects and you'd still prefer to use static imports, check out [vite-plugin-iso-import](https://github.com/bluwy/vite-plugin-iso-import) to support the `?client` import suffix. The import will be stripped out in SSR builds. However, note that you will lose the ability to use VS Code Intellisense if you use this method. - -```js -// @filename: ambient.d.ts -// @lib: ES2015 -declare module 'some-browser-only-library?client'; - -// @filename: index.js -// ---cut--- -import { onMount } from 'svelte'; -import { method } from 'some-browser-only-library?client'; - -onMount(() => { - method('hello world'); -}); -``` - -### How do I use a different backend API server? - -You can use [`event.fetch`](./load#making-fetch-requests) to request data from an external API server, but be aware that you would need to deal with [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), which will result in complications such as generally requiring requests to be preflighted resulting in higher latency. Requests to a separate subdomain may also increase latency due to an additional DNS lookup, TLS setup, etc. If you wish to use this method, you may find [`handleFetch`](./hooks#server-hooks-handlefetch) helpful. - -Another approach is to set up a proxy to bypass CORS headaches. In production, you would rewrite a path like `/api` to the API server; for local development, use Vite's [`server.proxy`](https://vitejs.dev/config/server-options.html#server-proxy) option. - -How to setup rewrites in production will depend on your deployment platform. If rewrites aren't an option, you could alternatively add an [API route](./routing#server): - -```js -/// file: src/routes/api/[...path]/+server.js -/** @type {import('./$types').RequestHandler} */ -export function GET({ params, url }) { - return fetch(`https://my-api-server.com/${params.path + url.search}`); -} -``` - -(Note that you may also need to proxy `POST`/`PATCH` etc requests, and forward `request.headers`, depending on your needs.) - -### How do I use middleware? - -`adapter-node` builds a middleware that you can use with your own server for production mode. In dev, you can add middleware to Vite by using a Vite plugin. For example: - -```js -// @errors: 2322 -// @filename: ambient.d.ts -declare module '@sveltejs/kit/vite'; // TODO this feels unnecessary, why can't it 'see' the declarations? - -// @filename: index.js -// ---cut--- -import { sveltekit } from '@sveltejs/kit/vite'; - -/** @type {import('vite').Plugin} */ -const myPlugin = { - name: 'log-request-middleware', - configureServer(server) { - server.middlewares.use((req, res, next) => { - console.log(`Got request ${req.url}`); - next(); - }); - } -}; - -/** @type {import('vite').UserConfig} */ -const config = { - plugins: [myPlugin, sveltekit()] -}; - -export default config; -``` - -See [Vite's `configureServer` docs](https://vitejs.dev/guide/api-plugin.html#configureserver) for more details including how to control ordering. - -### Does it work with Yarn 2? - -Sort of. The Plug'n'Play feature, aka 'pnp', is broken (it deviates from the Node module resolution algorithm, and [doesn't yet work with native JavaScript modules](https://github.com/yarnpkg/berry/issues/638) which SvelteKit — along with an [increasing number of packages](https://blog.sindresorhus.com/get-ready-for-esm-aa53530b3f77) — uses). You can use `nodeLinker: 'node-modules'` in your [`.yarnrc.yml`](https://yarnpkg.com/configuration/yarnrc#nodeLinker) file to disable pnp, but it's probably easier to just use npm or [pnpm](https://pnpm.io/), which is similarly fast and efficient but without the compatibility headaches. - -### How do I use with Yarn 3? - -Currently ESM Support within the latest Yarn (version 3) is considered [experimental](https://github.com/yarnpkg/berry/pull/2161). - -The below seems to work although your results may vary. - -First create a new application: - -```sh -yarn create svelte myapp -cd myapp -``` - -And enable Yarn Berry: - -```sh -yarn set version berry -yarn install -``` - -**Yarn 3 global cache** - -One of the more interesting features of Yarn Berry is the ability to have a single global cache for packages, instead of having multiple copies for each project on the disk. However, setting `enableGlobalCache` to true causes building to fail, so it is recommended to add the following to the `.yarnrc.yml` file: - -``` -nodeLinker: node-modules -``` - -This will cause packages to be downloaded into a local node_modules directory but avoids the above problem and is your best bet for using version 3 of Yarn at this point in time. diff --git a/documentation/docs/60-appendix/05-integrations.md b/documentation/docs/60-appendix/05-integrations.md deleted file mode 100644 index 4bf23ff97c51..000000000000 --- a/documentation/docs/60-appendix/05-integrations.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Integrations ---- - -## Preprocessors - -Preprocessors transform your `.svelte` files before passing them to the compiler. For example, if your `.svelte` file uses TypeScript and PostCSS, it must first be transformed into JavaScript and CSS so that the Svelte compiler can handle it. There are many [available preprocessors](https://sveltesociety.dev/tools#preprocessors). The Svelte team maintains two official ones discussed below. - -### `vitePreprocess` - -`vite-plugin-svelte` offers a [`vitePreprocess`](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/preprocess.md) feature which utilizes Vite for preprocessing. It is capable of handling the language flavors Vite handles: TypeScript, PostCSS, SCSS, Less, Stylus, and SugarSS. For convenience, it is re-exported from the `@sveltejs/kit/vite` package. If you set your project up with TypeScript it will be included by default: - -```js -// svelte.config.js -import { vitePreprocess } from '@sveltejs/kit/vite'; - -export default { - preprocess: [vitePreprocess()] -}; -``` - -### `svelte-preprocess` - -`svelte-preprocess` has some additional functionality not found in `vitePreprocess` such as support for Pug, Babel, and global styles. However, `vitePreprocess` may be faster and require less configuration, so it is used by default. Note that CoffeeScript is [not supported](https://github.com/sveltejs/kit/issues/2920#issuecomment-996469815) by SvelteKit. - -You will need to install `svelte-preprocess` with `npm install --save-dev svelte-preprocess` and [add it to your `svelte.config.js`](https://github.com/sveltejs/svelte-preprocess/blob/main/docs/usage.md#with-svelte-config). After that, you will often need to [install the corresponding library](https://github.com/sveltejs/svelte-preprocess/blob/main/docs/getting-started.md) such as `npm install -D sass` or `npm install -D less`. - -## Adders - -[Svelte Adders](https://sveltesociety.dev/templates#adders) allow you to setup many different complex integrations like Tailwind, PostCSS, Storybook, Firebase, GraphQL, mdsvex, and more with a single command. Please see [sveltesociety.dev](https://sveltesociety.dev/) for a full listing of templates, components, and tools available for use with Svelte and SvelteKit. - -## Vite plugins - -Since SvelteKit projects are built with Vite, you can use Vite plugins to enhance your project. See a list of available plugins at [`vitejs/awesome-vite`](https://github.com/vitejs/awesome-vite). - -## Integration FAQs - -The SvelteKit FAQ has a [how to do X with SvelteKit](./faq#how-do-i-use-x-with-sveltekit), which may be helpful if you still have questions. diff --git a/documentation/docs/60-appendix/10-faq.md b/documentation/docs/60-appendix/10-faq.md new file mode 100644 index 000000000000..4e14b5b84206 --- /dev/null +++ b/documentation/docs/60-appendix/10-faq.md @@ -0,0 +1,216 @@ +--- +title: Frequently asked questions +--- + +## Other resources + +Please see [the Svelte FAQ](../svelte/faq) and [`vite-plugin-svelte` FAQ](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md) as well for the answers to questions deriving from those libraries. + +## What can I make with SvelteKit? + +See [the documentation regarding project types](project-types) for more details. + +## How do I include details from package.json in my application? + +If you'd like to include your application's version number or other information from `package.json` in your application, you can load JSON like so: + +```ts +// @errors: 2732 +/// file: svelte.config.js +import pkg from './package.json' with { type: 'json' }; +``` + +## How do I fix the error I'm getting trying to include a package? + +Most issues related to including a library are due to incorrect packaging. You can check if a library's packaging is compatible with Node.js by entering it into [the publint website](https://publint.dev/). + +Here are a few things to keep in mind when checking if a library is packaged correctly: + +- `exports` takes precedence over the other entry point fields such as `main` and `module`. Adding an `exports` field may not be backwards-compatible as it prevents deep imports. +- ESM files should end with `.mjs` unless `"type": "module"` is set in which any case CommonJS files should end with `.cjs`. +- `main` should be defined if `exports` is not. It should be either a CommonJS or ESM file and adhere to the previous bullet. If a `module` field is defined, it should refer to an ESM file. +- Svelte components should be distributed as uncompiled `.svelte` files with any JS in the package written as ESM only. Custom script and style languages, like TypeScript and SCSS, should be preprocessed as vanilla JS and CSS respectively. We recommend using [`svelte-package`](./packaging) for packaging Svelte libraries, which will do this for you. + +Libraries work best in the browser with Vite when they distribute an ESM version, especially if they are dependencies of a Svelte component library. You may wish to suggest to library authors that they provide an ESM version. However, CommonJS (CJS) dependencies should work as well since, by default, [`vite-plugin-svelte` will ask Vite to pre-bundle them](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md#what-is-going-on-with-vite-and-pre-bundling-dependencies) using `esbuild` to convert them to ESM. + +If you are still encountering issues we recommend searching both [the Vite issue tracker](https://github.com/vitejs/vite/issues) and the issue tracker of the library in question. Sometimes issues can be worked around by fiddling with the [`optimizeDeps`](https://vitejs.dev/config/#dep-optimization-options) or [`ssr`](https://vitejs.dev/config/#ssr-options) config values though we recommend this as only a short-term workaround in favor of fixing the library in question. + +## How do I use the view transitions API? + +While SvelteKit does not have any specific integration with [view transitions](https://developer.chrome.com/docs/web-platform/view-transitions/), you can call `document.startViewTransition` in [`onNavigate`]($app-navigation#onNavigate) to trigger a view transition on every client-side navigation. + +```js +// @errors: 2339 2810 +import { onNavigate } from '$app/navigation'; + +onNavigate((navigation) => { + if (!document.startViewTransition) return; + + return new Promise((resolve) => { + document.startViewTransition(async () => { + resolve(); + await navigation.complete; + }); + }); +}); +``` + +For more, see ["Unlocking view transitions"](/blog/view-transitions) on the Svelte blog. + +## How do I set up a database? + +Put the code to query your database in a [server route](./routing#server) - don't query the database in .svelte files. You can create a `db.js` or similar that sets up a connection immediately and makes the client accessible throughout the app as a singleton. You can execute any one-time setup code in `hooks.server.js` and import your database helpers into any endpoint that needs them. + +You can use [the Svelte CLI](/docs/cli/overview) to automatically set up database integrations. + +## How do I use a client-side library accessing `document` or `window`? + +If you need access to the `document` or `window` variables or otherwise need code to run only on the client-side you can wrap it in a `browser` check: + +```js +/// <reference types="@sveltejs/kit" /> +// ---cut--- +import { browser } from '$app/environment'; + +if (browser) { + // client-only code here +} +``` + +You can also run code in `onMount` if you'd like to run it after the component has been first rendered to the DOM: + +```js +// @filename: ambient.d.ts +// @lib: ES2015 +declare module 'some-browser-only-library'; + +// @filename: index.js +// ---cut--- +import { onMount } from 'svelte'; + +onMount(async () => { + const { method } = await import('some-browser-only-library'); + method('hello world'); +}); +``` + +If the library you'd like to use is side-effect free you can also statically import it and it will be tree-shaken out in the server-side build where `onMount` will be automatically replaced with a no-op: + +```js +// @filename: ambient.d.ts +// @lib: ES2015 +declare module 'some-browser-only-library'; + +// @filename: index.js +// ---cut--- +import { onMount } from 'svelte'; +import { method } from 'some-browser-only-library'; + +onMount(() => { + method('hello world'); +}); +``` + +Finally, you may also consider using an `{#await}` block: +```svelte +<!--- file: index.svelte ---> +<script> + import { browser } from '$app/environment'; + + const ComponentConstructor = browser ? + import('some-browser-only-library').then((module) => module.Component) : + new Promise(() => {}); +</script> + +{#await ComponentConstructor} + <p>Loading...</p> +{:then component} + <svelte:component this={component} /> +{:catch error} + <p>Something went wrong: {error.message}</p> +{/await} +``` + +## How do I use a different backend API server? + +You can use [`event.fetch`](./load#Making-fetch-requests) to request data from an external API server, but be aware that you would need to deal with [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), which will result in complications such as generally requiring requests to be preflighted resulting in higher latency. Requests to a separate subdomain may also increase latency due to an additional DNS lookup, TLS setup, etc. If you wish to use this method, you may find [`handleFetch`](./hooks#Server-hooks-handleFetch) helpful. + +Another approach is to set up a proxy to bypass CORS headaches. In production, you would rewrite a path like `/api` to the API server; for local development, use Vite's [`server.proxy`](https://vitejs.dev/config/server-options.html#server-proxy) option. + +How to setup rewrites in production will depend on your deployment platform. If rewrites aren't an option, you could alternatively add an [API route](./routing#server): + +```js +/// file: src/routes/api/[...path]/+server.js +/** @type {import('./$types').RequestHandler} */ +export function GET({ params, url }) { + return fetch(`https://example.com/${params.path + url.search}`); +} +``` + +(Note that you may also need to proxy `POST`/`PATCH` etc requests, and forward `request.headers`, depending on your needs.) + +## How do I use middleware? + +`adapter-node` builds a middleware that you can use with your own server for production mode. In dev, you can add middleware to Vite by using a Vite plugin. For example: + +```js +// @errors: 2322 +// @filename: ambient.d.ts +declare module '@sveltejs/kit/vite'; // TODO this feels unnecessary, why can't it 'see' the declarations? + +// @filename: index.js +// ---cut--- +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').Plugin} */ +const myPlugin = { + name: 'log-request-middleware', + configureServer(server) { + server.middlewares.use((req, res, next) => { + console.log(`Got request ${req.url}`); + next(); + }); + } +}; + +/** @type {import('vite').UserConfig} */ +const config = { + plugins: [myPlugin, sveltekit()] +}; + +export default config; +``` + +See [Vite's `configureServer` docs](https://vitejs.dev/guide/api-plugin.html#configureserver) for more details including how to control ordering. + +## How do I use Yarn? + +### Does it work with Yarn 2? + +Sort of. The Plug'n'Play feature, aka 'pnp', is broken (it deviates from the Node module resolution algorithm, and [doesn't yet work with native JavaScript modules](https://github.com/yarnpkg/berry/issues/638) which SvelteKit — along with an [increasing number of packages](https://blog.sindresorhus.com/get-ready-for-esm-aa53530b3f77) — uses). You can use `nodeLinker: 'node-modules'` in your [`.yarnrc.yml`](https://yarnpkg.com/configuration/yarnrc#nodeLinker) file to disable pnp, but it's probably easier to just use npm or [pnpm](https://pnpm.io/), which is similarly fast and efficient but without the compatibility headaches. + +### How do I use with Yarn 3? + +Currently ESM Support within the latest Yarn (version 3) is considered [experimental](https://github.com/yarnpkg/berry/pull/2161). + +The below seems to work although your results may vary. First create a new application: + +```sh +yarn create svelte myapp +cd myapp +``` + +And enable Yarn Berry: + +```sh +yarn set version berry +yarn install +``` + +One of the more interesting features of Yarn Berry is the ability to have a single global cache for packages, instead of having multiple copies for each project on the disk. However, setting `enableGlobalCache` to true causes building to fail, so it is recommended to add the following to the `.yarnrc.yml` file: + +```yaml +nodeLinker: node-modules +``` + +This will cause packages to be downloaded into a local node_modules directory but avoids the above problem and is your best bet for using version 3 of Yarn at this point in time. diff --git a/documentation/docs/60-appendix/10-migrating.md b/documentation/docs/60-appendix/10-migrating.md deleted file mode 100644 index a2900db4e8a5..000000000000 --- a/documentation/docs/60-appendix/10-migrating.md +++ /dev/null @@ -1,199 +0,0 @@ ---- -title: Migrating from Sapper -rank: 1 ---- - -SvelteKit is the successor to Sapper and shares many elements of its design. - -If you have an existing Sapper app that you plan to migrate to SvelteKit, there are a number of changes you will need to make. You may find it helpful to view [some examples](additional-resources#examples) while migrating. - -## package.json - -### type: "module" - -Add `"type": "module"` to your `package.json`. You can do this step separately from the rest as part of an incremental migration if you are using Sapper 0.29.3 -or newer. - -### dependencies - -Remove `polka` or `express`, if you're using one of those, and any middleware such as `sirv` or `compression`. - -### devDependencies - -Remove `sapper` from your `devDependencies` and replace it with `@sveltejs/kit` and whichever [adapter](adapters) you plan to use (see [next section](migrating#project-files-configuration)). - -### scripts - -Any scripts that reference `sapper` should be updated: - -- `sapper build` should become `vite build` using the Node [adapter](adapters) -- `sapper export` should become `vite build` using the static [adapter](adapters) -- `sapper dev` should become `vite dev` -- `node __sapper__/build` should become `node build` - -## Project files - -The bulk of your app, in `src/routes`, can be left where it is, but several project files will need to be moved or updated. - -### Configuration - -Your `webpack.config.js` or `rollup.config.js` should be replaced with a `svelte.config.js`, as documented [here](configuration). Svelte preprocessor options should be moved to `config.preprocess`. - -You will need to add an [adapter](adapters). `sapper build` is roughly equivalent to [adapter-node](https://github.com/sveltejs/kit/tree/master/packages/adapter-node) while `sapper export` is roughly equivalent to [adapter-static](https://github.com/sveltejs/kit/tree/master/packages/adapter-static), though you might prefer to use an adapter designed for the platform you're deploying to. - -If you were using plugins for filetypes that are not automatically handled by [Vite](https://vitejs.dev), you will need to find Vite equivalents and add them to the [Vite config](project-structure#project-files-vite-config-js). - -### src/client.js - -This file has no equivalent in SvelteKit. Any custom logic (beyond `sapper.start(...)`) should be expressed in your `+layout.svelte` file, inside an `onMount` callback. - -### src/server.js - -When using `adapter-node` the equivalent is a [custom server](adapter-node#custom-server). Otherwise, this file has no direct equivalent, since SvelteKit apps can run in serverless environments. - -### src/service-worker.js - -Most imports from `@sapper/service-worker` have equivalents in [`$service-worker`](modules#$service-worker): - -- `files` is unchanged -- `routes` has been removed -- `shell` is now `build` -- `timestamp` is now `version` - -### src/template.html - -The `src/template.html` file should be renamed `src/app.html`. - -Remove `%sapper.base%`, `%sapper.scripts%` and `%sapper.styles%`. Replace `%sapper.head%` with `%sveltekit.head%` and `%sapper.html%` with `%sveltekit.body%`. The `<div id="sapper">` is no longer necessary. - -### src/node_modules - -A common pattern in Sapper apps is to put your internal library in a directory inside `src/node_modules`. This doesn't work with Vite, so we use [`src/lib`](modules#$lib) instead. - -## Pages and layouts - -### Renamed files - -Routes now are made up of the folder name exclusively to remove ambiguity, the folder names leading up to a `+page.svelte` correspond to the route. See [the routing docs](routing) for an overview. The following shows a old/new comparison: - -| Old | New | -| ------------------------- | ------------------------- | -| routes/about/index.svelte | routes/about/+page.svelte | -| routes/about.svelte | routes/about/+page.svelte | - -Your custom error page component should be renamed from `_error.svelte` to `+error.svelte`. Any `_layout.svelte` files should likewise be renamed `+layout.svelte`. [Any other files are ignored](routing#other-files). - -### Imports - -The `goto`, `prefetch` and `prefetchRoutes` imports from `@sapper/app` should be replaced with `goto`, `preloadData` and `preloadCode` imports respectively from [`$app/navigation`](modules#$app-navigation). - -The `stores` import from `@sapper/app` should be replaced — see the [Stores](migrating#pages-and-layouts-stores) section below. - -Any files you previously imported from directories in `src/node_modules` will need to be replaced with [`$lib`](modules#$lib) imports. - -### Preload - -As before, pages and layouts can export a function that allows data to be loaded before rendering takes place. - -This function has been renamed from `preload` to [`load`](load), it now lives in a `+page.js` (or `+layout.js`) next to its `+page.svelte` (or `+layout.svelte`), and its API has changed. Instead of two arguments — `page` and `session` — there is a single `event` argument. - -There is no more `this` object, and consequently no `this.fetch`, `this.error` or `this.redirect`. Instead, you can get [`fetch`](load#making-fetch-requests) from the input methods, and both [`error`](load#errors) and [`redirect`](load#redirects) are now thrown. - -### Stores - -In Sapper, you would get references to provided stores like so: - -```js -// @filename: ambient.d.ts -declare module '@sapper/app'; - -// @filename: index.js -// ---cut--- -import { stores } from '@sapper/app'; -const { preloading, page, session } = stores(); -``` - -The `page` store still exists; `preloading` has been replaced with a `navigating` store that contains `from` and `to` properties. `page` now has `url` and `params` properties, but no `path` or `query`. - -You access them differently in SvelteKit. `stores` is now `getStores`, but in most cases it is unnecessary since you can import `navigating`, and `page` directly from [`$app/stores`](modules#$app-stores). - -### Routing - -Regex routes are no longer supported. Instead, use [advanced route matching](advanced-routing#matching). - -### Segments - -Previously, layout components received a `segment` prop indicating the child segment. This has been removed; you should use the more flexible `$page.url.pathname` value to derive the segment you're interested in. - -### URLs - -In Sapper, all relative URLs were resolved against the base URL — usually `/`, unless the `basepath` option was used — rather than against the current page. - -This caused problems and is no longer the case in SvelteKit. Instead, relative URLs are resolved against the current page (or the destination page, for `fetch` URLs in `load` functions) instead. In most cases, it's easier to use root-relative (i.e. starts with `/`) URLs, since their meaning is not context-dependent. - -### <a> attributes - -- `sapper:prefetch` is now `data-sveltekit-preload-data` -- `sapper:noscroll` is now `data-sveltekit-noscroll` - -## Endpoints - -In Sapper, [server routes](routing#server) received the `req` and `res` objects exposed by Node's `http` module (or the augmented versions provided by frameworks like Polka and Express). - -SvelteKit is designed to be agnostic as to where the app is running — it could be running on a Node server, but could equally be running on a serverless platform or in a Cloudflare Worker. For that reason, you no longer interact directly with `req` and `res`. Your endpoints will need to be updated to match the new signature. - -To support this environment-agnostic behavior, `fetch` is now available in the global context, so you don't need to import `node-fetch`, `cross-fetch`, or similar server-side fetch implementations in order to use it. - -## Integrations - -See [integrations](./integrations) for detailed information about integrations. - -### HTML minifier - -Sapper includes `html-minifier` by default. SvelteKit does not include this, but you can add it as a prod dependency and then use it through a [hook](hooks#server-hooks-handle): - -```js -// @filename: ambient.d.ts -/// <reference types="@sveltejs/kit" /> -declare module 'html-minifier'; - -// @filename: index.js -// ---cut--- -import { minify } from 'html-minifier'; -import { building } from '$app/environment'; - -const minification_options = { - collapseBooleanAttributes: true, - collapseWhitespace: true, - conservativeCollapse: true, - decodeEntities: true, - html5: true, - ignoreCustomComments: [/^#/], - minifyCSS: true, - minifyJS: false, - removeAttributeQuotes: true, - removeComments: false, // some hydration code needs comments, so leave them in - removeOptionalTags: true, - removeRedundantAttributes: true, - removeScriptTypeAttributes: true, - removeStyleLinkTypeAttributes: true, - sortAttributes: true, - sortClassName: true -}; - -/** @type {import('@sveltejs/kit').Handle} */ -export async function handle({ event, resolve }) { - let page = ''; - - return resolve(event, { - transformPageChunk: ({ html, done }) => { - page += html; - if (done) { - return building ? minify(page, minification_options) : page; - } - } - }); -} -``` - -Note that `prerendering` is `false` when using `vite preview` to test the production build of the site, so to verify the results of minifying, you'll need to inspect the built HTML files directly. diff --git a/documentation/docs/60-appendix/20-additional-resources.md b/documentation/docs/60-appendix/20-additional-resources.md deleted file mode 100644 index 4877ef984568..000000000000 --- a/documentation/docs/60-appendix/20-additional-resources.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Additional resources ---- - -## FAQs - -Please see the [SvelteKit FAQ](../faq) for solutions to common issues and helpful tips and tricks. - -The [Svelte FAQ](https://svelte.dev/faq) and [`vite-plugin-svelte` FAQ](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md) may also be helpful for questions deriving from those libraries. - -## Examples - -We've written and published a few different SvelteKit sites as examples: - -- [`sveltejs/realworld`](https://github.com/sveltejs/realworld) contains an example blog site -- [The `sites/kit.svelte.dev` directory](https://github.com/sveltejs/kit/tree/master/sites/kit.svelte.dev) contains the code for this site -- [`sveltejs/sites`](https://github.com/sveltejs/sites) contains the code for [svelte.dev](https://github.com/sveltejs/sites/tree/master/sites/svelte.dev) and for a [HackerNews clone](https://github.com/sveltejs/sites/tree/master/sites/hn.svelte.dev) - -SvelteKit users have also published plenty of examples on GitHub, under the [#sveltekit](https://github.com/topics/sveltekit) and [#sveltekit-template](https://github.com/topics/sveltekit-template) topics, as well as on [the Svelte Society site](https://sveltesociety.dev/templates#svelte-kit). Note that these have not been vetted by the maintainers and may not be up to date. - -## Support - -You can ask for help on [Discord](https://svelte.dev/chat) and [StackOverflow](https://stackoverflow.com/questions/tagged/sveltekit). Please first search for information related to your issue in the FAQ, Google or another search engine, issue tracker, and Discord chat history in order to be respectful of others' time. There are many more people asking questions than answering them, so this will help in allowing the community to grow in a scalable fashion. diff --git a/documentation/docs/60-appendix/20-integrations.md b/documentation/docs/60-appendix/20-integrations.md new file mode 100644 index 000000000000..2433f55209d1 --- /dev/null +++ b/documentation/docs/60-appendix/20-integrations.md @@ -0,0 +1,55 @@ +--- +title: Integrations +--- + +## `vitePreprocess` + +Including [`vitePreprocess`](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/preprocess.md) in your project will allow you to use the various flavors of CSS that Vite supports: PostCSS, SCSS, Less, Stylus, and SugarSS. If you set your project up with TypeScript it will be included by default: + +```js +// svelte.config.js +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: [vitePreprocess()] +}; + +export default config; +``` + +You will also need to use a preprocessor if you're using TypeScript with Svelte 4. TypeScript is supported natively in Svelte 5 if you're using only the type syntax. To use more complex TypeScript syntax in Svelte 5, you will need still need a preprocessor and can use `vitePreprocess({ script: true })`. + +## Add-ons + +Run [`npx sv add`](/docs/cli/sv-add) to setup many different complex integrations with a single command including: +- prettier (formatting) +- eslint (linting) +- vitest (unit testing) +- playwright (e2e testing) +- lucia (auth) +- tailwind (CSS) +- drizzle (DB) +- paraglide (i18n) +- mdsvex (markdown) +- storybook (frontend workshop) + +## Packages + +Check out [the packages page](/packages) for a curated set of high quality Svelte packages. You can also see [sveltesociety.dev](https://sveltesociety.dev/) for additional libraries, templates, and resources. + +## Additional integrations + +### `svelte-preprocess` + +`svelte-preprocess` has some additional functionality not found in `vitePreprocess` such as support for Pug, Babel, and global styles. However, `vitePreprocess` may be faster and require less configuration, so it is used by default. Note that CoffeeScript is [not supported](https://github.com/sveltejs/kit/issues/2920#issuecomment-996469815) by SvelteKit. + +You will need to install `svelte-preprocess` with `npm i -D svelte-preprocess` and [add it to your `svelte.config.js`](https://github.com/sveltejs/svelte-preprocess/blob/main/docs/usage.md#with-svelte-config). After that, you will often need to [install the corresponding library](https://github.com/sveltejs/svelte-preprocess/blob/main/docs/getting-started.md) such as `npm i -D sass` or `npm i -D less`. + +## Vite plugins + +Since SvelteKit projects are built with Vite, you can use Vite plugins to enhance your project. See a list of available plugins at [`vitejs/awesome-vite`](https://github.com/vitejs/awesome-vite?tab=readme-ov-file#plugins). + +## Integration FAQs + +[The SvelteKit FAQ](./faq) answers many questions about how to do X with SvelteKit, which may be helpful if you still have questions. diff --git a/documentation/docs/60-appendix/25-debugging.md b/documentation/docs/60-appendix/25-debugging.md new file mode 100644 index 000000000000..68c3f055eee4 --- /dev/null +++ b/documentation/docs/60-appendix/25-debugging.md @@ -0,0 +1,68 @@ +--- +title: Breakpoint Debugging +--- + +In addition to the [`@debug`](../svelte/@debug) tag, you can also debug Svelte and SvelteKit projects using breakpoints within various tools and development environments. This includes both frontend and backend code. + +The following guides assume your JavaScript runtime environment is Node.js. + +## Visual Studio Code + +With the built-in debug terminal, you can set up breakpoints in source files within VSCode. + +1. Open the command palette: `CMD/Ctrl` + `Shift` + `P`. +2. Find and launch "Debug: JavaScript Debug Terminal". +3. Start your project using the debug terminal. For example: `npm run dev`. +4. Set some breakpoints in your client or server-side source code. +5. Trigger the breakpoint. + +### Launch via debug pane + +You may alternatively set up a `.vscode/launch.json` in your project. To set one up automatically: + +1. Go to the "Run and Debug" pane. +2. In the "Run" select menu, choose "Node.js...". +3. Select the "run script" that corresponds to your project, such as "Run script: dev". +4. Press the "Start debugging" play button, or hit `F5` to begin breakpoint debugging. + +Here's an example `launch.json`: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "command": "npm run dev", + "name": "Run development server", + "request": "launch", + "type": "node-terminal" + } + ] +} +``` + +Further reading: <https://code.visualstudio.com/docs/editor/debugging>. + +## Other Editors + +If you use a different editor, these community guides might be useful for you: + +- [WebStorm Svelte: Debug Your Application](https://www.jetbrains.com/help/webstorm/svelte.html#ws_svelte_debug) +- [Debugging JavaScript Frameworks in Neovim](https://theosteiner.de/debugging-javascript-frameworks-in-neovim) + +## Google Chrome and Microsoft Edge Developer Tools + +It's possible to debug Node.js applications using a browser-based debugger. + +> [!NOTE] Note this only works with debugging client-side SvelteKit source maps. + +1. Run the `--inspect` flag when starting the Vite server with Node.js. For instance: `NODE_OPTIONS="--inspect" npm run dev` +2. Open your site in a new tab. Typically at `localhost:5173`. +3. Open your browser's dev tools, and click on the "Open dedicated DevTools for Node.js" icon near the top-left. It should display the Node.js logo. +4. Set up breakpoints and debug your application. + +You may alternatively open the debugger devtools by navigating to `chrome://inspect` in Google Chrome, or `edge://inspect` in Microsoft Edge. + +## References + +- [Debugging Node.js](https://nodejs.org/en/learn/getting-started/debugging) diff --git a/documentation/docs/60-appendix/30-glossary.md b/documentation/docs/60-appendix/30-glossary.md deleted file mode 100644 index ce80e9ef60ea..000000000000 --- a/documentation/docs/60-appendix/30-glossary.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Glossary ---- - -The core of SvelteKit provides a highly configurable rendering engine. This section describes some of the terms used when discussing rendering. A reference for setting these options is provided in the documentation above. - -## CSR - -Client-side rendering (CSR) is the generation of the page contents in the web browser using JavaScript. - -In SvelteKit, client-side rendering will be used by default, but you can turn off JavaScript with [the `csr = false` page option](page-options#csr). - -## Hydration - -Svelte components store some state and update the DOM when the state is updated. When fetching data during SSR, by default SvelteKit will store this data and transmit it to the client along with the server-rendered HTML. The components can then be initialized on the client with that data without having to call the same API endpoints again. Svelte will then check that the DOM is in the expected state and attach event listeners in a process called hydration. Once the components are fully hydrated, they can react to changes to their properties just like any newly created Svelte component. - -In SvelteKit, pages will be hydrated by default, but you can turn off JavaScript with [the `csr = false` page option](page-options#csr). - -## Prerendering - -Prerendering means computing the contents of a page at build time and saving the HTML for display. This approach has the same benefits as traditional server-rendered pages, but avoids recomputing the page for each visitor and so scales nearly for free as the number of visitors increases. The tradeoff is that the build process is more expensive and prerendered content can only be updated by building and deploying a new version of the application. - -Not all pages can be prerendered. The basic rule is this: for content to be prerenderable, any two users hitting it directly must get the same content from the server, and the page must not contain [actions](form-actions). Note that you can still prerender content that is loaded based on the page's parameters as long as all users will be seeing the same prerendered content. - -Pre-rendered pages are not limited to static content. You can build personalized pages if user-specific data is fetched and rendered client-side. This is subject to the caveat that you will experience the downsides of not doing SSR for that content as discussed above. - -In SvelteKit, you can control prerendering with [the `prerender` page option](page-options#prerender) and [`prerender` config](configuration#prerender) in `svelte.config.js`. - -## Routing - -By default, when you navigate to a new page (by clicking on a link or using the browser's forward or back buttons), SvelteKit will intercept the attempted navigation and handle it instead of allowing the browser to send a request to the server for the destination page. SvelteKit will then update the displayed contents on the client by rendering the component for the new page, which in turn can make calls to the necessary API endpoints. This process of updating the page on the client in response to attempted navigation is called client-side routing. - -In SvelteKit, client-side routing will be used by default, but you can skip it with [`data-sveltekit-reload`](link-options#data-sveltekit-reload). - -## SPA - -A single-page app (SPA) is an application in which all requests to the server load a single HTML file which then does client-side rendering of the requested contents based on the requested URL. All navigation is handled on the client-side in a process called client-side routing with per-page contents being updated and common layout elements remaining largely unchanged. SPAs do not provide SSR, which has the shortcoming described above. However, some applications are not greatly impacted by these shortcomings such as a complex business application behind a login where SEO would not be important and it is known that users will be accessing the application from a consistent computing environment. - -In SvelteKit, you can [build an SPA with `adapter-static`](single-page-apps). - -## SSG - -Static Site Generation (SSG) is a term that refers to a site where every page is prerendered. SvelteKit was not built to do only static site generation like some tools and so may not scale as well to efficiently render a very large number of pages as tools built specifically for that purpose. However, in contrast to most purpose-built SSGs, SvelteKit does nicely allow for mixing and matching different rendering types on different pages. One benefit of fully prerendering a site is that you do not need to maintain or pay for servers to perform SSR. Once generated, the site can be served from CDNs, leading to great "time to first byte" performance. This delivery model is often referred to as JAMstack. - -In SvelteKit, you can do static site generation by using [`adapter-static`](adapter-static) or by configuring every page to be prerendered using [the `prerender` page option](page-options#prerender) or [`prerender` config](configuration#prerender) in `svelte.config.js`. - -## SSR - -Server-side rendering (SSR) is the generation of the page contents on the server. SSR is generally preferred for SEO. While some search engines can index content that is dynamically generated on the client-side it may take longer even in these cases. It also tends to improve perceived performance and makes your app accessible to users if JavaScript fails or is disabled (which happens [more often than you probably think](https://kryogenix.org/code/browser/everyonehasjs.html)). - -In SvelteKit, pages are server-side rendered by default. You can disable SSR with [the `ssr` page option](https://kit.svelte.dev/docs/page-options#ssr). diff --git a/documentation/docs/60-appendix/30-migrating-to-sveltekit-2.md b/documentation/docs/60-appendix/30-migrating-to-sveltekit-2.md new file mode 100644 index 000000000000..ed7666c54800 --- /dev/null +++ b/documentation/docs/60-appendix/30-migrating-to-sveltekit-2.md @@ -0,0 +1,193 @@ +--- +title: Migrating to SvelteKit v2 +--- + +Upgrading from SvelteKit version 1 to version 2 should be mostly seamless. There are a few breaking changes to note, which are listed here. You can use `npx sv migrate sveltekit-2` to migrate some of these changes automatically. + +We highly recommend upgrading to the most recent 1.x version before upgrading to 2.0, so that you can take advantage of targeted deprecation warnings. We also recommend [updating to Svelte 4](../svelte/v4-migration-guide) first: Later versions of SvelteKit 1.x support it, and SvelteKit 2.0 requires it. + +## `redirect` and `error` are no longer thrown by you + +Previously, you had to `throw` the values returned from `error(...)` and `redirect(...)` yourself. In SvelteKit 2 this is no longer the case — calling the functions is sufficient. + +```js +import { error } from '@sveltejs/kit' + +// ... +---throw error(500, 'something went wrong');--- ++++error(500, 'something went wrong');+++ +``` + +`svelte-migrate` will do these changes automatically for you. + +If the error or redirect is thrown inside a `try {...}` block (hint: don't do this!), you can distinguish them from unexpected errors using [`isHttpError`](@sveltejs-kit#isHttpError) and [`isRedirect`](@sveltejs-kit#isRedirect) imported from `@sveltejs/kit`. + +## path is required when setting cookies + +When receiving a `Set-Cookie` header that doesn't specify a `path`, browsers will [set the cookie path](https://www.rfc-editor.org/rfc/rfc6265#section-5.1.4) to the parent of the resource in question. This behaviour isn't particularly helpful or intuitive, and frequently results in bugs because the developer expected the cookie to apply to the domain as a whole. + +As of SvelteKit 2.0, you need to set a `path` when calling `cookies.set(...)`, `cookies.delete(...)` or `cookies.serialize(...)` so that there's no ambiguity. Most of the time, you probably want to use `path: '/'`, but you can set it to whatever you like, including relative paths — `''` means 'the current path', `'.'` means 'the current directory'. + +```js +/** @type {import('./$types').PageServerLoad} */ +export function load({ cookies }) { + cookies.set(name, value, +++{ path: '/' }+++); + return { response } +} +``` + +`svelte-migrate` will add comments highlighting the locations that need to be adjusted. + +## Top-level promises are no longer awaited + +In SvelteKit version 1, if the top-level properties of the object returned from a `load` function were promises, they were automatically awaited. With the introduction of [streaming](/blog/streaming-snapshots-sveltekit) this behavior became a bit awkward as it forces you to nest your streamed data one level deep. + +As of version 2, SvelteKit no longer differentiates between top-level and non-top-level promises. To get back the blocking behavior, use `await` (with `Promise.all` to prevent waterfalls, where appropriate): + +```js +// @filename: ambient.d.ts +declare const url: string; + +// @filename: index.js +// ---cut--- +// If you have a single promise +/** @type {import('./$types').PageServerLoad} */ +export +++async+++ function load({ fetch }) { + const response = +++await+++ fetch(url).then(r => r.json()); + return { response } +} +``` + +```js +// @filename: ambient.d.ts +declare const url1: string; +declare const url2: string; + +// @filename: index.js +// ---cut--- +// If you have multiple promises +/** @type {import('./$types').PageServerLoad} */ +export +++async+++ function load({ fetch }) { +--- const a = fetch(url1).then(r => r.json());--- +--- const b = fetch(url2).then(r => r.json());--- ++++ const [a, b] = await Promise.all([ + fetch(url1).then(r => r.json()), + fetch(url2).then(r => r.json()), + ]);+++ + return { a, b }; +} +``` + +## goto(...) changes + +`goto(...)` no longer accepts external URLs. To navigate to an external URL, use `window.location.href = url`. The `state` object now determines `$page.state` and must adhere to the `App.PageState` interface, if declared. See [shallow routing](shallow-routing) for more details. + +## paths are now relative by default + +In SvelteKit 1, `%sveltekit.assets%` in your `app.html` was replaced with a relative path by default (i.e. `.` or `..` or `../..` etc, depending on the path being rendered) during server-side rendering unless the [`paths.relative`](configuration#paths) config option was explicitly set to `false`. The same was true for `base` and `assets` imported from `$app/paths`, but only if the `paths.relative` option was explicitly set to `true`. + +This inconsistency is fixed in version 2. Paths are either always relative or always absolute, depending on the value of [`paths.relative`](configuration#paths). It defaults to `true` as this results in more portable apps: if the `base` is something other than the app expected (as is the case when viewed on the [Internet Archive](https://archive.org/), for example) or unknown at build time (as is the case when deploying to [IPFS](https://ipfs.tech/) and so on), fewer things are likely to break. + +## Server fetches are not trackable anymore + +Previously it was possible to track URLs from `fetch`es on the server in order to rerun load functions. This poses a possible security risk (private URLs leaking), and for this reason it was behind the `dangerZone.trackServerFetches` setting, which is now removed. + +## `preloadCode` arguments must be prefixed with `base` + +SvelteKit exposes two functions, [`preloadCode`]($app-navigation#preloadCode) and [`preloadData`]($app-navigation#preloadData), for programmatically loading the code and data associated with a particular path. In version 1, there was a subtle inconsistency — the path passed to `preloadCode` did not need to be prefixed with the `base` path (if set), while the path passed to `preloadData` did. + +This is fixed in SvelteKit 2 — in both cases, the path should be prefixed with `base` if it is set. + +Additionally, `preloadCode` now takes a single argument rather than _n_ arguments. + +## `resolvePath` has been removed + +SvelteKit 1 included a function called `resolvePath` which allows you to resolve a route ID (like `/blog/[slug]`) and a set of parameters (like `{ slug: 'hello' }`) to a pathname. Unfortunately the return value didn't include the `base` path, limiting its usefulness in cases where `base` was set. + +For this reason, SvelteKit 2 replaces `resolvePath` with a (slightly better named) function called `resolveRoute`, which is imported from `$app/paths` and which takes `base` into account. + +```js +---import { resolvePath } from '@sveltejs/kit'; +import { base } from '$app/paths';--- ++++import { resolveRoute } from '$app/paths';+++ + +---const path = base + resolvePath('/blog/[slug]', { slug });--- ++++const path = resolveRoute('/blog/[slug]', { slug });+++ +``` + +`svelte-migrate` will do the method replacement for you, though if you later prepend the result with `base`, you need to remove that yourself. + +## Improved error handling + +Errors are handled inconsistently in SvelteKit 1. Some errors trigger the `handleError` hook but there is no good way to discern their status (for example, the only way to tell a 404 from a 500 is by seeing if `event.route.id` is `null`), while others (such as 405 errors for `POST` requests to pages without actions) don't trigger `handleError` at all, but should. In the latter case, the resulting `$page.error` will deviate from the [`App.Error`](types#Error) type, if it is specified. + +SvelteKit 2 cleans this up by calling `handleError` hooks with two new properties: `status` and `message`. For errors thrown from your code (or library code called by your code) the status will be `500` and the message will be `Internal Error`. While `error.message` may contain sensitive information that should not be exposed to users, `message` is safe. + +## Dynamic environment variables cannot be used during prerendering + +The `$env/dynamic/public` and `$env/dynamic/private` modules provide access to _run time_ environment variables, as opposed to the _build time_ environment variables exposed by `$env/static/public` and `$env/static/private`. + +During prerendering in SvelteKit 1, they are one and the same. This means that prerendered pages that make use of 'dynamic' environment variables are really 'baking in' build time values, which is incorrect. Worse, `$env/dynamic/public` is populated in the browser with these stale values if the user happens to land on a prerendered page before navigating to dynamically-rendered pages. + +Because of this, dynamic environment variables can no longer be read during prerendering in SvelteKit 2 — you should use the `static` modules instead. If the user lands on a prerendered page, SvelteKit will request up-to-date values for `$env/dynamic/public` from the server (by default from a module called `/_app/env.js`) instead of reading them from the server-rendered HTML. + +## `form` and `data` have been removed from `use:enhance` callbacks + +If you provide a callback to [`use:enhance`](form-actions#Progressive-enhancement-use:enhance), it will be called with an object containing various useful properties. + +In SvelteKit 1, those properties included `form` and `data`. These were deprecated some time ago in favour of `formElement` and `formData`, and have been removed altogether in SvelteKit 2. + +## Forms containing file inputs must use `multipart/form-data` + +If a form contains an `<input type="file">` but does not have an `enctype="multipart/form-data"` attribute, non-JS submissions will omit the file. SvelteKit 2 will throw an error if it encounters a form like this during a `use:enhance` submission to ensure that your forms work correctly when JavaScript is not present. + +## Generated `tsconfig.json` is more strict + +Previously, the generated `tsconfig.json` was trying its best to still produce a somewhat valid config when your `tsconfig.json` included `paths` or `baseUrl`. In SvelteKit 2, the validation is more strict and will warn when you use either `paths` or `baseUrl` in your `tsconfig.json`. These settings are used to generate path aliases and you should use [the `alias` config](configuration#alias) option in your `svelte.config.js` instead, to also create a corresponding alias for the bundler. + +## `getRequest` no longer throws errors + +The `@sveltejs/kit/node` module exports helper functions for use in Node environments, including `getRequest` which turns a Node [`ClientRequest`](https://nodejs.org/api/http.html#class-httpclientrequest) into a standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. + +In SvelteKit 1, `getRequest` could throw if the `Content-Length` header exceeded the specified size limit. In SvelteKit 2, the error will not be thrown until later, when the request body (if any) is being read. This enables better diagnostics and simpler code. + +## `vitePreprocess` is no longer exported from `@sveltejs/kit/vite` + +Since `@sveltejs/vite-plugin-svelte` is now a peer dependency, SvelteKit 2 no longer re-exports `vitePreprocess`. You should import it directly from `@sveltejs/vite-plugin-svelte`. + +## Updated dependency requirements + +SvelteKit 2 requires Node `18.13` or higher, and the following minimum dependency versions: + +- `svelte@4` +- `vite@5` +- `typescript@5` +- `@sveltejs/vite-plugin-svelte@3` (this is now required as a `peerDependency` of SvelteKit — previously it was directly depended upon) +- `@sveltejs/adapter-cloudflare@3` (if you're using these adapters) +- `@sveltejs/adapter-cloudflare-workers@2` +- `@sveltejs/adapter-netlify@3` +- `@sveltejs/adapter-node@2` +- `@sveltejs/adapter-static@3` +- `@sveltejs/adapter-vercel@4` + +`svelte-migrate` will update your `package.json` for you. + +As part of the TypeScript upgrade, the generated `tsconfig.json` (the one your `tsconfig.json` extends from) now uses `"moduleResolution": "bundler"` (which is recommended by the TypeScript team, as it properly resolves types from packages with an `exports` map in package.json) and `verbatimModuleSyntax` (which replaces the existing `importsNotUsedAsValues ` and `preserveValueImports` flags — if you have those in your `tsconfig.json`, remove them. `svelte-migrate` will do this for you). + +## SvelteKit 2.12: $app/stores deprecated + +SvelteKit 2.12 introduced `$app/state` based on the [Svelte 5 runes API](/docs/svelte/what-are-runes). `$app/state` provides everything that `$app/stores` provides but with more flexibility as to where and how you use it. Most importantly, the `page` object is now fine-grained, e.g. updates to `page.state` will not invalidate `page.data` and vice-versa. + +As a consequence, `$app/stores` is deprecated and subject to be removed in SvelteKit 3. We recommend [upgrading to Svelte 5](/docs/svelte/v5-migration-guide), if you haven't already, and then migrate away from `$app/stores`. Most of the replacements should be pretty simple: Replace the `$app/stores` import with `$app/state` and remove the `$` prefixes from the usage sites. + +```svelte +<script> + ---import { page } from '$app/stores';--- + +++import { page } from '$app/state';+++ +</script> + +---{$page.data}--- ++++{page.data}+++ +``` + +Use `npx sv migrate app-state` to auto-migrate most of your `$app/stores` usages inside `.svelte` components. diff --git a/documentation/docs/60-appendix/40-migrating.md b/documentation/docs/60-appendix/40-migrating.md new file mode 100644 index 000000000000..927d927832db --- /dev/null +++ b/documentation/docs/60-appendix/40-migrating.md @@ -0,0 +1,199 @@ +--- +title: Migrating from Sapper +rank: 1 +--- + +SvelteKit is the successor to Sapper and shares many elements of its design. + +If you have an existing Sapper app that you plan to migrate to SvelteKit, there are a number of changes you will need to make. You may find it helpful to view [some examples](additional-resources#Examples) while migrating. + +## package.json + +### type: "module" + +Add `"type": "module"` to your `package.json`. You can do this step separately from the rest as part of an incremental migration if you are using Sapper 0.29.3 +or newer. + +### dependencies + +Remove `polka` or `express`, if you're using one of those, and any middleware such as `sirv` or `compression`. + +### devDependencies + +Remove `sapper` from your `devDependencies` and replace it with `@sveltejs/kit` and whichever [adapter](adapters) you plan to use (see [next section](migrating#Project-files-Configuration)). + +### scripts + +Any scripts that reference `sapper` should be updated: + +- `sapper build` should become `vite build` using the Node [adapter](adapters) +- `sapper export` should become `vite build` using the static [adapter](adapters) +- `sapper dev` should become `vite dev` +- `node __sapper__/build` should become `node build` + +## Project files + +The bulk of your app, in `src/routes`, can be left where it is, but several project files will need to be moved or updated. + +### Configuration + +Your `webpack.config.js` or `rollup.config.js` should be replaced with a `svelte.config.js`, as documented [here](configuration). Svelte preprocessor options should be moved to `config.preprocess`. + +You will need to add an [adapter](adapters). `sapper build` is roughly equivalent to [adapter-node](adapter-node) while `sapper export` is roughly equivalent to [adapter-static](adapter-static), though you might prefer to use an adapter designed for the platform you're deploying to. + +If you were using plugins for filetypes that are not automatically handled by [Vite](https://vitejs.dev), you will need to find Vite equivalents and add them to the [Vite config](project-structure#Project-files-vite.config.js). + +### src/client.js + +This file has no equivalent in SvelteKit. Any custom logic (beyond `sapper.start(...)`) should be expressed in your `+layout.svelte` file, inside an `onMount` callback. + +### src/server.js + +When using `adapter-node` the equivalent is a [custom server](adapter-node#Custom-server). Otherwise, this file has no direct equivalent, since SvelteKit apps can run in serverless environments. + +### src/service-worker.js + +Most imports from `@sapper/service-worker` have equivalents in [`$service-worker`]($service-worker): + +- `files` is unchanged +- `routes` has been removed +- `shell` is now `build` +- `timestamp` is now `version` + +### src/template.html + +The `src/template.html` file should be renamed `src/app.html`. + +Remove `%sapper.base%`, `%sapper.scripts%` and `%sapper.styles%`. Replace `%sapper.head%` with `%sveltekit.head%` and `%sapper.html%` with `%sveltekit.body%`. The `<div id="sapper">` is no longer necessary. + +### src/node_modules + +A common pattern in Sapper apps is to put your internal library in a directory inside `src/node_modules`. This doesn't work with Vite, so we use [`src/lib`]($lib) instead. + +## Pages and layouts + +### Renamed files + +Routes now are made up of the folder name exclusively to remove ambiguity, the folder names leading up to a `+page.svelte` correspond to the route. See [the routing docs](routing) for an overview. The following shows a old/new comparison: + +| Old | New | +| ------------------------- | ------------------------- | +| routes/about/index.svelte | routes/about/+page.svelte | +| routes/about.svelte | routes/about/+page.svelte | + +Your custom error page component should be renamed from `_error.svelte` to `+error.svelte`. Any `_layout.svelte` files should likewise be renamed `+layout.svelte`. [Any other files are ignored](routing#Other-files). + +### Imports + +The `goto`, `prefetch` and `prefetchRoutes` imports from `@sapper/app` should be replaced with `goto`, `preloadData` and `preloadCode` imports respectively from [`$app/navigation`]($app-navigation). + +The `stores` import from `@sapper/app` should be replaced — see the [Stores](migrating#Pages-and-layouts-Stores) section below. + +Any files you previously imported from directories in `src/node_modules` will need to be replaced with [`$lib`]($lib) imports. + +### Preload + +As before, pages and layouts can export a function that allows data to be loaded before rendering takes place. + +This function has been renamed from `preload` to [`load`](load), it now lives in a `+page.js` (or `+layout.js`) next to its `+page.svelte` (or `+layout.svelte`), and its API has changed. Instead of two arguments — `page` and `session` — there is a single `event` argument. + +There is no more `this` object, and consequently no `this.fetch`, `this.error` or `this.redirect`. Instead, you can get [`fetch`](load#Making-fetch-requests) from the input methods, and both [`error`](load#Errors) and [`redirect`](load#Redirects) are now thrown. + +### Stores + +In Sapper, you would get references to provided stores like so: + +```js +// @filename: ambient.d.ts +declare module '@sapper/app'; + +// @filename: index.js +// ---cut--- +import { stores } from '@sapper/app'; +const { preloading, page, session } = stores(); +``` + +The `page` store still exists; `preloading` has been replaced with a `navigating` store that contains `from` and `to` properties. `page` now has `url` and `params` properties, but no `path` or `query`. + +You access them differently in SvelteKit. `stores` is now `getStores`, but in most cases it is unnecessary since you can import `navigating`, and `page` directly from [`$app/stores`]($app-stores). If you're on Svelte 5 and SvelteKit 2.12 or higher, consider using [`$app/state`]($app-state) instead. + +### Routing + +Regex routes are no longer supported. Instead, use [advanced route matching](advanced-routing#Matching). + +### Segments + +Previously, layout components received a `segment` prop indicating the child segment. This has been removed; you should use the more flexible `$page.url.pathname` (or `page.url.pathname`) value to derive the segment you're interested in. + +### URLs + +In Sapper, all relative URLs were resolved against the base URL — usually `/`, unless the `basepath` option was used — rather than against the current page. + +This caused problems and is no longer the case in SvelteKit. Instead, relative URLs are resolved against the current page (or the destination page, for `fetch` URLs in `load` functions) instead. In most cases, it's easier to use root-relative (i.e. starts with `/`) URLs, since their meaning is not context-dependent. + +### <a> attributes + +- `sapper:prefetch` is now `data-sveltekit-preload-data` +- `sapper:noscroll` is now `data-sveltekit-noscroll` + +## Endpoints + +In Sapper, [server routes](routing#server) received the `req` and `res` objects exposed by Node's `http` module (or the augmented versions provided by frameworks like Polka and Express). + +SvelteKit is designed to be agnostic as to where the app is running — it could be running on a Node server, but could equally be running on a serverless platform or in a Cloudflare Worker. For that reason, you no longer interact directly with `req` and `res`. Your endpoints will need to be updated to match the new signature. + +To support this environment-agnostic behavior, `fetch` is now available in the global context, so you don't need to import `node-fetch`, `cross-fetch`, or similar server-side fetch implementations in order to use it. + +## Integrations + +See [integrations](./integrations) for detailed information about integrations. + +### HTML minifier + +Sapper includes `html-minifier` by default. SvelteKit does not include this, but you can add it as a prod dependency and then use it through a [hook](hooks#Server-hooks-handle): + +```js +// @filename: ambient.d.ts +/// <reference types="@sveltejs/kit" /> +declare module 'html-minifier'; + +// @filename: index.js +// ---cut--- +import { minify } from 'html-minifier'; +import { building } from '$app/environment'; + +const minification_options = { + collapseBooleanAttributes: true, + collapseWhitespace: true, + conservativeCollapse: true, + decodeEntities: true, + html5: true, + ignoreCustomComments: [/^#/], + minifyCSS: true, + minifyJS: false, + removeAttributeQuotes: true, + removeComments: false, // some hydration code needs comments, so leave them in + removeOptionalTags: true, + removeRedundantAttributes: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true, + sortAttributes: true, + sortClassName: true +}; + +/** @type {import('@sveltejs/kit').Handle} */ +export async function handle({ event, resolve }) { + let page = ''; + + return resolve(event, { + transformPageChunk: ({ html, done }) => { + page += html; + if (done) { + return building ? minify(page, minification_options) : page; + } + } + }); +} +``` + +Note that `prerendering` is `false` when using `vite preview` to test the production build of the site, so to verify the results of minifying, you'll need to inspect the built HTML files directly. diff --git a/documentation/docs/60-appendix/50-additional-resources.md b/documentation/docs/60-appendix/50-additional-resources.md new file mode 100644 index 000000000000..86f121aa4872 --- /dev/null +++ b/documentation/docs/60-appendix/50-additional-resources.md @@ -0,0 +1,23 @@ +--- +title: Additional resources +--- + +## FAQs + +Please see the [SvelteKit FAQ](faq) for solutions to common issues and helpful tips and tricks. + +The [Svelte FAQ](../svelte/faq) and [`vite-plugin-svelte` FAQ](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/faq.md) may also be helpful for questions deriving from those libraries. + +## Examples + +We've written and published a few different SvelteKit sites as examples: + +- [`sveltejs/realworld`](https://github.com/sveltejs/realworld) contains an example blog site +- [A HackerNews clone](https://github.com/sveltejs/sites/tree/master/sites/hn.svelte.dev) +- [`svelte.dev`](https://github.com/sveltejs/svelte.dev) + +SvelteKit users have also published plenty of examples on GitHub, under the [#sveltekit](https://github.com/topics/sveltekit) and [#sveltekit-template](https://github.com/topics/sveltekit-template) topics, as well as on [the Svelte Society site](https://sveltesociety.dev/recipe/sveltekit-templates-and-examples-e789ed397e7f38fc). Note that these have not been vetted by the maintainers and may not be up to date. + +## Support + +You can ask for help on [Discord](/chat) and [StackOverflow](https://stackoverflow.com/questions/tagged/sveltekit). Please first search for information related to your issue in the FAQ, Google or another search engine, issue tracker, and Discord chat history in order to be respectful of others' time. There are many more people asking questions than answering them, so this will help in allowing the community to grow in a scalable fashion. diff --git a/documentation/docs/60-appendix/60-glossary.md b/documentation/docs/60-appendix/60-glossary.md new file mode 100644 index 000000000000..25f300918ec2 --- /dev/null +++ b/documentation/docs/60-appendix/60-glossary.md @@ -0,0 +1,71 @@ +--- +title: Glossary +--- + +The core of SvelteKit provides a highly configurable rendering engine. This section describes some of the terms used when discussing rendering. A reference for setting these options is provided in the documentation above. + +## CSR + +Client-side rendering (CSR) is the generation of the page contents in the web browser using JavaScript. + +In SvelteKit, client-side rendering will be used by default, but you can turn off JavaScript with [the `csr = false` page option](page-options#csr). + +## Edge + +Rendering on the edge refers to rendering an application in a content delivery network (CDN) near the user. Edge rendering allows the request and response for a page to travel a shorter distance thus improving latency. + +## Hybrid app + +SvelteKit uses a hybrid rendering mode by default where it loads the initial HTML from the server (SSR), and then updates the page contents on subsequent navigations via client-side rendering (CSR). + +## Hydration + +Svelte components store some state and update the DOM when the state is updated. When fetching data during SSR, by default SvelteKit will store this data and transmit it to the client along with the server-rendered HTML. The components can then be initialized on the client with that data without having to call the same API endpoints again. Svelte will then check that the DOM is in the expected state and attach event listeners in a process called hydration. Once the components are fully hydrated, they can react to changes to their properties just like any newly created Svelte component. + +In SvelteKit, pages will be hydrated by default, but you can turn off JavaScript with [the `csr = false` page option](page-options#csr). + +## ISR + +Incremental static regeneration (ISR) allows you to generate static pages on your site as visitors request those pages without redeploying. This may reduces build times compared to [SSG](#SSG) sites with a large number of pages. You can do [ISR with `adapter-vercel`](adapter-vercel#Incremental-Static-Regeneration). + +## MPA + +Traditional applications that render each page view on the server — such as those written in languages other than JavaScript — are often referred to as multi-page apps (MPA). + +## Prerendering + +Prerendering means computing the contents of a page at build time and saving the HTML for display. This approach has the same benefits as traditional server-rendered pages, but avoids recomputing the page for each visitor and so scales nearly for free as the number of visitors increases. The tradeoff is that the build process is more expensive and prerendered content can only be updated by building and deploying a new version of the application. + +Not all pages can be prerendered. The basic rule is this: for content to be prerenderable, any two users hitting it directly must get the same content from the server, and the page must not contain [actions](form-actions). Note that you can still prerender content that is loaded based on the page's parameters as long as all users will be seeing the same prerendered content. + +Pre-rendered pages are not limited to static content. You can build personalized pages if user-specific data is fetched and rendered client-side. This is subject to the caveat that you will experience the downsides of not doing SSR for that content as discussed above. + +In SvelteKit, you can control prerendering with [the `prerender` page option](page-options#prerender) and [`prerender` config](configuration#prerender) in `svelte.config.js`. + +## PWA + +A progressive web app (PWA) is an app that's built using web APIs and technologies, but functions like a mobile or desktop app. Sites served as [PWAs can be installed](https://web.dev/learn/pwa/installation), allowing you to add a shortcut to the application on your launcher, home screen, or start menu. Many PWAs will utilize [service workers](service-workers) to build offline capabilities. + +## Routing + +By default, when you navigate to a new page (by clicking on a link or using the browser's forward or back buttons), SvelteKit will intercept the attempted navigation and handle it instead of allowing the browser to send a request to the server for the destination page. SvelteKit will then update the displayed contents on the client by rendering the component for the new page, which in turn can make calls to the necessary API endpoints. This process of updating the page on the client in response to attempted navigation is called client-side routing. + +In SvelteKit, client-side routing will be used by default, but you can skip it with [`data-sveltekit-reload`](link-options#data-sveltekit-reload). + +## SPA + +A single-page app (SPA) is an application in which all requests to the server load a single HTML file which then does client-side rendering based on the requested URL. All navigation is handled on the client-side in a process called client-side routing with per-page contents being updated and common layout elements remaining largely unchanged. Throughout this site, when we refer to a SPA, we use this definition where a SPA simply serves an empty shell on the initial request. It should not be confused with a [hybrid app](#Hybrid-app), which serves HTML on the initial request. It has a large performance impact by forcing two network round trips before rendering can begin. Because SPA mode has large negative performance and SEO impacts, it is recommended only in very limited circumstances such as when being wrapped in a mobile app. + +In SvelteKit, you can [build SPAs with `adapter-static`](single-page-apps). + +## SSG + +Static Site Generation (SSG) is a term that refers to a site where every page is prerendered. One benefit of fully prerendering a site is that you do not need to maintain or pay for servers to perform SSR. Once generated, the site can be served from CDNs, leading to great “time to first byte” performance. This delivery model is often referred to as JAMstack. + +In SvelteKit, you can do static site generation by using [`adapter-static`](adapter-static) or by configuring every page to be prerendered using [the `prerender` page option](page-options#prerender) or [`prerender` config](configuration#prerender) in `svelte.config.js`. + +## SSR + +Server-side rendering (SSR) is the generation of the page contents on the server. Returning the page contents from the server via SSR or prerendering is highly preferred for performance and SEO. It significantly improves performance by avoiding the introduction of extra round trips necessary in a SPA, and makes your app accessible to users if JavaScript fails or is disabled (which happens [more often than you probably think](https://kryogenix.org/code/browser/everyonehasjs.html)). While some search engines can index content that is dynamically generated on the client-side, it is likely to take longer even in these cases. + +In SvelteKit, pages are server-side rendered by default. You can disable SSR with [the `ssr` page option](page-options#ssr). diff --git a/documentation/docs/60-appendix/index.md b/documentation/docs/60-appendix/index.md new file mode 100644 index 000000000000..a8e02800ad96 --- /dev/null +++ b/documentation/docs/60-appendix/index.md @@ -0,0 +1,3 @@ +--- +title: Appendix +--- diff --git a/documentation/docs/60-appendix/meta.json b/documentation/docs/60-appendix/meta.json deleted file mode 100644 index 5b62b9209c1b..000000000000 --- a/documentation/docs/60-appendix/meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "title": "Appendix" -} diff --git a/documentation/docs/98-reference/10-@sveltejs-kit.md b/documentation/docs/98-reference/10-@sveltejs-kit.md new file mode 100644 index 000000000000..a89badb6359f --- /dev/null +++ b/documentation/docs/98-reference/10-@sveltejs-kit.md @@ -0,0 +1,11 @@ +--- +title: @sveltejs/kit +--- + +> MODULE: @sveltejs/kit + +## Private types + +The following are referenced by the public types documented above, but cannot be imported directly: + +> TYPES: Private types diff --git a/documentation/docs/98-reference/15-@sveltejs-kit-hooks.md b/documentation/docs/98-reference/15-@sveltejs-kit-hooks.md new file mode 100644 index 000000000000..b4a1456ab655 --- /dev/null +++ b/documentation/docs/98-reference/15-@sveltejs-kit-hooks.md @@ -0,0 +1,5 @@ +--- +title: @sveltejs/kit/hooks +--- + +> MODULE: @sveltejs/kit/hooks diff --git a/documentation/docs/98-reference/15-@sveltejs-kit-node-polyfills.md b/documentation/docs/98-reference/15-@sveltejs-kit-node-polyfills.md new file mode 100644 index 000000000000..a80c80ab3139 --- /dev/null +++ b/documentation/docs/98-reference/15-@sveltejs-kit-node-polyfills.md @@ -0,0 +1,5 @@ +--- +title: @sveltejs/kit/node/polyfills +--- + +> MODULE: @sveltejs/kit/node/polyfills diff --git a/documentation/docs/98-reference/15-@sveltejs-kit-node.md b/documentation/docs/98-reference/15-@sveltejs-kit-node.md new file mode 100644 index 000000000000..bf12433b0cc0 --- /dev/null +++ b/documentation/docs/98-reference/15-@sveltejs-kit-node.md @@ -0,0 +1,5 @@ +--- +title: @sveltejs/kit/node +--- + +> MODULE: @sveltejs/kit/node diff --git a/documentation/docs/98-reference/15-@sveltejs-kit-vite.md b/documentation/docs/98-reference/15-@sveltejs-kit-vite.md new file mode 100644 index 000000000000..4935ede2c1d5 --- /dev/null +++ b/documentation/docs/98-reference/15-@sveltejs-kit-vite.md @@ -0,0 +1,5 @@ +--- +title: @sveltejs/kit/vite +--- + +> MODULE: @sveltejs/kit/vite diff --git a/documentation/docs/98-reference/20-$app-environment.md b/documentation/docs/98-reference/20-$app-environment.md new file mode 100644 index 000000000000..4f81897214c5 --- /dev/null +++ b/documentation/docs/98-reference/20-$app-environment.md @@ -0,0 +1,5 @@ +--- +title: $app/environment +--- + +> MODULE: $app/environment diff --git a/documentation/docs/98-reference/20-$app-forms.md b/documentation/docs/98-reference/20-$app-forms.md new file mode 100644 index 000000000000..667c84c9a609 --- /dev/null +++ b/documentation/docs/98-reference/20-$app-forms.md @@ -0,0 +1,5 @@ +--- +title: $app/forms +--- + +> MODULE: $app/forms diff --git a/documentation/docs/98-reference/20-$app-navigation.md b/documentation/docs/98-reference/20-$app-navigation.md new file mode 100644 index 000000000000..cb9b0b967985 --- /dev/null +++ b/documentation/docs/98-reference/20-$app-navigation.md @@ -0,0 +1,5 @@ +--- +title: $app/navigation +--- + +> MODULE: $app/navigation diff --git a/documentation/docs/98-reference/20-$app-paths.md b/documentation/docs/98-reference/20-$app-paths.md new file mode 100644 index 000000000000..32a350965e4a --- /dev/null +++ b/documentation/docs/98-reference/20-$app-paths.md @@ -0,0 +1,5 @@ +--- +title: $app/paths +--- + +> MODULE: $app/paths diff --git a/documentation/docs/98-reference/20-$app-server.md b/documentation/docs/98-reference/20-$app-server.md new file mode 100644 index 000000000000..284a470ffae0 --- /dev/null +++ b/documentation/docs/98-reference/20-$app-server.md @@ -0,0 +1,5 @@ +--- +title: $app/server +--- + +> MODULE: $app/server diff --git a/documentation/docs/98-reference/20-$app-state.md b/documentation/docs/98-reference/20-$app-state.md new file mode 100644 index 000000000000..9362eacad82f --- /dev/null +++ b/documentation/docs/98-reference/20-$app-state.md @@ -0,0 +1,10 @@ +--- +title: $app/state +--- + +SvelteKit makes three read-only state objects available via the `$app/state` module — `page`, `navigating` and `updated`. + +> [!NOTE] +> This module was added in 2.12. If you're using an earlier version of SvelteKit, use [`$app/stores`]($app-stores) instead. + +> MODULE: $app/state diff --git a/documentation/docs/98-reference/20-$app-stores.md b/documentation/docs/98-reference/20-$app-stores.md new file mode 100644 index 000000000000..ff7cf8999e82 --- /dev/null +++ b/documentation/docs/98-reference/20-$app-stores.md @@ -0,0 +1,7 @@ +--- +title: $app/stores +--- + +This module contains store-based equivalents of the exports from [`$app/state`]($app-state). If you're using SvelteKit 2.12 or later, use that module instead. + +> MODULE: $app/stores diff --git a/documentation/docs/98-reference/20-$app-types.md b/documentation/docs/98-reference/20-$app-types.md new file mode 100644 index 000000000000..97053e762869 --- /dev/null +++ b/documentation/docs/98-reference/20-$app-types.md @@ -0,0 +1,91 @@ +--- +title: $app/types +--- + +This module contains generated types for the routes in your app. + +<blockquote class="since note"> + <p>Available since 2.26</p> +</blockquote> + +```js +// @noErrors +import type { RouteId, RouteParams, LayoutParams } from '$app/types'; +``` + +## Asset + +A union of all the filenames of assets contained in your `static` directory, plus a `string` wildcard for asset paths generated from `import` declarations. + +<div class="ts-block"> + +```dts +type Asset = '/favicon.png' | '/robots.txt' | (string & {}); +``` + +</div> + +## RouteId + +A union of all the route IDs in your app. Used for `page.route.id` and `event.route.id`. + +<div class="ts-block"> + +```dts +type RouteId = '/' | '/my-route' | '/my-other-route/[param]'; +``` + +</div> + +## Pathname + +A union of all valid pathnames in your app. + +<div class="ts-block"> + +```dts +type Pathname = '/' | '/my-route' | `/my-other-route/${string}` & {}; +``` + +</div> + +## ResolvedPathname + +Similar to `Pathname`, but possibly prefixed with a [base path](configuration#paths). Used for `page.url.pathname`. + +<div class="ts-block"> + +```dts +type ResolvedPathname = `${'' | `/${string}`}/` | `${'' | `/${string}`}/my-route` | `${'' | `/${string}`}/my-other-route/${string}` | {}; +``` + +</div> + +## RouteParams + +A utility for getting the parameters associated with a given route. + +```ts +// @errors: 2552 +type BlogParams = RouteParams<'/blog/[slug]'>; // { slug: string } +``` + +<div class="ts-block"> + +```dts +type RouteParams<T extends RouteId> = { /* generated */ } | Record<string, never>; +``` + +</div> + +## LayoutParams + +A utility for getting the parameters associated with a given layout, which is similar to `RouteParams` but also includes optional parameters for any child route. + +<div class="ts-block"> + +```dts +type RouteParams<T extends RouteId> = { /* generated */ } | Record<string, never>; +``` + +</div> diff --git a/documentation/docs/98-reference/25-$env-dynamic-private.md b/documentation/docs/98-reference/25-$env-dynamic-private.md new file mode 100644 index 000000000000..e16a1b68dec8 --- /dev/null +++ b/documentation/docs/98-reference/25-$env-dynamic-private.md @@ -0,0 +1,5 @@ +--- +title: $env/dynamic/private +--- + +> MODULE: $env/dynamic/private diff --git a/documentation/docs/98-reference/25-$env-dynamic-public.md b/documentation/docs/98-reference/25-$env-dynamic-public.md new file mode 100644 index 000000000000..6f6ee967fafa --- /dev/null +++ b/documentation/docs/98-reference/25-$env-dynamic-public.md @@ -0,0 +1,5 @@ +--- +title: $env/dynamic/public +--- + +> MODULE: $env/dynamic/public diff --git a/documentation/docs/98-reference/25-$env-static-private.md b/documentation/docs/98-reference/25-$env-static-private.md new file mode 100644 index 000000000000..d33ddd685c59 --- /dev/null +++ b/documentation/docs/98-reference/25-$env-static-private.md @@ -0,0 +1,5 @@ +--- +title: $env/static/private +--- + +> MODULE: $env/static/private diff --git a/documentation/docs/98-reference/25-$env-static-public.md b/documentation/docs/98-reference/25-$env-static-public.md new file mode 100644 index 000000000000..590aabd8efc5 --- /dev/null +++ b/documentation/docs/98-reference/25-$env-static-public.md @@ -0,0 +1,5 @@ +--- +title: $env/static/public +--- + +> MODULE: $env/static/public diff --git a/documentation/docs/98-reference/26-$lib.md b/documentation/docs/98-reference/26-$lib.md new file mode 100644 index 000000000000..be9d72b8ec42 --- /dev/null +++ b/documentation/docs/98-reference/26-$lib.md @@ -0,0 +1,19 @@ +--- +title: $lib +--- + +SvelteKit automatically makes files under `src/lib` available using the `$lib` import alias. + +```svelte +<!--- file: src/lib/Component.svelte ---> +A reusable component +``` + +```svelte +<!--- file: src/routes/+page.svelte ---> +<script> + import Component from '$lib/Component.svelte'; +</script> + +<Component /> +``` diff --git a/documentation/docs/98-reference/27-$service-worker.md b/documentation/docs/98-reference/27-$service-worker.md new file mode 100644 index 000000000000..a8f7388ba7a9 --- /dev/null +++ b/documentation/docs/98-reference/27-$service-worker.md @@ -0,0 +1,5 @@ +--- +title: $service-worker +--- + +> MODULE: $service-worker diff --git a/documentation/docs/98-reference/50-configuration.md b/documentation/docs/98-reference/50-configuration.md new file mode 100644 index 000000000000..ab9e48e7841f --- /dev/null +++ b/documentation/docs/98-reference/50-configuration.md @@ -0,0 +1,37 @@ +--- +title: Configuration +--- + +Your project's configuration lives in a `svelte.config.js` file at the root of your project. As well as SvelteKit, this config object is used by other tooling that integrates with Svelte such as editor extensions. + +```js +/// file: svelte.config.js +// @filename: ambient.d.ts +declare module '@sveltejs/adapter-auto' { + const plugin: () => import('@sveltejs/kit').Adapter; + export default plugin; +} + +// @filename: index.js +// ---cut--- +import adapter from '@sveltejs/adapter-auto'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter() + } +}; + +export default config; +``` + +## Config + +> TYPES: Configuration#Config + +## KitConfig + +The `kit` property configures SvelteKit, and can have the following properties: + +> EXPANDED_TYPES: Configuration#KitConfig diff --git a/documentation/docs/50-reference/20-cli.md b/documentation/docs/98-reference/52-cli.md similarity index 100% rename from documentation/docs/50-reference/20-cli.md rename to documentation/docs/98-reference/52-cli.md diff --git a/documentation/docs/98-reference/54-types.md b/documentation/docs/98-reference/54-types.md new file mode 100644 index 000000000000..b8877b9beb8b --- /dev/null +++ b/documentation/docs/98-reference/54-types.md @@ -0,0 +1,209 @@ +--- +title: Types +--- + +## Generated types + +The `RequestHandler` and `Load` types both accept a `Params` argument allowing you to type the `params` object. For example this endpoint expects `foo`, `bar` and `baz` params: + +```js +/// file: src/routes/[foo]/[bar]/[baz]/+server.js +// @errors: 2355 2322 1360 +/** @type {import('@sveltejs/kit').RequestHandler<{ + foo: string; + bar: string; + baz: string + }>} */ +export async function GET({ params }) { + // ... +} +``` + +Needless to say, this is cumbersome to write out, and less portable (if you were to rename the `[foo]` directory to `[qux]`, the type would no longer reflect reality). + +To solve this problem, SvelteKit generates `.d.ts` files for each of your endpoints and pages: + +```ts +/// file: .svelte-kit/types/src/routes/[foo]/[bar]/[baz]/$types.d.ts +/// link: true +import type * as Kit from '@sveltejs/kit'; + +type RouteParams = { + foo: string; + bar: string; + baz: string; +}; + +export type RequestHandler = Kit.RequestHandler<RouteParams>; +export type PageLoad = Kit.Load<RouteParams>; +``` + +These files can be imported into your endpoints and pages as siblings, thanks to the [`rootDirs`](https://www.typescriptlang.org/tsconfig#rootDirs) option in your TypeScript configuration: + +```js +/// file: src/routes/[foo]/[bar]/[baz]/+server.js +// @filename: $types.d.ts +import type * as Kit from '@sveltejs/kit'; + +type RouteParams = { + foo: string; + bar: string; + baz: string; +} + +export type RequestHandler = Kit.RequestHandler<RouteParams>; + +// @filename: index.js +// @errors: 2355 2322 +// ---cut--- +/** @type {import('./$types').RequestHandler} */ +export async function GET({ params }) { + // ... +} +``` + +```js +/// file: src/routes/[foo]/[bar]/[baz]/+page.js +// @filename: $types.d.ts +import type * as Kit from '@sveltejs/kit'; + +type RouteParams = { + foo: string; + bar: string; + baz: string; +} + +export type PageLoad = Kit.Load<RouteParams>; + +// @filename: index.js +// @errors: 2355 +// ---cut--- +/** @type {import('./$types').PageLoad} */ +export async function load({ params, fetch }) { + // ... +} +``` + +The return types of the load functions are then available through the `$types` module as `PageData` and `LayoutData` respectively, while the union of the return values of all `Actions` is available as `ActionData`. + +Starting with version 2.16.0, two additional helper types are provided: `PageProps` defines `data: PageData`, as well as `form: ActionData`, when there are actions defined, while `LayoutProps` defines `data: LayoutData`, as well as `children: Snippet`. + +```svelte +<!--- file: src/routes/+page.svelte ---> +<script> + /** @type {import('./$types').PageProps} */ + let { data, form } = $props(); +</script> +``` + +> [!LEGACY] +> Before 2.16.0: +> ```svelte +> <!--- file: src/routes/+page.svelte ---> +> <script> +> /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ +> let { data, form } = $props(); +> </script> +> ``` +> +> Using Svelte 4: +> ```svelte +> <!--- file: src/routes/+page.svelte ---> +> <script> +> /** @type {import('./$types').PageData} */ +> export let data; +> /** @type {import('./$types').ActionData} */ +> export let form; +> </script> +> ``` + +> [!NOTE] For this to work, your own `tsconfig.json` or `jsconfig.json` should extend from the generated `.svelte-kit/tsconfig.json` (where `.svelte-kit` is your [`outDir`](configuration#outDir)): +> +> `{ "extends": "./.svelte-kit/tsconfig.json" }` + +### Default tsconfig.json + +The generated `.svelte-kit/tsconfig.json` file contains a mixture of options. Some are generated programmatically based on your project configuration, and should generally not be overridden without good reason: + +```json +/// file: .svelte-kit/tsconfig.json +{ + "compilerOptions": { + "paths": { + "$lib": ["../src/lib"], + "$lib/*": ["../src/lib/*"] + }, + "rootDirs": ["..", "./types"] + }, + "include": [ + "ambient.d.ts", + "non-ambient.d.ts", + "./types/**/$types.d.ts", + "../vite.config.js", + "../vite.config.ts", + "../src/**/*.js", + "../src/**/*.ts", + "../src/**/*.svelte", + "../tests/**/*.js", + "../tests/**/*.ts", + "../tests/**/*.svelte" + ], + "exclude": [ + "../node_modules/**", + "../src/service-worker.js", + "../src/service-worker/**/*.js", + "../src/service-worker.ts", + "../src/service-worker/**/*.ts", + "../src/service-worker.d.ts", + "../src/service-worker/**/*.d.ts" + ] +} +``` + +Others are required for SvelteKit to work properly, and should also be left untouched unless you know what you're doing: + +```json +/// file: .svelte-kit/tsconfig.json +{ + "compilerOptions": { + // this ensures that types are explicitly + // imported with `import type`, which is + // necessary as Svelte/Vite cannot + // otherwise compile components correctly + "verbatimModuleSyntax": true, + + // Vite compiles one TypeScript module + // at a time, rather than compiling + // the entire module graph + "isolatedModules": true, + + // Tell TS it's used only for type-checking + "noEmit": true, + + // This ensures both `vite build` + // and `svelte-package` work correctly + "lib": ["esnext", "DOM", "DOM.Iterable"], + "moduleResolution": "bundler", + "module": "esnext", + "target": "esnext" + } +} +``` + +Use the [`typescript.config` setting](configuration#typescript) in `svelte.config.js` to extend or modify the generated `tsconfig.json`. + +## $lib + +This is a simple alias to `src/lib`, or whatever directory is specified as [`config.kit.files.lib`](configuration#files). It allows you to access common components and utility modules without `../../../../` nonsense. + +### $lib/server + +A subdirectory of `$lib`. SvelteKit will prevent you from importing any modules in `$lib/server` into client-side code. See [server-only modules](server-only-modules). + +## app.d.ts + +The `app.d.ts` file is home to the ambient types of your apps, i.e. types that are available without explicitly importing them. + +Always part of this file is the `App` namespace. This namespace contains several types that influence the shape of certain SvelteKit features you interact with. + +> TYPES: App diff --git a/documentation/docs/98-reference/index.md b/documentation/docs/98-reference/index.md new file mode 100644 index 000000000000..b98302768eb0 --- /dev/null +++ b/documentation/docs/98-reference/index.md @@ -0,0 +1,3 @@ +--- +title: Reference +--- diff --git a/documentation/docs/index.md b/documentation/docs/index.md new file mode 100644 index 000000000000..e653b7f063c6 --- /dev/null +++ b/documentation/docs/index.md @@ -0,0 +1,3 @@ +--- +title: SvelteKit +--- diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000000..b6a6419237a4 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,65 @@ +import svelte_config from '@sveltejs/eslint-config'; +import noRuntimeToExportsImports from './.eslint/no-runtime-to-exports-imports.js'; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + ...svelte_config, + { + rules: { + 'no-undef': 'off' + } + }, + { + files: ['packages/kit/src/runtime/**/*.js'], + plugins: { + 'kit-custom': { + rules: { + 'no-runtime-to-exports-imports': noRuntimeToExportsImports + } + } + }, + rules: { + 'kit-custom/no-runtime-to-exports-imports': 'error' + } + }, + { + ignores: [ + '**/.svelte-kit', + '**/.wrangler', + '**/test-results', + '**/build', + '**/dist', + '**/.custom-out-dir', + 'packages/adapter-*/files', + 'packages/kit/src/core/config/fixtures/multiple', // dir contains svelte config with multiple extensions tripping eslint + 'packages/package/test/fixtures/typescript-svelte-config/expected', + 'packages/package/test/errors/**/*', + 'packages/package/test/fixtures/**/*' + ] + }, + { + languageOptions: { + parserOptions: { + projectService: true + } + }, + rules: { + '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/require-await': 'error', + '@typescript-eslint/no-floating-promises': 'error' + }, + ignores: [ + 'packages/adapter-cloudflare/test/apps/**/*', + 'packages/adapter-netlify/test/apps/**/*', + 'packages/adapter-node/rollup.config.js', + 'packages/adapter-node/tests/smoke.spec_disabled.js', + 'packages/adapter-static/test/apps/**/*', + 'packages/kit/src/core/sync/create_manifest_data/test/samples/**/*', + 'packages/kit/test/apps/**/*', + 'packages/kit/test/build-errors/**/*', + 'packages/kit/test/prerendering/**/*', + 'packages/test-redirect-importer/index.js' + ] + } +]; diff --git a/package.json b/package.json index 26de3310fe0c..7777ae053358 100644 --- a/package.json +++ b/package.json @@ -5,40 +5,49 @@ "private": true, "type": "module", "scripts": { - "test": "pnpm test -r --filter=./packages/* --filter=!./packages/create-svelte", + "test:kit": "pnpm run --dir packages/kit test", "test:cross-platform:dev": "pnpm run --dir packages/kit test:cross-platform:dev", "test:cross-platform:build": "pnpm run --dir packages/kit test:cross-platform:build", - "test:vite-ecosystem-ci": "pnpm test --dir packages/kit", - "test:create-svelte": "pnpm run --dir packages/create-svelte test", + "test:server-side-route-resolution:dev": "pnpm run --dir packages/kit test:server-side-route-resolution:dev", + "test:server-side-route-resolution:build": "pnpm run --dir packages/kit test:server-side-route-resolution:build", + "test:svelte-async:dev": "pnpm run --dir packages/kit test:svelte-async:dev", + "test:svelte-async:build": "pnpm run --dir packages/kit test:svelte-async:build", + "test:vite-ecosystem-ci": "pnpm --dir packages/kit test", + "test:others": "pnpm -r --filter='./packages/*' --filter=!./packages/kit/ --workspace-concurrency=1 test", "check": "pnpm -r prepublishOnly && pnpm -r check", "lint": "pnpm -r lint && eslint --cache --cache-location node_modules/.eslintcache 'packages/**/*.js'", "format": "pnpm -r format", "precommit": "pnpm format && pnpm lint", "changeset:version": "changeset version && pnpm -r generate:version && git add --all", "changeset:release": "changeset publish", - "start": "cd sites/kit.svelte.dev && npm run dev", - "postinstall": "pnpm -r generate:types" + "build": "pnpm --filter '@sveltejs/*' -r build", + "sync-all": "node scripts/sync-all.js" }, "devDependencies": { - "@changesets/cli": "^2.26.0", - "@rollup/plugin-commonjs": "^25.0.0", - "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-node-resolve": "^15.0.1", - "@sveltejs/eslint-config": "^6.0.4", - "@svitejs/changesets-changelog-github-compact": "^1.1.0", - "@typescript-eslint/eslint-plugin": "^6.0.0", - "eslint": "^8.45.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-svelte": "^2.31.0", - "eslint-plugin-unicorn": "^48.0.0", - "playwright": "1.30.0", - "prettier": "^2.8.0", - "rollup": "^3.7.0", - "svelte": "^4.0.5", - "typescript": "^4.9.4" + "@changesets/cli": "catalog:", + "@playwright/test": "catalog:", + "@sveltejs/eslint-config": "catalog:", + "@svitejs/changesets-changelog-github-compact": "catalog:", + "eslint": "catalog:", + "prettier": "^3.6.0", + "prettier-plugin-svelte": "^3.4.0", + "typescript-eslint": "catalog:" }, - "packageManager": "pnpm@8.9.2", + "packageManager": "pnpm@10.27.0", "engines": { - "pnpm": "^8.0.0" + "pnpm": ">=9.0.0" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "@parcel/watcher", + "esbuild", + "netlify-cli", + "protobufjs", + "rolldown", + "sharp", + "svelte-preprocess", + "unix-dgram", + "workerd" + ] } } diff --git a/packages/adapter-auto/CHANGELOG.md b/packages/adapter-auto/CHANGELOG.md index ac7c9c066087..e5aa0ea619bb 100644 --- a/packages/adapter-auto/CHANGELOG.md +++ b/packages/adapter-auto/CHANGELOG.md @@ -1,5 +1,173 @@ # @sveltejs/adapter-auto +## 7.0.0 +### Major Changes + + +- feat: update adapter-vercel to version 6 ([#14737](https://github.com/sveltejs/kit/pull/14737)) + +## 6.1.1 +### Patch Changes + + +- chore: update "homepage" field in package.json ([#14579](https://github.com/sveltejs/kit/pull/14579)) + +## 6.1.0 +### Minor Changes + + +- feat: add Deno as a supported package manager ([#14163](https://github.com/sveltejs/kit/pull/14163)) + + +### Patch Changes + +- Updated dependencies [[`ece3906`](https://github.com/sveltejs/kit/commit/ece3906e11ab0eeac3778abc4666f2145f98f22e), [`5ac9d27`](https://github.com/sveltejs/kit/commit/5ac9d2737263364c9b6e63b115b7aa9792bd1b3f), [`fed6331`](https://github.com/sveltejs/kit/commit/fed6331722a3ea47df9dd8ab01ba23f549fe5385), [`69f4e5f`](https://github.com/sveltejs/kit/commit/69f4e5feacfee429025e78b0e1e0a7b2e0639dc9), [`6b34122`](https://github.com/sveltejs/kit/commit/6b34122b4446da545d261ad96217016412b16510), [`9493537`](https://github.com/sveltejs/kit/commit/949353793a2d27e7cc44dddbdbcf40639b33a4b7), [`f67ba09`](https://github.com/sveltejs/kit/commit/f67ba09e43a598e20d7f8bba62bbc0547fd3f8b1)]: + - @sveltejs/kit@2.28.0 + +## 6.0.2 +### Patch Changes + + +- chore: add `.git` to the end of `package.json` repository url ([#14134](https://github.com/sveltejs/kit/pull/14134)) + +- Updated dependencies [[`c968aef`](https://github.com/sveltejs/kit/commit/c968aef5727f978244d5160657b4a7ac651384ae)]: + - @sveltejs/kit@2.27.3 + +## 6.0.1 +### Patch Changes + + +- chore: remove `import-meta-resolve` dependency ([#13629](https://github.com/sveltejs/kit/pull/13629)) + +- Updated dependencies [[`bd1c04662332cbafa843c35a2e783486116af3d5`](https://github.com/sveltejs/kit/commit/bd1c04662332cbafa843c35a2e783486116af3d5), [`09f61ec2a14573e27769edb403c58aea5433a39f`](https://github.com/sveltejs/kit/commit/09f61ec2a14573e27769edb403c58aea5433a39f), [`09f61ec2a14573e27769edb403c58aea5433a39f`](https://github.com/sveltejs/kit/commit/09f61ec2a14573e27769edb403c58aea5433a39f)]: + - @sveltejs/kit@2.21.0 + +## 6.0.0 +### Major Changes + + +- feat: upgrade `@sveltejs/adapter-cloudflare` to version 7 ([#13661](https://github.com/sveltejs/kit/pull/13661)) + +## 5.0.0 +### Major Changes + + +- feat: update Netlify and Cloudflare Pages major versions ([#13615](https://github.com/sveltejs/kit/pull/13615)) + +## 4.0.0 +### Major Changes + + +- feat: update Vercel, Cloudflare Pages, and Netlify adapter major versions ([#13142](https://github.com/sveltejs/kit/pull/13142)) + + +### Patch Changes + +- Updated dependencies [[`1bedcc1cfc1f2d85946c1423f60faa8a2a56148b`](https://github.com/sveltejs/kit/commit/1bedcc1cfc1f2d85946c1423f60faa8a2a56148b), [`e201fa9380a00e072a80a2dcab56de3d77e5b67c`](https://github.com/sveltejs/kit/commit/e201fa9380a00e072a80a2dcab56de3d77e5b67c), [`f3f08582d41b08c3fd1daf742e5703d9cdca7823`](https://github.com/sveltejs/kit/commit/f3f08582d41b08c3fd1daf742e5703d9cdca7823), [`d4bcfccb4503b12fe76140dbb6cfddc81f9419fc`](https://github.com/sveltejs/kit/commit/d4bcfccb4503b12fe76140dbb6cfddc81f9419fc), [`d09bc033123903f359c1ad6fd3a6d8d7fc19298a`](https://github.com/sveltejs/kit/commit/d09bc033123903f359c1ad6fd3a6d8d7fc19298a)]: + - @sveltejs/kit@2.15.3 + +## 3.3.1 +### Patch Changes + + +- docs: update URLs for new svelte.dev site ([#12857](https://github.com/sveltejs/kit/pull/12857)) + +- Updated dependencies [[`dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46`](https://github.com/sveltejs/kit/commit/dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46), [`4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d`](https://github.com/sveltejs/kit/commit/4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d), [`3a9b78f04786898ca93f6d4b75ab18d26bc45192`](https://github.com/sveltejs/kit/commit/3a9b78f04786898ca93f6d4b75ab18d26bc45192), [`723eb8b31e6a22c82f730c30e485386c8676b746`](https://github.com/sveltejs/kit/commit/723eb8b31e6a22c82f730c30e485386c8676b746), [`8ec471c875345b751344e67580ff1b772ef2735b`](https://github.com/sveltejs/kit/commit/8ec471c875345b751344e67580ff1b772ef2735b)]: + - @sveltejs/kit@2.7.3 + +## 3.3.0 +### Minor Changes + + +- feat: add support for Bun package manager ([#12854](https://github.com/sveltejs/kit/pull/12854)) + +## 3.2.5 +### Patch Changes + + +- fix: import `node:process` instead of using globals ([#12641](https://github.com/sveltejs/kit/pull/12641)) + +- Updated dependencies [[`e798ef718f163bed4f93e1918bd8294f765376ad`](https://github.com/sveltejs/kit/commit/e798ef718f163bed4f93e1918bd8294f765376ad)]: + - @sveltejs/kit@2.5.28 + +## 3.2.4 +### Patch Changes + + +- chore: configure provenance in a simpler manner ([#12570](https://github.com/sveltejs/kit/pull/12570)) + +- Updated dependencies [[`087a43d391fc38b8c008fb39a804dc6988974101`](https://github.com/sveltejs/kit/commit/087a43d391fc38b8c008fb39a804dc6988974101)]: + - @sveltejs/kit@2.5.22 + +## 3.2.3 +### Patch Changes + + +- chore: package provenance ([#12567](https://github.com/sveltejs/kit/pull/12567)) + +- Updated dependencies [[`4930a8443caa53bcecee7b690cd28e429b1c8a20`](https://github.com/sveltejs/kit/commit/4930a8443caa53bcecee7b690cd28e429b1c8a20)]: + - @sveltejs/kit@2.5.21 + +## 3.2.2 + +### Patch Changes + +- chore: add keywords for discovery in npm search ([#12330](https://github.com/sveltejs/kit/pull/12330)) + +- Updated dependencies [[`25acb1d9fce998dccd8050b93cf4142c2b082611`](https://github.com/sveltejs/kit/commit/25acb1d9fce998dccd8050b93cf4142c2b082611), [`642c4a4aff4351b786fe6274aa2f0bf7d905faf9`](https://github.com/sveltejs/kit/commit/642c4a4aff4351b786fe6274aa2f0bf7d905faf9), [`0a0e9aa897123ebec50af08e9385b2ca4fc5bb28`](https://github.com/sveltejs/kit/commit/0a0e9aa897123ebec50af08e9385b2ca4fc5bb28)]: + - @sveltejs/kit@2.5.11 + +## 3.2.1 + +### Patch Changes + +- fix: bump import-meta-resolve to remove deprecation warnings ([#12240](https://github.com/sveltejs/kit/pull/12240)) + +- Updated dependencies [[`460d4526c80358958e58a2451fe1b663fdc656e9`](https://github.com/sveltejs/kit/commit/460d4526c80358958e58a2451fe1b663fdc656e9), [`16cd900c304e0cf0f16b484aa61a5ba0531d8751`](https://github.com/sveltejs/kit/commit/16cd900c304e0cf0f16b484aa61a5ba0531d8751)]: + - @sveltejs/kit@2.5.10 + +## 3.2.0 + +### Minor Changes + +- feat: add support for Google Cloud Run ([#12015](https://github.com/sveltejs/kit/pull/12015)) + +## 3.1.1 + +### Patch Changes + +- fix: better error message when using `read` ([#11689](https://github.com/sveltejs/kit/pull/11689)) + +## 3.1.0 + +### Minor Changes + +- feat: bump Azure adapter version ([#11496](https://github.com/sveltejs/kit/pull/11496)) + +## 3.0.1 + +### Patch Changes + +- chore: update primary branch from master to main ([`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d)) + +- Updated dependencies [[`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d), [`16961e8cd3fa6a7f382153b1ff056bc2aae9b31b`](https://github.com/sveltejs/kit/commit/16961e8cd3fa6a7f382153b1ff056bc2aae9b31b), [`197e01f95652f511160f38b37b9da73a124ecd48`](https://github.com/sveltejs/kit/commit/197e01f95652f511160f38b37b9da73a124ecd48), [`102e4a5ae5b29624302163faf5a20c94a64a5b2c`](https://github.com/sveltejs/kit/commit/102e4a5ae5b29624302163faf5a20c94a64a5b2c), [`f8e3d8b9728c9f1ab63389342c31d7246b6f9db6`](https://github.com/sveltejs/kit/commit/f8e3d8b9728c9f1ab63389342c31d7246b6f9db6)]: + - @sveltejs/kit@2.0.4 + +## 3.0.0 + +### Major Changes + +- breaking: require SvelteKit 2 ([#11316](https://github.com/sveltejs/kit/pull/11316)) + +## 2.1.1 + +### Patch Changes + +- chore(deps): update dependency `import-meta-resolve` to v4 ([#10970](https://github.com/sveltejs/kit/pull/10970)) + +- Updated dependencies [[`072430ec5`](https://github.com/sveltejs/kit/commit/072430ec5c64782cd25c750f40534fea9cab4b40)]: + - @sveltejs/kit@1.27.3 + ## 2.1.0 ### Minor Changes diff --git a/packages/adapter-auto/README.md b/packages/adapter-auto/README.md index c0e2ba93474c..ed96335346a5 100644 --- a/packages/adapter-auto/README.md +++ b/packages/adapter-auto/README.md @@ -4,8 +4,8 @@ Automatically chooses the SvelteKit adapter for your current environment, if pos ## Docs -[Docs](https://kit.svelte.dev/docs/adapter-auto) +[Docs](https://svelte.dev/docs/kit/adapter-auto) ## Changelog -[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-auto/CHANGELOG.md). +[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/main/packages/adapter-auto/CHANGELOG.md). diff --git a/packages/adapter-auto/adapters.js b/packages/adapter-auto/adapters.js index 13ec6d189b97..2b12295fc674 100644 --- a/packages/adapter-auto/adapters.js +++ b/packages/adapter-auto/adapters.js @@ -1,3 +1,5 @@ +import process from 'node:process'; + // List of adapters to check for. `version` is used to pin the installed adapter version and should point // to the latest version of the adapter that is compatible with adapter-auto's current peerDependency version of SvelteKit. export const adapters = [ @@ -5,30 +7,36 @@ export const adapters = [ name: 'Vercel', test: () => !!process.env.VERCEL, module: '@sveltejs/adapter-vercel', - version: '2' + version: '6' }, { name: 'Cloudflare Pages', test: () => !!process.env.CF_PAGES, module: '@sveltejs/adapter-cloudflare', - version: '2' + version: '7' }, { name: 'Netlify', test: () => !!process.env.NETLIFY, module: '@sveltejs/adapter-netlify', - version: '2' + version: '5' }, { name: 'Azure Static Web Apps', test: () => process.env.GITHUB_ACTION_REPOSITORY === 'Azure/static-web-apps-deploy', module: 'svelte-adapter-azure-swa', - version: '0.13' + version: '0.20' }, { name: 'AWS via SST', test: () => !!process.env.SST, module: 'svelte-kit-sst', version: '2' + }, + { + name: 'Google Cloud Run', + test: () => !!process.env.GCP_BUILDPACKS, + module: '@sveltejs/adapter-node', + version: '5' } ]; diff --git a/packages/adapter-auto/index.js b/packages/adapter-auto/index.js index 14115c45b888..751f44eec424 100644 --- a/packages/adapter-auto/index.js +++ b/packages/adapter-auto/index.js @@ -1,25 +1,42 @@ import { execSync } from 'node:child_process'; -import { pathToFileURL } from 'node:url'; -import { resolve } from 'import-meta-resolve'; import { adapters } from './adapters.js'; -import { dirname, join } from 'node:path'; -import { existsSync } from 'node:fs'; +import path from 'node:path'; +import fs from 'node:fs'; +import process from 'node:process'; + +/** + * @template T + * @template {keyof T} K + * @typedef {Partial<Omit<T, K>> & Required<Pick<T, K>>} PartialExcept + */ + +/** + * We use a custom `Builder` type here to support the minimum version of SvelteKit. + * @typedef {PartialExcept<import('@sveltejs/kit').Builder, 'log' | 'rimraf' | 'mkdirp' | 'config' | 'prerendered' | 'routes' | 'createEntries' | 'generateFallback' | 'generateEnvModule' | 'generateManifest' | 'getBuildDirectory' | 'getClientDirectory' | 'getServerDirectory' | 'getAppPath' | 'writeClient' | 'writePrerendered' | 'writePrerendered' | 'writeServer' | 'copy' | 'compress'>} Builder2_0_0 + */ /** @type {Record<string, (name: string, version: string) => string>} */ const commands = { npm: (name, version) => `npm install -D ${name}@${version}`, pnpm: (name, version) => `pnpm add -D ${name}@${version}`, - yarn: (name, version) => `yarn add -D ${name}@${version}` + yarn: (name, version) => `yarn add -D ${name}@${version}`, + bun: (name, version) => `bun add -D ${name}@${version}`, + deno: (name, version) => `deno install -D npm:${name}@${version}` }; function detect_lockfile() { let dir = process.cwd(); + /** @param {string} file */ + const exists = (file) => fs.existsSync(path.join(dir, file)); + do { - if (existsSync(join(dir, 'pnpm-lock.yaml'))) return 'pnpm'; - if (existsSync(join(dir, 'yarn.lock'))) return 'yarn'; - if (existsSync(join(dir, 'package-lock.json'))) return 'npm'; - } while (dir !== (dir = dirname(dir))); + if (exists('pnpm-lock.yaml')) return 'pnpm'; + if (exists('yarn.lock')) return 'yarn'; + if (exists('package-lock.json')) return 'npm'; + if (exists('bun.lockb') || exists('bun.lock')) return 'bun'; + if (exists('deno.lock')) return 'deno'; + } while (dir !== (dir = path.dirname(dir))); return 'npm'; } @@ -35,12 +52,40 @@ function detect_package_manager() { } } -/** @param {string} name */ -async function import_from_cwd(name) { - const cwd = pathToFileURL(process.cwd()).href; - const url = await resolve(name, cwd + '/x.js'); +/** + * Resolves a peer dependency relative to the current CWD. Duplicated with `packages/kit` + * @param {string} dependency + */ +function resolve_peer(dependency) { + let [name, ...parts] = dependency.split('/'); + if (name[0] === '@') name += `/${parts.shift()}`; + + let dir = process.cwd(); + + while (!fs.existsSync(`${dir}/node_modules/${name}/package.json`)) { + if (dir === (dir = path.dirname(dir))) { + throw new Error( + `Could not resolve peer dependency "${name}" relative to your project — please install it and try again.` + ); + } + } + + const pkg_dir = `${dir}/node_modules/${name}`; + const pkg = JSON.parse(fs.readFileSync(`${pkg_dir}/package.json`, 'utf-8')); - return import(url); + const subpackage = ['.', ...parts].join('/'); + + let exported = pkg.exports[subpackage]; + + while (typeof exported !== 'string') { + if (!exported) { + throw new Error(`Could not find valid "${subpackage}" export in ${name}/package.json`); + } + + exported = exported['import'] ?? exported['default']; + } + + return path.resolve(pkg_dir, exported); } /** @typedef {import('@sveltejs/kit').Adapter} Adapter */ @@ -53,47 +98,43 @@ async function get_adapter() { if (!match) return; - /** @type {{ default: () => Adapter }} */ - let module; + /** @type {string} */ + let resolved; try { - module = await import_from_cwd(match.module); - } catch (error) { - if ( - error.code === 'ERR_MODULE_NOT_FOUND' && - error.message.startsWith(`Cannot find package '${match.module}'`) - ) { - const package_manager = detect_package_manager(); - const command = commands[package_manager](match.module, match.version); - - try { - console.log(`Installing ${match.module}...`); - - execSync(command, { - stdio: 'inherit', - env: { - ...process.env, - NODE_ENV: undefined - } - }); - - module = await import_from_cwd(match.module); - - console.log(`Successfully installed ${match.module}.`); - console.warn( - `\nIf you plan on staying on this deployment platform, consider replacing @sveltejs/adapter-auto with ${match.module}. This will give you faster and more robust installs, and more control over deployment configuration.\n` - ); - } catch (e) { - throw new Error( - `Could not install ${match.module}. Please install it yourself by adding it to your package.json's devDependencies and try building your project again.`, - { cause: e } - ); - } - } else { - throw error; + resolved = resolve_peer(match.module); + } catch { + const package_manager = detect_package_manager(); + const command = commands[package_manager](match.module, match.version); + + try { + console.log(`Installing ${match.module}...`); + + execSync(command, { + stdio: 'inherit', + env: { + ...process.env, + NODE_ENV: undefined + } + }); + + resolved = resolve_peer(match.module); + + console.log(`Successfully installed ${match.module}.`); + console.warn( + `\nIf you plan on staying on this deployment platform, consider replacing @sveltejs/adapter-auto with ${match.module}. This will give you faster and more robust installs, and more control over deployment configuration.\n` + ); + } catch (e) { + throw new Error( + `Could not install ${match.module}. Please install it yourself by adding it to your package.json's devDependencies and try building your project again.`, + { cause: e } + ); } } + /** @type {{ default: () => Adapter }} */ + const module = await import(resolved); + const adapter = module.default(); return { @@ -108,13 +149,35 @@ async function get_adapter() { /** @type {() => Adapter} */ export default () => ({ name: '@sveltejs/adapter-auto', + /** @param {Builder2_0_0} builder */ adapt: async (builder) => { const adapter = await get_adapter(); - if (adapter) return adapter.adapt(builder); + if (adapter) return adapter.adapt(/** @type {import('@sveltejs/kit').Builder} */ (builder)); builder.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' + 'Could not detect a supported production environment. See https://svelte.dev/docs/kit/adapters to learn how to configure your app to run on the platform of your choosing' ); + }, + supports: { + read: () => { + supports_error( + 'The read function imported from $app/server only works in certain environments' + ); + }, + instrumentation: () => { + supports_error('`instrumentation.server.js` only works in certain environments'); + } } }); + +/** + * @param {string} message + * @returns {never} + * @throws {Error} + */ +function supports_error(message) { + throw new Error( + `${message}. Since you're using @sveltejs/adapter-auto, SvelteKit cannot determine whether it will work when your app is deployed. Please replace it with an adapter tailored to your target environment.` + ); +} diff --git a/packages/adapter-auto/package.json b/packages/adapter-auto/package.json index 856e80a17038..95216bc30984 100644 --- a/packages/adapter-auto/package.json +++ b/packages/adapter-auto/package.json @@ -1,14 +1,23 @@ { "name": "@sveltejs/adapter-auto", - "version": "2.1.0", + "version": "7.0.0", "description": "Automatically chooses the SvelteKit adapter for your current environment, if possible.", + "keywords": [ + "adapter", + "automatically", + "deploy", + "hosting", + "platform", + "svelte", + "sveltekit" + ], "repository": { "type": "git", - "url": "https://github.com/sveltejs/kit", + "url": "git+https://github.com/sveltejs/kit.git", "directory": "packages/adapter-auto" }, "license": "MIT", - "homepage": "https://kit.svelte.dev", + "homepage": "https://svelte.dev/docs/kit/adapter-auto", "type": "module", "exports": { ".": { @@ -25,19 +34,19 @@ "adapters.js" ], "scripts": { - "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", + "lint": "prettier --check .", "format": "pnpm lint --write", - "check": "tsc" + "check": "tsc", + "test": "vitest run" }, "devDependencies": { "@sveltejs/kit": "workspace:^", - "@types/node": "^16.18.6", - "typescript": "^4.9.4" - }, - "dependencies": { - "import-meta-resolve": "^3.0.0" + "@sveltejs/vite-plugin-svelte": "catalog:", + "@types/node": "catalog:", + "typescript": "^5.3.3", + "vitest": "catalog:" }, "peerDependencies": { - "@sveltejs/kit": "^1.0.0" + "@sveltejs/kit": "^2.0.0" } } diff --git a/packages/adapter-auto/test/adapters.spec.js b/packages/adapter-auto/test/adapters.spec.js new file mode 100644 index 000000000000..89b98c0bbbfb --- /dev/null +++ b/packages/adapter-auto/test/adapters.spec.js @@ -0,0 +1,16 @@ +import { assert, test } from 'vitest'; +import { adapters } from '../adapters.js'; +import { existsSync, readFileSync } from 'node:fs'; + +test('adapter versions are up to date', () => { + for (const adapter of adapters) { + const dir = adapter.module.replace('@sveltejs/', ''); + const package_json = `../${dir}/package.json`; + if (!existsSync(package_json)) { + continue; + } + const adapter_version = JSON.parse(readFileSync(package_json, 'utf-8')).version; + const [major] = adapter_version.split('.'); + assert.equal(adapter.version, major, `${adapter.name} adapter is outdated`); + } +}); diff --git a/packages/adapter-auto/tsconfig.json b/packages/adapter-auto/tsconfig.json index d42c1c9c4b32..c68e02a6b620 100644 --- a/packages/adapter-auto/tsconfig.json +++ b/packages/adapter-auto/tsconfig.json @@ -4,9 +4,9 @@ "checkJs": true, "noEmit": true, "noImplicitAny": true, - "module": "esnext", - "target": "esnext", - "skipLibCheck": true, + "strictNullChecks": true, + "target": "es2022", + "module": "node16", "moduleResolution": "node16", "baseUrl": "." }, diff --git a/packages/adapter-cloudflare-workers/.gitignore b/packages/adapter-cloudflare-workers/.gitignore deleted file mode 100644 index 9daa8247da45..000000000000 --- a/packages/adapter-cloudflare-workers/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.DS_Store -node_modules diff --git a/packages/adapter-cloudflare-workers/CHANGELOG.md b/packages/adapter-cloudflare-workers/CHANGELOG.md deleted file mode 100644 index 7ee49c4401af..000000000000 --- a/packages/adapter-cloudflare-workers/CHANGELOG.md +++ /dev/null @@ -1,520 +0,0 @@ -# @sveltejs/adapter-cloudflare-workers - -## 1.1.4 - -### Patch Changes - -- fix: mark `cloudflare:` packages as external ([#10404](https://github.com/sveltejs/kit/pull/10404)) - -- Updated dependencies [[`0f0049810`](https://github.com/sveltejs/kit/commit/0f00498100361ef0a4ea8b0b4e8465e442fa22a6), [`6f36aefe1`](https://github.com/sveltejs/kit/commit/6f36aefe13bf55cfaef14166c60ecee989061ddd)]: - - @sveltejs/kit@1.22.4 - -## 1.1.3 - -### Patch Changes - -- chore: upgrade to esbuild 0.18.11 ([#10330](https://github.com/sveltejs/kit/pull/10330)) - -- Updated dependencies [[`23d1df702`](https://github.com/sveltejs/kit/commit/23d1df702f0fd77983040404352d8d83fd1dd8a1), [`486a971fe`](https://github.com/sveltejs/kit/commit/486a971fe7c375aae1585f1fa2505e28f86f4b8e)]: - - @sveltejs/kit@1.22.2 - -## 1.1.2 - -### Patch Changes - -- fix: Copy .wasm files during build ([#9940](https://github.com/sveltejs/kit/pull/9940)) - -- Updated dependencies [[`50acb22ca`](https://github.com/sveltejs/kit/commit/50acb22caf2901283e044cdfda36db6f07b3e0ae), [`2e6da9496`](https://github.com/sveltejs/kit/commit/2e6da9496bdace2c65040b9d1845c44801ca868c), [`a81106b3a`](https://github.com/sveltejs/kit/commit/a81106b3a817829c41e048207d6253e63988c58c), [`a6338a0b1`](https://github.com/sveltejs/kit/commit/a6338a0b124f54bda7ba3fe64be1d6173e118d00), [`4a85b7f08`](https://github.com/sveltejs/kit/commit/4a85b7f0820d35c7830c00afe1df3c94fcbf8c3d), [`26d2b7f8f`](https://github.com/sveltejs/kit/commit/26d2b7f8f5ca29c60ef61b936ff86deaeb1636ce), [`bc70b4e63`](https://github.com/sveltejs/kit/commit/bc70b4e636fcbd9593356996bf737e014ff8c238), [`ab9f57721`](https://github.com/sveltejs/kit/commit/ab9f57721fca146af7c4eb41f4875fafa5dfc0d2)]: - - @sveltejs/kit@1.17.0 - -## 1.1.1 - -### Patch Changes - -- chore: update all dependencies with minor version bumps ([#9761](https://github.com/sveltejs/kit/pull/9761)) - -## 1.1.0 - -### Minor Changes - -- feat: use `es2022` target ([#9293](https://github.com/sveltejs/kit/pull/9293)) - -### Patch Changes - -- Updated dependencies [[`2b647fd8`](https://github.com/sveltejs/kit/commit/2b647fd85be028bc5775894567ee8b13f91411a7), [`fbe4fe76`](https://github.com/sveltejs/kit/commit/fbe4fe768140abac09bd66edd12c77787cafc2c5), [`744dc81c`](https://github.com/sveltejs/kit/commit/744dc81c6b0d8cade087df733c6d3d3f1281e68c)]: - - @sveltejs/kit@1.10.0 - -## 1.0.6 - -### Patch Changes - -- feat: expose `App.Platform` interface automatically ([#8531](https://github.com/sveltejs/kit/pull/8531)) - -- docs: move adapter docs to site ([#8531](https://github.com/sveltejs/kit/pull/8531)) - -- fix: amend `App.Platform` ([#8531](https://github.com/sveltejs/kit/pull/8531)) - -## 1.0.5 - -### Patch Changes - -- fix: publish missing files ([#8532](https://github.com/sveltejs/kit/pull/8532)) - -## 1.0.4 - -### Patch Changes - -- chore: remove superfluous main field from package.json ([#8519](https://github.com/sveltejs/kit/pull/8519)) - -- Updated dependencies [[`7e2d3405`](https://github.com/sveltejs/kit/commit/7e2d34056e99f371e22406d941b764df365a2649)]: - - @sveltejs/kit@1.1.1 - -## 1.0.3 - -### Patch Changes - -- fix: don't load ambient worker types ([#8483](https://github.com/sveltejs/kit/pull/8483)) - -## 1.0.2 - -### Patch Changes - -- docs: add note about inability to access file system at runtime ([#8441](https://github.com/sveltejs/kit/pull/8441)) - -- Updated dependencies [[`9c01c32e`](https://github.com/sveltejs/kit/commit/9c01c32ef72bbed630fadcb8283f8f8533ced5e1), [`b6ca02a6`](https://github.com/sveltejs/kit/commit/b6ca02a684dbf13a3138b552e2d2be64697f2254), [`130abe43`](https://github.com/sveltejs/kit/commit/130abe43cef2cfbaf922aa16b20cbd4332a07c15), [`c4137536`](https://github.com/sveltejs/kit/commit/c4137536f2c6572eaeec1a82ccea0852f5be6b98), [`40464efa`](https://github.com/sveltejs/kit/commit/40464efab172a17f0b637d7dadea30d77ef1ed10), [`ce028470`](https://github.com/sveltejs/kit/commit/ce0284708184198efdd30f3ff72fd579cef830b4)]: - - @sveltejs/kit@1.0.12 - -## 1.0.1 - -### Patch Changes - -- chore: update @cloudflare/kv-asset-handler ([`30e1130e`](https://github.com/sveltejs/kit/commit/30e1130ef84e56a77be5cb1136d5c53edef6e5f9)) -- Updated dependencies [[`fab0de4f`](https://github.com/sveltejs/kit/commit/fab0de4f06ac5b1e9b049e106889b193975c1c29), [`89b8d94b`](https://github.com/sveltejs/kit/commit/89b8d94b1b20d586e1ca525c30d07587c3f2d8f2)]: - - @sveltejs/kit@1.0.2 - -## 1.0.0 - -### Major Changes - -First major release, see below for the history of changes that lead up to this. -Starting from now all releases follow semver and changes will be listed as Major/Minor/Patch - -## 1.0.0-next.65 - -### Patch Changes - -- breaking: remove warnings/errors about removed/changed APIs ([#8019](https://github.com/sveltejs/kit/pull/8019)) -- Updated dependencies [[`f42604a2`](https://github.com/sveltejs/kit/commit/f42604a2b4c04026d3d8bad95807720b79529539)]: - - @sveltejs/kit@1.0.0-next.589 - -## 1.0.0-next.64 - -### Patch Changes - -- chore: add peerDependencies, add more specific next version ([#8141](https://github.com/sveltejs/kit/pull/8141)) - -## 1.0.0-next.63 - -### Patch Changes - -- fix: revert platform change from browser to neutral ([#8122](https://github.com/sveltejs/kit/pull/8122)) - -## 1.0.0-next.62 - -### Patch Changes - -- fix: set esbuild platform to neutral ([#8083](https://github.com/sveltejs/kit/pull/8083)) - -## 1.0.0-next.61 - -### Patch Changes - -- update esbuild to ^0.16.3 ([#7543](https://github.com/sveltejs/kit/pull/7543)) - -## 1.0.0-next.60 - -### Patch Changes - -- update dependencies ([#7355](https://github.com/sveltejs/kit/pull/7355)) - -## 1.0.0-next.59 - -### Patch Changes - -- Use config.kit.paths.base prefix for static assets ([#4448](https://github.com/sveltejs/kit/pull/4448)) - -## 1.0.0-next.58 - -### Patch Changes - -- Add config option to set custom `wrangler.toml` file name ([#7104](https://github.com/sveltejs/kit/pull/7104)) - -## 1.0.0-next.57 - -### Patch Changes - -- Include ambient.d.ts files in adapter packages. ([#6917](https://github.com/sveltejs/kit/pull/6917)) - -## 1.0.0-next.56 - -### Patch Changes - -- chore: bump esbuild ([#6829](https://github.com/sveltejs/kit/pull/6829)) - -## 1.0.0-next.55 - -### Patch Changes - -- Update to esbuild 0.15 ([#6740](https://github.com/sveltejs/kit/pull/6740)) - -## 1.0.0-next.54 - -### Patch Changes - -- feat: Moved hooks.js initialization from Server.respond into Server.init ([#6179](https://github.com/sveltejs/kit/pull/6179)) - -## 1.0.0-next.53 - -### Patch Changes - -- expose caches on platform ([#5887](https://github.com/sveltejs/kit/pull/5887)) - -## 1.0.0-next.52 - -### Patch Changes - -- Initialise `env` ([#5663](https://github.com/sveltejs/kit/pull/5663)) - -## 1.0.0-next.51 - -### Patch Changes - -- breaking: remove writeStatic to align with Vite ([#5618](https://github.com/sveltejs/kit/pull/5618)) - -## 1.0.0-next.50 - -### Patch Changes - -- Update dependencies ([#5005](https://github.com/sveltejs/kit/pull/5005)) - -## 1.0.0-next.49 - -### Patch Changes - -- breaking: Don't pass arbitrary options to esbuild ([#4639](https://github.com/sveltejs/kit/pull/4639)) - -## 1.0.0-next.48 - -### Patch Changes - -- Expose App interfaces ([#5386](https://github.com/sveltejs/kit/pull/5386)) - -## 1.0.0-next.47 - -### Patch Changes - -- chore: upgrade TypeScript to 4.7.4 ([#5414](https://github.com/sveltejs/kit/pull/5414)) - -## 1.0.0-next.46 - -### Patch Changes - -- Generate sourcemaps for server-side functions when bundling with esbuild ([#5258](https://github.com/sveltejs/kit/pull/5258)) - -## 1.0.0-next.45 - -### Patch Changes - -- Simplify example wrangler.toml, and fix outdated README ([#5187](https://github.com/sveltejs/kit/pull/5187)) - -## 1.0.0-next.44 - -### Patch Changes - -- Update dependencies ([#5121](https://github.com/sveltejs/kit/pull/5121)) - -## 1.0.0-next.43 - -### Patch Changes - -- Update adapter entrypoint typings to be NodeNext/ESNext-compatible ([#5111](https://github.com/sveltejs/kit/pull/5111)) - -## 1.0.0-next.42 - -### Patch Changes - -- only serve `_app/immutable` with immutable cache header, not `_app/version.json` ([#5051](https://github.com/sveltejs/kit/pull/5051)) - -## 1.0.0-next.41 - -### Patch Changes - -- Add types to pkg.exports ([#5045](https://github.com/sveltejs/kit/pull/5045)) - -## 1.0.0-next.40 - -### Patch Changes - -- breaking: support Wrangler 2, drop Wrangler 1 ([#4887](https://github.com/sveltejs/kit/pull/4887)) - -## 1.0.0-next.39 - -### Patch Changes - -- breaking: Remove try-catch around server.respond ([#4738](https://github.com/sveltejs/kit/pull/4738)) - -## 1.0.0-next.38 - -### Patch Changes - -- - Fix an issue related to prerendered pages incorrectly resolving in @sveltejs/adapter-cloudflare-workers ([#4626](https://github.com/sveltejs/kit/pull/4626)) - -## 1.0.0-next.37 - -### Patch Changes - -- Breaking: refactor implementation from "Service Worker" pattern to "Module Worker" used in adapter-cloudflare ([#4276](https://github.com/sveltejs/kit/pull/4276)) - - #### add the following to your wrangler.toml - - ```toml - [build.upload] - format = "modules" - main = "./worker.mjs" - ``` - -## 1.0.0-next.36 - -### Patch Changes - -- Provide getClientAddress function ([#4289](https://github.com/sveltejs/kit/pull/4289)) - -## 1.0.0-next.35 - -### Patch Changes - -- breaking: replace builder.prerender() with builder.writePrerendered() and builder.prerendered ([#4192](https://github.com/sveltejs/kit/pull/4192)) ([#4229](https://github.com/sveltejs/kit/pull/4229)) - -## 1.0.0-next.34 - -### Patch Changes - -- breaking: rename `app.render` to `server.respond` ([#4034](https://github.com/sveltejs/kit/pull/4034)) - -## 1.0.0-next.33 - -### Patch Changes - -- revert change to Cloudflare ES target version ([#3847](https://github.com/sveltejs/kit/pull/3847)) - -## 1.0.0-next.32 - -### Patch Changes - -- Fix Cloudflare adapter targets ([#3827](https://github.com/sveltejs/kit/pull/3827)) - -## 1.0.0-next.31 - -### Patch Changes - -- update to Vite 2.8 and esbuild 0.14 ([#3791](https://github.com/sveltejs/kit/pull/3791)) - -## 1.0.0-next.30 - -### Patch Changes - -- Breaking: change app.render signature to (request: Request) => Promise<Response> ([#3384](https://github.com/sveltejs/kit/pull/3384)) - -## 1.0.0-next.29 - -### Patch Changes - -- Add immutable cache headers to generated assets ([#3222](https://github.com/sveltejs/kit/pull/3222)) - -## 1.0.0-next.28 - -### Patch Changes - -- use path/posix to resolve relative paths for esmodules ([#3212](https://github.com/sveltejs/kit/pull/3212)) - -## 1.0.0-next.27 - -### Patch Changes - -- Overhaul adapter API ([#2931](https://github.com/sveltejs/kit/pull/2931)) -- Remove esbuild options ([#2931](https://github.com/sveltejs/kit/pull/2931)) -- Update adapters to provide app.render with a url ([#3133](https://github.com/sveltejs/kit/pull/3133)) - -## 1.0.0-next.26 - -### Patch Changes - -- update to esbuild 0.13.15 and other dependency updates ([#2957](https://github.com/sveltejs/kit/pull/2957)) - -## 1.0.0-next.25 - -### Patch Changes - -- chore: upgrade `@cloudflare/kv-asset-handler` ([#2650](https://github.com/sveltejs/kit/pull/2650)) - -## 1.0.0-next.24 - -### Patch Changes - -- update dependencies ([#2574](https://github.com/sveltejs/kit/pull/2574)) - -## 1.0.0-next.23 - -### Patch Changes - -- update to vite 2.6.0 and esbuild 0.13 ([#2522](https://github.com/sveltejs/kit/pull/2522)) - -## 1.0.0-next.22 - -### Patch Changes - -- chore: add links to repository and homepage to package.json ([#2425](https://github.com/sveltejs/kit/pull/2425)) - -## 1.0.0-next.21 - -### Patch Changes - -- chore: export package.json from adapters ([#2351](https://github.com/sveltejs/kit/pull/2351)) - -## 1.0.0-next.20 - -### Patch Changes - -- Support assigning multiple values to a header ([#2313](https://github.com/sveltejs/kit/pull/2313)) - -## 1.0.0-next.19 - -### Patch Changes - -- Ensure the raw body is an Uint8Array before passing it to request handlers ([#2215](https://github.com/sveltejs/kit/pull/2215)) - -## 1.0.0-next.18 - -### Patch Changes - -- d81de603: revert adapters automatically updating .gitignore ([#1924](https://github.com/sveltejs/kit/pull/1924)) - -## 1.0.0-next.17 - -### Patch Changes - -- e9f78999: fix: include esbuild config in adapter type definition ([#1954](https://github.com/sveltejs/kit/pull/1954)) - -## 1.0.0-next.16 - -### Patch Changes - -- e6995797: feat(adapters): expose esbuild configuration ([#1914](https://github.com/sveltejs/kit/pull/1914)) - -## 1.0.0-next.15 - -### Patch Changes - -- 4720b67: Default body parsing to binary ([#1890](https://github.com/sveltejs/kit/pull/1890)) - -## 1.0.0-next.14 - -### Patch Changes - -- 7faf52f: Update and consolidate checks for binary body types ([#1687](https://github.com/sveltejs/kit/pull/1687)) - -## 1.0.0-next.13 - -### Patch Changes - -- 9f0c54a: Externalize app initialization to adapters ([#1804](https://github.com/sveltejs/kit/pull/1804)) - -## 1.0.0-next.12 - -### Patch Changes - -- c51ab7d: Upgrade esbuild to ^0.12.5 ([#1627](https://github.com/sveltejs/kit/pull/1627)) - -## 1.0.0-next.11 - -### Patch Changes - -- edc307d: Remove peerDependencies due to pnpm bug ([#1621](https://github.com/sveltejs/kit/pull/1621)) -- 2636e68: Attempt to fix peerDependencies specification ([#1620](https://github.com/sveltejs/kit/pull/1620)) - -## 1.0.0-next.10 - -### Patch Changes - -- 028abd9: Pass validated svelte config to adapter adapt function ([#1559](https://github.com/sveltejs/kit/pull/1559)) -- Updated dependencies [6372690] -- Updated dependencies [c3d36a3] -- Updated dependencies [bf77940] -- Updated dependencies [2172469] -- Updated dependencies [028abd9] - - @sveltejs/kit@1.0.0-next.110 - -## 1.0.0-next.9 - -### Patch Changes - -- 71e293d: change toml parser to support dotted keys and other language features added after the TOML v0.4.0 spec ([#1509](https://github.com/sveltejs/kit/pull/1509)) -- dca4946: Make kit a peerDependency of the adapters ([#1505](https://github.com/sveltejs/kit/pull/1505)) -- Updated dependencies [261ee1c] -- Updated dependencies [ec156c6] -- Updated dependencies [586785d] - - @sveltejs/kit@1.0.0-next.109 - -## 1.0.0-next.8 - -### Patch Changes - -- dad93fc: Fix workspace dependencies ([#1434](https://github.com/sveltejs/kit/pull/1434)) - -## 1.0.0-next.7 - -### Patch Changes - -- 11e7840: Ensure rawBody is a string or Uint8Array ([#1382](https://github.com/sveltejs/kit/pull/1382)) - -## 0.0.2-next.6 - -### Patch Changes - -- c6fde99: Convert to ESM ([#1323](https://github.com/sveltejs/kit/pull/1323)) - -## 0.0.2-next.5 - -### Patch Changes - -- 9e67505: Add es2020 target to esbuild function to solve Unexpected character '#' error ([#1287](https://github.com/sveltejs/kit/pull/1287)) - -## 0.0.2-next.4 - -### Patch Changes - -- 2e72a94: Add type declarations ([#1230](https://github.com/sveltejs/kit/pull/1230)) - -## 0.0.2-next.3 - -### Patch Changes - -- b372d61: Generate required package.json ([#1116](https://github.com/sveltejs/kit/pull/1116)) - -## 0.0.2-next.2 - -### Patch Changes - -- 1237eb3: Pass rawBody to SvelteKit, bundle worker with esbuild ([#1109](https://github.com/sveltejs/kit/pull/1109)) - -## 0.0.2-next.1 - -### Patch Changes - -- 4325b39: Aligns request/response API of cloudflare-workers adapter with others ([#946](https://github.com/sveltejs/kit/pull/946)) - -## 0.0.2-next.0 - -### Patch Changes - -- e890031: Fix dev/prod deps (oops) ([#749](https://github.com/sveltejs/kit/pull/749)) diff --git a/packages/adapter-cloudflare-workers/README.md b/packages/adapter-cloudflare-workers/README.md deleted file mode 100644 index ab34b9418023..000000000000 --- a/packages/adapter-cloudflare-workers/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# adapter-cloudflare-workers - -SvelteKit adapter that creates a Cloudflare Workers site using a function for dynamic server rendering. - -**Requires [Wrangler v2](https://developers.cloudflare.com/workers/wrangler/get-started/).** Wrangler v1 is no longer supported. - -## Docs - -[Docs](https://kit.svelte.dev/docs/adapter-cloudflare-workers) - -## Changelog - -[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-cloudflare-workers/CHANGELOG.md). diff --git a/packages/adapter-cloudflare-workers/ambient.d.ts b/packages/adapter-cloudflare-workers/ambient.d.ts deleted file mode 100644 index 5978f0dd3850..000000000000 --- a/packages/adapter-cloudflare-workers/ambient.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CacheStorage } from '@cloudflare/workers-types'; - -declare global { - namespace App { - export interface Platform { - context: { - waitUntil(promise: Promise<any>): void; - }; - caches: CacheStorage; - } - } -} diff --git a/packages/adapter-cloudflare-workers/files/_package.json b/packages/adapter-cloudflare-workers/files/_package.json deleted file mode 100644 index bc4c8d4aabac..000000000000 --- a/packages/adapter-cloudflare-workers/files/_package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "private": true, - "version": "0.0.1", - "description": "Worker site generated by SvelteKit", - "main": "index.js", - "dependencies": { - "@cloudflare/kv-asset-handler": "~0.1.3" - } -} diff --git a/packages/adapter-cloudflare-workers/files/entry.js b/packages/adapter-cloudflare-workers/files/entry.js deleted file mode 100644 index 0969ba1bbf07..000000000000 --- a/packages/adapter-cloudflare-workers/files/entry.js +++ /dev/null @@ -1,110 +0,0 @@ -import { Server } from 'SERVER'; -import { manifest, prerendered } from 'MANIFEST'; -import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler'; -import static_asset_manifest_json from '__STATIC_CONTENT_MANIFEST'; -const static_asset_manifest = JSON.parse(static_asset_manifest_json); - -const server = new Server(manifest); - -const app_path = `/${manifest.appPath}/`; - -export default { - /** - * @param {Request} req - * @param {any} env - * @param {any} context - */ - async fetch(req, env, context) { - await server.init({ env }); - - const url = new URL(req.url); - - // static assets - if (url.pathname.startsWith(app_path)) { - /** @type {Response} */ - const res = await get_asset_from_kv(req, env, context); - if (is_error(res.status)) return res; - - const cache_control = url.pathname.startsWith(app_path + 'immutable/') - ? 'public, immutable, max-age=31536000' - : 'no-cache'; - - return new Response(res.body, { - headers: { - // include original headers, minus cache-control which - // is overridden, and etag which is no longer useful - 'cache-control': cache_control, - 'content-type': res.headers.get('content-type'), - 'x-robots-tag': 'noindex' - } - }); - } - - // prerendered pages and index.html files - const pathname = url.pathname.replace(/\/$/, ''); - let file = pathname.substring(1); - - try { - file = decodeURIComponent(file); - } catch (err) { - // ignore - } - - if ( - manifest.assets.has(file) || - manifest.assets.has(file + '/index.html') || - prerendered.has(pathname || '/') - ) { - return get_asset_from_kv(req, env, context, (request, options) => { - if (prerendered.has(pathname || '/')) { - url.pathname = '/' + prerendered.get(pathname || '/').file; - return new Request(url.toString(), request); - } - - return mapRequestToAsset(request, options); - }); - } - - // dynamically-generated pages - return await server.respond(req, { - platform: { - env, - context, - // @ts-expect-error lib.dom is interfering with workers-types - caches - }, - getClientAddress() { - return req.headers.get('cf-connecting-ip'); - } - }); - } -}; - -/** - * @param {Request} req - * @param {any} env - * @param {any} context - */ -async function get_asset_from_kv(req, env, context, map = mapRequestToAsset) { - return await getAssetFromKV( - { - request: req, - waitUntil(promise) { - return context.waitUntil(promise); - } - }, - { - ASSET_NAMESPACE: env.__STATIC_CONTENT, - ASSET_MANIFEST: static_asset_manifest, - mapRequestToAsset: map - } - ); -} - -/** - * @param {number} status - * @returns {boolean} - */ -function is_error(status) { - return status > 399; -} diff --git a/packages/adapter-cloudflare-workers/index.d.ts b/packages/adapter-cloudflare-workers/index.d.ts deleted file mode 100644 index 8b90611b73c3..000000000000 --- a/packages/adapter-cloudflare-workers/index.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Adapter } from '@sveltejs/kit'; -import './ambient.js'; - -export default function plugin(options: { config?: string }): Adapter; diff --git a/packages/adapter-cloudflare-workers/index.js b/packages/adapter-cloudflare-workers/index.js deleted file mode 100644 index 6ab595989024..000000000000 --- a/packages/adapter-cloudflare-workers/index.js +++ /dev/null @@ -1,137 +0,0 @@ -import { existsSync, readFileSync, writeFileSync } from 'node:fs'; -import { posix, dirname } from 'node:path'; -import { execSync } from 'node:child_process'; -import esbuild from 'esbuild'; -import toml from '@iarna/toml'; -import { fileURLToPath } from 'node:url'; - -/** - * @typedef {{ - * main: string; - * site: { - * bucket: string; - * } - * }} WranglerConfig - */ - -/** @type {import('.').default} */ -export default function ({ config = 'wrangler.toml' } = {}) { - return { - name: '@sveltejs/adapter-cloudflare-workers', - - async adapt(builder) { - const { main, site } = validate_config(builder, config); - - const files = fileURLToPath(new URL('./files', import.meta.url).href); - const tmp = builder.getBuildDirectory('cloudflare-workers-tmp'); - - builder.rimraf(site.bucket); - builder.rimraf(dirname(main)); - - builder.log.info('Installing worker dependencies...'); - builder.copy(`${files}/_package.json`, `${tmp}/package.json`); - - // TODO would be cool if we could make this step unnecessary somehow - const stdout = execSync('npm install', { cwd: tmp }); - builder.log.info(stdout.toString()); - - builder.log.minor('Generating worker...'); - const relativePath = posix.relative(tmp, builder.getServerDirectory()); - - builder.copy(`${files}/entry.js`, `${tmp}/entry.js`, { - replace: { - SERVER: `${relativePath}/index.js`, - MANIFEST: './manifest.js' - } - }); - - writeFileSync( - `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ - relativePath - })};\n\nexport const prerendered = new Map(${JSON.stringify( - Array.from(builder.prerendered.pages.entries()) - )});\n` - ); - - await esbuild.build({ - platform: 'browser', - conditions: ['worker', 'browser'], - sourcemap: 'linked', - target: 'es2022', - entryPoints: [`${tmp}/entry.js`], - outfile: main, - bundle: true, - external: ['__STATIC_CONTENT_MANIFEST', 'cloudflare:*'], - format: 'esm', - loader: { - '.wasm': 'copy' - } - }); - - builder.log.minor('Copying assets...'); - const bucket_dir = `${site.bucket}${builder.config.kit.paths.base}`; - builder.writeClient(bucket_dir); - builder.writePrerendered(bucket_dir); - } - }; -} - -/** - * @param {import('@sveltejs/kit').Builder} builder - * @param {string} config_file - * @returns {WranglerConfig} - */ -function validate_config(builder, config_file) { - if (existsSync(config_file)) { - /** @type {WranglerConfig} */ - let wrangler_config; - - try { - wrangler_config = /** @type {WranglerConfig} */ ( - toml.parse(readFileSync(config_file, 'utf-8')) - ); - } catch (err) { - err.message = `Error parsing ${config_file}: ${err.message}`; - throw err; - } - - if (!wrangler_config.site?.bucket) { - throw new Error( - `You must specify site.bucket in ${config_file}. Consult https://developers.cloudflare.com/workers/platform/sites/configuration` - ); - } - - if (!wrangler_config.main) { - throw new Error( - `You must specify main option in ${config_file}. Consult https://github.com/sveltejs/kit/tree/master/packages/adapter-cloudflare-workers` - ); - } - - return wrangler_config; - } - - builder.log.error( - 'Consult https://developers.cloudflare.com/workers/platform/sites/configuration on how to setup your site' - ); - - builder.log( - ` - Sample wrangler.toml: - - name = "<your-site-name>" - account_id = "<your-account-id>" - - main = "./.cloudflare/worker.js" - site.bucket = "./.cloudflare/public" - - build.command = "npm run build" - - compatibility_date = "2021-11-12" - workers_dev = true` - .replace(/^\t+/gm, '') - .trim() - ); - - throw new Error(`Missing a ${config_file} file`); -} diff --git a/packages/adapter-cloudflare-workers/package.json b/packages/adapter-cloudflare-workers/package.json deleted file mode 100644 index 2af2481474b2..000000000000 --- a/packages/adapter-cloudflare-workers/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@sveltejs/adapter-cloudflare-workers", - "version": "1.1.4", - "description": "SvelteKit adapter that creates a Cloudflare Workers site using a function for dynamic server rendering", - "repository": { - "type": "git", - "url": "https://github.com/sveltejs/kit", - "directory": "packages/adapter-cloudflare-workers" - }, - "license": "MIT", - "homepage": "https://kit.svelte.dev", - "type": "module", - "exports": { - ".": { - "types": "./index.d.ts", - "import": "./index.js" - }, - "./package.json": "./package.json" - }, - "types": "index.d.ts", - "files": [ - "ambient.d.ts", - "files", - "index.js", - "index.d.ts" - ], - "scripts": { - "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", - "format": "pnpm lint --write", - "check": "tsc" - }, - "dependencies": { - "@cloudflare/workers-types": "^4.20230404.0", - "@iarna/toml": "^2.2.5", - "esbuild": "^0.18.11" - }, - "devDependencies": { - "@cloudflare/kv-asset-handler": "^0.3.0", - "@types/node": "^16.18.6", - "typescript": "^4.9.4" - }, - "peerDependencies": { - "@sveltejs/kit": "^1.0.0" - } -} diff --git a/packages/adapter-cloudflare-workers/placeholders.d.ts b/packages/adapter-cloudflare-workers/placeholders.d.ts deleted file mode 100644 index 78ceb0e62887..000000000000 --- a/packages/adapter-cloudflare-workers/placeholders.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -declare module 'SERVER' { - export { Server } from '@sveltejs/kit'; -} - -declare module 'MANIFEST' { - import { SSRManifest } from '@sveltejs/kit'; - - export const manifest: SSRManifest; - export const prerendered: Map<string, { file: string }>; -} - -declare module '__STATIC_CONTENT_MANIFEST' { - const json: string; - export default json; -} diff --git a/packages/adapter-cloudflare-workers/tsconfig.json b/packages/adapter-cloudflare-workers/tsconfig.json deleted file mode 100644 index f4c98fd3be96..000000000000 --- a/packages/adapter-cloudflare-workers/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "noEmit": true, - "noImplicitAny": true, - "module": "es2020", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "baseUrl": ".", - "paths": { - "@sveltejs/kit": ["../kit/types/index"] - } - }, - "include": ["**/*.js", "placeholders.d.ts"] -} diff --git a/packages/adapter-cloudflare/CHANGELOG.md b/packages/adapter-cloudflare/CHANGELOG.md index 9802f2dc9f5f..0c66fdf9ae55 100644 --- a/packages/adapter-cloudflare/CHANGELOG.md +++ b/packages/adapter-cloudflare/CHANGELOG.md @@ -1,5 +1,438 @@ # @sveltejs/adapter-cloudflare +## 7.2.4 +### Patch Changes + + +- chore: update "homepage" field in package.json ([#14579](https://github.com/sveltejs/kit/pull/14579)) + +## 7.2.3 +### Patch Changes + + +- fix: improve the error message when `read(...)` fails ([#14306](https://github.com/sveltejs/kit/pull/14306)) + +## 7.2.2 +### Patch Changes + + +- chore: update error message link to the Workers Sites migration guide ([#14237](https://github.com/sveltejs/kit/pull/14237)) + +- Updated dependencies [[`b2c5d02`](https://github.com/sveltejs/kit/commit/b2c5d02994a6d83275d6fb3645e6f9a2518c8d20), [`0bf6185`](https://github.com/sveltejs/kit/commit/0bf6185748d7b777fa8b8d37cef331be92ecedde), [`b2c5d02`](https://github.com/sveltejs/kit/commit/b2c5d02994a6d83275d6fb3645e6f9a2518c8d20), [`c5f7139`](https://github.com/sveltejs/kit/commit/c5f713951e41af2000f21929d42eb9d30c9d3a5c)]: + - @sveltejs/kit@2.34.1 + +## 7.2.1 +### Patch Changes + + +- fix: avoid erroring on builder properties that only exist on the latest version of SvelteKit ([#14233](https://github.com/sveltejs/kit/pull/14233)) + +- Updated dependencies [[`f2db41c`](https://github.com/sveltejs/kit/commit/f2db41c0d3a0aefbb080ab6a9aa5822b3e41625c)]: + - @sveltejs/kit@2.31.1 + +## 7.2.0 +### Minor Changes + + +- feat: add `instrumentation.server.ts` for tracing and observability setup ([#13899](https://github.com/sveltejs/kit/pull/13899)) + + +### Patch Changes + +- Updated dependencies [[`f635678`](https://github.com/sveltejs/kit/commit/f63567812505597b1edc3e01010eca622b03b126), [`f635678`](https://github.com/sveltejs/kit/commit/f63567812505597b1edc3e01010eca622b03b126)]: + - @sveltejs/kit@2.31.0 + +## 7.1.3 +### Patch Changes + + +- chore: add `.git` to the end of `package.json` repository url ([#14134](https://github.com/sveltejs/kit/pull/14134)) + +- Updated dependencies [[`c968aef`](https://github.com/sveltejs/kit/commit/c968aef5727f978244d5160657b4a7ac651384ae)]: + - @sveltejs/kit@2.27.3 + +## 7.1.2 +### Patch Changes + + +- fix: resolve the absolute path of the Wrangler config setting `assets.directory` in case the config file is in a different directory than the root project ([#14036](https://github.com/sveltejs/kit/pull/14036)) + +- Updated dependencies [[`793ae28`](https://github.com/sveltejs/kit/commit/793ae28a339ca33b7e27f14158b1726bfeedd729)]: + - @sveltejs/kit@2.27.0 + +## 7.1.1 +### Patch Changes + + +- fix: support assets-only workers in wrangler validation ([#14019](https://github.com/sveltejs/kit/pull/14019)) + +- Updated dependencies [[`fda0165`](https://github.com/sveltejs/kit/commit/fda0165804011d221bb196a26565eea3e08b9d42)]: + - @sveltejs/kit@2.25.2 + +## 7.1.0 +### Minor Changes + + +- feat: add support for `read` imported from `$app/server` ([#13859](https://github.com/sveltejs/kit/pull/13859)) + + +### Patch Changes + + +- fix: include missing utils file ([#14009](https://github.com/sveltejs/kit/pull/14009)) + + +- fix: correctly warn users when assets key is missing ([#13844](https://github.com/sveltejs/kit/pull/13844)) + +- Updated dependencies [[`e5ce8bb`](https://github.com/sveltejs/kit/commit/e5ce8bb42ea020b88bd0a4ff18dc600745657541), [`cf88369`](https://github.com/sveltejs/kit/commit/cf883692fa0e163cff6b1a2f9b17a568af14124d)]: + - @sveltejs/kit@2.25.0 + +## 7.0.5 +### Patch Changes + + +- fix: deprecate `platform.context` in favor of `platform.ctx` to align with Cloudflare's naming convention ([#13856](https://github.com/sveltejs/kit/pull/13856)) + +- Updated dependencies [[`bcdaf21`](https://github.com/sveltejs/kit/commit/bcdaf215c2182524e7678a1049a5f1ccbbe71e21)]: + - @sveltejs/kit@2.22.3 + +## 7.0.4 +### Patch Changes + + +- fix: address build failure when using `paths.base` (#13769) ([#13846](https://github.com/sveltejs/kit/pull/13846)) + +- Updated dependencies [[`6a6538c4bd937667a56ef5f6673cdef3f2ea7a77`](https://github.com/sveltejs/kit/commit/6a6538c4bd937667a56ef5f6673cdef3f2ea7a77), [`6261a877ae97ff85d07277c51391d925ed1bd096`](https://github.com/sveltejs/kit/commit/6261a877ae97ff85d07277c51391d925ed1bd096), [`e7b57e74bdea976ae070562bda76d4bb78cdb6da`](https://github.com/sveltejs/kit/commit/e7b57e74bdea976ae070562bda76d4bb78cdb6da), [`408e1f5c2ae593b460861098e8e01f945af395ab`](https://github.com/sveltejs/kit/commit/408e1f5c2ae593b460861098e8e01f945af395ab), [`c6cd8c3a5ed96d423a65af5cb5468e3e963cfb54`](https://github.com/sveltejs/kit/commit/c6cd8c3a5ed96d423a65af5cb5468e3e963cfb54), [`1a406752aafc2b80d9ccb49f15ebc10301c84480`](https://github.com/sveltejs/kit/commit/1a406752aafc2b80d9ccb49f15ebc10301c84480), [`6c442395a8e6656ff49ecd4041d8e12ed65e80dd`](https://github.com/sveltejs/kit/commit/6c442395a8e6656ff49ecd4041d8e12ed65e80dd)]: + - @sveltejs/kit@2.21.3 + +## 7.0.3 +### Patch Changes + + +- chore(deps): upgrade @cloudflare/workers-types to 4.20250507.0 ([#13773](https://github.com/sveltejs/kit/pull/13773)) + +## 7.0.2 +### Patch Changes + + +- chore(deps): upgrade @cloudflare/workers-types to 4.20250415.0 ([#13716](https://github.com/sveltejs/kit/pull/13716)) + +- Updated dependencies [[`c51fb554416e0c4a21655c1d79e834f69743d1d5`](https://github.com/sveltejs/kit/commit/c51fb554416e0c4a21655c1d79e834f69743d1d5)]: + - @sveltejs/kit@2.20.8 + +## 7.0.1 +### Patch Changes + + +- fix: correctly write the worker to the `pages_build_output_dir` path if set in the Wrangler configuration path ([#13671](https://github.com/sveltejs/kit/pull/13671)) + + +- fix: correctly resolve paths provided by the Wrangler config on Windows ([#13671](https://github.com/sveltejs/kit/pull/13671)) + +- Updated dependencies [[`7fd7bcb7142e7d0d2dd64174fa1a94d56a45d643`](https://github.com/sveltejs/kit/commit/7fd7bcb7142e7d0d2dd64174fa1a94d56a45d643)]: + - @sveltejs/kit@2.20.4 + +## 7.0.0 +### Major Changes + + +- feat: support specifically building for Cloudflare Workers Static Assets ([#13634](https://github.com/sveltejs/kit/pull/13634)) + + +### Patch Changes + + +- chore: remove `esbuild` as dependency ([#13633](https://github.com/sveltejs/kit/pull/13633)) + +- Updated dependencies [[`370e9f95c1d6efd5393f73d2dbef68143b27f681`](https://github.com/sveltejs/kit/commit/370e9f95c1d6efd5393f73d2dbef68143b27f681)]: + - @sveltejs/kit@2.20.3 + +## 6.0.1 +### Patch Changes + + +- fix: revert writing server files to the cloudflare build directory ([#13622](https://github.com/sveltejs/kit/pull/13622)) + +## 6.0.0 +### Major Changes + + +- fix: copy the `_headers` and `_redirects` files from the project root instead of the `/static` directory ([#13227](https://github.com/sveltejs/kit/pull/13227)) + + +### Patch Changes + + +- fix: write server files to the cloudflare build directory ([#13610](https://github.com/sveltejs/kit/pull/13610)) + +## 5.1.0 +### Minor Changes + + +- feat: support wrangler 4 ([#13580](https://github.com/sveltejs/kit/pull/13580)) + + +### Patch Changes + +- Updated dependencies [[`001bc04dece9b0983efc2187225772c19d135345`](https://github.com/sveltejs/kit/commit/001bc04dece9b0983efc2187225772c19d135345)]: + - @sveltejs/kit@2.19.2 + +## 5.0.3 +### Patch Changes + + +- fix: exclude the dynamic route `/_app/env.js` from the adapter config `routes.exclude` special value `<build>` ([#13411](https://github.com/sveltejs/kit/pull/13411)) + +- Updated dependencies [[`9612a60a0277aef0ab4723a0e7ed8dd03a7ffb95`](https://github.com/sveltejs/kit/commit/9612a60a0277aef0ab4723a0e7ed8dd03a7ffb95), [`3d88ae33fc14b08a1d48c2cb7315739c8cfcd9fd`](https://github.com/sveltejs/kit/commit/3d88ae33fc14b08a1d48c2cb7315739c8cfcd9fd)]: + - @sveltejs/kit@2.17.2 + +## 5.0.2 +### Patch Changes + + +- fix: prevent Vitest from hanging, which was not fully addressed in [#12830](https://github.com/sveltejs/kit/pull/12830) ([#13373](https://github.com/sveltejs/kit/pull/13373)) + +- Updated dependencies [[`09296d0f19c8d1ff57d699e637bd1beabb69d438`](https://github.com/sveltejs/kit/commit/09296d0f19c8d1ff57d699e637bd1beabb69d438), [`d62ed39a431f0db3db4dd90bf6b17ed2a2a2de79`](https://github.com/sveltejs/kit/commit/d62ed39a431f0db3db4dd90bf6b17ed2a2a2de79), [`f30352f874790b9de0bd0eba985a21aef23e158e`](https://github.com/sveltejs/kit/commit/f30352f874790b9de0bd0eba985a21aef23e158e), [`180fa3467e195065c0a25206c6328a908e6952d7`](https://github.com/sveltejs/kit/commit/180fa3467e195065c0a25206c6328a908e6952d7), [`5906e9708965b848b468d0014999c36272dc8d50`](https://github.com/sveltejs/kit/commit/5906e9708965b848b468d0014999c36272dc8d50), [`d62ed39a431f0db3db4dd90bf6b17ed2a2a2de79`](https://github.com/sveltejs/kit/commit/d62ed39a431f0db3db4dd90bf6b17ed2a2a2de79)]: + - @sveltejs/kit@2.17.0 + +## 5.0.1 +### Patch Changes + + +- fix: prevent vitest from hanging ([#12830](https://github.com/sveltejs/kit/pull/12830)) + +- Updated dependencies [[`1bedcc1cfc1f2d85946c1423f60faa8a2a56148b`](https://github.com/sveltejs/kit/commit/1bedcc1cfc1f2d85946c1423f60faa8a2a56148b), [`e201fa9380a00e072a80a2dcab56de3d77e5b67c`](https://github.com/sveltejs/kit/commit/e201fa9380a00e072a80a2dcab56de3d77e5b67c), [`f3f08582d41b08c3fd1daf742e5703d9cdca7823`](https://github.com/sveltejs/kit/commit/f3f08582d41b08c3fd1daf742e5703d9cdca7823), [`d4bcfccb4503b12fe76140dbb6cfddc81f9419fc`](https://github.com/sveltejs/kit/commit/d4bcfccb4503b12fe76140dbb6cfddc81f9419fc), [`d09bc033123903f359c1ad6fd3a6d8d7fc19298a`](https://github.com/sveltejs/kit/commit/d09bc033123903f359c1ad6fd3a6d8d7fc19298a)]: + - @sveltejs/kit@2.15.3 + +## 5.0.0 +### Major Changes + + +- feat: remove esbuild step ([#13132](https://github.com/sveltejs/kit/pull/13132)) + + +### Patch Changes + +- Updated dependencies [[`12ce7eb19fb57907e3db29ef981a8c7a0afc4b6f`](https://github.com/sveltejs/kit/commit/12ce7eb19fb57907e3db29ef981a8c7a0afc4b6f), [`528af75f846f971ef64e4d109ac5e22fca046b90`](https://github.com/sveltejs/kit/commit/528af75f846f971ef64e4d109ac5e22fca046b90)]: + - @sveltejs/kit@2.15.1 + +## 4.9.0 +### Minor Changes + + +- feat: generate `.assetsignore` file for use with Cloudflare Workers Static Assets ([#13109](https://github.com/sveltejs/kit/pull/13109)) + + +### Patch Changes + +- Updated dependencies [[`20f2720aa3455f38fa2630a33d52f7532da27fce`](https://github.com/sveltejs/kit/commit/20f2720aa3455f38fa2630a33d52f7532da27fce)]: + - @sveltejs/kit@2.13.0 + +## 4.8.0 +### Minor Changes + + +- chore: upgrade esbuild to 0.24.0 ([#12270](https://github.com/sveltejs/kit/pull/12270)) + + +### Patch Changes + +- Updated dependencies [[`d030f4bb285e70844d09b3f0c87809bae43014b8`](https://github.com/sveltejs/kit/commit/d030f4bb285e70844d09b3f0c87809bae43014b8), [`67dd214863cbc5852eb0e8512efbb7bad5358e8a`](https://github.com/sveltejs/kit/commit/67dd214863cbc5852eb0e8512efbb7bad5358e8a)]: + - @sveltejs/kit@2.9.0 + +## 4.7.4 +### Patch Changes + + +- docs: update URLs for new svelte.dev site ([#12857](https://github.com/sveltejs/kit/pull/12857)) + +- Updated dependencies [[`dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46`](https://github.com/sveltejs/kit/commit/dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46), [`4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d`](https://github.com/sveltejs/kit/commit/4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d), [`3a9b78f04786898ca93f6d4b75ab18d26bc45192`](https://github.com/sveltejs/kit/commit/3a9b78f04786898ca93f6d4b75ab18d26bc45192), [`723eb8b31e6a22c82f730c30e485386c8676b746`](https://github.com/sveltejs/kit/commit/723eb8b31e6a22c82f730c30e485386c8676b746), [`8ec471c875345b751344e67580ff1b772ef2735b`](https://github.com/sveltejs/kit/commit/8ec471c875345b751344e67580ff1b772ef2735b)]: + - @sveltejs/kit@2.7.3 + +## 4.7.3 +### Patch Changes + + +- fix: correctly handle relative paths when fetching assets on the server ([#12113](https://github.com/sveltejs/kit/pull/12113)) + +- Updated dependencies [[`df48fc6ede3859beabaae9fd7c6f722215bb8a42`](https://github.com/sveltejs/kit/commit/df48fc6ede3859beabaae9fd7c6f722215bb8a42), [`5780deba8e3ebd0e2b0abea029068ad0c6daf6ad`](https://github.com/sveltejs/kit/commit/5780deba8e3ebd0e2b0abea029068ad0c6daf6ad), [`6f9aefdb8699fc126d76a88471602cb9a80822eb`](https://github.com/sveltejs/kit/commit/6f9aefdb8699fc126d76a88471602cb9a80822eb), [`8aa95b4b3431e79f62f580abdcdcb157b4de86cd`](https://github.com/sveltejs/kit/commit/8aa95b4b3431e79f62f580abdcdcb157b4de86cd)]: + - @sveltejs/kit@2.7.0 + +## 4.7.2 +### Patch Changes + + +- chore: configure provenance in a simpler manner ([#12570](https://github.com/sveltejs/kit/pull/12570)) + +- Updated dependencies [[`087a43d391fc38b8c008fb39a804dc6988974101`](https://github.com/sveltejs/kit/commit/087a43d391fc38b8c008fb39a804dc6988974101)]: + - @sveltejs/kit@2.5.22 + +## 4.7.1 +### Patch Changes + + +- chore: package provenance ([#12567](https://github.com/sveltejs/kit/pull/12567)) + +- Updated dependencies [[`4930a8443caa53bcecee7b690cd28e429b1c8a20`](https://github.com/sveltejs/kit/commit/4930a8443caa53bcecee7b690cd28e429b1c8a20)]: + - @sveltejs/kit@2.5.21 + +## 4.7.0 +### Minor Changes + + +- feat: generate static `_redirects` for Cloudflare Pages ([#12199](https://github.com/sveltejs/kit/pull/12199)) + + +### Patch Changes + + +- fix: correctly return static assets if base path is set ([#12075](https://github.com/sveltejs/kit/pull/12075)) + +## 4.6.1 +### Patch Changes + + +- fix: copy `.eot`, `.otf`, `.ttf`, `.woff`, and `woff2` font files when bundling ([#12439](https://github.com/sveltejs/kit/pull/12439)) + +## 4.6.0 +### Minor Changes + + +- chore(deps): upgrade to esbuild 0.21 ([#12415](https://github.com/sveltejs/kit/pull/12415)) + + +### Patch Changes + +- Updated dependencies [[`84298477a014ec471839adf7a4448d91bc7949e4`](https://github.com/sveltejs/kit/commit/84298477a014ec471839adf7a4448d91bc7949e4), [`5645614f497931f587b7cb8b3c885fce892a6a72`](https://github.com/sveltejs/kit/commit/5645614f497931f587b7cb8b3c885fce892a6a72), [`84298477a014ec471839adf7a4448d91bc7949e4`](https://github.com/sveltejs/kit/commit/84298477a014ec471839adf7a4448d91bc7949e4)]: + - @sveltejs/kit@2.5.18 + +## 4.5.0 + +### Minor Changes + +- feat: validate that no `_routes.json` is present to avoid overwriting it ([#12360](https://github.com/sveltejs/kit/pull/12360)) + +### Patch Changes + +- Updated dependencies [[`121836fcbf6c615fd18c79a12203613ddbe49acf`](https://github.com/sveltejs/kit/commit/121836fcbf6c615fd18c79a12203613ddbe49acf)]: + - @sveltejs/kit@2.5.17 + +## 4.4.1 + +### Patch Changes + +- chore: add keywords for discovery in npm search ([#12330](https://github.com/sveltejs/kit/pull/12330)) + +- Updated dependencies [[`25acb1d9fce998dccd8050b93cf4142c2b082611`](https://github.com/sveltejs/kit/commit/25acb1d9fce998dccd8050b93cf4142c2b082611), [`642c4a4aff4351b786fe6274aa2f0bf7d905faf9`](https://github.com/sveltejs/kit/commit/642c4a4aff4351b786fe6274aa2f0bf7d905faf9), [`0a0e9aa897123ebec50af08e9385b2ca4fc5bb28`](https://github.com/sveltejs/kit/commit/0a0e9aa897123ebec50af08e9385b2ca4fc5bb28)]: + - @sveltejs/kit@2.5.11 + +## 4.4.0 + +### Minor Changes + +- chore(deps): upgrade esbuild ([#12118](https://github.com/sveltejs/kit/pull/12118)) + +### Patch Changes + +- Updated dependencies [[`bbab296f6fcc05af6b999182798bcdedabbaa4c9`](https://github.com/sveltejs/kit/commit/bbab296f6fcc05af6b999182798bcdedabbaa4c9)]: + - @sveltejs/kit@2.5.6 + +## 4.3.0 + +### Minor Changes + +- feat: support platform emulation configuration via the `platformProxy` adapter option ([#12011](https://github.com/sveltejs/kit/pull/12011)) + +## 4.2.1 + +### Patch Changes + +- fix: add `workerd` to esbuild conditions ([#12069](https://github.com/sveltejs/kit/pull/12069)) + +## 4.2.0 + +### Minor Changes + +- feat: emulate Cloudflare Workers bindings and incoming request properties in `event.platform` for `dev` and `preview` ([#11974](https://github.com/sveltejs/kit/pull/11974)) + +### Patch Changes + +- Updated dependencies [[`4562275ed42964148df03c79434172024897c08c`](https://github.com/sveltejs/kit/commit/4562275ed42964148df03c79434172024897c08c)]: + - @sveltejs/kit@2.5.4 + +## 4.1.0 + +### Minor Changes + +- feat: more helpful errors when using incompatible Node modules ([#11673](https://github.com/sveltejs/kit/pull/11673)) + +- feat: support compatible node modules without prefixes ([#11672](https://github.com/sveltejs/kit/pull/11672)) + +- feat: Add Node.js compatibility ([#10544](https://github.com/sveltejs/kit/pull/10544)) + +### Patch Changes + +- Updated dependencies [[`288f731c8a5b20cadb9e219f9583f3f16bf8c7b8`](https://github.com/sveltejs/kit/commit/288f731c8a5b20cadb9e219f9583f3f16bf8c7b8)]: + - @sveltejs/kit@2.4.0 + +## 4.0.2 + +### Patch Changes + +- chore(deps): update dependency worktop to v0.8.0-next.18 ([#11618](https://github.com/sveltejs/kit/pull/11618)) + +## 4.0.1 + +### Patch Changes + +- chore: upgrade esbuild to 0.19.11 ([#11632](https://github.com/sveltejs/kit/pull/11632)) + +## 4.0.0 + +### Major Changes + +- breaking: generate plaintext 404.html instead of SPA-style fallback page ([#11596](https://github.com/sveltejs/kit/pull/11596)) + +### Patch Changes + +- Updated dependencies [[`2137717ea8592c310ada93490feabbd9eea125ea`](https://github.com/sveltejs/kit/commit/2137717ea8592c310ada93490feabbd9eea125ea)]: + - @sveltejs/kit@2.3.3 + +## 3.0.2 + +### Patch Changes + +- fix: serve static files in `_app` from function, if not already handled ([#11593](https://github.com/sveltejs/kit/pull/11593)) + +- Updated dependencies [[`553e14c8320ad9c6ebb3c554c35f1482755c9555`](https://github.com/sveltejs/kit/commit/553e14c8320ad9c6ebb3c554c35f1482755c9555), [`48576de0dc8b1fbbab7954113004540ea4e76935`](https://github.com/sveltejs/kit/commit/48576de0dc8b1fbbab7954113004540ea4e76935)]: + - @sveltejs/kit@2.3.2 + +## 3.0.1 + +### Patch Changes + +- chore: update primary branch from master to main ([`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d)) + +- Updated dependencies [[`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d), [`16961e8cd3fa6a7f382153b1ff056bc2aae9b31b`](https://github.com/sveltejs/kit/commit/16961e8cd3fa6a7f382153b1ff056bc2aae9b31b), [`197e01f95652f511160f38b37b9da73a124ecd48`](https://github.com/sveltejs/kit/commit/197e01f95652f511160f38b37b9da73a124ecd48), [`102e4a5ae5b29624302163faf5a20c94a64a5b2c`](https://github.com/sveltejs/kit/commit/102e4a5ae5b29624302163faf5a20c94a64a5b2c), [`f8e3d8b9728c9f1ab63389342c31d7246b6f9db6`](https://github.com/sveltejs/kit/commit/f8e3d8b9728c9f1ab63389342c31d7246b6f9db6)]: + - @sveltejs/kit@2.0.4 + +## 3.0.0 + +### Major Changes + +- breaking: require SvelteKit 2 ([#11316](https://github.com/sveltejs/kit/pull/11316)) + +- chore: upgrade esbuild ([#11122](https://github.com/sveltejs/kit/pull/11122)) + +## 2.3.4 + +### Patch Changes + +- fix: retain URL query string for trailing slash redirects to prerendered pages ([#11142](https://github.com/sveltejs/kit/pull/11142)) + +- Updated dependencies [[`a7f8bdcfa`](https://github.com/sveltejs/kit/commit/a7f8bdcfabce5cda85dd073a21d0afb6138a7a08), [`a4d91304e`](https://github.com/sveltejs/kit/commit/a4d91304eebc08bf2e748d83a46d3548a546e3ab)]: + - @sveltejs/kit@1.27.7 + ## 2.3.3 ### Patch Changes diff --git a/packages/adapter-cloudflare/README.md b/packages/adapter-cloudflare/README.md index 775351116cc0..fb8ce1266e61 100644 --- a/packages/adapter-cloudflare/README.md +++ b/packages/adapter-cloudflare/README.md @@ -1,11 +1,11 @@ # adapter-cloudflare -[Adapter](https://kit.svelte.dev/docs/building-your-app) for building SvelteKit applications on [Cloudflare Pages](https://developers.cloudflare.com/pages/) with [Workers integration](https://developers.cloudflare.com/pages/platform/functions). +[Adapter](https://svelte.dev/docs/kit/building-your-app) for building SvelteKit applications on [Cloudflare Workers](https://developers.cloudflare.com/workers/) with [static assets](https://developers.cloudflare.com/workers/static-assets/) or [Cloudflare Pages](https://developers.cloudflare.com/pages/) with [Workers integration](https://developers.cloudflare.com/pages/functions/). ## Docs -[Docs](https://kit.svelte.dev/docs/adapter-cloudflare) +[Docs](https://svelte.dev/docs/kit/adapter-cloudflare) ## Changelog -[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-cloudflare/CHANGELOG.md). +[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/main/packages/adapter-cloudflare/CHANGELOG.md). diff --git a/packages/adapter-cloudflare/ambient.d.ts b/packages/adapter-cloudflare/ambient.d.ts index 5b3c951b4139..d99dd15ab6cc 100644 --- a/packages/adapter-cloudflare/ambient.d.ts +++ b/packages/adapter-cloudflare/ambient.d.ts @@ -1,12 +1,17 @@ -import { Cache, CacheStorage, IncomingRequestCfProperties } from '@cloudflare/workers-types'; +import { + CacheStorage, + IncomingRequestCfProperties, + ExecutionContext +} from '@cloudflare/workers-types'; declare global { namespace App { export interface Platform { - context?: { - waitUntil(promise: Promise<any>): void; - }; - caches?: CacheStorage & { default: Cache }; + env: unknown; + ctx: ExecutionContext; + /** @deprecated Use `ctx` instead */ + context: ExecutionContext; + caches: CacheStorage; cf?: IncomingRequestCfProperties; } } diff --git a/packages/adapter-cloudflare/index.d.ts b/packages/adapter-cloudflare/index.d.ts index e6fb925ff102..0c02fb786cee 100644 --- a/packages/adapter-cloudflare/index.d.ts +++ b/packages/adapter-cloudflare/index.d.ts @@ -1,12 +1,41 @@ import { Adapter } from '@sveltejs/kit'; import './ambient.js'; +import { GetPlatformProxyOptions } from 'wrangler'; export default function plugin(options?: AdapterOptions): Adapter; export interface AdapterOptions { /** - * Customize the automatically-generated `_routes.json` file - * https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file + * Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). + */ + config?: string; + /** + * Whether to render a plaintext 404.html page or a rendered SPA fallback page + * for non-matching asset requests. + * + * For Cloudflare Workers, the default behaviour is to return a null-body + * 404-status response for non-matching assets requests. However, if the + * [`assets.not_found_handling`](https://developers.cloudflare.com/workers/static-assets/routing/#2-not_found_handling) + * Wrangler configuration setting is set to `"404-page"`, this page will be + * served if a request fails to match an asset. If `assets.not_found_handling` + * is set to `"single-page-application"`, the adapter will render a SPA fallback + * index.html page regardless of the `fallback` option specified. + * + * For Cloudflare Pages, this page will only be served when a request that + * matches an entry in `routes.exclude` fails to match an asset. + * + * Most of the time `plaintext` is sufficient, but if you are using `routes.exclude` to manually + * exclude a set of prerendered pages without exceeding the 100 route limit, you may wish to + * use `spa` instead to avoid showing an unstyled 404 page to users. + * + * See [Cloudflare Pages' Not Found behavior](https://developers.cloudflare.com/pages/configuration/serving-pages/#not-found-behavior) for more info. + * + * @default 'plaintext' + */ + fallback?: 'plaintext' | 'spa'; + + /** + * Only for Cloudflare Pages. Customize the automatically-generated [`_routes.json`](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) file. */ routes?: { /** @@ -30,6 +59,12 @@ export interface AdapterOptions { */ exclude?: string[]; }; + + /** + * Config object passed to [`getPlatformProxy`](https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy) + * during development and preview. + */ + platformProxy?: GetPlatformProxyOptions; } export interface RoutesJSONSpec { diff --git a/packages/adapter-cloudflare/index.js b/packages/adapter-cloudflare/index.js index 1dd0abd06470..777041a0d079 100644 --- a/packages/adapter-cloudflare/index.js +++ b/packages/adapter-cloudflare/index.js @@ -1,76 +1,222 @@ -import { writeFileSync } from 'node:fs'; -import * as path from 'node:path'; +import { VERSION } from '@sveltejs/kit'; +import { copyFileSync, existsSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; import { fileURLToPath } from 'node:url'; -import * as esbuild from 'esbuild'; +import { getPlatformProxy, unstable_readConfig } from 'wrangler'; +import { is_building_for_cloudflare_pages, validate_worker_settings } from './utils.js'; -/** @type {import('.').default} */ +const name = '@sveltejs/adapter-cloudflare'; +const [kit_major, kit_minor] = VERSION.split('.'); + +/** + * @template T + * @template {keyof T} K + * @typedef {Partial<Omit<T, K>> & Required<Pick<T, K>>} PartialExcept + */ + +/** + * We use a custom `Builder` type here to support the minimum version of SvelteKit. + * @typedef {PartialExcept<import('@sveltejs/kit').Builder, 'log' | 'rimraf' | 'mkdirp' | 'config' | 'prerendered' | 'routes' | 'createEntries' | 'generateFallback' | 'generateEnvModule' | 'generateManifest' | 'getBuildDirectory' | 'getClientDirectory' | 'getServerDirectory' | 'getAppPath' | 'writeClient' | 'writePrerendered' | 'writePrerendered' | 'writeServer' | 'copy' | 'compress'>} Builder2_0_0 + */ + +/** @type {import('./index.js').default} */ export default function (options = {}) { return { - name: '@sveltejs/adapter-cloudflare', + name, + /** @param {Builder2_0_0} builder */ async adapt(builder) { + if (existsSync('_routes.json')) { + throw new Error( + "Cloudflare Pages' _routes.json should be configured in svelte.config.js. See https://svelte.dev/docs/kit/adapter-cloudflare#Options-routes" + ); + } + + if (existsSync(`${builder.config.kit.files.assets}/_headers`)) { + throw new Error( + `The _headers file should be placed in the project root rather than the ${builder.config.kit.files.assets} directory` + ); + } + + if (existsSync(`${builder.config.kit.files.assets}/_redirects`)) { + throw new Error( + `The _redirects file should be placed in the project root rather than the ${builder.config.kit.files.assets} directory` + ); + } + + const wrangler_config = validate_wrangler_config(options.config); + + const building_for_cloudflare_pages = is_building_for_cloudflare_pages(wrangler_config); + + let dest = builder.getBuildDirectory('cloudflare'); + let worker_dest = `${dest}/_worker.js`; + let assets_binding = 'ASSETS'; + + if (building_for_cloudflare_pages) { + if (wrangler_config.pages_build_output_dir) { + dest = wrangler_config.pages_build_output_dir; + worker_dest = `${dest}/_worker.js`; + } + } else { + if (wrangler_config.main) { + worker_dest = wrangler_config.main; + } + if (wrangler_config.assets?.directory) { + // wrangler doesn't resolve `assets.directory` to an absolute path unlike + // `main` and `pages_build_output_dir` so we need to do it ourselves here + const parent_dir = wrangler_config.configPath + ? path.dirname(path.resolve(wrangler_config.configPath)) + : process.cwd(); + dest = path.resolve(parent_dir, wrangler_config.assets.directory); + } + if (wrangler_config.assets?.binding) { + assets_binding = wrangler_config.assets.binding; + } + } + const files = fileURLToPath(new URL('./files', import.meta.url).href); - const dest = builder.getBuildDirectory('cloudflare'); const tmp = builder.getBuildDirectory('cloudflare-tmp'); builder.rimraf(dest); - builder.rimraf(tmp); - builder.mkdirp(tmp); - - // generate 404.html first which can then be overridden by prerendering, if the user defined such a page - await builder.generateFallback(path.join(dest, '404.html')); + builder.rimraf(worker_dest); - const dest_dir = `${dest}${builder.config.kit.paths.base}`; - const written_files = builder.writeClient(dest_dir); - builder.writePrerendered(dest_dir); + builder.mkdirp(dest); + builder.mkdirp(tmp); - const relativePath = path.posix.relative(tmp, builder.getServerDirectory()); + // client assets and prerendered pages + const assets_dest = `${dest}${builder.config.kit.paths.base}`; + builder.mkdirp(assets_dest); + if ( + building_for_cloudflare_pages || + wrangler_config.assets?.not_found_handling === '404-page' + ) { + // generate plaintext 404.html first which can then be overridden by prerendering, if the user defined such a page + const fallback = path.join(assets_dest, '404.html'); + if (options.fallback === 'spa') { + await builder.generateFallback(fallback); + } else { + writeFileSync(fallback, 'Not Found'); + } + } + const client_assets = builder.writeClient(assets_dest); + builder.writePrerendered(assets_dest); + if ( + !building_for_cloudflare_pages && + wrangler_config.assets?.not_found_handling === 'single-page-application' + ) { + await builder.generateFallback(path.join(assets_dest, 'index.html')); + } + // worker + const worker_dest_dir = path.dirname(worker_dest); writeFileSync( `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ relativePath })};\n\n` + - `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n` - ); - - writeFileSync( - `${dest}/_routes.json`, - JSON.stringify(get_routes_json(builder, written_files, options.routes ?? {}), null, '\t') + `export const manifest = ${builder.generateManifest({ relativePath: path.posix.relative(tmp, builder.getServerDirectory()) })};\n\n` + + `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n\n` + + `export const base_path = ${JSON.stringify(builder.config.kit.paths.base)};\n` ); + builder.copy(`${files}/worker.js`, worker_dest, { + replace: { + // the paths returned by the Wrangler config might be Windows paths, + // so we need to convert them to POSIX paths or else the backslashes + // will be interpreted as escape characters and create an incorrect import path + SERVER: `${posixify(path.relative(worker_dest_dir, builder.getServerDirectory()))}/index.js`, + MANIFEST: `${posixify(path.relative(worker_dest_dir, tmp))}/manifest.js`, + ASSETS: assets_binding + } + }); + if (builder.hasServerInstrumentationFile?.()) { + builder.instrument?.({ + entrypoint: worker_dest, + instrumentation: `${builder.getServerDirectory()}/instrumentation.server.js` + }); + } + // _headers + if (existsSync('_headers')) { + copyFileSync('_headers', `${dest}/_headers`); + } writeFileSync(`${dest}/_headers`, generate_headers(builder.getAppPath()), { flag: 'a' }); - builder.copy(`${files}/worker.js`, `${tmp}/_worker.js`, { - replace: { - SERVER: `${relativePath}/index.js`, - MANIFEST: './manifest.js' + // _redirects + if (existsSync('_redirects')) { + copyFileSync('_redirects', `${dest}/_redirects`); + } + if (builder.prerendered.redirects.size > 0) { + writeFileSync(`${dest}/_redirects`, generate_redirects(builder.prerendered.redirects), { + flag: 'a' + }); + } + + writeFileSync(`${dest}/.assetsignore`, generate_assetsignore(), { flag: 'a' }); + + if (building_for_cloudflare_pages) { + writeFileSync( + `${dest}/_routes.json`, + JSON.stringify(get_routes_json(builder, client_assets, options.routes ?? {}), null, '\t') + ); + } + }, + emulate() { + // we want to invoke `getPlatformProxy` only once, but await it only when it is accessed. + // If we would await it here, it would hang indefinitely because the platform proxy only resolves once a request happens + const get_emulated = async () => { + const proxy = await getPlatformProxy(options.platformProxy); + const platform = /** @type {App.Platform} */ ({ + env: proxy.env, + ctx: proxy.ctx, + context: proxy.ctx, // deprecated in favor of ctx + caches: proxy.caches, + cf: proxy.cf + }); + /** @type {Record<string, any>} */ + const env = {}; + const prerender_platform = /** @type {App.Platform} */ (/** @type {unknown} */ ({ env })); + for (const key in proxy.env) { + Object.defineProperty(env, key, { + get: () => { + throw new Error(`Cannot access platform.env.${key} in a prerenderable route`); + } + }); } - }); + return { platform, prerender_platform }; + }; - await esbuild.build({ - platform: 'browser', - conditions: ['worker', 'browser'], - sourcemap: 'linked', - target: 'es2022', - entryPoints: [`${tmp}/_worker.js`], - outfile: `${dest}/_worker.js`, - allowOverwrite: true, - format: 'esm', - bundle: true, - loader: { - '.wasm': 'copy' - }, - external: ['cloudflare:*'] - }); + /** @type {{ platform: App.Platform, prerender_platform: App.Platform }} */ + let emulated; + + return { + platform: async ({ prerender }) => { + emulated ??= await get_emulated(); + return prerender ? emulated.prerender_platform : emulated.platform; + } + }; + }, + supports: { + read: ({ route }) => { + // TODO bump peer dep in next adapter major to simplify this + if (kit_major === '2' && kit_minor < '25') { + throw new Error( + `${name}: Cannot use \`read\` from \`$app/server\` in route \`${route.id}\` when using SvelteKit < 2.25.0` + ); + } + + return true; + }, + instrumentation: () => true } }; } /** - * @param {import('@sveltejs/kit').Builder} builder + * @param {Builder2_0_0} builder * @param {string[]} assets - * @param {import('./index').AdapterOptions['routes']} routes - * @returns {import('.').RoutesJSONSpec} + * @param {import('./index.js').AdapterOptions['routes']} routes + * @returns {import('./index.js').RoutesJSONSpec} */ -function get_routes_json(builder, assets, { include = ['/*'], exclude = ['<all>'] }) { +function get_routes_json(builder, assets, routes) { + let { include = ['/*'], exclude = ['<all>'] } = routes || {}; + if (!Array.isArray(include) || !Array.isArray(exclude)) { throw new Error('routes.include and routes.exclude must be arrays'); } @@ -87,7 +233,7 @@ function get_routes_json(builder, assets, { include = ['/*'], exclude = ['<all>' .flatMap((rule) => (rule === '<all>' ? ['<build>', '<files>', '<prerendered>'] : rule)) .flatMap((rule) => { if (rule === '<build>') { - return `/${builder.getAppPath()}/*`; + return [`/${builder.getAppPath()}/immutable/*`, `/${builder.getAppPath()}/version.json`]; } if (rule === '<files>') { @@ -100,18 +246,11 @@ function get_routes_json(builder, assets, { include = ['/*'], exclude = ['<all>' file === '_redirects' ) ) - .map((file) => `/${file}`); + .map((file) => `${builder.config.kit.paths.base}/${file}`); } if (rule === '<prerendered>') { - const prerendered = []; - for (const path of builder.prerendered.paths) { - if (!builder.prerendered.redirects.has(path)) { - prerendered.push(path); - } - } - - return prerendered; + return builder.prerendered.paths; } return rule; @@ -119,7 +258,7 @@ function get_routes_json(builder, assets, { include = ['/*'], exclude = ['<all>' const excess = include.length + exclude.length - 100; if (excess > 0) { - const message = `Function includes/excludes exceeds _routes.json limits (see https://developers.cloudflare.com/pages/platform/functions/routing/#limits). Dropping ${excess} exclude rules — this will cause unnecessary function invocations.`; + const message = `Cloudflare Pages Functions' includes/excludes exceeds _routes.json limits (see https://developers.cloudflare.com/pages/platform/functions/routing/#limits). Dropping ${excess} exclude rules — this will cause unnecessary function invocations.`; builder.log.warn(message); exclude.length -= excess; @@ -146,3 +285,47 @@ function generate_headers(app_dir) { # === END AUTOGENERATED SVELTE IMMUTABLE HEADERS === `.trimEnd(); } + +/** @param {Map<string, { status: number; location: string }>} redirects */ +function generate_redirects(redirects) { + const rules = Array.from( + redirects.entries(), + ([path, redirect]) => `${path} ${redirect.location} ${redirect.status}` + ).join('\n'); + + return ` +# === START AUTOGENERATED SVELTE PRERENDERED REDIRECTS === +${rules} +# === END AUTOGENERATED SVELTE PRERENDERED REDIRECTS === +`.trimEnd(); +} + +function generate_assetsignore() { + // this comes from https://github.com/cloudflare/workers-sdk/blob/main/packages/create-cloudflare/templates-experimental/svelte/templates/static/.assetsignore + return ` +_worker.js +_routes.json +_headers +_redirects +`; +} + +/** + * @param {string | undefined} config_file + * @returns {import('wrangler').Unstable_Config} + */ +function validate_wrangler_config(config_file = undefined) { + const wrangler_config = unstable_readConfig({ config: config_file }); + + if (!is_building_for_cloudflare_pages(wrangler_config)) { + // probably deploying to Cloudflare Workers + validate_worker_settings(wrangler_config); + } + + return wrangler_config; +} + +/** @param {string} str */ +function posixify(str) { + return str.replace(/\\/g, '/'); +} diff --git a/packages/adapter-cloudflare/internal.d.ts b/packages/adapter-cloudflare/internal.d.ts new file mode 100644 index 000000000000..6c79569f7f7f --- /dev/null +++ b/packages/adapter-cloudflare/internal.d.ts @@ -0,0 +1,12 @@ +declare module 'SERVER' { + export { Server } from '@sveltejs/kit'; +} + +declare module 'MANIFEST' { + import { SSRManifest } from '@sveltejs/kit'; + + export const manifest: SSRManifest; + export const prerendered: Set<string>; + export const app_path: string; + export const base_path: string; +} diff --git a/packages/adapter-cloudflare/package.json b/packages/adapter-cloudflare/package.json index a686bfa841f3..f6f4c493cf8d 100644 --- a/packages/adapter-cloudflare/package.json +++ b/packages/adapter-cloudflare/package.json @@ -1,14 +1,22 @@ { "name": "@sveltejs/adapter-cloudflare", - "version": "2.3.3", + "version": "7.2.4", "description": "Adapter for building SvelteKit applications on Cloudflare Pages with Workers integration", + "keywords": [ + "adapter", + "cloudflare pages", + "deploy", + "hosting", + "svelte", + "sveltekit" + ], "repository": { "type": "git", - "url": "https://github.com/sveltejs/kit", + "url": "git+https://github.com/sveltejs/kit.git", "directory": "packages/adapter-cloudflare" }, "license": "MIT", - "homepage": "https://kit.svelte.dev", + "homepage": "https://svelte.dev/docs/kit/adapter-cloudflare", "type": "module", "exports": { ".": { @@ -21,30 +29,34 @@ "files": [ "files", "index.js", + "utils.js", "index.d.ts", "ambient.d.ts" ], "scripts": { - "build": "esbuild src/worker.js --bundle --outfile=files/worker.js --external:SERVER --external:MANIFEST --format=esm", - "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", + "build": "esbuild src/worker.js --bundle --outfile=files/worker.js --external:SERVER --external:MANIFEST --external:cloudflare:workers --format=esm", + "lint": "prettier --check .", "format": "pnpm lint --write", "check": "tsc --skipLibCheck", + "test:unit": "vitest run", + "test:e2e": "pnpm build && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test", + "test": "pnpm test:unit && pnpm test:e2e", "prepublishOnly": "pnpm build" }, "dependencies": { - "@cloudflare/workers-types": "^4.20230404.0", - "esbuild": "^0.18.11", - "worktop": "0.8.0-next.15" + "@cloudflare/workers-types": "^4.20250507.0", + "worktop": "0.8.0-next.18" }, "devDependencies": { - "@types/node": "^16.18.6", - "@types/ws": "^8.5.3", - "typescript": "^4.9.4" + "@playwright/test": "catalog:", + "@sveltejs/kit": "workspace:^", + "@types/node": "catalog:", + "esbuild": "catalog:", + "typescript": "^5.3.3", + "vitest": "catalog:" }, "peerDependencies": { - "@sveltejs/kit": "^1.0.0" - }, - "publishConfig": { - "access": "public" + "@sveltejs/kit": "^2.0.0", + "wrangler": "^4.0.0" } } diff --git a/packages/adapter-cloudflare/placeholders.ts b/packages/adapter-cloudflare/placeholders.ts deleted file mode 100644 index 3689c3f5e51e..000000000000 --- a/packages/adapter-cloudflare/placeholders.ts +++ /dev/null @@ -1,10 +0,0 @@ -declare module 'SERVER' { - export { Server } from '@sveltejs/kit'; -} - -declare module 'MANIFEST' { - import { SSRManifest } from '@sveltejs/kit'; - - export const manifest: SSRManifest; - export const prerendered: Set<string>; -} diff --git a/packages/adapter-cloudflare/src/worker.js b/packages/adapter-cloudflare/src/worker.js index 59e6af9f54a3..bcee34b882e2 100644 --- a/packages/adapter-cloudflare/src/worker.js +++ b/packages/adapter-cloudflare/src/worker.js @@ -1,20 +1,62 @@ import { Server } from 'SERVER'; -import { manifest, prerendered } from 'MANIFEST'; +import { manifest, prerendered, base_path } from 'MANIFEST'; +import { env } from 'cloudflare:workers'; import * as Cache from 'worktop/cfw.cache'; const server = new Server(manifest); -/** @type {import('worktop/cfw').Module.Worker<{ ASSETS: import('worktop/cfw.durable').Durable.Object }>} */ -const worker = { - async fetch(req, env, context) { - // @ts-ignore - await server.init({ env }); +const app_path = `/${manifest.appPath}`; + +const immutable = `${app_path}/immutable/`; +const version_file = `${app_path}/version.json`; + +/** + * We don't know the origin until we receive a request, but + * that's guaranteed to happen before we call `read` + * @type {string} + */ +let origin; + +const initialized = server.init({ + // @ts-expect-error env contains environment variables and bindings + env, + read: async (file) => { + const url = `${origin}/${file}`; + const response = await /** @type {{ ASSETS: { fetch: typeof fetch } }} */ (env).ASSETS.fetch( + url + ); + + if (!response.ok) { + throw new Error( + `read(...) failed: could not fetch ${url} (${response.status} ${response.statusText})` + ); + } + + return response.body; + } +}); + +export default { + /** + * @param {Request} req + * @param {{ ASSETS: { fetch: typeof fetch } }} env + * @param {ExecutionContext} ctx + * @returns {Promise<Response>} + */ + async fetch(req, env, ctx) { + if (!origin) { + origin = new URL(req.url).origin; + } + + // always await initialization to prevent race condition with concurrent requests + await initialized; + // skip cache if "cache-control: no-cache" in request let pragma = req.headers.get('cache-control') || ''; let res = !pragma.includes('no-cache') && (await Cache.lookup(req)); if (res) return res; - let { pathname } = new URL(req.url); + let { pathname, search } = new URL(req.url); try { pathname = decodeURIComponent(pathname); } catch { @@ -23,19 +65,29 @@ const worker = { const stripped_pathname = pathname.replace(/\/$/, ''); - // prerendered pages and /static files + // files in /static, the service worker, and Vite imported server assets let is_static_asset = false; - const filename = stripped_pathname.substring(1); + const filename = stripped_pathname.slice(base_path.length + 1); if (filename) { is_static_asset = - manifest.assets.has(filename) || manifest.assets.has(filename + '/index.html'); + manifest.assets.has(filename) || + manifest.assets.has(filename + '/index.html') || + filename in manifest._.server_assets || + filename + '/index.html' in manifest._.server_assets; } - const location = pathname.at(-1) === '/' ? stripped_pathname : pathname + '/'; + let location = pathname.at(-1) === '/' ? stripped_pathname : pathname + '/'; - if (is_static_asset || prerendered.has(pathname)) { + if ( + is_static_asset || + prerendered.has(pathname) || + pathname === version_file || + pathname.startsWith(immutable) + ) { res = await env.ASSETS.fetch(req); } else if (location && prerendered.has(location)) { + // trailing slash redirect for prerendered pages + if (search) location += search; res = new Response('', { status: 308, headers: { @@ -45,10 +97,17 @@ const worker = { } else { // dynamically-generated pages res = await server.respond(req, { - // @ts-ignore - platform: { env, context, caches, cf: req.cf }, + platform: { + env, + ctx, + context: ctx, // deprecated in favor of ctx + // @ts-expect-error webworker types from worktop are not compatible with Cloudflare Workers types + caches, + // @ts-expect-error the type is correct but ts is confused because platform.cf uses the type from index.ts while req.cf uses the type from index.d.ts + cf: req.cf + }, getClientAddress() { - return req.headers.get('cf-connecting-ip'); + return /** @type {string} */ (req.headers.get('cf-connecting-ip')); } }); } @@ -56,8 +115,6 @@ const worker = { // write to `Cache` only if response is not an error, // let `Cache.save` handle the Cache-Control and Vary headers pragma = res.headers.get('cache-control') || ''; - return pragma && res.status < 400 ? Cache.save(req, res, context) : res; + return pragma && res.status < 400 ? Cache.save(req, res, ctx) : res; } }; - -export default worker; diff --git a/packages/adapter-cloudflare/test/apps/pages/.gitignore b/packages/adapter-cloudflare/test/apps/pages/.gitignore new file mode 100644 index 000000000000..1bd7b63de4b6 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +node_modules +/.svelte-kit +/.wrangler \ No newline at end of file diff --git a/packages/adapter-cloudflare/test/apps/pages/package.json b/packages/adapter-cloudflare/test/apps/pages/package.json new file mode 100644 index 000000000000..0839ddd0a69f --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/package.json @@ -0,0 +1,21 @@ +{ + "name": "test-cloudflare-pages", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "wrangler pages dev .svelte-kit/cloudflare --port 8787", + "prepare": "svelte-kit sync || echo ''", + "test": "playwright test" + }, + "devDependencies": { + "@sveltejs/kit": "workspace:^", + "@sveltejs/vite-plugin-svelte": "catalog:", + "server-side-dep": "file:server-side-dep", + "svelte": "catalog:", + "vite": "catalog:", + "wrangler": "catalog:" + }, + "type": "module" +} diff --git a/packages/adapter-cloudflare/test/apps/pages/playwright.config.js b/packages/adapter-cloudflare/test/apps/pages/playwright.config.js new file mode 100644 index 000000000000..33d36b651014 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/playwright.config.js @@ -0,0 +1 @@ +export { config as default } from '../../utils.js'; diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts new file mode 100644 index 000000000000..0e1188e04a67 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.d.ts @@ -0,0 +1 @@ +export function sum(a: number, b: number): number; diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js new file mode 100644 index 000000000000..568b90577d43 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/index.js @@ -0,0 +1,4 @@ +/** @type {import('./index.js').sum} */ +export function sum(a, b) { + return a + b; +} diff --git a/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json new file mode 100644 index 000000000000..5b26c9d1855a --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/server-side-dep/package.json @@ -0,0 +1,11 @@ +{ + "name": "server-side-dep", + "version": "0.0.1", + "type": "module", + "exports": { + ".": { + "default": "./index.js", + "types": "./index.d.ts" + } + } +} diff --git a/packages/adapter-cloudflare/test/apps/pages/src/app.html b/packages/adapter-cloudflare/test/apps/pages/src/app.html new file mode 100644 index 000000000000..d533c5e31716 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/src/app.html @@ -0,0 +1,11 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + %sveltekit.head% + </head> + <body> + <div>%sveltekit.body%</div> + </body> +</html> diff --git a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js new file mode 100644 index 000000000000..0eeb7d398ffd --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.server.js @@ -0,0 +1,8 @@ +// this tests that Wrangler can correctly resolve and bundle server-side dependencies +import { sum } from 'server-side-dep'; + +export function load() { + return { + sum: sum(1, 2) + }; +} diff --git a/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte new file mode 100644 index 000000000000..d5e339683387 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/src/routes/+page.svelte @@ -0,0 +1,5 @@ +<script> + export let data; +</script> + +<h1>Sum: {data.sum}</h1> diff --git a/packages/adapter-cloudflare/test/apps/pages/svelte.config.js b/packages/adapter-cloudflare/test/apps/pages/svelte.config.js new file mode 100644 index 000000000000..20cd2b3ff5b8 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/svelte.config.js @@ -0,0 +1,10 @@ +import adapter from '../../../index.js'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter() + } +}; + +export default config; diff --git a/packages/adapter-cloudflare/test/apps/pages/test/test.js b/packages/adapter-cloudflare/test/apps/pages/test/test.js new file mode 100644 index 000000000000..0619c030bbc2 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/test/test.js @@ -0,0 +1,6 @@ +import { expect, test } from '@playwright/test'; + +test('worker', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('h1')).toContainText('Sum: 3'); +}); diff --git a/packages/adapter-cloudflare/test/apps/pages/tsconfig.json b/packages/adapter-cloudflare/test/apps/pages/tsconfig.json new file mode 100644 index 000000000000..34380ebc986e --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + }, + "extends": "./.svelte-kit/tsconfig.json" +} diff --git a/packages/adapter-cloudflare/test/apps/pages/vite.config.js b/packages/adapter-cloudflare/test/apps/pages/vite.config.js new file mode 100644 index 000000000000..29ad08debe6a --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/pages/vite.config.js @@ -0,0 +1,11 @@ +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + build: { + minify: false + }, + plugins: [sveltekit()] +}; + +export default config; diff --git a/packages/adapter-cloudflare/test/apps/workers/.gitignore b/packages/adapter-cloudflare/test/apps/workers/.gitignore new file mode 100644 index 000000000000..f9ee23cc8621 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +node_modules +/.svelte-kit +/.wrangler +/dist \ No newline at end of file diff --git a/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc new file mode 100644 index 000000000000..0bf8a9db8c3a --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/config/wrangler.jsonc @@ -0,0 +1,10 @@ +// we've moved the wrangler config away from the root of the project +// to test that the adapter still resolves the paths correctly +{ + "$schema": "../node_modules/wrangler/config-schema.json", + "main": "../dist/index.js", + "assets": { + "directory": "../dist/public", + "binding": "ASSETS" + } +} diff --git a/packages/adapter-cloudflare/test/apps/workers/package.json b/packages/adapter-cloudflare/test/apps/workers/package.json new file mode 100644 index 000000000000..4032805d1ec6 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/package.json @@ -0,0 +1,23 @@ +{ + "name": "test-cloudflare-workers", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "wrangler dev dist/index.js", + "prepare": "svelte-kit sync || echo ''", + "test:dev": "DEV=true playwright test", + "test:build": "playwright test", + "test": "pnpm test:dev && pnpm test:build" + }, + "devDependencies": { + "@sveltejs/kit": "workspace:^", + "@sveltejs/vite-plugin-svelte": "catalog:", + "server-side-dep": "file:server-side-dep", + "svelte": "catalog:", + "vite": "catalog:", + "wrangler": "catalog:" + }, + "type": "module" +} diff --git a/packages/adapter-cloudflare/test/apps/workers/playwright.config.js b/packages/adapter-cloudflare/test/apps/workers/playwright.config.js new file mode 100644 index 000000000000..33d36b651014 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/playwright.config.js @@ -0,0 +1 @@ +export { config as default } from '../../utils.js'; diff --git a/packages/adapter-cloudflare/test/apps/workers/server-side-dep/index.d.ts b/packages/adapter-cloudflare/test/apps/workers/server-side-dep/index.d.ts new file mode 100644 index 000000000000..0e1188e04a67 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/server-side-dep/index.d.ts @@ -0,0 +1 @@ +export function sum(a: number, b: number): number; diff --git a/packages/adapter-cloudflare/test/apps/workers/server-side-dep/index.js b/packages/adapter-cloudflare/test/apps/workers/server-side-dep/index.js new file mode 100644 index 000000000000..568b90577d43 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/server-side-dep/index.js @@ -0,0 +1,4 @@ +/** @type {import('./index.js').sum} */ +export function sum(a, b) { + return a + b; +} diff --git a/packages/adapter-cloudflare/test/apps/workers/server-side-dep/package.json b/packages/adapter-cloudflare/test/apps/workers/server-side-dep/package.json new file mode 100644 index 000000000000..5b26c9d1855a --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/server-side-dep/package.json @@ -0,0 +1,11 @@ +{ + "name": "server-side-dep", + "version": "0.0.1", + "type": "module", + "exports": { + ".": { + "default": "./index.js", + "types": "./index.d.ts" + } + } +} diff --git a/packages/adapter-cloudflare/test/apps/workers/src/app.d.ts b/packages/adapter-cloudflare/test/apps/workers/src/app.d.ts new file mode 100644 index 000000000000..101a9db0b8bf --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/app.d.ts @@ -0,0 +1,3 @@ +// TODO: remove this in 3.0 once svelte.config.js is included by the generated tsconfig.json +// this ensures we get the ambient types from the adapter +import '../../../../index.js'; diff --git a/packages/adapter-cloudflare/test/apps/workers/src/app.html b/packages/adapter-cloudflare/test/apps/workers/src/app.html new file mode 100644 index 000000000000..d533c5e31716 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/app.html @@ -0,0 +1,11 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + %sveltekit.head% + </head> + <body> + <div>%sveltekit.body%</div> + </body> +</html> diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/+page.server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/+page.server.js new file mode 100644 index 000000000000..0eeb7d398ffd --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/+page.server.js @@ -0,0 +1,8 @@ +// this tests that Wrangler can correctly resolve and bundle server-side dependencies +import { sum } from 'server-side-dep'; + +export function load() { + return { + sum: sum(1, 2) + }; +} diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/+page.svelte b/packages/adapter-cloudflare/test/apps/workers/src/routes/+page.svelte new file mode 100644 index 000000000000..d5e339683387 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/+page.svelte @@ -0,0 +1,5 @@ +<script> + export let data; +</script> + +<h1>Sum: {data.sum}</h1> diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js new file mode 100644 index 000000000000..e26ae2cbbd71 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/ctx/+server.js @@ -0,0 +1,3 @@ +export function GET({ platform }) { + return new Response(platform?.ctx.waitUntil ? 'ctx works' : 'ctx does not work'); +} diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js b/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js new file mode 100644 index 000000000000..47717c4e0511 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/read/+server.js @@ -0,0 +1,6 @@ +import { read } from '$app/server'; +import file from './file.txt?url'; + +export function GET() { + return read(file); +} diff --git a/packages/adapter-cloudflare/test/apps/workers/src/routes/read/file.txt b/packages/adapter-cloudflare/test/apps/workers/src/routes/read/file.txt new file mode 100644 index 000000000000..7704b735d2c5 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/src/routes/read/file.txt @@ -0,0 +1 @@ +Hello! This file is read by `read` from `$app/server`. diff --git a/packages/adapter-cloudflare/test/apps/workers/svelte.config.js b/packages/adapter-cloudflare/test/apps/workers/svelte.config.js new file mode 100644 index 000000000000..26cd6a965908 --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/svelte.config.js @@ -0,0 +1,12 @@ +import adapter from '../../../index.js'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter({ + config: 'config/wrangler.jsonc' + }) + } +}; + +export default config; diff --git a/packages/adapter-cloudflare/test/apps/workers/test/test.js b/packages/adapter-cloudflare/test/apps/workers/test/test.js new file mode 100644 index 000000000000..40072953a16f --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/test/test.js @@ -0,0 +1,22 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { expect, test } from '@playwright/test'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +test('worker', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('h1')).toContainText('Sum: 3'); +}); + +test('ctx', async ({ request }) => { + const res = await request.get('/ctx'); + expect(await res.text()).toBe('ctx works'); +}); + +test('read from $app/server works', async ({ request }) => { + const content = fs.readFileSync(path.resolve(__dirname, '../src/routes/read/file.txt'), 'utf-8'); + const response = await request.get('/read'); + expect(await response.text()).toBe(content); +}); diff --git a/packages/adapter-cloudflare/test/apps/workers/tsconfig.json b/packages/adapter-cloudflare/test/apps/workers/tsconfig.json new file mode 100644 index 000000000000..34380ebc986e --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + }, + "extends": "./.svelte-kit/tsconfig.json" +} diff --git a/packages/adapter-cloudflare/test/apps/workers/vite.config.js b/packages/adapter-cloudflare/test/apps/workers/vite.config.js new file mode 100644 index 000000000000..29ad08debe6a --- /dev/null +++ b/packages/adapter-cloudflare/test/apps/workers/vite.config.js @@ -0,0 +1,11 @@ +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + build: { + minify: false + }, + plugins: [sveltekit()] +}; + +export default config; diff --git a/packages/adapter-cloudflare/test/utils.js b/packages/adapter-cloudflare/test/utils.js new file mode 100644 index 000000000000..3b15f891cf0a --- /dev/null +++ b/packages/adapter-cloudflare/test/utils.js @@ -0,0 +1,28 @@ +import { devices } from '@playwright/test'; +import process from 'node:process'; + +/** @type {import('@playwright/test').PlaywrightTestConfig} */ +export const config = { + forbidOnly: !!process.env.CI, + // generous timeouts on CI + timeout: process.env.CI ? 45000 : 15000, + webServer: { + command: process.env.DEV ? 'pnpm dev' : 'pnpm build && pnpm preview', + port: process.env.DEV ? 5173 : 8787 + }, + retries: process.env.CI ? 2 : 0, + projects: [ + { + name: 'chromium' + } + ], + use: { + ...devices['Desktop Chrome'], + screenshot: 'only-on-failure', + trace: 'retain-on-failure' + }, + workers: process.env.CI ? 2 : undefined, + reporter: 'list', + testDir: 'test', + testMatch: /(.+\.)?(test|spec)\.[jt]s/ +}; diff --git a/packages/adapter-cloudflare/tsconfig.json b/packages/adapter-cloudflare/tsconfig.json index 4a37ad69ac8c..7421129f0c56 100644 --- a/packages/adapter-cloudflare/tsconfig.json +++ b/packages/adapter-cloudflare/tsconfig.json @@ -4,12 +4,24 @@ "checkJs": true, "noEmit": true, "noImplicitAny": true, - "module": "es2020", - "moduleResolution": "node", + "strictNullChecks": true, + "target": "es2022", + "module": "node16", + "moduleResolution": "node16", "baseUrl": ".", "paths": { "@sveltejs/kit": ["../kit/types/index"] - } + }, + // taken from the Cloudflare Workers TypeScript template https://github.com/cloudflare/workers-sdk/blob/main/packages/create-cloudflare/templates/hello-world/ts/tsconfig.json + "lib": ["es2021"], + "types": ["@cloudflare/workers-types"] }, - "include": ["index.js", "placeholders.ts", "src/worker.js"] + "include": [ + "index.js", + "utils.js", + "utils.spec.js", + "test/utils.js", + "internal.d.ts", + "src/worker.js" + ] } diff --git a/packages/adapter-cloudflare/utils.js b/packages/adapter-cloudflare/utils.js new file mode 100644 index 000000000000..b6cd04e53cd8 --- /dev/null +++ b/packages/adapter-cloudflare/utils.js @@ -0,0 +1,51 @@ +import process from 'node:process'; + +/** + * @param {import('wrangler').Unstable_Config} wrangler_config + * @returns {boolean} + */ +export function is_building_for_cloudflare_pages(wrangler_config) { + if (process.env.CF_PAGES || wrangler_config.pages_build_output_dir) { + return true; + } + + if (wrangler_config.main || wrangler_config.assets) { + return false; + } + + return true; +} + +/** + * @param {import('wrangler').Unstable_Config} wrangler_config + */ +export function validate_worker_settings(wrangler_config) { + // we don't support workers sites + if (wrangler_config.site) { + throw new Error( + `You must remove all \`site\` keys in ${wrangler_config.configPath}. Consult https://svelte.dev/docs/kit/adapter-cloudflare#Migrating-from-Workers-Sites` + ); + } + + // we need the `assets.directory` key so that the static assets are deployed + if ((wrangler_config.main || wrangler_config.assets) && !wrangler_config.assets?.directory) { + throw new Error( + `You must specify the \`assets.directory\` key in ${wrangler_config.configPath}. Consult https://developers.cloudflare.com/workers/static-assets/binding/#directory` + ); + } + + // we need the `assets.binding` key so that the Worker can access the static assets + if (wrangler_config.main && !wrangler_config.assets?.binding) { + throw new Error( + `You must specify the \`assets.binding\` key in ${wrangler_config.configPath} before deploying your Worker. Consult https://developers.cloudflare.com/workers/static-assets/binding/#binding` + ); + } + + // the user might have forgot the `main` key or should remove the `assets.binding` + // key to deploy static assets without a Worker + if (!wrangler_config.main && wrangler_config.assets?.binding) { + throw new Error( + `You must set the \`main\` key in ${wrangler_config.configPath} if you want to deploy a Worker alongside your static assets or remove the \`assets.binding\` key if you only want to deploy static assets.` + ); + } +} diff --git a/packages/adapter-cloudflare/utils.spec.js b/packages/adapter-cloudflare/utils.spec.js new file mode 100644 index 000000000000..2652bb4f2a91 --- /dev/null +++ b/packages/adapter-cloudflare/utils.spec.js @@ -0,0 +1,144 @@ +import { describe, test, vi, expect } from 'vitest'; +import { is_building_for_cloudflare_pages, validate_worker_settings } from './utils.js'; + +describe('detects Cloudflare Pages project', () => { + test('by default', () => { + expect( + is_building_for_cloudflare_pages(/** @type {import('wrangler').Unstable_Config} */ ({})) + ).toBe(true); + }); + + test('CF_PAGES environment variable', () => { + vi.stubEnv('CF_PAGES', '1'); + const result = is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({}) + ); + vi.unstubAllEnvs(); + expect(result).toBe(true); + }); + + test('empty Wrangler configuration file', () => { + expect( + is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc' + }) + ) + ).toBe(true); + }); + + test('pages_build_output_dir config key', () => { + expect( + is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + pages_build_output_dir: 'dist' + }) + ) + ).toBe(true); + }); +}); + +describe('detects Cloudflare Workers project', () => { + test('main config key', () => { + expect( + is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + main: 'dist/index.js' + }) + ) + ).toBe(false); + }); + + test('assets config key', () => { + expect( + is_building_for_cloudflare_pages( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + assets: { + directory: 'dist/assets' + } + }) + ) + ).toBe(false); + }); +}); + +describe('validates Wrangler config', () => { + test('Worker and static assets', () => { + expect(() => + validate_worker_settings( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + main: 'dist/index.js', + assets: { + directory: 'dist/assets', + binding: 'ASSETS' + } + }) + ) + ).not.toThrow(); + }); + + test('static assets only', () => { + expect(() => + validate_worker_settings( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + assets: { + directory: 'dist/assets' + } + }) + ) + ).not.toThrow(); + }); + + test('missing `assets.directory` key', () => { + expect(() => + validate_worker_settings( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + main: 'dist/index.js', + assets: { + binding: 'ASSETS' + } + }) + ) + ).toThrow( + `You must specify the \`assets.directory\` key in wrangler.jsonc. Consult https://developers.cloudflare.com/workers/static-assets/binding/#directory` + ); + }); + + test('missing `assets.binding` key', () => { + expect(() => + validate_worker_settings( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + main: 'dist/index.js', + assets: { + directory: 'dist/assets' + } + }) + ) + ).toThrow( + `You must specify the \`assets.binding\` key in wrangler.jsonc before deploying your Worker. Consult https://developers.cloudflare.com/workers/static-assets/binding/#binding` + ); + }); + + test('missing `main` key or should remove `assets.binding` key', () => { + expect(() => + validate_worker_settings( + /** @type {import('wrangler').Unstable_Config} */ ({ + configPath: 'wrangler.jsonc', + assets: { + directory: 'dist/assets', + binding: 'ASSETS' + } + }) + ) + ).toThrow( + `You must set the \`main\` key in wrangler.jsonc if you want to deploy a Worker alongside your static assets or remove the \`assets.binding\` key if you only want to deploy static assets.` + ); + }); +}); diff --git a/packages/adapter-netlify/CHANGELOG.md b/packages/adapter-netlify/CHANGELOG.md index b6f7b7bfe41d..e8db08b78174 100644 --- a/packages/adapter-netlify/CHANGELOG.md +++ b/packages/adapter-netlify/CHANGELOG.md @@ -1,5 +1,250 @@ # @sveltejs/adapter-netlify +## 5.2.4 +### Patch Changes + + +- chore: update "homepage" field in package.json ([#14579](https://github.com/sveltejs/kit/pull/14579)) + +## 5.2.3 +### Patch Changes + + +- fix: improve the error message when `read(...)` fails from an edge function ([#14306](https://github.com/sveltejs/kit/pull/14306)) + +## 5.2.2 +### Patch Changes + + +- fix: include remote functions when deploying to Netlify edge functions ([#14214](https://github.com/sveltejs/kit/pull/14214)) + +- Updated dependencies [[`c8f7ac3`](https://github.com/sveltejs/kit/commit/c8f7ac38e9ae1763e7880a29b7f7df01df964a6d), [`107f767`](https://github.com/sveltejs/kit/commit/107f767e3a1a54187527defb29dce753c4f5fa3f)]: + - @sveltejs/kit@2.33.1 + +## 5.2.1 +### Patch Changes + + +- fix: avoid erroring on builder properties that only exist on the latest version of SvelteKit ([#14233](https://github.com/sveltejs/kit/pull/14233)) + +- Updated dependencies [[`f2db41c`](https://github.com/sveltejs/kit/commit/f2db41c0d3a0aefbb080ab6a9aa5822b3e41625c)]: + - @sveltejs/kit@2.31.1 + +## 5.2.0 +### Minor Changes + + +- feat: add `instrumentation.server.ts` for tracing and observability setup ([#13899](https://github.com/sveltejs/kit/pull/13899)) + + +### Patch Changes + +- Updated dependencies [[`f635678`](https://github.com/sveltejs/kit/commit/f63567812505597b1edc3e01010eca622b03b126), [`f635678`](https://github.com/sveltejs/kit/commit/f63567812505597b1edc3e01010eca622b03b126)]: + - @sveltejs/kit@2.31.0 + +## 5.1.1 +### Patch Changes + + +- chore: add `.git` to the end of `package.json` repository url ([#14134](https://github.com/sveltejs/kit/pull/14134)) + +- Updated dependencies [[`c968aef`](https://github.com/sveltejs/kit/commit/c968aef5727f978244d5160657b4a7ac651384ae)]: + - @sveltejs/kit@2.27.3 + +## 5.1.0 +### Minor Changes + + +- feat: add support for `read` imported from `$app/server` in edge functions ([#13859](https://github.com/sveltejs/kit/pull/13859)) + + +### Patch Changes + +- Updated dependencies [[`e5ce8bb`](https://github.com/sveltejs/kit/commit/e5ce8bb42ea020b88bd0a4ff18dc600745657541), [`cf88369`](https://github.com/sveltejs/kit/commit/cf883692fa0e163cff6b1a2f9b17a568af14124d)]: + - @sveltejs/kit@2.25.0 + +## 5.0.2 +### Patch Changes + + +- chore(deps): upgrade to esbuild 0.25.4 ([#13770](https://github.com/sveltejs/kit/pull/13770)) + +## 5.0.1 +### Patch Changes + + +- chore(deps): upgrade esbuild to 0.25.2 ([#13716](https://github.com/sveltejs/kit/pull/13716)) + +- Updated dependencies [[`c51fb554416e0c4a21655c1d79e834f69743d1d5`](https://github.com/sveltejs/kit/commit/c51fb554416e0c4a21655c1d79e834f69743d1d5)]: + - @sveltejs/kit@2.20.8 + +## 5.0.0 +### Major Changes + + +- fix: error if the `_headers` and `_redirects` files are in the `/static` directory instead of the project root ([#13227](https://github.com/sveltejs/kit/pull/13227)) + +## 4.4.2 +### Patch Changes + + +- fix: correctly import manifest on Windows machines ([#13495](https://github.com/sveltejs/kit/pull/13495)) + +- Updated dependencies [[`28cf64589a331ea4770c0883216c5e16d1de7496`](https://github.com/sveltejs/kit/commit/28cf64589a331ea4770c0883216c5e16d1de7496), [`ef1e8047225e7a79c6d121d8ed1a571e5ea44f08`](https://github.com/sveltejs/kit/commit/ef1e8047225e7a79c6d121d8ed1a571e5ea44f08), [`2e6527b92875976b79cc00e7b75ee0ad8b69a239`](https://github.com/sveltejs/kit/commit/2e6527b92875976b79cc00e7b75ee0ad8b69a239), [`0c0172e1463218fe63a67c587173bb3065a53c49`](https://github.com/sveltejs/kit/commit/0c0172e1463218fe63a67c587173bb3065a53c49)]: + - @sveltejs/kit@2.17.3 + +## 4.4.1 +### Patch Changes + + +- fix: avoid unnecessary Netlify edge function invocations for static files, which resolves a conflict between Netlify Edge Functions and Netlify Identity ([#12052](https://github.com/sveltejs/kit/pull/12052)) + +## 4.4.0 +### Minor Changes + + +- chore: upgrade esbuild to 0.24.0 ([#12270](https://github.com/sveltejs/kit/pull/12270)) + + +### Patch Changes + +- Updated dependencies [[`d030f4bb285e70844d09b3f0c87809bae43014b8`](https://github.com/sveltejs/kit/commit/d030f4bb285e70844d09b3f0c87809bae43014b8), [`67dd214863cbc5852eb0e8512efbb7bad5358e8a`](https://github.com/sveltejs/kit/commit/67dd214863cbc5852eb0e8512efbb7bad5358e8a)]: + - @sveltejs/kit@2.9.0 + +## 4.3.6 +### Patch Changes + + +- docs: update URLs for new svelte.dev site ([#12857](https://github.com/sveltejs/kit/pull/12857)) + +- Updated dependencies [[`dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46`](https://github.com/sveltejs/kit/commit/dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46), [`4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d`](https://github.com/sveltejs/kit/commit/4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d), [`3a9b78f04786898ca93f6d4b75ab18d26bc45192`](https://github.com/sveltejs/kit/commit/3a9b78f04786898ca93f6d4b75ab18d26bc45192), [`723eb8b31e6a22c82f730c30e485386c8676b746`](https://github.com/sveltejs/kit/commit/723eb8b31e6a22c82f730c30e485386c8676b746), [`8ec471c875345b751344e67580ff1b772ef2735b`](https://github.com/sveltejs/kit/commit/8ec471c875345b751344e67580ff1b772ef2735b)]: + - @sveltejs/kit@2.7.3 + +## 4.3.5 +### Patch Changes + + +- fix: correctly handle relative paths when fetching assets on the server ([#12113](https://github.com/sveltejs/kit/pull/12113)) + +- Updated dependencies [[`df48fc6ede3859beabaae9fd7c6f722215bb8a42`](https://github.com/sveltejs/kit/commit/df48fc6ede3859beabaae9fd7c6f722215bb8a42), [`5780deba8e3ebd0e2b0abea029068ad0c6daf6ad`](https://github.com/sveltejs/kit/commit/5780deba8e3ebd0e2b0abea029068ad0c6daf6ad), [`6f9aefdb8699fc126d76a88471602cb9a80822eb`](https://github.com/sveltejs/kit/commit/6f9aefdb8699fc126d76a88471602cb9a80822eb), [`8aa95b4b3431e79f62f580abdcdcb157b4de86cd`](https://github.com/sveltejs/kit/commit/8aa95b4b3431e79f62f580abdcdcb157b4de86cd)]: + - @sveltejs/kit@2.7.0 + +## 4.3.4 +### Patch Changes + + +- fix: import `node:process` instead of using globals ([#12641](https://github.com/sveltejs/kit/pull/12641)) + +- Updated dependencies [[`e798ef718f163bed4f93e1918bd8294f765376ad`](https://github.com/sveltejs/kit/commit/e798ef718f163bed4f93e1918bd8294f765376ad)]: + - @sveltejs/kit@2.5.28 + +## 4.3.3 +### Patch Changes + + +- chore: configure provenance in a simpler manner ([#12570](https://github.com/sveltejs/kit/pull/12570)) + +- Updated dependencies [[`087a43d391fc38b8c008fb39a804dc6988974101`](https://github.com/sveltejs/kit/commit/087a43d391fc38b8c008fb39a804dc6988974101)]: + - @sveltejs/kit@2.5.22 + +## 4.3.2 +### Patch Changes + + +- chore: package provenance ([#12567](https://github.com/sveltejs/kit/pull/12567)) + +- Updated dependencies [[`4930a8443caa53bcecee7b690cd28e429b1c8a20`](https://github.com/sveltejs/kit/commit/4930a8443caa53bcecee7b690cd28e429b1c8a20)]: + - @sveltejs/kit@2.5.21 + +## 4.3.1 +### Patch Changes + + +- fix: copy `.eot`, `.otf`, `.ttf`, `.woff`, and `woff2` font files when bundling ([#12439](https://github.com/sveltejs/kit/pull/12439)) + +## 4.3.0 +### Minor Changes + + +- chore(deps): upgrade to esbuild 0.21 ([#12415](https://github.com/sveltejs/kit/pull/12415)) + + +### Patch Changes + +- Updated dependencies [[`84298477a014ec471839adf7a4448d91bc7949e4`](https://github.com/sveltejs/kit/commit/84298477a014ec471839adf7a4448d91bc7949e4), [`5645614f497931f587b7cb8b3c885fce892a6a72`](https://github.com/sveltejs/kit/commit/5645614f497931f587b7cb8b3c885fce892a6a72), [`84298477a014ec471839adf7a4448d91bc7949e4`](https://github.com/sveltejs/kit/commit/84298477a014ec471839adf7a4448d91bc7949e4)]: + - @sveltejs/kit@2.5.18 + +## 4.2.1 + +### Patch Changes + +- chore: update to @rollup/plugin-commonjs@26 ([#12326](https://github.com/sveltejs/kit/pull/12326)) + +- chore: add keywords for discovery in npm search ([#12330](https://github.com/sveltejs/kit/pull/12330)) + +- Updated dependencies [[`25acb1d9fce998dccd8050b93cf4142c2b082611`](https://github.com/sveltejs/kit/commit/25acb1d9fce998dccd8050b93cf4142c2b082611), [`642c4a4aff4351b786fe6274aa2f0bf7d905faf9`](https://github.com/sveltejs/kit/commit/642c4a4aff4351b786fe6274aa2f0bf7d905faf9), [`0a0e9aa897123ebec50af08e9385b2ca4fc5bb28`](https://github.com/sveltejs/kit/commit/0a0e9aa897123ebec50af08e9385b2ca4fc5bb28)]: + - @sveltejs/kit@2.5.11 + +## 4.2.0 + +### Minor Changes + +- chore(deps): upgrade esbuild ([#12118](https://github.com/sveltejs/kit/pull/12118)) + +### Patch Changes + +- Updated dependencies [[`bbab296f6fcc05af6b999182798bcdedabbaa4c9`](https://github.com/sveltejs/kit/commit/bbab296f6fcc05af6b999182798bcdedabbaa4c9)]: + - @sveltejs/kit@2.5.6 + +## 4.1.0 + +### Minor Changes + +- feat: allow Node.js built-in modules when targeting edge functions ([#11675](https://github.com/sveltejs/kit/pull/11675)) + +### Patch Changes + +- Updated dependencies [[`36dc54ac740b8b4c6a2b904a1d0aadd8923a875c`](https://github.com/sveltejs/kit/commit/36dc54ac740b8b4c6a2b904a1d0aadd8923a875c), [`5dae3676b8cc6f8ee0def57340e6a6e591bafecd`](https://github.com/sveltejs/kit/commit/5dae3676b8cc6f8ee0def57340e6a6e591bafecd), [`ada595908b5501b8f4ac30c89c0d6314f364fde3`](https://github.com/sveltejs/kit/commit/ada595908b5501b8f4ac30c89c0d6314f364fde3), [`e228f8997840b89c6248e1c5df6f3108008a06be`](https://github.com/sveltejs/kit/commit/e228f8997840b89c6248e1c5df6f3108008a06be)]: + - @sveltejs/kit@2.4.1 + +## 4.0.0 + +### Major Changes + +- breaking: update peer dependency on `@sveltejs/kit` ([#11649](https://github.com/sveltejs/kit/pull/11649)) + +### Minor Changes + +- feat: support `read` from `$app/server` ([#11649](https://github.com/sveltejs/kit/pull/11649)) + +### Patch Changes + +- Updated dependencies [[`288f731c8a5b20cadb9e219f9583f3f16bf8c7b8`](https://github.com/sveltejs/kit/commit/288f731c8a5b20cadb9e219f9583f3f16bf8c7b8)]: + - @sveltejs/kit@2.4.0 + +## 3.0.2 + +### Patch Changes + +- chore: upgrade esbuild to 0.19.11 ([#11632](https://github.com/sveltejs/kit/pull/11632)) + +## 3.0.1 + +### Patch Changes + +- chore: update primary branch from master to main ([`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d)) + +- Updated dependencies [[`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d), [`16961e8cd3fa6a7f382153b1ff056bc2aae9b31b`](https://github.com/sveltejs/kit/commit/16961e8cd3fa6a7f382153b1ff056bc2aae9b31b), [`197e01f95652f511160f38b37b9da73a124ecd48`](https://github.com/sveltejs/kit/commit/197e01f95652f511160f38b37b9da73a124ecd48), [`102e4a5ae5b29624302163faf5a20c94a64a5b2c`](https://github.com/sveltejs/kit/commit/102e4a5ae5b29624302163faf5a20c94a64a5b2c), [`f8e3d8b9728c9f1ab63389342c31d7246b6f9db6`](https://github.com/sveltejs/kit/commit/f8e3d8b9728c9f1ab63389342c31d7246b6f9db6)]: + - @sveltejs/kit@2.0.4 + +## 3.0.0 + +### Major Changes + +- breaking: require SvelteKit 2 ([#11316](https://github.com/sveltejs/kit/pull/11316)) + +- chore: upgrade esbuild ([#11122](https://github.com/sveltejs/kit/pull/11122)) + ## 2.0.8 ### Patch Changes diff --git a/packages/adapter-netlify/README.md b/packages/adapter-netlify/README.md index a5ae3a601d8e..339311ffb2df 100644 --- a/packages/adapter-netlify/README.md +++ b/packages/adapter-netlify/README.md @@ -4,8 +4,8 @@ A SvelteKit adapter that creates a Netlify app. ## Docs -[Docs](https://kit.svelte.dev/docs/adapter-netlify) +[Docs](https://svelte.dev/docs/kit/adapter-netlify) ## Changelog -[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-netlify/CHANGELOG.md). +[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/main/packages/adapter-netlify/CHANGELOG.md). diff --git a/packages/adapter-netlify/ambient.d.ts b/packages/adapter-netlify/ambient.d.ts deleted file mode 100644 index 450140da9871..000000000000 --- a/packages/adapter-netlify/ambient.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -declare module '0SERVER' { - export { Server } from '@sveltejs/kit'; -} - -declare module 'MANIFEST' { - import { SSRManifest } from '@sveltejs/kit'; - - export const manifest: SSRManifest; - export const prerendered: Set<string>; -} diff --git a/packages/adapter-netlify/index.d.ts b/packages/adapter-netlify/index.d.ts index aef282740b2f..6bdc8db882dc 100644 --- a/packages/adapter-netlify/index.d.ts +++ b/packages/adapter-netlify/index.d.ts @@ -1,4 +1,3 @@ import { Adapter } from '@sveltejs/kit'; -import './ambient.js'; export default function plugin(opts?: { split?: boolean; edge?: boolean }): Adapter; diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js index 3307a730935e..bc130602d855 100644 --- a/packages/adapter-netlify/index.js +++ b/packages/adapter-netlify/index.js @@ -1,8 +1,14 @@ +/** @import { BuildOptions } from 'esbuild' */ import { appendFileSync, existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, join, resolve, posix } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { builtinModules } from 'node:module'; +import process from 'node:process'; import esbuild from 'esbuild'; import toml from '@iarna/toml'; +import { VERSION } from '@sveltejs/kit'; + +const [kit_major, kit_minor] = VERSION.split('.'); /** * @typedef {{ @@ -12,21 +18,17 @@ import toml from '@iarna/toml'; */ /** - * @typedef {{ - * functions: Array< - * | { - * function: string; - * path: string; - * } - * | { - * function: string; - * pattern: string; - * } - * >; - * version: 1; - * }} HandlerManifest + * @template T + * @template {keyof T} K + * @typedef {Partial<Omit<T, K>> & Required<Pick<T, K>>} PartialExcept + */ + +/** + * We use a custom `Builder` type here to support the minimum version of SvelteKit. + * @typedef {PartialExcept<import('@sveltejs/kit').Builder, 'log' | 'rimraf' | 'mkdirp' | 'config' | 'prerendered' | 'routes' | 'createEntries' | 'findServerAssets' | 'generateFallback' | 'generateEnvModule' | 'generateManifest' | 'getBuildDirectory' | 'getClientDirectory' | 'getServerDirectory' | 'getAppPath' | 'writeClient' | 'writePrerendered' | 'writePrerendered' | 'writeServer' | 'copy' | 'compress'>} Builder2_4_0 */ +const name = '@sveltejs/adapter-netlify'; const files = fileURLToPath(new URL('./files', import.meta.url).href); const edge_set_in_env_var = @@ -35,11 +37,11 @@ const edge_set_in_env_var = const FUNCTION_PREFIX = 'sveltekit-'; -/** @type {import('.').default} */ +/** @type {import('./index.js').default} */ export default function ({ split = false, edge = edge_set_in_env_var } = {}) { return { - name: '@sveltejs/adapter-netlify', - + name, + /** @param {Builder2_4_0} builder */ async adapt(builder) { if (!builder.routes) { throw new Error( @@ -48,6 +50,18 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) { ); } + if (existsSync(`${builder.config.kit.files.assets}/_headers`)) { + throw new Error( + `The _headers file should be placed in the project root rather than the ${builder.config.kit.files.assets} directory` + ); + } + + if (existsSync(`${builder.config.kit.files.assets}/_redirects`)) { + throw new Error( + `The _redirects file should be placed in the project root rather than the ${builder.config.kit.files.assets} directory` + ); + } + const netlify_config = get_netlify_config(); // "build" is the default publish directory when Netlify detects SvelteKit @@ -90,14 +104,28 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) { await generate_edge_functions({ builder }); } else { - await generate_lambda_functions({ builder, split, publish }); + generate_lambda_functions({ builder, split, publish }); } + }, + + supports: { + read: ({ route }) => { + // TODO bump peer dep in next adapter major to simplify this + if (edge && kit_major === '2' && kit_minor < '25') { + throw new Error( + `${name}: Cannot use \`read\` from \`$app/server\` in route \`${route.id}\` when using edge functions and SvelteKit < 2.25.0` + ); + } + + return true; + }, + instrumentation: () => true } }; } /** * @param { object } params - * @param {import('@sveltejs/kit').Builder} params.builder + * @param {Builder2_4_0} params.builder */ async function generate_edge_functions({ builder }) { const tmp = builder.getBuildDirectory('netlify-tmp'); @@ -106,23 +134,6 @@ async function generate_edge_functions({ builder }) { builder.mkdirp('.netlify/edge-functions'); - // Don't match the static directory - const pattern = '^/.*$'; - - // Go doesn't support lookarounds, so we can't do this - // const pattern = appDir ? `^/(?!${escapeStringRegexp(appDir)}).*$` : '^/.*$'; - - /** @type {HandlerManifest} */ - const edge_manifest = { - functions: [ - { - function: 'render', - pattern - } - ], - version: 1 - }; - builder.log.minor('Generating Edge Function...'); const relativePath = posix.relative(tmp, builder.getServerDirectory()); @@ -137,32 +148,97 @@ async function generate_edge_functions({ builder }) { relativePath }); - writeFileSync( - `${tmp}/manifest.js`, - `export const manifest = ${manifest};\n\nexport const prerendered = new Set(${JSON.stringify( - builder.prerendered.paths - )});\n` - ); + writeFileSync(`${tmp}/manifest.js`, `export const manifest = ${manifest};\n`); + + /** @type {{ assets: Set<string> }} */ + // we have to prepend the file:// protocol because Windows doesn't support absolute path imports + const { assets } = (await import(`file://${tmp}/manifest.js`)).manifest; + + const path = '/*'; + // We only need to specify paths without the trailing slash because + // Netlify will handle the optional trailing slash for us + const excluded = [ + // Contains static files + `/${builder.getAppPath()}/immutable/*`, + `/${builder.getAppPath()}/version.json`, + ...builder.prerendered.paths, + ...Array.from(assets).flatMap((asset) => { + if (asset.endsWith('/index.html')) { + const dir = asset.replace(/\/index\.html$/, ''); + return [ + `${builder.config.kit.paths.base}/${asset}`, + `${builder.config.kit.paths.base}/${dir}` + ]; + } + return `${builder.config.kit.paths.base}/${asset}`; + }), + // Should not be served by SvelteKit at all + '/.netlify/*' + ]; + + /** @type {import('@netlify/edge-functions').Manifest} */ + const edge_manifest = { + functions: [ + { + function: 'render', + path, + excludedPath: /** @type {`/${string}`[]} */ (excluded) + } + ], + version: 1 + }; - await esbuild.build({ - entryPoints: [`${tmp}/entry.js`], - outfile: '.netlify/edge-functions/render.js', + /** @type {BuildOptions} */ + const esbuild_config = { bundle: true, format: 'esm', platform: 'browser', sourcemap: 'linked', - target: 'es2020' - }); + target: 'es2020', + loader: { + '.wasm': 'copy', + '.woff': 'copy', + '.woff2': 'copy', + '.ttf': 'copy', + '.eot': 'copy', + '.otf': 'copy' + }, + // Node built-ins are allowed, but must be prefixed with `node:` + // https://docs.netlify.com/edge-functions/api/#runtime-environment + external: builtinModules.map((id) => `node:${id}`), + alias: Object.fromEntries(builtinModules.map((id) => [id, `node:${id}`])) + }; + await Promise.all([ + esbuild.build({ + entryPoints: [`${tmp}/entry.js`], + outfile: '.netlify/edge-functions/render.js', + ...esbuild_config + }), + builder.hasServerInstrumentationFile?.() && + esbuild.build({ + entryPoints: [`${builder.getServerDirectory()}/instrumentation.server.js`], + outfile: '.netlify/edge/instrumentation.server.js', + ...esbuild_config + }) + ]); + + if (builder.hasServerInstrumentationFile?.()) { + builder.instrument?.({ + entrypoint: '.netlify/edge-functions/render.js', + instrumentation: '.netlify/edge/instrumentation.server.js', + start: '.netlify/edge/start.js' + }); + } writeFileSync('.netlify/edge-functions/manifest.json', JSON.stringify(edge_manifest)); } /** * @param { object } params - * @param {import('@sveltejs/kit').Builder} params.builder + * @param {Builder2_4_0} params.builder * @param { string } params.publish * @param { boolean } params.split */ -async function generate_lambda_functions({ builder, publish, split }) { +function generate_lambda_functions({ builder, publish, split }) { builder.mkdirp('.netlify/functions-internal/.svelte-kit'); /** @type {string[]} */ @@ -173,7 +249,7 @@ async function generate_lambda_functions({ builder, publish, split }) { '0SERVER': './server/index.js' // digit prefix prevents CJS build from using this as a variable name, which would also get replaced }; - builder.copy(`${files}/esm`, '.netlify', { replace }); + builder.copy(files, '.netlify', { replace }); // Configuring the function to use ESM as the output format. const fn_config = JSON.stringify({ config: { nodeModuleFormat: 'esm' }, version: 1 }); @@ -230,6 +306,16 @@ async function generate_lambda_functions({ builder, publish, split }) { writeFileSync(`.netlify/functions-internal/${name}.mjs`, fn); writeFileSync(`.netlify/functions-internal/${name}.json`, fn_config); + if (builder.hasServerInstrumentationFile?.()) { + builder.instrument?.({ + entrypoint: `.netlify/functions-internal/${name}.mjs`, + instrumentation: '.netlify/server/instrumentation.server.js', + start: `.netlify/functions-start/${name}.start.mjs`, + module: { + exports: ['handler'] + } + }); + } const redirect = `/.netlify/functions/${name} 200`; redirects.push(`${pattern} ${redirect}`); @@ -244,6 +330,17 @@ async function generate_lambda_functions({ builder, publish, split }) { writeFileSync(`.netlify/functions-internal/${FUNCTION_PREFIX}render.json`, fn_config); writeFileSync(`.netlify/functions-internal/${FUNCTION_PREFIX}render.mjs`, fn); + if (builder.hasServerInstrumentationFile?.()) { + builder.instrument?.({ + entrypoint: `.netlify/functions-internal/${FUNCTION_PREFIX}render.mjs`, + instrumentation: '.netlify/server/instrumentation.server.js', + start: `.netlify/functions-start/${FUNCTION_PREFIX}render.start.mjs`, + module: { + exports: ['handler'] + } + }); + } + redirects.push(`* /.netlify/functions/${FUNCTION_PREFIX}render 200`); } @@ -251,12 +348,12 @@ async function generate_lambda_functions({ builder, publish, split }) { // so that generated redirects are appended to custom redirects // rather than replaced by them builder.log.minor('Writing redirects...'); - const redirect_file = join(publish, '_redirects'); + const redirects_file = join(publish, '_redirects'); if (existsSync('_redirects')) { - builder.copy('_redirects', redirect_file); + builder.copy('_redirects', redirects_file); } - builder.mkdirp(dirname(redirect_file)); - appendFileSync(redirect_file, `\n\n${redirects.join('\n')}`); + builder.mkdirp(dirname(redirects_file)); + appendFileSync(redirects_file, `\n\n${redirects.join('\n')}`); } function get_netlify_config() { @@ -271,8 +368,8 @@ function get_netlify_config() { } /** - * @param {NetlifyConfig} netlify_config - * @param {import('@sveltejs/kit').Builder} builder + * @param {NetlifyConfig | null} netlify_config + * @param {Builder2_4_0} builder **/ function get_publish_directory(netlify_config, builder) { if (netlify_config) { @@ -295,7 +392,7 @@ function get_publish_directory(netlify_config, builder) { } builder.log.warn( - 'No netlify.toml found. Using default publish directory. Consult https://kit.svelte.dev/docs/adapter-netlify#usage for more details' + 'No netlify.toml found. Using default publish directory. Consult https://svelte.dev/docs/kit/adapter-netlify#usage for more details' ); } diff --git a/packages/adapter-netlify/internal.d.ts b/packages/adapter-netlify/internal.d.ts new file mode 100644 index 000000000000..55da8ba1fbf5 --- /dev/null +++ b/packages/adapter-netlify/internal.d.ts @@ -0,0 +1,9 @@ +declare module '0SERVER' { + export { Server } from '@sveltejs/kit'; +} + +declare module 'MANIFEST' { + import { SSRManifest } from '@sveltejs/kit'; + + export const manifest: SSRManifest; +} diff --git a/packages/adapter-netlify/package.json b/packages/adapter-netlify/package.json index f690d3c931a8..0eb7b6d90bf0 100644 --- a/packages/adapter-netlify/package.json +++ b/packages/adapter-netlify/package.json @@ -1,14 +1,22 @@ { "name": "@sveltejs/adapter-netlify", - "version": "2.0.8", + "version": "5.2.4", "description": "A SvelteKit adapter that creates a Netlify app", + "keywords": [ + "adapter", + "deploy", + "hosting", + "netlify", + "svelte", + "sveltekit" + ], "repository": { "type": "git", - "url": "https://github.com/sveltejs/kit", + "url": "git+https://github.com/sveltejs/kit.git", "directory": "packages/adapter-netlify" }, "license": "MIT", - "homepage": "https://kit.svelte.dev", + "homepage": "https://svelte.dev/docs/kit/adapter-netlify", "type": "module", "exports": { ".": { @@ -24,32 +32,38 @@ "index.d.ts" ], "scripts": { - "dev": "node -e \"fs.rmSync('files', { force: true, recursive: true })\" && rollup -cw", - "build": "node -e \"fs.rmSync('files', { force: true, recursive: true })\" && rollup -c && cp src/edge.js files/edge.js", - "test": "vitest run", + "dev": "rollup -cw", + "build": "rollup -c", "check": "tsc", - "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", + "lint": "prettier --check .", "format": "pnpm lint --write", + "test": "pnpm test:unit && pnpm test:integration", + "test:unit": "vitest run", + "test:integration": "pnpm build && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test", "prepublishOnly": "pnpm build" }, "dependencies": { "@iarna/toml": "^2.2.5", - "esbuild": "^0.18.11", + "esbuild": "^0.25.4", "set-cookie-parser": "^2.6.0" }, "devDependencies": { - "@netlify/functions": "^2.0.1", - "@rollup/plugin-commonjs": "^25.0.0", - "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-node-resolve": "^15.0.1", + "@netlify/edge-functions": "catalog:", + "@netlify/functions": "catalog:", + "@netlify/node-cookies": "^0.1.0", + "@netlify/types": "^2.1.0", + "@rollup/plugin-commonjs": "catalog:", + "@rollup/plugin-json": "catalog:", + "@rollup/plugin-node-resolve": "catalog:", "@sveltejs/kit": "workspace:^", - "@types/node": "^16.18.6", - "@types/set-cookie-parser": "^2.4.2", - "rollup": "^3.7.0", - "typescript": "^4.9.4", - "vitest": "^0.34.5" + "@sveltejs/vite-plugin-svelte": "catalog:", + "@types/node": "catalog:", + "@types/set-cookie-parser": "catalog:", + "rollup": "^4.14.2", + "typescript": "^5.3.3", + "vitest": "catalog:" }, "peerDependencies": { - "@sveltejs/kit": "^1.5.0" + "@sveltejs/kit": "^2.4.0" } } diff --git a/packages/adapter-netlify/rollup.config.js b/packages/adapter-netlify/rollup.config.js index 7eca711c728c..82d61b85d29d 100644 --- a/packages/adapter-netlify/rollup.config.js +++ b/packages/adapter-netlify/rollup.config.js @@ -1,19 +1,39 @@ import { nodeResolve } from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; +import { rmSync } from 'node:fs'; + +/** + * @param {string} filepath + * @returns {import('rollup').Plugin} + */ +function clearOutput(filepath) { + return { + name: 'clear-output', + buildStart: { + order: 'pre', + sequential: true, + handler() { + rmSync(filepath, { recursive: true, force: true }); + } + } + }; +} /** @type {import('rollup').RollupOptions} */ const config = { input: { serverless: 'src/serverless.js', - shims: 'src/shims.js' + shims: 'src/shims.js', + edge: 'src/edge.js' }, output: { - dir: 'files/esm', + dir: 'files', format: 'esm' }, - plugins: [nodeResolve({ preferBuiltins: true }), commonjs(), json()], - external: (id) => id === '0SERVER' || id.startsWith('node:'), + // @ts-ignore https://github.com/rollup/plugins/issues/1329 + plugins: [clearOutput('files'), nodeResolve({ preferBuiltins: true }), commonjs(), json()], + external: (id) => id === '0SERVER' || id === 'MANIFEST' || id.startsWith('node:'), preserveEntrySignatures: 'exports-only' }; diff --git a/packages/adapter-netlify/src/edge.js b/packages/adapter-netlify/src/edge.js index 819c6047e825..f0d9944741ca 100644 --- a/packages/adapter-netlify/src/edge.js +++ b/packages/adapter-netlify/src/edge.js @@ -1,27 +1,39 @@ import { Server } from '0SERVER'; -import { manifest, prerendered } from 'MANIFEST'; +import { manifest } from 'MANIFEST'; const server = new Server(manifest); -const prefix = `/${manifest.appPath}/`; + +/** + * We don't know the origin until we receive a request, but + * that's guaranteed to happen before we call `read` + * @type {string} + */ +let origin; const initialized = server.init({ // @ts-ignore - env: Deno.env.toObject() + env: Deno.env.toObject(), + read: async (file) => { + const url = `${origin}/${file}`; + const response = await fetch(url); + + if (!response.ok) { + throw new Error( + `read(...) failed: could not fetch ${url} (${response.status} ${response.statusText})` + ); + } + + return response.body; + } }); -/** - * @param { Request } request - * @param { any } context - * @returns { Promise<Response> } - */ +/** @type {import('@netlify/edge-functions').EdgeFunction} */ export default async function handler(request, context) { - if (is_static_file(request)) { - // Static files can skip the handler - // TODO can we serve _app/immutable files with an immutable cache header? - return; + if (!origin) { + origin = new URL(request.url).origin; + await initialized; } - await initialized; return server.respond(request, { platform: { context }, getClientAddress() { @@ -29,31 +41,3 @@ export default async function handler(request, context) { } }); } - -/** - * @param {Request} request - */ -function is_static_file(request) { - const url = new URL(request.url); - - // Assets in the app dir - if (url.pathname.startsWith(prefix)) { - return true; - } - - // prerendered pages and index.html files - const pathname = url.pathname.replace(/\/$/, ''); - let file = pathname.substring(1); - - try { - file = decodeURIComponent(file); - } catch (err) { - // ignore - } - - return ( - manifest.assets.has(file) || - manifest.assets.has(file + '/index.html') || - prerendered.has(pathname || '/') - ); -} diff --git a/packages/adapter-netlify/src/serverless.js b/packages/adapter-netlify/src/serverless.js index f898796b8902..132de92b3295 100644 --- a/packages/adapter-netlify/src/serverless.js +++ b/packages/adapter-netlify/src/serverless.js @@ -1,6 +1,8 @@ import './shims'; import { Server } from '0SERVER'; -import { split_headers } from './headers'; +import { split_headers } from './headers.js'; +import { createReadableStream } from '@sveltejs/kit/node'; +import process from 'node:process'; /** * @param {import('@sveltejs/kit').SSRManifest} manifest @@ -9,8 +11,10 @@ import { split_headers } from './headers'; export function init(manifest) { const server = new Server(manifest); + /** @type {Promise<void> | null} */ let init_promise = server.init({ - env: process.env + env: /** @type {Record<string, string>} */ (process.env), + read: (file) => createReadableStream(`.netlify/server/${file}`) }); return async (event, context) => { @@ -22,7 +26,7 @@ export function init(manifest) { const response = await server.respond(to_request(event), { platform: { context }, getClientAddress() { - return event.headers['x-nf-client-connection-ip']; + return /** @type {string} */ (event.headers['x-nf-client-connection-ip']); } }); @@ -53,13 +57,11 @@ export function init(manifest) { * @param {import('@netlify/functions').HandlerEvent} event * @returns {Request} */ -function to_request(event) { - const { httpMethod, headers, rawUrl, body, isBase64Encoded } = event; - +function to_request({ httpMethod, headers, rawUrl, body, isBase64Encoded }) { /** @type {RequestInit} */ const init = { method: httpMethod, - headers: new Headers(headers) + headers: new Headers(/** @type {Record<string, string>} */ (headers)) }; if (httpMethod !== 'GET' && httpMethod !== 'HEAD') { diff --git a/packages/adapter-netlify/test/apps/basic/.gitignore b/packages/adapter-netlify/test/apps/basic/.gitignore new file mode 100644 index 000000000000..88f661c765c6 --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +node_modules +/.svelte-kit +/.netlify +/build +deno.lock \ No newline at end of file diff --git a/packages/adapter-netlify/test/apps/basic/netlify.toml b/packages/adapter-netlify/test/apps/basic/netlify.toml new file mode 100644 index 000000000000..4fa4a7e4621c --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/netlify.toml @@ -0,0 +1,2 @@ +[dev] + publish = "build" \ No newline at end of file diff --git a/packages/adapter-netlify/test/apps/basic/package.json b/packages/adapter-netlify/test/apps/basic/package.json new file mode 100644 index 000000000000..5480705dd37a --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/package.json @@ -0,0 +1,20 @@ +{ + "name": "test-netlify-basic", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "BROWSER=none netlify serve", + "prepare": "svelte-kit sync || echo ''", + "test": "playwright test" + }, + "devDependencies": { + "@sveltejs/kit": "workspace:^", + "@sveltejs/vite-plugin-svelte": "catalog:", + "netlify-cli": "catalog:", + "svelte": "catalog:", + "vite": "catalog:" + }, + "type": "module" +} diff --git a/packages/adapter-netlify/test/apps/basic/playwright.config.js b/packages/adapter-netlify/test/apps/basic/playwright.config.js new file mode 100644 index 000000000000..33d36b651014 --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/playwright.config.js @@ -0,0 +1 @@ +export { config as default } from '../../utils.js'; diff --git a/packages/adapter-netlify/test/apps/basic/src/app.html b/packages/adapter-netlify/test/apps/basic/src/app.html new file mode 100644 index 000000000000..26bc25de4e52 --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/src/app.html @@ -0,0 +1,12 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + %sveltekit.head% + </head> + + <body> + <div>%sveltekit.body%</div> + </body> +</html> diff --git a/packages/adapter-netlify/test/apps/basic/src/instrumentation.server.js b/packages/adapter-netlify/test/apps/basic/src/instrumentation.server.js new file mode 100644 index 000000000000..acc9022e1d64 --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/src/instrumentation.server.js @@ -0,0 +1 @@ +// this is just here to make sure the changes resulting from it work diff --git a/packages/adapter-netlify/test/apps/basic/src/routes/read/+server.js b/packages/adapter-netlify/test/apps/basic/src/routes/read/+server.js new file mode 100644 index 000000000000..47717c4e0511 --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/src/routes/read/+server.js @@ -0,0 +1,6 @@ +import { read } from '$app/server'; +import file from './file.txt?url'; + +export function GET() { + return read(file); +} diff --git a/packages/adapter-netlify/test/apps/basic/src/routes/read/file.txt b/packages/adapter-netlify/test/apps/basic/src/routes/read/file.txt new file mode 100644 index 000000000000..7704b735d2c5 --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/src/routes/read/file.txt @@ -0,0 +1 @@ +Hello! This file is read by `read` from `$app/server`. diff --git a/packages/adapter-netlify/test/apps/basic/svelte.config.js b/packages/adapter-netlify/test/apps/basic/svelte.config.js new file mode 100644 index 000000000000..050579db13ba --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/svelte.config.js @@ -0,0 +1,15 @@ +import adapter from '../../../index.js'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter(), + experimental: { + instrumentation: { + server: true + } + } + } +}; + +export default config; diff --git a/packages/adapter-netlify/test/apps/basic/test/test.js b/packages/adapter-netlify/test/apps/basic/test/test.js new file mode 100644 index 000000000000..50329932eb04 --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/test/test.js @@ -0,0 +1,12 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { expect, test } from '@playwright/test'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +test('read from $app/server works', async ({ request }) => { + const content = fs.readFileSync(path.resolve(__dirname, '../src/routes/read/file.txt'), 'utf-8'); + const response = await request.get('/read'); + expect(await response.text()).toBe(content); +}); diff --git a/packages/adapter-netlify/test/apps/basic/tsconfig.json b/packages/adapter-netlify/test/apps/basic/tsconfig.json new file mode 100644 index 000000000000..34380ebc986e --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + }, + "extends": "./.svelte-kit/tsconfig.json" +} diff --git a/packages/adapter-netlify/test/apps/basic/vite.config.ts b/packages/adapter-netlify/test/apps/basic/vite.config.ts new file mode 100644 index 000000000000..29ad08debe6a --- /dev/null +++ b/packages/adapter-netlify/test/apps/basic/vite.config.ts @@ -0,0 +1,11 @@ +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + build: { + minify: false + }, + plugins: [sveltekit()] +}; + +export default config; diff --git a/packages/adapter-netlify/test/apps/edge/.gitignore b/packages/adapter-netlify/test/apps/edge/.gitignore new file mode 100644 index 000000000000..88f661c765c6 --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +node_modules +/.svelte-kit +/.netlify +/build +deno.lock \ No newline at end of file diff --git a/packages/adapter-netlify/test/apps/edge/netlify.toml b/packages/adapter-netlify/test/apps/edge/netlify.toml new file mode 100644 index 000000000000..4fa4a7e4621c --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/netlify.toml @@ -0,0 +1,2 @@ +[dev] + publish = "build" \ No newline at end of file diff --git a/packages/adapter-netlify/test/apps/edge/package.json b/packages/adapter-netlify/test/apps/edge/package.json new file mode 100644 index 000000000000..c41bef818668 --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/package.json @@ -0,0 +1,20 @@ +{ + "name": "test-netlify-edge", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "BROWSER=none netlify serve", + "prepare": "svelte-kit sync || echo ''", + "test": "playwright test" + }, + "devDependencies": { + "@sveltejs/kit": "workspace:^", + "@sveltejs/vite-plugin-svelte": "catalog:", + "netlify-cli": "catalog:", + "svelte": "catalog:", + "vite": "catalog:" + }, + "type": "module" +} diff --git a/packages/adapter-netlify/test/apps/edge/playwright.config.js b/packages/adapter-netlify/test/apps/edge/playwright.config.js new file mode 100644 index 000000000000..33d36b651014 --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/playwright.config.js @@ -0,0 +1 @@ +export { config as default } from '../../utils.js'; diff --git a/packages/adapter-netlify/test/apps/edge/src/app.html b/packages/adapter-netlify/test/apps/edge/src/app.html new file mode 100644 index 000000000000..26bc25de4e52 --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/src/app.html @@ -0,0 +1,12 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + %sveltekit.head% + </head> + + <body> + <div>%sveltekit.body%</div> + </body> +</html> diff --git a/packages/adapter-netlify/test/apps/edge/src/routes/read/+server.js b/packages/adapter-netlify/test/apps/edge/src/routes/read/+server.js new file mode 100644 index 000000000000..47717c4e0511 --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/src/routes/read/+server.js @@ -0,0 +1,6 @@ +import { read } from '$app/server'; +import file from './file.txt?url'; + +export function GET() { + return read(file); +} diff --git a/packages/adapter-netlify/test/apps/edge/src/routes/read/file.txt b/packages/adapter-netlify/test/apps/edge/src/routes/read/file.txt new file mode 100644 index 000000000000..7704b735d2c5 --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/src/routes/read/file.txt @@ -0,0 +1 @@ +Hello! This file is read by `read` from `$app/server`. diff --git a/packages/adapter-netlify/test/apps/edge/svelte.config.js b/packages/adapter-netlify/test/apps/edge/svelte.config.js new file mode 100644 index 000000000000..c633196b8ea8 --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/svelte.config.js @@ -0,0 +1,12 @@ +import adapter from '../../../index.js'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + adapter: adapter({ + edge: true + }) + } +}; + +export default config; diff --git a/packages/adapter-netlify/test/apps/edge/test/test.js b/packages/adapter-netlify/test/apps/edge/test/test.js new file mode 100644 index 000000000000..50329932eb04 --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/test/test.js @@ -0,0 +1,12 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { expect, test } from '@playwright/test'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +test('read from $app/server works', async ({ request }) => { + const content = fs.readFileSync(path.resolve(__dirname, '../src/routes/read/file.txt'), 'utf-8'); + const response = await request.get('/read'); + expect(await response.text()).toBe(content); +}); diff --git a/packages/adapter-netlify/test/apps/edge/tsconfig.json b/packages/adapter-netlify/test/apps/edge/tsconfig.json new file mode 100644 index 000000000000..34380ebc986e --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + }, + "extends": "./.svelte-kit/tsconfig.json" +} diff --git a/packages/adapter-netlify/test/apps/edge/vite.config.ts b/packages/adapter-netlify/test/apps/edge/vite.config.ts new file mode 100644 index 000000000000..29ad08debe6a --- /dev/null +++ b/packages/adapter-netlify/test/apps/edge/vite.config.ts @@ -0,0 +1,11 @@ +import { sveltekit } from '@sveltejs/kit/vite'; + +/** @type {import('vite').UserConfig} */ +const config = { + build: { + minify: false + }, + plugins: [sveltekit()] +}; + +export default config; diff --git a/packages/adapter-netlify/test/utils.js b/packages/adapter-netlify/test/utils.js new file mode 100644 index 000000000000..368ee6818e98 --- /dev/null +++ b/packages/adapter-netlify/test/utils.js @@ -0,0 +1,28 @@ +import { devices } from '@playwright/test'; +import process from 'node:process'; + +/** @type {import('@playwright/test').PlaywrightTestConfig} */ +export const config = { + forbidOnly: !!process.env.CI, + // generous timeouts on CI + timeout: process.env.CI ? 45000 : 15000, + webServer: { + command: 'pnpm build && pnpm preview', + port: 8888 + }, + retries: process.env.CI ? 2 : 0, + projects: [ + { + name: 'chromium' + } + ], + use: { + ...devices['Desktop Chrome'], + screenshot: 'only-on-failure', + trace: 'retain-on-failure' + }, + workers: process.env.CI ? 2 : undefined, + reporter: 'list', + testDir: 'test', + testMatch: /(.+\.)?(test|spec)\.[jt]s/ +}; diff --git a/packages/adapter-netlify/tsconfig.json b/packages/adapter-netlify/tsconfig.json index 0a6b886c8407..80d2a47380f3 100644 --- a/packages/adapter-netlify/tsconfig.json +++ b/packages/adapter-netlify/tsconfig.json @@ -4,14 +4,15 @@ "checkJs": true, "noEmit": true, "noImplicitAny": true, - "module": "es2022", + "strictNullChecks": true, "target": "es2022", - "moduleResolution": "node", + "module": "node16", + "moduleResolution": "node16", "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { "@sveltejs/kit": ["../kit/types/index"] } }, - "include": ["*.js", "src/**/*.js", "ambient.d.ts"] + "include": ["*.js", "src/**/*.js", "internal.d.ts", "test/utils.js"] } diff --git a/packages/adapter-node/CHANGELOG.md b/packages/adapter-node/CHANGELOG.md index 086a4c0cabb9..dd9d94ad11de 100644 --- a/packages/adapter-node/CHANGELOG.md +++ b/packages/adapter-node/CHANGELOG.md @@ -1,5 +1,359 @@ # @sveltejs/adapter-node +## 5.5.0 +### Minor Changes + + +- feat: add env vars for keepAliveTimeout and headersTimeout ([#15125](https://github.com/sveltejs/kit/pull/15125)) + +## 5.4.0 +### Minor Changes + + +- feat: add [`signal`](https://developer.mozilla.org/en-US/docs/Web/API/Request/signal) property to request ([#14715](https://github.com/sveltejs/kit/pull/14715)) + + +### Patch Changes + +- Updated dependencies [[`9c933a2`](https://github.com/sveltejs/kit/commit/9c933a29e64c04db03e9150fa4e2d74a894d3d12), [`dedda71`](https://github.com/sveltejs/kit/commit/dedda7184dc04543e2ba6ea3e7f50b3836505463)]: + - @sveltejs/kit@2.47.0 + +## 5.3.3 +### Patch Changes + + +- chore: update "homepage" field in package.json ([#14579](https://github.com/sveltejs/kit/pull/14579)) + +## 5.3.2 +### Patch Changes + + +- fix: bump bundled sirv version to 3.0.2 ([#14385](https://github.com/sveltejs/kit/pull/14385)) + +- Updated dependencies [[`e6c3171`](https://github.com/sveltejs/kit/commit/e6c317150d330413a7691c1b00dc94ce121fdb89)]: + - @sveltejs/kit@2.38.0 + +## 5.3.1 +### Patch Changes + + +- fix: avoid erroring on builder properties that only exist on the latest version of SvelteKit ([#14233](https://github.com/sveltejs/kit/pull/14233)) + +- Updated dependencies [[`f2db41c`](https://github.com/sveltejs/kit/commit/f2db41c0d3a0aefbb080ab6a9aa5822b3e41625c)]: + - @sveltejs/kit@2.31.1 + +## 5.3.0 +### Minor Changes + + +- feat: add `instrumentation.server.ts` for tracing and observability setup ([#13899](https://github.com/sveltejs/kit/pull/13899)) + + +### Patch Changes + +- Updated dependencies [[`f635678`](https://github.com/sveltejs/kit/commit/f63567812505597b1edc3e01010eca622b03b126), [`f635678`](https://github.com/sveltejs/kit/commit/f63567812505597b1edc3e01010eca622b03b126)]: + - @sveltejs/kit@2.31.0 + +## 5.2.16 +### Patch Changes + + +- fix: handling of PROTOCOL_HEADER and HOST_HEADER env vars ([#14221](https://github.com/sveltejs/kit/pull/14221)) + +- Updated dependencies [[`ea95533`](https://github.com/sveltejs/kit/commit/ea9553380a9f1b5f3b2fcf3bebd15f900c51d77c)]: + - @sveltejs/kit@2.30.1 + +## 5.2.15 +### Patch Changes + + +- fix: fallback to `host` header if header specified by `HOST_HEADER` is not in request headers ([#11154](https://github.com/sveltejs/kit/pull/11154)) + +- Updated dependencies [[`6ab60e5`](https://github.com/sveltejs/kit/commit/6ab60e509f28e13dae9e15a4af340e92ec6fcdf3), [`bfdb564`](https://github.com/sveltejs/kit/commit/bfdb5643968f201ead51d890207b9c42e400a227), [`94b30c4`](https://github.com/sveltejs/kit/commit/94b30c468ce6f6d1fa0aa985d86ef9216cff83f2)]: + - @sveltejs/kit@2.30.0 + +## 5.2.14 +### Patch Changes + + +- chore: add `.git` to the end of `package.json` repository url ([#14134](https://github.com/sveltejs/kit/pull/14134)) + +- Updated dependencies [[`c968aef`](https://github.com/sveltejs/kit/commit/c968aef5727f978244d5160657b4a7ac651384ae)]: + - @sveltejs/kit@2.27.3 + +## 5.2.13 +### Patch Changes + + +- fix: remove unnecessary static directory serving middleware ([#13953](https://github.com/sveltejs/kit/pull/13953)) + +- Updated dependencies [[`bcdaf21`](https://github.com/sveltejs/kit/commit/bcdaf215c2182524e7678a1049a5f1ccbbe71e21)]: + - @sveltejs/kit@2.22.3 + +## 5.2.12 +### Patch Changes + + +- fix: include ambient type declarations ([#12088](https://github.com/sveltejs/kit/pull/12088)) + +- Updated dependencies [[`d440c68acac67ed64eea4b9bda267e229303db7b`](https://github.com/sveltejs/kit/commit/d440c68acac67ed64eea4b9bda267e229303db7b), [`6774ebc34330b12ae8c0cae08e98b577d819fffb`](https://github.com/sveltejs/kit/commit/6774ebc34330b12ae8c0cae08e98b577d819fffb), [`777c8ef11f17d2ab48aee0f2347c051663da5826`](https://github.com/sveltejs/kit/commit/777c8ef11f17d2ab48aee0f2347c051663da5826), [`f451f6c4a3dbbc73dc86667c6ff89ab2f46ca9d2`](https://github.com/sveltejs/kit/commit/f451f6c4a3dbbc73dc86667c6ff89ab2f46ca9d2), [`34a03ff16af29e917abebb649b31eadfc40a98a0`](https://github.com/sveltejs/kit/commit/34a03ff16af29e917abebb649b31eadfc40a98a0), [`1c77e283896058084c1cb5752d9ec207987a585e`](https://github.com/sveltejs/kit/commit/1c77e283896058084c1cb5752d9ec207987a585e), [`04958cca5905aaeeff367c9e4a5ce6e90fc64779`](https://github.com/sveltejs/kit/commit/04958cca5905aaeeff367c9e4a5ce6e90fc64779), [`9dc5c0e3e01a3c07010e9996688169be68e1dde8`](https://github.com/sveltejs/kit/commit/9dc5c0e3e01a3c07010e9996688169be68e1dde8), [`00e1a7621de554054d068e4525a9e505d1c2e588`](https://github.com/sveltejs/kit/commit/00e1a7621de554054d068e4525a9e505d1c2e588), [`9fcd1e7574197fa6e7ac000a030378d877cb8837`](https://github.com/sveltejs/kit/commit/9fcd1e7574197fa6e7ac000a030378d877cb8837), [`e541a4057a00f5ab6740fb51b7f88f17776da50a`](https://github.com/sveltejs/kit/commit/e541a4057a00f5ab6740fb51b7f88f17776da50a), [`37f72fbb075b481de8263f62c77125333735f382`](https://github.com/sveltejs/kit/commit/37f72fbb075b481de8263f62c77125333735f382), [`b60707ca8e755be95c86490122aa1b792b9bd6be`](https://github.com/sveltejs/kit/commit/b60707ca8e755be95c86490122aa1b792b9bd6be), [`699f4405c752261cf46c1ad32e4dbadceaffc75b`](https://github.com/sveltejs/kit/commit/699f4405c752261cf46c1ad32e4dbadceaffc75b), [`e2a4538c48295cde06f64fb8c7f0b333fbf95496`](https://github.com/sveltejs/kit/commit/e2a4538c48295cde06f64fb8c7f0b333fbf95496), [`a91ba1f326b6e244503de9a010771d942b461dad`](https://github.com/sveltejs/kit/commit/a91ba1f326b6e244503de9a010771d942b461dad)]: + - @sveltejs/kit@2.16.0 + +## 5.2.11 +### Patch Changes + + +- chore(deps): update dependency @rollup/plugin-node-resolve to v16 ([#13164](https://github.com/sveltejs/kit/pull/13164)) + +- Updated dependencies [[`19823dd716568140011eaa86110bb8868f0e2513`](https://github.com/sveltejs/kit/commit/19823dd716568140011eaa86110bb8868f0e2513), [`5d30191d13a596bda50e27e54522c10f3f6461e3`](https://github.com/sveltejs/kit/commit/5d30191d13a596bda50e27e54522c10f3f6461e3)]: + - @sveltejs/kit@2.12.2 + +## 5.2.10 +### Patch Changes + + +- chore: append http on listening address ([#13146](https://github.com/sveltejs/kit/pull/13146)) + +- Updated dependencies [[`11a9f66922199ee5925cc71c0efc513376753754`](https://github.com/sveltejs/kit/commit/11a9f66922199ee5925cc71c0efc513376753754), [`3d9e03a8a55a539b02c76d44df4874e4642bbd17`](https://github.com/sveltejs/kit/commit/3d9e03a8a55a539b02c76d44df4874e4642bbd17)]: + - @sveltejs/kit@2.11.0 + +## 5.2.9 +### Patch Changes + + +- docs: update URLs for new svelte.dev site ([#12857](https://github.com/sveltejs/kit/pull/12857)) + +- Updated dependencies [[`dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46`](https://github.com/sveltejs/kit/commit/dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46), [`4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d`](https://github.com/sveltejs/kit/commit/4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d), [`3a9b78f04786898ca93f6d4b75ab18d26bc45192`](https://github.com/sveltejs/kit/commit/3a9b78f04786898ca93f6d4b75ab18d26bc45192), [`723eb8b31e6a22c82f730c30e485386c8676b746`](https://github.com/sveltejs/kit/commit/723eb8b31e6a22c82f730c30e485386c8676b746), [`8ec471c875345b751344e67580ff1b772ef2735b`](https://github.com/sveltejs/kit/commit/8ec471c875345b751344e67580ff1b772ef2735b)]: + - @sveltejs/kit@2.7.3 + +## 5.2.8 +### Patch Changes + + +- chore: upgrade @rollup/plugin-commonjs to 28.0.1 ([#12817](https://github.com/sveltejs/kit/pull/12817)) + +## 5.2.7 +### Patch Changes + + +- chore: upgrade to sirv 3.0 ([#12796](https://github.com/sveltejs/kit/pull/12796)) + +- Updated dependencies [[`dbc9c9486f962af7dbd9123d12d8a6829700d00f`](https://github.com/sveltejs/kit/commit/dbc9c9486f962af7dbd9123d12d8a6829700d00f), [`96642d234d1f5e961e919da725a1f6188e8aba2c`](https://github.com/sveltejs/kit/commit/96642d234d1f5e961e919da725a1f6188e8aba2c)]: + - @sveltejs/kit@2.7.1 + +## 5.2.6 +### Patch Changes + + +- fix: use `node:` imports to support Deno ([#12785](https://github.com/sveltejs/kit/pull/12785)) + +## 5.2.5 +### Patch Changes + + +- chore: upgrade rollup plugins ([#12706](https://github.com/sveltejs/kit/pull/12706)) + +- Updated dependencies [[`a233f53f28fcb4c3ea63d2faf156fba09a18456c`](https://github.com/sveltejs/kit/commit/a233f53f28fcb4c3ea63d2faf156fba09a18456c), [`5b40b04608023a3fcb6c601a5f2d36485ce07196`](https://github.com/sveltejs/kit/commit/5b40b04608023a3fcb6c601a5f2d36485ce07196)]: + - @sveltejs/kit@2.6.0 + +## 5.2.4 +### Patch Changes + + +- chore: upgrade polka to 1.0.0-next.28 ([#12693](https://github.com/sveltejs/kit/pull/12693)) + +## 5.2.3 +### Patch Changes + + +- fix: import `node:process` instead of using globals ([#12641](https://github.com/sveltejs/kit/pull/12641)) + +- Updated dependencies [[`e798ef718f163bed4f93e1918bd8294f765376ad`](https://github.com/sveltejs/kit/commit/e798ef718f163bed4f93e1918bd8294f765376ad)]: + - @sveltejs/kit@2.5.28 + +## 5.2.2 +### Patch Changes + + +- chore: configure provenance in a simpler manner ([#12570](https://github.com/sveltejs/kit/pull/12570)) + +- Updated dependencies [[`087a43d391fc38b8c008fb39a804dc6988974101`](https://github.com/sveltejs/kit/commit/087a43d391fc38b8c008fb39a804dc6988974101)]: + - @sveltejs/kit@2.5.22 + +## 5.2.1 +### Patch Changes + + +- chore: package provenance ([#12567](https://github.com/sveltejs/kit/pull/12567)) + +- Updated dependencies [[`4930a8443caa53bcecee7b690cd28e429b1c8a20`](https://github.com/sveltejs/kit/commit/4930a8443caa53bcecee7b690cd28e429b1c8a20)]: + - @sveltejs/kit@2.5.21 + +## 5.2.0 + +### Minor Changes + +- feat: add unit suffixes to the `BODY_SIZE_LIMIT` environment variable ([#11680](https://github.com/sveltejs/kit/pull/11680)) + +### Patch Changes + +- Updated dependencies [[`121836fcbf6c615fd18c79a12203613ddbe49acf`](https://github.com/sveltejs/kit/commit/121836fcbf6c615fd18c79a12203613ddbe49acf)]: + - @sveltejs/kit@2.5.17 + +## 5.1.1 + +### Patch Changes + +- fix: do not create more than one shutdown timeout ([#12359](https://github.com/sveltejs/kit/pull/12359)) + +- Updated dependencies [[`835ebf6ce9979203992cca30f434b3785afae818`](https://github.com/sveltejs/kit/commit/835ebf6ce9979203992cca30f434b3785afae818)]: + - @sveltejs/kit@2.5.16 + +## 5.1.0 + +### Minor Changes + +- feat: add shutdown event ([#12153](https://github.com/sveltejs/kit/pull/12153)) + +### Patch Changes + +- fix: close keep-alive connections as soon as possible during graceful shutdown rather than accepting new requests ([#12153](https://github.com/sveltejs/kit/pull/12153)) + +## 5.0.2 + +### Patch Changes + +- chore: update to @rollup/plugin-commonjs@26 ([#12326](https://github.com/sveltejs/kit/pull/12326)) + +- chore(deps): update dependency @rollup/plugin-commonjs to v26 ([`9aa59db67080248eb22fd227e7a491102b011c47`](https://github.com/sveltejs/kit/commit/9aa59db67080248eb22fd227e7a491102b011c47)) + +- chore: add keywords for discovery in npm search ([#12330](https://github.com/sveltejs/kit/pull/12330)) + +- Updated dependencies [[`25acb1d9fce998dccd8050b93cf4142c2b082611`](https://github.com/sveltejs/kit/commit/25acb1d9fce998dccd8050b93cf4142c2b082611), [`642c4a4aff4351b786fe6274aa2f0bf7d905faf9`](https://github.com/sveltejs/kit/commit/642c4a4aff4351b786fe6274aa2f0bf7d905faf9), [`0a0e9aa897123ebec50af08e9385b2ca4fc5bb28`](https://github.com/sveltejs/kit/commit/0a0e9aa897123ebec50af08e9385b2ca4fc5bb28)]: + - @sveltejs/kit@2.5.11 + +## 5.0.1 + +### Patch Changes + +- fix: regression preventing built application from starting ([#11960](https://github.com/sveltejs/kit/pull/11960)) + +## 5.0.0 + +### Major Changes + +- breaking: add graceful shutdown ([#11653](https://github.com/sveltejs/kit/pull/11653)) + +- breaking: change default value of `precompress` option to true to serve precompressed assets by default ([#11945](https://github.com/sveltejs/kit/pull/11945)) + +### Minor Changes + +- feat: add systemd socket activation ([#11653](https://github.com/sveltejs/kit/pull/11653)) + +### Patch Changes + +- Updated dependencies [[`873a09eac86817d1cbb53bf3f9c8842d7346d20d`](https://github.com/sveltejs/kit/commit/873a09eac86817d1cbb53bf3f9c8842d7346d20d), [`30c78f223a4f9e6c4547d087338a8b8cc477ba55`](https://github.com/sveltejs/kit/commit/30c78f223a4f9e6c4547d087338a8b8cc477ba55)]: + - @sveltejs/kit@2.5.3 + +## 4.0.1 + +### Patch Changes + +- fix: return 400 response if request construction fails ([#11713](https://github.com/sveltejs/kit/pull/11713)) + +- Updated dependencies [[`f56781fa47a0f958b228e4a51bb3cbf173854f12`](https://github.com/sveltejs/kit/commit/f56781fa47a0f958b228e4a51bb3cbf173854f12)]: + - @sveltejs/kit@2.4.3 + +## 4.0.0 + +### Major Changes + +- breaking: update peer dependency on `@sveltejs/kit` ([#11649](https://github.com/sveltejs/kit/pull/11649)) + +### Minor Changes + +- feat: support `read` from `$app/server` ([#11649](https://github.com/sveltejs/kit/pull/11649)) + +### Patch Changes + +- Updated dependencies [[`288f731c8a5b20cadb9e219f9583f3f16bf8c7b8`](https://github.com/sveltejs/kit/commit/288f731c8a5b20cadb9e219f9583f3f16bf8c7b8)]: + - @sveltejs/kit@2.4.0 + +## 3.0.3 + +### Patch Changes + +- fix: return 400 response if request construction fails + +## 3.0.2 + +### Patch Changes + +- fix: return 400 response if request construction fails + +## 3.0.1 + +### Patch Changes + +- chore: upgrade rollup to 4.9.5 ([#11632](https://github.com/sveltejs/kit/pull/11632)) + +## 3.0.0 + +### Major Changes + +- breaking: allow any numeric value for `BODY_SIZE_LIMIT`, and interpret literally. Use `Infinity` rather than `0` for unrestricted body sizes ([#11589](https://github.com/sveltejs/kit/pull/11589)) + +## 2.1.2 + +### Patch Changes + +- fix: return 400 response if request construction fails + +## 2.1.1 + +### Patch Changes + +- fix: correctly handle BODY_SIZE_LIMIT=0 ([#11574](https://github.com/sveltejs/kit/pull/11574)) + +## 2.1.0 + +### Minor Changes + +- feat: add `PORT_HEADER` env var for reverse proxies with non-standard ports ([#11249](https://github.com/sveltejs/kit/pull/11249)) + +### Patch Changes + +- Updated dependencies [[`9556abae4ba28c02ba468735beb9eb868876a9a1`](https://github.com/sveltejs/kit/commit/9556abae4ba28c02ba468735beb9eb868876a9a1), [`8468af597c6240f7a3687ef1ed3873990b944f8c`](https://github.com/sveltejs/kit/commit/8468af597c6240f7a3687ef1ed3873990b944f8c)]: + - @sveltejs/kit@2.1.1 + +## 2.0.2 + +### Patch Changes + +- fix: upgrade `sirv` and `mrmime` to modernize javascript mime type ([#11419](https://github.com/sveltejs/kit/pull/11419)) + +- Updated dependencies [[`b999cfdea24e72043505e032ed9d621590896263`](https://github.com/sveltejs/kit/commit/b999cfdea24e72043505e032ed9d621590896263), [`2a091dcbbdf7d2a70a38316638e16761bd1b9b59`](https://github.com/sveltejs/kit/commit/2a091dcbbdf7d2a70a38316638e16761bd1b9b59)]: + - @sveltejs/kit@2.0.5 + +## 2.0.1 + +### Patch Changes + +- chore: update primary branch from master to main ([`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d)) + +- Updated dependencies [[`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d), [`16961e8cd3fa6a7f382153b1ff056bc2aae9b31b`](https://github.com/sveltejs/kit/commit/16961e8cd3fa6a7f382153b1ff056bc2aae9b31b), [`197e01f95652f511160f38b37b9da73a124ecd48`](https://github.com/sveltejs/kit/commit/197e01f95652f511160f38b37b9da73a124ecd48), [`102e4a5ae5b29624302163faf5a20c94a64a5b2c`](https://github.com/sveltejs/kit/commit/102e4a5ae5b29624302163faf5a20c94a64a5b2c), [`f8e3d8b9728c9f1ab63389342c31d7246b6f9db6`](https://github.com/sveltejs/kit/commit/f8e3d8b9728c9f1ab63389342c31d7246b6f9db6)]: + - @sveltejs/kit@2.0.4 + +## 2.0.0 + +### Major Changes + +- breaking: require SvelteKit 2 peer dependency ([#11289](https://github.com/sveltejs/kit/pull/11289)) + +- breaking: remove polyfill option. fetch APIs will now always come from the platform being used. File and crypto APIs will be polyfilled if not available ([#11172](https://github.com/sveltejs/kit/pull/11172)) + +- chore: upgrade rollup ([#11122](https://github.com/sveltejs/kit/pull/11122)) + ## 1.3.1 ### Patch Changes diff --git a/packages/adapter-node/README.md b/packages/adapter-node/README.md index 804a6ee9504d..22e355fee5ed 100644 --- a/packages/adapter-node/README.md +++ b/packages/adapter-node/README.md @@ -1,14 +1,14 @@ # @sveltejs/adapter-node -[Adapter](https://kit.svelte.dev/docs/adapters) for SvelteKit apps that generates a standalone Node server. +[Adapter](https://svelte.dev/docs/kit/adapters) for SvelteKit apps that generates a standalone Node server. ## Docs -[Docs](https://kit.svelte.dev/docs/adapter-node) +[Docs](https://svelte.dev/docs/kit/adapter-node) ## Changelog -[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-node/CHANGELOG.md). +[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/main/packages/adapter-node/CHANGELOG.md). ## License diff --git a/packages/adapter-node/ambient.d.ts b/packages/adapter-node/ambient.d.ts index 7d45ea6dc64a..7fbdd5adb583 100644 --- a/packages/adapter-node/ambient.d.ts +++ b/packages/adapter-node/ambient.d.ts @@ -1,27 +1,12 @@ -declare module 'ENV' { - export function env(key: string, fallback?: any): string; -} - -declare module 'HANDLER' { - export const handler: import('polka').Middleware; -} - -declare module 'MANIFEST' { - import { SSRManifest } from '@sveltejs/kit'; - - export const manifest: SSRManifest; - export const prerendered: Set<string>; -} - -declare module 'SERVER' { - export { Server } from '@sveltejs/kit'; -} - -declare namespace App { - export interface Platform { - /** - * The original Node request object (https://nodejs.org/api/http.html#class-httpincomingmessage) - */ - req: import('http').IncomingMessage; +import http from 'node:http'; + +declare global { + namespace App { + export interface Platform { + /** + * The original Node request object (https://nodejs.org/api/http.html#class-httpincomingmessage) + */ + req: http.IncomingMessage; + } } } diff --git a/packages/adapter-node/index.d.ts b/packages/adapter-node/index.d.ts index 12ea6273dd66..d32510e25186 100644 --- a/packages/adapter-node/index.d.ts +++ b/packages/adapter-node/index.d.ts @@ -9,7 +9,6 @@ interface AdapterOptions { out?: string; precompress?: boolean; envPrefix?: string; - polyfill?: boolean; } export default function plugin(options?: AdapterOptions): Adapter; diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js index 8d5c1629a4cf..deed72068bc3 100644 --- a/packages/adapter-node/index.js +++ b/packages/adapter-node/index.js @@ -5,15 +5,26 @@ import { nodeResolve } from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; +/** + * @template T + * @template {keyof T} K + * @typedef {Partial<Omit<T, K>> & Required<Pick<T, K>>} PartialExcept + */ + +/** + * We use a custom `Builder` type here to support the minimum version of SvelteKit. + * @typedef {PartialExcept<import('@sveltejs/kit').Builder, 'log' | 'rimraf' | 'mkdirp' | 'config' | 'prerendered' | 'routes' | 'createEntries' | 'findServerAssets' | 'generateFallback' | 'generateEnvModule' | 'generateManifest' | 'getBuildDirectory' | 'getClientDirectory' | 'getServerDirectory' | 'getAppPath' | 'writeClient' | 'writePrerendered' | 'writePrerendered' | 'writeServer' | 'copy' | 'compress'>} Builder2_4_0 + */ + const files = fileURLToPath(new URL('./files', import.meta.url).href); -/** @type {import('.').default} */ +/** @type {import('./index.js').default} */ export default function (opts = {}) { - const { out = 'build', precompress, envPrefix = '', polyfill = true } = opts; + const { out = 'build', precompress = true, envPrefix = '' } = opts; return { name: '@sveltejs/adapter-node', - + /** @param {Builder2_4_0} builder */ async adapt(builder) { const tmp = builder.getBuildDirectory('adapter-node'); @@ -39,20 +50,30 @@ export default function (opts = {}) { writeFileSync( `${tmp}/manifest.js`, - `export const manifest = ${builder.generateManifest({ relativePath: './' })};\n\n` + - `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n` + [ + `export const manifest = ${builder.generateManifest({ relativePath: './' })};`, + `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});`, + `export const base = ${JSON.stringify(builder.config.kit.paths.base)};` + ].join('\n\n') ); const pkg = JSON.parse(readFileSync('package.json', 'utf8')); + /** @type {Record<string, string>} */ + const input = { + index: `${tmp}/index.js`, + manifest: `${tmp}/manifest.js` + }; + + if (builder.hasServerInstrumentationFile?.()) { + input['instrumentation.server'] = `${tmp}/instrumentation.server.js`; + } + // we bundle the Vite output so that deployments only need // their production dependencies. Anything in devDependencies // will get included in the bundled code const bundle = await rollup({ - input: { - index: `${tmp}/index.js`, - manifest: `${tmp}/manifest.js` - }, + input, external: [ // dependencies could have deep exports, so we need a regex ...Object.keys(pkg.dependencies || {}).map((d) => new RegExp(`^${d}(\\/.*)?$`)) @@ -62,7 +83,9 @@ export default function (opts = {}) { preferBuiltins: true, exportConditions: ['node'] }), + // @ts-ignore https://github.com/rollup/plugins/issues/1329 commonjs({ strictRequires: true }), + // @ts-ignore https://github.com/rollup/plugins/issues/1329 json() ] }); @@ -85,10 +108,20 @@ export default function (opts = {}) { } }); - // If polyfills aren't wanted then clear the file - if (!polyfill) { - writeFileSync(`${out}/shims.js`, '', 'utf-8'); + if (builder.hasServerInstrumentationFile?.()) { + builder.instrument?.({ + entrypoint: `${out}/index.js`, + instrumentation: `${out}/server/instrumentation.server.js`, + module: { + exports: ['path', 'host', 'port', 'server'] + } + }); } + }, + + supports: { + read: () => true, + instrumentation: () => true } }; } diff --git a/packages/adapter-node/internal.d.ts b/packages/adapter-node/internal.d.ts new file mode 100644 index 000000000000..8387855f7893 --- /dev/null +++ b/packages/adapter-node/internal.d.ts @@ -0,0 +1,20 @@ +declare module 'ENV' { + export function env(key: string, fallback?: any): string; + export function timeout_env(key: string, fallback?: any): number | undefined; +} + +declare module 'HANDLER' { + export const handler: import('polka').Middleware; +} + +declare module 'MANIFEST' { + import { SSRManifest } from '@sveltejs/kit'; + + export const base: string; + export const manifest: SSRManifest; + export const prerendered: Set<string>; +} + +declare module 'SERVER' { + export { Server } from '@sveltejs/kit'; +} diff --git a/packages/adapter-node/package.json b/packages/adapter-node/package.json index 6b13c7272a30..6f8f1cb879ad 100644 --- a/packages/adapter-node/package.json +++ b/packages/adapter-node/package.json @@ -1,14 +1,22 @@ { "name": "@sveltejs/adapter-node", - "version": "1.3.1", + "version": "5.5.0", "description": "Adapter for SvelteKit apps that generates a standalone Node server", + "keywords": [ + "adapter", + "deploy", + "hosting", + "node.js", + "svelte", + "sveltekit" + ], "repository": { "type": "git", - "url": "https://github.com/sveltejs/kit", + "url": "git+https://github.com/sveltejs/kit.git", "directory": "packages/adapter-node" }, "license": "MIT", - "homepage": "https://kit.svelte.dev", + "homepage": "https://svelte.dev/docs/kit/adapter-node", "type": "module", "exports": { ".": { @@ -21,34 +29,35 @@ "files": [ "files", "index.js", - "index.d.ts" + "index.d.ts", + "ambient.d.ts" ], "scripts": { - "dev": "node -e \"fs.rmSync('files', { force: true, recursive: true })\" && rollup -cw", - "build": "node -e \"fs.rmSync('files', { force: true, recursive: true })\" && rollup -c", - "test": "echo \"tests temporarily disabled\" # c8 vitest run", + "dev": "rollup -cw", + "build": "rollup -c", + "test": "vitest run", "check": "tsc", - "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", + "lint": "prettier --check .", "format": "pnpm lint --write", "prepublishOnly": "pnpm build" }, "devDependencies": { - "@polka/url": "^1.0.0-next.21", + "@polka/url": "catalog:", "@sveltejs/kit": "workspace:^", - "@types/node": "^16.18.6", - "c8": "^8.0.0", - "polka": "^1.0.0-next.22", - "sirv": "^2.0.3", - "typescript": "^4.9.4", - "vitest": "^0.34.5" + "@sveltejs/vite-plugin-svelte": "catalog:", + "@types/node": "catalog:", + "polka": "catalog:", + "sirv": "^3.0.2", + "typescript": "^5.3.3", + "vitest": "catalog:" }, "dependencies": { - "@rollup/plugin-commonjs": "^25.0.0", - "@rollup/plugin-json": "^6.0.0", - "@rollup/plugin-node-resolve": "^15.0.1", - "rollup": "^3.7.0" + "@rollup/plugin-commonjs": "^28.0.1", + "@rollup/plugin-json": "^6.1.0", + "@rollup/plugin-node-resolve": "^16.0.0", + "rollup": "^4.9.5" }, "peerDependencies": { - "@sveltejs/kit": "^1.0.0" + "@sveltejs/kit": "^2.4.0" } } diff --git a/packages/adapter-node/rollup.config.js b/packages/adapter-node/rollup.config.js index 16c4a20c37a7..40f8e07558aa 100644 --- a/packages/adapter-node/rollup.config.js +++ b/packages/adapter-node/rollup.config.js @@ -2,6 +2,38 @@ import { nodeResolve } from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import json from '@rollup/plugin-json'; import { builtinModules } from 'node:module'; +import { rmSync } from 'node:fs'; + +/** + * @param {string} filepath + * @returns {import('rollup').Plugin} + */ +function clearOutput(filepath) { + return { + name: 'clear-output', + buildStart: { + order: 'pre', + sequential: true, + handler() { + rmSync(filepath, { recursive: true, force: true }); + } + } + }; +} + +/** + * @returns {import('rollup').Plugin} + */ +function prefixBuiltinModules() { + return { + name: 'prefix-built-in-modules', + resolveId(source) { + if (builtinModules.includes(source)) { + return { id: 'node:' + source, external: true }; + } + } + }; +} export default [ { @@ -10,8 +42,14 @@ export default [ file: 'files/index.js', format: 'esm' }, - plugins: [nodeResolve({ preferBuiltins: true }), commonjs(), json()], - external: ['ENV', 'HANDLER', ...builtinModules] + plugins: [ + clearOutput('files/index.js'), + nodeResolve({ preferBuiltins: true }), + commonjs(), + json(), + prefixBuiltinModules() + ], + external: ['ENV', 'HANDLER'] }, { input: 'src/env.js', @@ -19,8 +57,14 @@ export default [ file: 'files/env.js', format: 'esm' }, - plugins: [nodeResolve(), commonjs(), json()], - external: ['HANDLER', ...builtinModules] + plugins: [ + clearOutput('files/env.js'), + nodeResolve(), + commonjs(), + json(), + prefixBuiltinModules() + ], + external: ['HANDLER'] }, { input: 'src/handler.js', @@ -29,8 +73,14 @@ export default [ format: 'esm', inlineDynamicImports: true }, - plugins: [nodeResolve(), commonjs(), json()], - external: ['ENV', 'MANIFEST', 'SERVER', 'SHIMS', ...builtinModules] + plugins: [ + clearOutput('files/handler.js'), + nodeResolve(), + commonjs(), + json(), + prefixBuiltinModules() + ], + external: ['ENV', 'MANIFEST', 'SERVER', 'SHIMS'] }, { input: 'src/shims.js', @@ -38,7 +88,6 @@ export default [ file: 'files/shims.js', format: 'esm' }, - plugins: [nodeResolve(), commonjs()], - external: builtinModules + plugins: [clearOutput('files/shims.js'), nodeResolve(), commonjs(), prefixBuiltinModules()] } ]; diff --git a/packages/adapter-node/src/env.js b/packages/adapter-node/src/env.js index 841bddf2c237..05251f8658b2 100644 --- a/packages/adapter-node/src/env.js +++ b/packages/adapter-node/src/env.js @@ -1,4 +1,5 @@ /* global ENV_PREFIX */ +import process from 'node:process'; const expected = new Set([ 'SOCKET_PATH', @@ -9,9 +10,16 @@ const expected = new Set([ 'ADDRESS_HEADER', 'PROTOCOL_HEADER', 'HOST_HEADER', - 'BODY_SIZE_LIMIT' + 'PORT_HEADER', + 'BODY_SIZE_LIMIT', + 'SHUTDOWN_TIMEOUT', + 'IDLE_TIMEOUT', + 'KEEP_ALIVE_TIMEOUT', + 'HEADERS_TIMEOUT' ]); +const expected_unprefixed = new Set(['LISTEN_PID', 'LISTEN_FDS']); + if (ENV_PREFIX) { for (const name in process.env) { if (name.startsWith(ENV_PREFIX)) { @@ -30,6 +38,54 @@ if (ENV_PREFIX) { * @param {any} fallback */ export function env(name, fallback) { - const prefixed = ENV_PREFIX + name; + const prefix = expected_unprefixed.has(name) ? '' : ENV_PREFIX; + const prefixed = prefix + name; return prefixed in process.env ? process.env[prefixed] : fallback; } + +const integer_regexp = /^\d+$/; + +/** + * Throw a consistently-structured parsing error for environment variables. + * @param {string} name + * @param {any} value + * @param {string} description + * @returns {never} + */ +function parsing_error(name, value, description) { + throw new Error( + `Invalid value for environment variable ${name}: ${JSON.stringify(value)} (${description})` + ); +} + +/** + * Check the environment for a timeout value (non-negative integer) in seconds. + * @param {string} name + * @param {number} [fallback] + * @returns {number | undefined} + */ +export function timeout_env(name, fallback) { + const raw = env(name, fallback); + if (!raw) { + return fallback; + } + + if (!integer_regexp.test(raw)) { + parsing_error(name, raw, 'should be a non-negative integer'); + } + + const parsed = Number.parseInt(raw, 10); + + // We don't technically need to check `Number.isNaN` because the value already passed the regexp test. + // However, just in case there's some new codepath introduced somewhere down the line, it's probably good + // to stick this in here. + if (Number.isNaN(parsed)) { + parsing_error(name, raw, 'should be a non-negative integer'); + } + + if (parsed < 0) { + parsing_error(name, raw, 'should be a non-negative integer'); + } + + return parsed; +} diff --git a/packages/adapter-node/src/handler.js b/packages/adapter-node/src/handler.js index bae1854bdd7e..b1a40bb19955 100644 --- a/packages/adapter-node/src/handler.js +++ b/packages/adapter-node/src/handler.js @@ -1,48 +1,67 @@ import 'SHIMS'; import fs from 'node:fs'; import path from 'node:path'; +import process from 'node:process'; import sirv from 'sirv'; import { fileURLToPath } from 'node:url'; import { parse as polka_url_parser } from '@polka/url'; -import { getRequest, setResponse } from '@sveltejs/kit/node'; +import { getRequest, setResponse, createReadableStream } from '@sveltejs/kit/node'; import { Server } from 'SERVER'; -import { manifest, prerendered } from 'MANIFEST'; +import { manifest, prerendered, base } from 'MANIFEST'; import { env } from 'ENV'; +import { parse_as_bytes } from '../utils.js'; /* global ENV_PREFIX */ const server = new Server(manifest); -await server.init({ env: process.env }); + const origin = env('ORIGIN', undefined); const xff_depth = parseInt(env('XFF_DEPTH', '1')); const address_header = env('ADDRESS_HEADER', '').toLowerCase(); const protocol_header = env('PROTOCOL_HEADER', '').toLowerCase(); -const host_header = env('HOST_HEADER', 'host').toLowerCase(); -const body_size_limit = parseInt(env('BODY_SIZE_LIMIT', '524288')); +const host_header = env('HOST_HEADER', '').toLowerCase(); +const port_header = env('PORT_HEADER', '').toLowerCase(); + +const body_size_limit = parse_as_bytes(env('BODY_SIZE_LIMIT', '512K')); + +if (isNaN(body_size_limit)) { + throw new Error( + `Invalid BODY_SIZE_LIMIT: '${env('BODY_SIZE_LIMIT')}'. Please provide a numeric value.` + ); +} const dir = path.dirname(fileURLToPath(import.meta.url)); +const asset_dir = `${dir}/client${base}`; + +await server.init({ + env: /** @type {Record<string, string>} */ (process.env), + read: (file) => createReadableStream(`${asset_dir}/${file}`) +}); + /** * @param {string} path * @param {boolean} client */ function serve(path, client = false) { - return ( - fs.existsSync(path) && - sirv(path, { - etag: true, - gzip: true, - brotli: true, - setHeaders: - client && - ((res, pathname) => { - // only apply to build directory, not e.g. version.json - if (pathname.startsWith(`/${manifest.appPath}/immutable/`) && res.statusCode === 200) { - res.setHeader('cache-control', 'public,max-age=31536000,immutable'); - } - }) - }) - ); + return fs.existsSync(path) + ? sirv(path, { + etag: true, + gzip: true, + brotli: true, + setHeaders: client + ? (res, pathname) => { + // only apply to build directory, not e.g. version.json + if ( + pathname.startsWith(`/${manifest.appPath}/immutable/`) && + res.statusCode === 200 + ) { + res.setHeader('cache-control', 'public,max-age=31536000,immutable'); + } + } + : undefined + }) + : undefined; } // required because the static file server ignores trailing slashes @@ -60,7 +79,7 @@ function serve_prerendered() { } if (prerendered.has(pathname)) { - return handler(req, res, next); + return handler?.(req, res, next); } // remove or add trailing slash as appropriate @@ -69,14 +88,14 @@ function serve_prerendered() { if (query) location += search; res.writeHead(308, { location }).end(); } else { - next(); + void next(); } }; } /** @type {import('polka').Middleware} */ const ssr = async (req, res) => { - /** @type {Request | undefined} */ + /** @type {Request} */ let request; try { @@ -85,13 +104,13 @@ const ssr = async (req, res) => { request: req, bodySizeLimit: body_size_limit }); - } catch (err) { - res.statusCode = err.status || 400; - res.end('Invalid request body'); + } catch { + res.statusCode = 400; + res.end('Bad Request'); return; } - setResponse( + await setResponse( res, await server.respond(request, { platform: { req }, @@ -166,15 +185,13 @@ function sequence(handlers) { */ function get_origin(headers) { const protocol = (protocol_header && headers[protocol_header]) || 'https'; - const host = headers[host_header]; - return `${protocol}://${host}`; + const host = (host_header && headers[host_header]) || headers['host']; + const port = port_header && headers[port_header]; + + return port ? `${protocol}://${host}:${port}` : `${protocol}://${host}`; } export const handler = sequence( - [ - serve(path.join(dir, 'client'), true), - serve(path.join(dir, 'static')), - serve_prerendered(), - ssr - ].filter(Boolean) + /** @type {(import('sirv').RequestHandler | import('polka').Middleware)[]} */ + ([serve(path.join(dir, 'client'), true), serve_prerendered(), ssr].filter(Boolean)) ); diff --git a/packages/adapter-node/src/index.js b/packages/adapter-node/src/index.js index ea3feee05860..a7a5c96b5184 100644 --- a/packages/adapter-node/src/index.js +++ b/packages/adapter-node/src/index.js @@ -1,15 +1,117 @@ +import http from 'node:http'; +import process from 'node:process'; import { handler } from 'HANDLER'; -import { env } from 'ENV'; +import { env, timeout_env } from 'ENV'; import polka from 'polka'; export const path = env('SOCKET_PATH', false); export const host = env('HOST', '0.0.0.0'); export const port = env('PORT', !path && '3000'); -const server = polka().use(handler); +const shutdown_timeout = parseInt(env('SHUTDOWN_TIMEOUT', '30')); +const idle_timeout = parseInt(env('IDLE_TIMEOUT', '0')); +const listen_pid = parseInt(env('LISTEN_PID', '0')); +const listen_fds = parseInt(env('LISTEN_FDS', '0')); +// https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds.html +const SD_LISTEN_FDS_START = 3; -server.listen({ path, host, port }, () => { - console.log(`Listening on ${path ? path : host + ':' + port}`); -}); +if (listen_pid !== 0 && listen_pid !== process.pid) { + throw new Error(`received LISTEN_PID ${listen_pid} but current process id is ${process.pid}`); +} +if (listen_fds > 1) { + throw new Error( + `only one socket is allowed for socket activation, but LISTEN_FDS was set to ${listen_fds}` + ); +} + +const socket_activation = listen_pid === process.pid && listen_fds === 1; + +let requests = 0; +/** @type {NodeJS.Timeout | void} */ +let shutdown_timeout_id; +/** @type {NodeJS.Timeout | void} */ +let idle_timeout_id; + +// Initialize the HTTP server here so that we can set properties before starting to listen. +// Otherwise, polka delays creating the server until listen() is called. Settings these +// properties after the server has started listening could lead to race conditions. +const httpServer = http.createServer(); + +const keep_alive_timeout = timeout_env('KEEP_ALIVE_TIMEOUT'); +if (keep_alive_timeout !== undefined) { + // Convert the keep-alive timeout from seconds to milliseconds (the unit Node.js expects). + httpServer.keepAliveTimeout = keep_alive_timeout * 1000; +} + +const headers_timeout = timeout_env('HEADERS_TIMEOUT'); +if (headers_timeout !== undefined) { + // Convert the headers timeout from seconds to milliseconds (the unit Node.js expects). + httpServer.headersTimeout = headers_timeout * 1000; +} + +const server = polka({ server: httpServer }).use(handler); + +if (socket_activation) { + server.listen({ fd: SD_LISTEN_FDS_START }, () => { + console.log(`Listening on file descriptor ${SD_LISTEN_FDS_START}`); + }); +} else { + server.listen({ path, host, port }, () => { + console.log(`Listening on ${path || `http://${host}:${port}`}`); + }); +} + +/** @param {'SIGINT' | 'SIGTERM' | 'IDLE'} reason */ +function graceful_shutdown(reason) { + if (shutdown_timeout_id) return; + + // If a connection was opened with a keep-alive header close() will wait for the connection to + // time out rather than close it even if it is not handling any requests, so call this first + httpServer.closeIdleConnections(); + + httpServer.close((error) => { + // occurs if the server is already closed + if (error) return; + + if (shutdown_timeout_id) { + clearTimeout(shutdown_timeout_id); + } + if (idle_timeout_id) { + clearTimeout(idle_timeout_id); + } + + // @ts-expect-error custom events cannot be typed + process.emit('sveltekit:shutdown', reason); + }); + + shutdown_timeout_id = setTimeout(() => httpServer.closeAllConnections(), shutdown_timeout * 1000); +} + +httpServer.on( + 'request', + /** @param {import('node:http').IncomingMessage} req */ + (req) => { + requests++; + + if (socket_activation && idle_timeout_id) { + idle_timeout_id = clearTimeout(idle_timeout_id); + } + + req.on('close', () => { + requests--; + + if (shutdown_timeout_id) { + // close connections as soon as they become idle, so they don't accept new requests + httpServer.closeIdleConnections(); + } + if (requests === 0 && socket_activation && idle_timeout) { + idle_timeout_id = setTimeout(() => graceful_shutdown('IDLE'), idle_timeout * 1000); + } + }); + } +); + +process.on('SIGTERM', graceful_shutdown); +process.on('SIGINT', graceful_shutdown); export { server }; diff --git a/packages/adapter-node/tests/env.spec.ts b/packages/adapter-node/tests/env.spec.ts new file mode 100644 index 000000000000..20fc23a6c2a6 --- /dev/null +++ b/packages/adapter-node/tests/env.spec.ts @@ -0,0 +1,52 @@ +import { afterEach, expect, test, describe, vi } from 'vitest'; +import { timeout_env } from '../src/env.js'; + +vi.hoisted(() => { + vi.stubGlobal('ENV_PREFIX', ''); +}); + +describe('timeout_env', () => { + afterEach(() => { + vi.unstubAllEnvs(); + }); + + test('parses zero correctly', () => { + vi.stubEnv('TIMEOUT', '0'); + + const timeout = timeout_env('TIMEOUT'); + expect(timeout).toBe(0); + }); + + test('parses positive integers correctly', () => { + vi.stubEnv('TIMEOUT', '60'); + + const timeout = timeout_env('TIMEOUT'); + expect(timeout).toBe(60); + }); + + test('returns the fallback when variable is not set', () => { + const timeout = timeout_env('TIMEOUT', 30); + expect(timeout).toBe(30); + }); + + test('returns undefined when variable is not set and no fallback is provided', () => { + const timeout = timeout_env('TIMEOUT'); + expect(timeout).toBeUndefined(); + }); + + test('throws an error for negative integers', () => { + vi.stubEnv('TIMEOUT', '-10'); + + expect(() => timeout_env('TIMEOUT')).toThrow( + 'Invalid value for environment variable TIMEOUT: "-10" (should be a non-negative integer)' + ); + }); + + test('throws an error for non-integer values', () => { + vi.stubEnv('TIMEOUT', 'abc'); + + expect(() => timeout_env('TIMEOUT')).toThrow( + 'Invalid value for environment variable TIMEOUT: "abc" (should be a non-negative integer)' + ); + }); +}); diff --git a/packages/adapter-node/tests/smoke.spec.js b/packages/adapter-node/tests/smoke.spec.js deleted file mode 100644 index ea7f27658cf6..000000000000 --- a/packages/adapter-node/tests/smoke.spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import { assert, test } from 'vitest'; -import { create_kit_middleware } from '../src/handler.js'; -import fetch from 'node-fetch'; -import polka from 'polka'; - -const { PORT = 3000 } = process.env; -const DEFAULT_SERVER_OPTS = { render: () => {} }; - -async function startServer(opts = DEFAULT_SERVER_OPTS) { - return new Promise((fulfil, reject) => { - const server = polka().use(create_kit_middleware(opts)); - server.listen(PORT, (err) => { - if (err) { - reject(err); - } - fulfil(server); - }); - }); -} - -test('starts a server', async () => { - const server = await startServer(); - assert.ok('server started'); - server.server.close(); -}); - -test('serves a 404', async () => { - const server = await startServer(); - const res = await fetch(`http://localhost:${PORT}/nothing`); - assert.equal(res.status, 404); - server.server.close(); -}); - -test('responses with the rendered status code', async () => { - const server = await startServer({ - render: () => { - return { - headers: 'wow', - status: 203, - body: 'ok' - }; - } - }); - const res = await fetch(`http://localhost:${PORT}/wow`); - assert.equal(res.status, 203); - server.server.close(); -}); - -test('passes through umlaut as encoded path', async () => { - const server = await startServer({ - render: (incoming) => { - return { - status: 200, - body: incoming.path - }; - } - }); - const res = await fetch(`http://localhost:${PORT}/%C3%BCber-uns`); - assert.equal(await res.text(), '/%C3%BCber-uns'); - server.server.close(); -}); - -test('serve a 400 when we have a malformed url', async () => { - const server = await startServer(); - const res = await fetch(`http://localhost:${PORT}//`); - assert.equal(res.status, 400); - server.server.close(); -}); - -test.run(); diff --git a/packages/adapter-node/tests/smoke.spec_disabled.js b/packages/adapter-node/tests/smoke.spec_disabled.js new file mode 100644 index 000000000000..58809bf6cb59 --- /dev/null +++ b/packages/adapter-node/tests/smoke.spec_disabled.js @@ -0,0 +1,71 @@ +import process from 'node:process'; +import { assert, test } from 'vitest'; +import { create_kit_middleware } from '../src/handler.js'; +import fetch from 'node-fetch'; +import polka from 'polka'; + +const { PORT = 3000 } = process.env; +const DEFAULT_SERVER_OPTS = { render: () => {} }; + +async function startServer(opts = DEFAULT_SERVER_OPTS) { + return new Promise((fulfil, reject) => { + const server = polka().use(create_kit_middleware(opts)); + server.listen(PORT, (err) => { + if (err) { + reject(err); + } + fulfil(server); + }); + }); +} + +test('starts a server', async () => { + const server = await startServer(); + assert.ok('server started'); + server.server.close(); +}); + +test('serves a 404', async () => { + const server = await startServer(); + const res = await fetch(`http://localhost:${PORT}/nothing`); + assert.equal(res.status, 404); + server.server.close(); +}); + +test('responses with the rendered status code', async () => { + const server = await startServer({ + render: () => { + return { + headers: 'wow', + status: 203, + body: 'ok' + }; + } + }); + const res = await fetch(`http://localhost:${PORT}/wow`); + assert.equal(res.status, 203); + server.server.close(); +}); + +test('passes through umlaut as encoded path', async () => { + const server = await startServer({ + render: (incoming) => { + return { + status: 200, + body: incoming.path + }; + } + }); + const res = await fetch(`http://localhost:${PORT}/%C3%BCber-uns`); + assert.equal(await res.text(), '/%C3%BCber-uns'); + server.server.close(); +}); + +test('serve a 400 when we have a malformed url', async () => { + const server = await startServer(); + const res = await fetch(`http://localhost:${PORT}//`); + assert.equal(res.status, 400); + server.server.close(); +}); + +test.run(); diff --git a/packages/adapter-node/tests/utils.spec.ts b/packages/adapter-node/tests/utils.spec.ts new file mode 100644 index 000000000000..75d0f2774e05 --- /dev/null +++ b/packages/adapter-node/tests/utils.spec.ts @@ -0,0 +1,16 @@ +import { expect, test, describe } from 'vitest'; +import { parse_as_bytes } from '../utils.js'; + +describe('parse_as_bytes', () => { + test.each([ + ['200', 200], + ['512K', 512 * 1024], + ['200M', 200 * 1024 * 1024], + ['1G', 1024 * 1024 * 1024], + ['Infinity', Infinity], + ['asdf', NaN] + ] as const)('parses correctly', (input, expected) => { + const actual = parse_as_bytes(input); + expect(actual, `Testing input '${input}'`).toBe(expected); + }); +}); diff --git a/packages/adapter-node/tsconfig.json b/packages/adapter-node/tsconfig.json index 63ea97316689..4234750243cc 100644 --- a/packages/adapter-node/tsconfig.json +++ b/packages/adapter-node/tsconfig.json @@ -4,14 +4,23 @@ "checkJs": true, "noEmit": true, "noImplicitAny": true, + "strictNullChecks": true, "allowSyntheticDefaultImports": true, - "moduleResolution": "node", - "module": "es2022", - "target": "es2017", + "target": "es2022", + "module": "node16", + "moduleResolution": "node16", "baseUrl": ".", "paths": { "@sveltejs/kit": ["../kit/types/index"] } }, - "include": ["index.js", "src/**/*.js", "ambient.d.ts"] + "include": [ + "index.js", + "src/**/*.js", + "tests/**/*.js", + "tests/**/*.ts", + "internal.d.ts", + "utils.js" + ], + "exclude": ["tests/smoke.spec_disabled.js"] } diff --git a/packages/adapter-node/utils.js b/packages/adapter-node/utils.js new file mode 100644 index 000000000000..16769eaa0027 --- /dev/null +++ b/packages/adapter-node/utils.js @@ -0,0 +1,15 @@ +/** + * Parses the given value into number of bytes. + * + * @param {string} value - Size in bytes. Can also be specified with a unit suffix kilobytes (K), megabytes (M), or gigabytes (G). + * @returns {number} + */ +export function parse_as_bytes(value) { + const multiplier = + { + K: 1024, + M: 1024 * 1024, + G: 1024 * 1024 * 1024 + }[value[value.length - 1]?.toUpperCase()] ?? 1; + return Number(multiplier != 1 ? value.substring(0, value.length - 1) : value) * multiplier; +} diff --git a/packages/adapter-static/CHANGELOG.md b/packages/adapter-static/CHANGELOG.md index f4c59705696b..9202756132d2 100644 --- a/packages/adapter-static/CHANGELOG.md +++ b/packages/adapter-static/CHANGELOG.md @@ -1,5 +1,92 @@ # @sveltejs/adapter-static +## 3.0.10 +### Patch Changes + + +- chore: update "homepage" field in package.json ([#14579](https://github.com/sveltejs/kit/pull/14579)) + +## 3.0.9 +### Patch Changes + + +- chore: add `.git` to the end of `package.json` repository url ([#14134](https://github.com/sveltejs/kit/pull/14134)) + +- Updated dependencies [[`c968aef`](https://github.com/sveltejs/kit/commit/c968aef5727f978244d5160657b4a7ac651384ae)]: + - @sveltejs/kit@2.27.3 + +## 3.0.8 +### Patch Changes + + +- fix: use optional chaining when checking router type ([#13218](https://github.com/sveltejs/kit/pull/13218)) + +## 3.0.7 +### Patch Changes + + +- fix: allow dynamic routes with missing fallback in hash mode ([#13213](https://github.com/sveltejs/kit/pull/13213)) + +## 3.0.6 +### Patch Changes + + +- docs: update URLs for new svelte.dev site ([#12857](https://github.com/sveltejs/kit/pull/12857)) + +- Updated dependencies [[`dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46`](https://github.com/sveltejs/kit/commit/dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46), [`4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d`](https://github.com/sveltejs/kit/commit/4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d), [`3a9b78f04786898ca93f6d4b75ab18d26bc45192`](https://github.com/sveltejs/kit/commit/3a9b78f04786898ca93f6d4b75ab18d26bc45192), [`723eb8b31e6a22c82f730c30e485386c8676b746`](https://github.com/sveltejs/kit/commit/723eb8b31e6a22c82f730c30e485386c8676b746), [`8ec471c875345b751344e67580ff1b772ef2735b`](https://github.com/sveltejs/kit/commit/8ec471c875345b751344e67580ff1b772ef2735b)]: + - @sveltejs/kit@2.7.3 + +## 3.0.5 +### Patch Changes + + +- fix: import `node:process` instead of using globals ([#12641](https://github.com/sveltejs/kit/pull/12641)) + +- Updated dependencies [[`e798ef718f163bed4f93e1918bd8294f765376ad`](https://github.com/sveltejs/kit/commit/e798ef718f163bed4f93e1918bd8294f765376ad)]: + - @sveltejs/kit@2.5.28 + +## 3.0.4 +### Patch Changes + + +- chore: configure provenance in a simpler manner ([#12570](https://github.com/sveltejs/kit/pull/12570)) + +- Updated dependencies [[`087a43d391fc38b8c008fb39a804dc6988974101`](https://github.com/sveltejs/kit/commit/087a43d391fc38b8c008fb39a804dc6988974101)]: + - @sveltejs/kit@2.5.22 + +## 3.0.3 +### Patch Changes + + +- chore: package provenance ([#12567](https://github.com/sveltejs/kit/pull/12567)) + +- Updated dependencies [[`4930a8443caa53bcecee7b690cd28e429b1c8a20`](https://github.com/sveltejs/kit/commit/4930a8443caa53bcecee7b690cd28e429b1c8a20)]: + - @sveltejs/kit@2.5.21 + +## 3.0.2 + +### Patch Changes + +- chore: add keywords for discovery in npm search ([#12330](https://github.com/sveltejs/kit/pull/12330)) + +- Updated dependencies [[`25acb1d9fce998dccd8050b93cf4142c2b082611`](https://github.com/sveltejs/kit/commit/25acb1d9fce998dccd8050b93cf4142c2b082611), [`642c4a4aff4351b786fe6274aa2f0bf7d905faf9`](https://github.com/sveltejs/kit/commit/642c4a4aff4351b786fe6274aa2f0bf7d905faf9), [`0a0e9aa897123ebec50af08e9385b2ca4fc5bb28`](https://github.com/sveltejs/kit/commit/0a0e9aa897123ebec50af08e9385b2ca4fc5bb28)]: + - @sveltejs/kit@2.5.11 + +## 3.0.1 + +### Patch Changes + +- chore: update primary branch from master to main ([`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d)) + +- Updated dependencies [[`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d), [`16961e8cd3fa6a7f382153b1ff056bc2aae9b31b`](https://github.com/sveltejs/kit/commit/16961e8cd3fa6a7f382153b1ff056bc2aae9b31b), [`197e01f95652f511160f38b37b9da73a124ecd48`](https://github.com/sveltejs/kit/commit/197e01f95652f511160f38b37b9da73a124ecd48), [`102e4a5ae5b29624302163faf5a20c94a64a5b2c`](https://github.com/sveltejs/kit/commit/102e4a5ae5b29624302163faf5a20c94a64a5b2c), [`f8e3d8b9728c9f1ab63389342c31d7246b6f9db6`](https://github.com/sveltejs/kit/commit/f8e3d8b9728c9f1ab63389342c31d7246b6f9db6)]: + - @sveltejs/kit@2.0.4 + +## 3.0.0 + +### Major Changes + +- breaking: update SvelteKit peer dependency to version 2 ([#11277](https://github.com/sveltejs/kit/pull/11277)) + ## 2.0.3 ### Patch Changes diff --git a/packages/adapter-static/README.md b/packages/adapter-static/README.md index f32fef4240c4..b612f2aa6122 100644 --- a/packages/adapter-static/README.md +++ b/packages/adapter-static/README.md @@ -1,14 +1,14 @@ # @sveltejs/adapter-static -[Adapter](https://kit.svelte.dev/docs/adapters) for SvelteKit apps that prerenders your entire site as a collection of static files. It's also possible to create an SPA with it by specifying a fallback page which renders an empty shell. If you'd like to prerender only some pages and not create an SPA for those left out, you will need to use a different adapter together with [the `prerender` option](https://kit.svelte.dev/docs/page-options#prerender). +[Adapter](https://svelte.dev/docs/kit/adapters) for SvelteKit apps that prerenders your entire site as a collection of static files. It's also possible to create an SPA with it by specifying a fallback page which renders an empty shell. If you'd like to prerender only some pages and not create an SPA for those left out, you will need to use a different adapter together with [the `prerender` option](https://svelte.dev/docs/kit/page-options#prerender). ## Docs -[Docs](https://kit.svelte.dev/docs/adapter-static) +[Docs](https://svelte.dev/docs/kit/adapter-static) ## Changelog -[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-static/CHANGELOG.md). +[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/main/packages/adapter-static/CHANGELOG.md). ## License diff --git a/packages/adapter-static/index.js b/packages/adapter-static/index.js index 96ccf731dd98..c2b87301c8a8 100644 --- a/packages/adapter-static/index.js +++ b/packages/adapter-static/index.js @@ -1,13 +1,13 @@ import path from 'node:path'; import { platforms } from './platforms.js'; -/** @type {import('.').default} */ +/** @type {import('./index.js').default} */ export default function (options) { return { name: '@sveltejs/adapter-static', - + /** @param {import('./internal.js').Builder2_0_0} builder */ async adapt(builder) { - if (!options?.fallback) { + if (!options?.fallback && builder.config.kit.router?.type !== 'hash') { const dynamic_routes = builder.routes.filter((route) => route.prerender !== true); if (dynamic_routes.length > 0 && options?.strict !== false) { const prefix = path.relative('.', builder.config.kit.files.routes); @@ -18,7 +18,7 @@ export default function (options) { has_param_routes ? '(routes with parameters are not part of entry points by default)' : '' - } — see https://kit.svelte.dev/docs/configuration#prerender for more info.` + } — see https://svelte.dev/docs/kit/configuration#prerender for more info.` : ''; builder.log.error( @@ -26,14 +26,14 @@ export default function (options) { ${dynamic_routes.map((route) => ` - ${path.posix.join(prefix, route.id)}`).join('\n')} You have the following options: - - set the \`fallback\` option — see https://kit.svelte.dev/docs/single-page-apps#usage for more info. + - set the \`fallback\` option — see https://svelte.dev/docs/kit/single-page-apps#usage for more info. - add \`export const prerender = true\` to your root \`+layout.js/.ts\` or \`+layout.server.js/.ts\` file. This will try to prerender all pages. - add \`export const prerender = true\` to any \`+server.js/ts\` files that are not fetched by page \`load\` functions. ${config_option} - - pass \`strict: false\` to \`adapter-static\` to ignore this error. Only do this if you are sure you don't need the routes in question in your final app, as they will be unavailable. See https://github.com/sveltejs/kit/tree/master/packages/adapter-static#strict for more info. + - pass \`strict: false\` to \`adapter-static\` to ignore this error. Only do this if you are sure you don't need the routes in question in your final app, as they will be unavailable. See https://github.com/sveltejs/kit/tree/main/packages/adapter-static#strict for more info. If this doesn't help, you may need to use a different adapter. @sveltejs/adapter-static can only be used for sites that don't need a server for dynamic rendering, and can run on just a static file server. -See https://kit.svelte.dev/docs/page-options#prerender for more details` +See https://svelte.dev/docs/kit/page-options#prerender for more details` ); throw new Error('Encountered dynamic routes'); } @@ -52,15 +52,17 @@ See https://kit.svelte.dev/docs/page-options#prerender for more details` } const { + // @ts-ignore pages = 'build', assets = pages, fallback, precompress - } = options ?? platform?.defaults ?? /** @type {import('./index').AdapterOptions} */ ({}); + } = options ?? platform?.defaults ?? /** @type {import('./index.js').AdapterOptions} */ ({}); builder.rimraf(assets); builder.rimraf(pages); + builder.generateEnvModule(); builder.writeClient(assets); builder.writePrerendered(pages); diff --git a/packages/adapter-static/internal.d.ts b/packages/adapter-static/internal.d.ts new file mode 100644 index 000000000000..9857dbae43cd --- /dev/null +++ b/packages/adapter-static/internal.d.ts @@ -0,0 +1,30 @@ +/** + * Utility type that makes all properties optional except for the specified keys + */ +export type PartialExcept<T, K extends keyof T> = Partial<Omit<T, K>> & Required<Pick<T, K>>; + +/** + * We use a custom `Builder` type here to support the minimum version of SvelteKit. + */ +export type Builder2_0_0 = PartialExcept< + import('@sveltejs/kit').Builder, + | 'log' + | 'rimraf' + | 'mkdirp' + | 'config' + | 'prerendered' + | 'routes' + | 'createEntries' + | 'generateFallback' + | 'generateEnvModule' + | 'generateManifest' + | 'getBuildDirectory' + | 'getClientDirectory' + | 'getServerDirectory' + | 'getAppPath' + | 'writeClient' + | 'writePrerendered' + | 'writeServer' + | 'copy' + | 'compress' +>; diff --git a/packages/adapter-static/package.json b/packages/adapter-static/package.json index 0a070b6ae962..99ce29a0392f 100644 --- a/packages/adapter-static/package.json +++ b/packages/adapter-static/package.json @@ -1,14 +1,23 @@ { "name": "@sveltejs/adapter-static", - "version": "2.0.3", + "version": "3.0.10", "description": "Adapter for SvelteKit apps that prerenders your entire site as a collection of static files", + "keywords": [ + "adapter", + "deploy", + "hosting", + "ssg", + "static site generation", + "svelte", + "sveltekit" + ], "repository": { "type": "git", - "url": "https://github.com/sveltejs/kit", + "url": "git+https://github.com/sveltejs/kit.git", "directory": "packages/adapter-static" }, "license": "MIT", - "homepage": "https://kit.svelte.dev", + "homepage": "https://svelte.dev/docs/kit/adapter-static", "type": "module", "exports": { ".": { @@ -24,21 +33,22 @@ "platforms.js" ], "scripts": { - "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", + "lint": "prettier --check .", "check": "tsc", "format": "pnpm lint --write", "test": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test" }, "devDependencies": { - "@playwright/test": "1.30.0", + "@playwright/test": "catalog:", "@sveltejs/kit": "workspace:^", - "@types/node": "^16.18.6", - "sirv": "^2.0.3", - "svelte": "^4.0.5", - "typescript": "^4.9.4", - "vite": "^4.4.9" + "@sveltejs/vite-plugin-svelte": "catalog:", + "@types/node": "catalog:", + "sirv": "^3.0.0", + "svelte": "catalog:", + "typescript": "^5.3.3", + "vite": "catalog:" }, "peerDependencies": { - "@sveltejs/kit": "^1.5.0" + "@sveltejs/kit": "^2.0.0" } } diff --git a/packages/adapter-static/platforms.js b/packages/adapter-static/platforms.js index 189865b106b8..6ed421625668 100644 --- a/packages/adapter-static/platforms.js +++ b/packages/adapter-static/platforms.js @@ -1,16 +1,17 @@ import fs from 'node:fs'; +import process from 'node:process'; /** * @typedef {{ * name: string; * test: () => boolean; - * defaults: import('./index').AdapterOptions; - * done: (builder: import('@sveltejs/kit').Builder) => void; + * defaults: import('./index.js').AdapterOptions; + * done: (builder: import('./internal.js').Builder2_0_0) => void; * }} * Platform */ // This function is duplicated in adapter-vercel -/** @param {import('@sveltejs/kit').Builder} builder */ +/** @param {import('./internal.js').Builder2_0_0} builder */ function static_vercel_config(builder) { /** @type {any[]} */ const prerendered_redirects = []; diff --git a/packages/adapter-static/test/apps/prerendered/package.json b/packages/adapter-static/test/apps/prerendered/package.json index ee51a824ea8a..6ac8a9517693 100644 --- a/packages/adapter-static/test/apps/prerendered/package.json +++ b/packages/adapter-static/test/apps/prerendered/package.json @@ -10,9 +10,10 @@ }, "devDependencies": { "@sveltejs/kit": "workspace:^", - "sirv-cli": "^2.0.2", - "svelte": "^4.0.5", - "vite": "^4.4.9" + "@sveltejs/vite-plugin-svelte": "catalog:", + "sirv-cli": "catalog:", + "svelte": "catalog:", + "vite": "catalog:" }, "type": "module" } diff --git a/packages/adapter-static/test/apps/prerendered/src/app.html b/packages/adapter-static/test/apps/prerendered/src/app.html index 568b69b887b8..d533c5e31716 100644 --- a/packages/adapter-static/test/apps/prerendered/src/app.html +++ b/packages/adapter-static/test/apps/prerendered/src/app.html @@ -1,4 +1,4 @@ -<!DOCTYPE html> +<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> diff --git a/packages/adapter-static/test/apps/prerendered/src/routes/public-env/+page.svelte b/packages/adapter-static/test/apps/prerendered/src/routes/public-env/+page.svelte index 1c06b45b43e4..a36f01afaa54 100644 --- a/packages/adapter-static/test/apps/prerendered/src/routes/public-env/+page.svelte +++ b/packages/adapter-static/test/apps/prerendered/src/routes/public-env/+page.svelte @@ -1,5 +1,11 @@ <script> + import { browser } from '$app/environment'; + import { PUBLIC_ANSWER } from '$env/static/public'; import { env } from '$env/dynamic/public'; </script> -<h1>The answer is {env.PUBLIC_ANSWER}</h1> +<h1>The answer is {PUBLIC_ANSWER}</h1> + +{#if browser} + <h2>The dynamic answer is {env.PUBLIC_ANSWER}</h2> +{/if} diff --git a/packages/adapter-static/test/apps/prerendered/test/test.js b/packages/adapter-static/test/apps/prerendered/test/test.js index 6a5ea0c2ee8b..6b16d2fed83b 100644 --- a/packages/adapter-static/test/apps/prerendered/test/test.js +++ b/packages/adapter-static/test/apps/prerendered/test/test.js @@ -1,4 +1,5 @@ import * as fs from 'node:fs'; +import process from 'node:process'; import { expect, test } from '@playwright/test'; const cwd = process.cwd(); @@ -24,4 +25,5 @@ test('prerenders a referenced endpoint with implicit `prerender` setting', async test('exposes public env vars to the client', async ({ page }) => { await page.goto('/public-env'); expect(await page.textContent('h1')).toEqual('The answer is 42'); + expect(await page.textContent('h2')).toEqual('The dynamic answer is 42'); }); diff --git a/packages/adapter-static/test/apps/spa/.env.production b/packages/adapter-static/test/apps/spa/.env.production new file mode 100644 index 000000000000..79d571855ca8 --- /dev/null +++ b/packages/adapter-static/test/apps/spa/.env.production @@ -0,0 +1 @@ +PUBLIC_VALUE="a .env.production variable should NOT be included in a mode=staging build" \ No newline at end of file diff --git a/packages/adapter-static/test/apps/spa/.env.staging b/packages/adapter-static/test/apps/spa/.env.staging new file mode 100644 index 000000000000..8483bf2722dd --- /dev/null +++ b/packages/adapter-static/test/apps/spa/.env.staging @@ -0,0 +1 @@ +PUBLIC_VALUE=42 \ No newline at end of file diff --git a/packages/adapter-static/test/apps/spa/README.md b/packages/adapter-static/test/apps/spa/README.md deleted file mode 100644 index c7f00c26d735..000000000000 --- a/packages/adapter-static/test/apps/spa/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# create-svelte - -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte); - -## Creating a project - -If you're seeing this, you've probably already done this step. Congrats! - -```bash -# create a new project in the current directory -npm init svelte - -# create a new project in my-app -npm init svelte my-app -``` - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```bash -npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` - -## Building - -Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. Then: - -```bash -npm run build -``` - -> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production. diff --git a/packages/adapter-static/test/apps/spa/package.json b/packages/adapter-static/test/apps/spa/package.json index bfac503ef925..4d4d0a6f5123 100644 --- a/packages/adapter-static/test/apps/spa/package.json +++ b/packages/adapter-static/test/apps/spa/package.json @@ -4,16 +4,16 @@ "private": true, "scripts": { "dev": "vite dev", - "build": "vite build", + "build": "vite build --mode staging", "preview": "sirv -p 5173 -s 200.html build", "test": "playwright test" }, "devDependencies": { - "@sveltejs/adapter-node": "workspace:^", "@sveltejs/kit": "workspace:^", - "sirv-cli": "^2.0.2", - "svelte": "^4.0.5", - "vite": "^4.4.9" + "@sveltejs/vite-plugin-svelte": "catalog:", + "sirv-cli": "catalog:", + "svelte": "catalog:", + "vite": "catalog:" }, "type": "module" } diff --git a/packages/adapter-static/test/apps/spa/src/app.html b/packages/adapter-static/test/apps/spa/src/app.html index 568b69b887b8..d533c5e31716 100644 --- a/packages/adapter-static/test/apps/spa/src/app.html +++ b/packages/adapter-static/test/apps/spa/src/app.html @@ -1,4 +1,4 @@ -<!DOCTYPE html> +<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> diff --git a/packages/adapter-static/test/apps/spa/src/routes/+error.svelte b/packages/adapter-static/test/apps/spa/src/routes/+error.svelte index 9b0abbfdda47..fe313e063894 100644 --- a/packages/adapter-static/test/apps/spa/src/routes/+error.svelte +++ b/packages/adapter-static/test/apps/spa/src/routes/+error.svelte @@ -1,5 +1,5 @@ <script> - import { page } from '$app/stores'; + import { page } from '$app/state'; </script> -<h1>{$page.status}</h1> +<h1>{page.status}</h1> diff --git a/packages/adapter-static/test/apps/spa/src/routes/+layout.svelte b/packages/adapter-static/test/apps/spa/src/routes/+layout.svelte index d89ceb3fed43..439e52c54c97 100644 --- a/packages/adapter-static/test/apps/spa/src/routes/+layout.svelte +++ b/packages/adapter-static/test/apps/spa/src/routes/+layout.svelte @@ -3,4 +3,4 @@ <a href="/about">about</a> </nav> -<slot></slot> \ No newline at end of file +<slot /> diff --git a/packages/adapter-static/test/apps/spa/src/routes/+page.svelte b/packages/adapter-static/test/apps/spa/src/routes/+page.svelte index 913f0784cbb6..aaa08e08c0a1 100644 --- a/packages/adapter-static/test/apps/spa/src/routes/+page.svelte +++ b/packages/adapter-static/test/apps/spa/src/routes/+page.svelte @@ -1 +1 @@ -<h1>This page was not prerendered</h1> \ No newline at end of file +<h1>This page was not prerendered</h1> diff --git a/packages/adapter-static/test/apps/spa/src/routes/about/+page.svelte b/packages/adapter-static/test/apps/spa/src/routes/about/+page.svelte index 404fd77511d2..07dd2ffa5038 100644 --- a/packages/adapter-static/test/apps/spa/src/routes/about/+page.svelte +++ b/packages/adapter-static/test/apps/spa/src/routes/about/+page.svelte @@ -1 +1 @@ -<h1>This page was prerendered</h1> \ No newline at end of file +<h1>This page was prerendered</h1> diff --git a/packages/adapter-static/test/apps/spa/src/routes/fallback/[...rest]/+page.svelte b/packages/adapter-static/test/apps/spa/src/routes/fallback/[...rest]/+page.svelte index 671b43ec7195..55b4798e99c5 100644 --- a/packages/adapter-static/test/apps/spa/src/routes/fallback/[...rest]/+page.svelte +++ b/packages/adapter-static/test/apps/spa/src/routes/fallback/[...rest]/+page.svelte @@ -1 +1,7 @@ +<script> + import { PUBLIC_VALUE } from '$env/static/public'; +</script> + <h1>the fallback page was rendered</h1> + +<b>{PUBLIC_VALUE}</b> diff --git a/packages/adapter-static/test/apps/spa/test/test.js b/packages/adapter-static/test/apps/spa/test/test.js index 70f18670ea1b..5963f490df7f 100644 --- a/packages/adapter-static/test/apps/spa/test/test.js +++ b/packages/adapter-static/test/apps/spa/test/test.js @@ -1,4 +1,5 @@ import * as fs from 'node:fs'; +import process from 'node:process'; import { expect, test } from '@playwright/test'; const cwd = process.cwd(); @@ -27,3 +28,8 @@ test('renders error page for missing page', async ({ page }) => { await page.goto('/nosuchpage'); expect(await page.textContent('h1')).toEqual('404'); }); + +test('uses correct environment variables for fallback page (mode = staging)', async ({ page }) => { + await page.goto('/fallback/x/y/z'); + expect(await page.textContent('b')).toEqual('42'); +}); diff --git a/packages/adapter-static/test/utils.js b/packages/adapter-static/test/utils.js index 28a931073483..52d5adbf7759 100644 --- a/packages/adapter-static/test/utils.js +++ b/packages/adapter-static/test/utils.js @@ -1,4 +1,5 @@ import { devices } from '@playwright/test'; +import process from 'node:process'; /** @type {import('@playwright/test').PlaywrightTestConfig} */ export const config = { diff --git a/packages/adapter-static/tsconfig.json b/packages/adapter-static/tsconfig.json index 7953ec985e76..ad019b486258 100644 --- a/packages/adapter-static/tsconfig.json +++ b/packages/adapter-static/tsconfig.json @@ -4,14 +4,15 @@ "checkJs": true, "noEmit": true, "noImplicitAny": true, - "module": "es2022", + "strictNullChecks": true, "target": "es2022", - "moduleResolution": "node", + "module": "node16", + "moduleResolution": "node16", "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { "@sveltejs/kit": ["../kit/types/index"] } }, - "include": ["index.js"] + "include": ["index.js", "internal.d.ts", "test/utils.js"] } diff --git a/packages/adapter-vercel/CHANGELOG.md b/packages/adapter-vercel/CHANGELOG.md index 52911cba3385..bbe32d20ec2f 100644 --- a/packages/adapter-vercel/CHANGELOG.md +++ b/packages/adapter-vercel/CHANGELOG.md @@ -1,5 +1,454 @@ # @sveltejs/adapter-vercel +## 6.3.0 +### Minor Changes + + +- chore: mark `RequestContext` as deprecated and refer to `@vercel/functions` ([#14725](https://github.com/sveltejs/kit/pull/14725)) + + +### Patch Changes + +- Updated dependencies [[`e67613c`](https://github.com/sveltejs/kit/commit/e67613cf0dde5b7b22e7ef2e2fa2d9171505c784), [`a5c313e`](https://github.com/sveltejs/kit/commit/a5c313e026659874981868fe9628ed6a311dcc06), [`06de550`](https://github.com/sveltejs/kit/commit/06de5506b6db0e39e2ded47e239af39f64d647a8)]: + - @sveltejs/kit@2.49.3 + +## 6.2.0 +### Minor Changes + + +- feat: Node 24 support ([#14982](https://github.com/sveltejs/kit/pull/14982)) + +## 6.1.2 +### Patch Changes + + +- chore(deps): upgrade to `@vercel/nft` version 1.0.0 to reduce dependencies ([#14950](https://github.com/sveltejs/kit/pull/14950)) + +- Updated dependencies [[`0889a2a`](https://github.com/sveltejs/kit/commit/0889a2aa897b64194bc55183d7224df65d8147c1), [`2ff3951`](https://github.com/sveltejs/kit/commit/2ff3951e79e59b522c648138c846f31ad858d5b0), [`5b30755`](https://github.com/sveltejs/kit/commit/5b30755a1d84a1054370b211e56d576e3c96d442)]: + - @sveltejs/kit@2.48.7 + +## 6.1.1 +### Patch Changes + + +- chore: improve runtime config parsing ([#14838](https://github.com/sveltejs/kit/pull/14838)) + +- Updated dependencies [[`cd72d94`](https://github.com/sveltejs/kit/commit/cd72d942a75eed2c1c2ee4b9d6cba5829a4fdb7a), [`53b1b73`](https://github.com/sveltejs/kit/commit/53b1b73256d5b314b1e08ff13f920d520c4a6aed), [`2ccc638`](https://github.com/sveltejs/kit/commit/2ccc63806fa4049c0c05ebc43dce8905e355ed5a)]: + - @sveltejs/kit@2.48.3 + +## 6.1.0 +### Minor Changes + + +- feat: Add experimental support for Bun runtime ([#14817](https://github.com/sveltejs/kit/pull/14817)) + + +### Patch Changes + +- Updated dependencies [[`102aecf`](https://github.com/sveltejs/kit/commit/102aecfd228dd632664d748b9e87bc6d219294c4)]: + - @sveltejs/kit@2.48.1 + +## 6.0.0 +### Major Changes + + +- breaking: remove Node polyfills (and by extension support for Node 18) ([#14732](https://github.com/sveltejs/kit/pull/14732)) + + +### Minor Changes + + +- feat: parse isr.expiration, allowing it to be a string ([#14691](https://github.com/sveltejs/kit/pull/14691)) + + +### Patch Changes + +- Updated dependencies [[`9c933a2`](https://github.com/sveltejs/kit/commit/9c933a29e64c04db03e9150fa4e2d74a894d3d12), [`dedda71`](https://github.com/sveltejs/kit/commit/dedda7184dc04543e2ba6ea3e7f50b3836505463)]: + - @sveltejs/kit@2.47.0 + +## 5.10.3 +### Patch Changes + + +- chore: update "homepage" field in package.json ([#14579](https://github.com/sveltejs/kit/pull/14579)) + +## 5.10.2 +### Patch Changes + + +- fix: ensure `read` works in an edge function that has deployment protection. Protection bypass automation must be enabled ([#14147](https://github.com/sveltejs/kit/pull/14147)) + +- Updated dependencies [[`c8f7ac3`](https://github.com/sveltejs/kit/commit/c8f7ac38e9ae1763e7880a29b7f7df01df964a6d), [`107f767`](https://github.com/sveltejs/kit/commit/107f767e3a1a54187527defb29dce753c4f5fa3f)]: + - @sveltejs/kit@2.33.1 + +## 5.10.1 +### Patch Changes + + +- chore: deprecate `runtime` config ([#14253](https://github.com/sveltejs/kit/pull/14253)) + +## 5.10.0 +### Minor Changes + + +- feat: use web standard `fetch` export ([#14251](https://github.com/sveltejs/kit/pull/14251)) + + +### Patch Changes + +- Updated dependencies [[`1d04a77`](https://github.com/sveltejs/kit/commit/1d04a7786725d2d8c412268c36722e55f841fdfe), [`5db4cd4`](https://github.com/sveltejs/kit/commit/5db4cd46489d72471a99a8677a9faf32324b9d7e)]: + - @sveltejs/kit@2.32.0 + +## 5.9.1 +### Patch Changes + + +- fix: avoid erroring on builder properties that only exist on the latest version of SvelteKit ([#14233](https://github.com/sveltejs/kit/pull/14233)) + +- Updated dependencies [[`f2db41c`](https://github.com/sveltejs/kit/commit/f2db41c0d3a0aefbb080ab6a9aa5822b3e41625c)]: + - @sveltejs/kit@2.31.1 + +## 5.9.0 +### Minor Changes + + +- feat: add `instrumentation.server.ts` for tracing and observability setup ([#13899](https://github.com/sveltejs/kit/pull/13899)) + + +### Patch Changes + +- Updated dependencies [[`f635678`](https://github.com/sveltejs/kit/commit/f63567812505597b1edc3e01010eca622b03b126), [`f635678`](https://github.com/sveltejs/kit/commit/f63567812505597b1edc3e01010eca622b03b126)]: + - @sveltejs/kit@2.31.0 + +## 5.8.2 +### Patch Changes + + +- chore: add `.git` to the end of `package.json` repository url ([#14134](https://github.com/sveltejs/kit/pull/14134)) + +- Updated dependencies [[`c968aef`](https://github.com/sveltejs/kit/commit/c968aef5727f978244d5160657b4a7ac651384ae)]: + - @sveltejs/kit@2.27.3 + +## 5.8.1 +### Patch Changes + + +- chore(deps): update dependency @vercel/nft to ^0.30.0 ([#14033](https://github.com/sveltejs/kit/pull/14033)) + +## 5.8.0 +### Minor Changes + + +- feat: add support for `read` imported from `$app/server` in edge functions ([#13859](https://github.com/sveltejs/kit/pull/13859)) + + +### Patch Changes + +- Updated dependencies [[`e5ce8bb`](https://github.com/sveltejs/kit/commit/e5ce8bb42ea020b88bd0a4ff18dc600745657541), [`cf88369`](https://github.com/sveltejs/kit/commit/cf883692fa0e163cff6b1a2f9b17a568af14124d)]: + - @sveltejs/kit@2.25.0 + +## 5.7.2 +### Patch Changes + + +- chore(deps): upgrade to esbuild 0.25.4 ([#13770](https://github.com/sveltejs/kit/pull/13770)) + +## 5.7.1 +### Patch Changes + + +- chore(deps): upgrade esbuild to 0.25.2 ([#13716](https://github.com/sveltejs/kit/pull/13716)) + + +- fix: include the `edge-light` bundling condition when building edge functions ([#13720](https://github.com/sveltejs/kit/pull/13720)) + +- Updated dependencies [[`c51fb554416e0c4a21655c1d79e834f69743d1d5`](https://github.com/sveltejs/kit/commit/c51fb554416e0c4a21655c1d79e834f69743d1d5)]: + - @sveltejs/kit@2.20.8 + +## 5.7.0 +### Minor Changes + + +- feat: create symlink functions for each route, for better observability ([#13679](https://github.com/sveltejs/kit/pull/13679)) + + +### Patch Changes + +- Updated dependencies [[`7fd7bcb7142e7d0d2dd64174fa1a94d56a45d643`](https://github.com/sveltejs/kit/commit/7fd7bcb7142e7d0d2dd64174fa1a94d56a45d643)]: + - @sveltejs/kit@2.20.4 + +## 5.6.3 +### Patch Changes + + +- chore(deps): upgrade @vercel/nft to fix glob deprecation warnings ([`b1e9781a6dff41841d8e1509311d948421956746`](https://github.com/sveltejs/kit/commit/b1e9781a6dff41841d8e1509311d948421956746)) + +## 5.6.2 +### Patch Changes + + +- fix: change server-side route resolution endpoint ([#13461](https://github.com/sveltejs/kit/pull/13461)) + +- Updated dependencies [[`9612a60a0277aef0ab4723a0e7ed8dd03a7ffb95`](https://github.com/sveltejs/kit/commit/9612a60a0277aef0ab4723a0e7ed8dd03a7ffb95), [`3d88ae33fc14b08a1d48c2cb7315739c8cfcd9fd`](https://github.com/sveltejs/kit/commit/3d88ae33fc14b08a1d48c2cb7315739c8cfcd9fd)]: + - @sveltejs/kit@2.17.2 + +## 5.6.1 +### Patch Changes + + +- fix: correct edge function path for route resolution endpoint ([#13409](https://github.com/sveltejs/kit/pull/13409)) + +## 5.6.0 +### Minor Changes + + +- feat: generate edge function dedicated to server side route resolution when using that option in SvelteKit ([#13379](https://github.com/sveltejs/kit/pull/13379)) + + +### Patch Changes + +- Updated dependencies [[`09296d0f19c8d1ff57d699e637bd1beabb69d438`](https://github.com/sveltejs/kit/commit/09296d0f19c8d1ff57d699e637bd1beabb69d438), [`d62ed39a431f0db3db4dd90bf6b17ed2a2a2de79`](https://github.com/sveltejs/kit/commit/d62ed39a431f0db3db4dd90bf6b17ed2a2a2de79), [`f30352f874790b9de0bd0eba985a21aef23e158e`](https://github.com/sveltejs/kit/commit/f30352f874790b9de0bd0eba985a21aef23e158e), [`180fa3467e195065c0a25206c6328a908e6952d7`](https://github.com/sveltejs/kit/commit/180fa3467e195065c0a25206c6328a908e6952d7), [`5906e9708965b848b468d0014999c36272dc8d50`](https://github.com/sveltejs/kit/commit/5906e9708965b848b468d0014999c36272dc8d50), [`d62ed39a431f0db3db4dd90bf6b17ed2a2a2de79`](https://github.com/sveltejs/kit/commit/d62ed39a431f0db3db4dd90bf6b17ed2a2a2de79)]: + - @sveltejs/kit@2.17.0 + +## 5.5.3 +### Patch Changes + + +- fix: include ambient type declarations ([#12088](https://github.com/sveltejs/kit/pull/12088)) + +- Updated dependencies [[`d440c68acac67ed64eea4b9bda267e229303db7b`](https://github.com/sveltejs/kit/commit/d440c68acac67ed64eea4b9bda267e229303db7b), [`6774ebc34330b12ae8c0cae08e98b577d819fffb`](https://github.com/sveltejs/kit/commit/6774ebc34330b12ae8c0cae08e98b577d819fffb), [`777c8ef11f17d2ab48aee0f2347c051663da5826`](https://github.com/sveltejs/kit/commit/777c8ef11f17d2ab48aee0f2347c051663da5826), [`f451f6c4a3dbbc73dc86667c6ff89ab2f46ca9d2`](https://github.com/sveltejs/kit/commit/f451f6c4a3dbbc73dc86667c6ff89ab2f46ca9d2), [`34a03ff16af29e917abebb649b31eadfc40a98a0`](https://github.com/sveltejs/kit/commit/34a03ff16af29e917abebb649b31eadfc40a98a0), [`1c77e283896058084c1cb5752d9ec207987a585e`](https://github.com/sveltejs/kit/commit/1c77e283896058084c1cb5752d9ec207987a585e), [`04958cca5905aaeeff367c9e4a5ce6e90fc64779`](https://github.com/sveltejs/kit/commit/04958cca5905aaeeff367c9e4a5ce6e90fc64779), [`9dc5c0e3e01a3c07010e9996688169be68e1dde8`](https://github.com/sveltejs/kit/commit/9dc5c0e3e01a3c07010e9996688169be68e1dde8), [`00e1a7621de554054d068e4525a9e505d1c2e588`](https://github.com/sveltejs/kit/commit/00e1a7621de554054d068e4525a9e505d1c2e588), [`9fcd1e7574197fa6e7ac000a030378d877cb8837`](https://github.com/sveltejs/kit/commit/9fcd1e7574197fa6e7ac000a030378d877cb8837), [`e541a4057a00f5ab6740fb51b7f88f17776da50a`](https://github.com/sveltejs/kit/commit/e541a4057a00f5ab6740fb51b7f88f17776da50a), [`37f72fbb075b481de8263f62c77125333735f382`](https://github.com/sveltejs/kit/commit/37f72fbb075b481de8263f62c77125333735f382), [`b60707ca8e755be95c86490122aa1b792b9bd6be`](https://github.com/sveltejs/kit/commit/b60707ca8e755be95c86490122aa1b792b9bd6be), [`699f4405c752261cf46c1ad32e4dbadceaffc75b`](https://github.com/sveltejs/kit/commit/699f4405c752261cf46c1ad32e4dbadceaffc75b), [`e2a4538c48295cde06f64fb8c7f0b333fbf95496`](https://github.com/sveltejs/kit/commit/e2a4538c48295cde06f64fb8c7f0b333fbf95496), [`a91ba1f326b6e244503de9a010771d942b461dad`](https://github.com/sveltejs/kit/commit/a91ba1f326b6e244503de9a010771d942b461dad)]: + - @sveltejs/kit@2.16.0 + +## 5.5.2 +### Patch Changes + + +- chore: upgrade `@vercel/nft` to 0.27.9 ([#13129](https://github.com/sveltejs/kit/pull/13129)) + +- Updated dependencies [[`9fc5ff3339e543b956f7ce5eb31267fa73ee332a`](https://github.com/sveltejs/kit/commit/9fc5ff3339e543b956f7ce5eb31267fa73ee332a), [`85b57168189fa16fe966434ec50cc19425cab275`](https://github.com/sveltejs/kit/commit/85b57168189fa16fe966434ec50cc19425cab275)]: + - @sveltejs/kit@2.10.0 + +## 5.5.1 +### Patch Changes + + +- chore: upgrade @vercel/nft to 0.27.7 ([#13082](https://github.com/sveltejs/kit/pull/13082)) + +- Updated dependencies [[`78404dfe1eb346723eefc183278b85f25485b419`](https://github.com/sveltejs/kit/commit/78404dfe1eb346723eefc183278b85f25485b419)]: + - @sveltejs/kit@2.9.1 + +## 5.5.0 +### Minor Changes + + +- chore: upgrade esbuild to 0.24.0 ([#12270](https://github.com/sveltejs/kit/pull/12270)) + + +### Patch Changes + +- Updated dependencies [[`d030f4bb285e70844d09b3f0c87809bae43014b8`](https://github.com/sveltejs/kit/commit/d030f4bb285e70844d09b3f0c87809bae43014b8), [`67dd214863cbc5852eb0e8512efbb7bad5358e8a`](https://github.com/sveltejs/kit/commit/67dd214863cbc5852eb0e8512efbb7bad5358e8a)]: + - @sveltejs/kit@2.9.0 + +## 5.4.8 +### Patch Changes + + +- chore: support building with Node 22 ([#13043](https://github.com/sveltejs/kit/pull/13043)) + +- Updated dependencies [[`570562b74d9e9f295d9b617478088a650f51e96b`](https://github.com/sveltejs/kit/commit/570562b74d9e9f295d9b617478088a650f51e96b), [`1358cccd52190df3c74bdd8970dbfb06ffc4ec72`](https://github.com/sveltejs/kit/commit/1358cccd52190df3c74bdd8970dbfb06ffc4ec72)]: + - @sveltejs/kit@2.8.2 + +## 5.4.7 +### Patch Changes + + +- fix: disregard presence/absence of trailing slash in prerendered redirect ([#12966](https://github.com/sveltejs/kit/pull/12966)) + +- Updated dependencies [[`92b2686314a7dbebee1761c3da7719d599f003c7`](https://github.com/sveltejs/kit/commit/92b2686314a7dbebee1761c3da7719d599f003c7)]: + - @sveltejs/kit@2.8.0 + +## 5.4.6 +### Patch Changes + + +- docs: update URLs for new svelte.dev site ([#12857](https://github.com/sveltejs/kit/pull/12857)) + +- Updated dependencies [[`dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46`](https://github.com/sveltejs/kit/commit/dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46), [`4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d`](https://github.com/sveltejs/kit/commit/4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d), [`3a9b78f04786898ca93f6d4b75ab18d26bc45192`](https://github.com/sveltejs/kit/commit/3a9b78f04786898ca93f6d4b75ab18d26bc45192), [`723eb8b31e6a22c82f730c30e485386c8676b746`](https://github.com/sveltejs/kit/commit/723eb8b31e6a22c82f730c30e485386c8676b746), [`8ec471c875345b751344e67580ff1b772ef2735b`](https://github.com/sveltejs/kit/commit/8ec471c875345b751344e67580ff1b772ef2735b)]: + - @sveltejs/kit@2.7.3 + +## 5.4.5 +### Patch Changes + + +- fix: updated `@default` annotation for runtime ([#12717](https://github.com/sveltejs/kit/pull/12717)) + +- Updated dependencies [[`3591411e880ed5337123c66365433afe8c2f747b`](https://github.com/sveltejs/kit/commit/3591411e880ed5337123c66365433afe8c2f747b), [`2292170ecba27c70600fae0c2adc473ac9d938e8`](https://github.com/sveltejs/kit/commit/2292170ecba27c70600fae0c2adc473ac9d938e8), [`809983f377f0b18c4651a8a4f3af7b69c0df20ab`](https://github.com/sveltejs/kit/commit/809983f377f0b18c4651a8a4f3af7b69c0df20ab)]: + - @sveltejs/kit@2.6.3 + +## 5.4.4 +### Patch Changes + + +- fix: import `node:process` instead of using globals ([#12641](https://github.com/sveltejs/kit/pull/12641)) + +- Updated dependencies [[`e798ef718f163bed4f93e1918bd8294f765376ad`](https://github.com/sveltejs/kit/commit/e798ef718f163bed4f93e1918bd8294f765376ad)]: + - @sveltejs/kit@2.5.28 + +## 5.4.3 +### Patch Changes + + +- chore: configure provenance in a simpler manner ([#12570](https://github.com/sveltejs/kit/pull/12570)) + +- Updated dependencies [[`087a43d391fc38b8c008fb39a804dc6988974101`](https://github.com/sveltejs/kit/commit/087a43d391fc38b8c008fb39a804dc6988974101)]: + - @sveltejs/kit@2.5.22 + +## 5.4.2 +### Patch Changes + + +- chore: package provenance ([#12567](https://github.com/sveltejs/kit/pull/12567)) + +- Updated dependencies [[`4930a8443caa53bcecee7b690cd28e429b1c8a20`](https://github.com/sveltejs/kit/commit/4930a8443caa53bcecee7b690cd28e429b1c8a20)]: + - @sveltejs/kit@2.5.21 + +## 5.4.1 +### Patch Changes + + +- fix: copy `.eot`, `.otf`, `.ttf`, `.woff`, and `woff2` font files when bundling ([#12439](https://github.com/sveltejs/kit/pull/12439)) + +## 5.4.0 +### Minor Changes + + +- chore(deps): upgrade to esbuild 0.21 ([#12415](https://github.com/sveltejs/kit/pull/12415)) + + +### Patch Changes + +- Updated dependencies [[`84298477a014ec471839adf7a4448d91bc7949e4`](https://github.com/sveltejs/kit/commit/84298477a014ec471839adf7a4448d91bc7949e4), [`5645614f497931f587b7cb8b3c885fce892a6a72`](https://github.com/sveltejs/kit/commit/5645614f497931f587b7cb8b3c885fce892a6a72), [`84298477a014ec471839adf7a4448d91bc7949e4`](https://github.com/sveltejs/kit/commit/84298477a014ec471839adf7a4448d91bc7949e4)]: + - @sveltejs/kit@2.5.18 + +## 5.3.2 + +### Patch Changes + +- fix: remove `images` from route-level config ([#12280](https://github.com/sveltejs/kit/pull/12280)) + +- chore: add keywords for discovery in npm search ([#12330](https://github.com/sveltejs/kit/pull/12330)) + +- Updated dependencies [[`25acb1d9fce998dccd8050b93cf4142c2b082611`](https://github.com/sveltejs/kit/commit/25acb1d9fce998dccd8050b93cf4142c2b082611), [`642c4a4aff4351b786fe6274aa2f0bf7d905faf9`](https://github.com/sveltejs/kit/commit/642c4a4aff4351b786fe6274aa2f0bf7d905faf9), [`0a0e9aa897123ebec50af08e9385b2ca4fc5bb28`](https://github.com/sveltejs/kit/commit/0a0e9aa897123ebec50af08e9385b2ca4fc5bb28)]: + - @sveltejs/kit@2.5.11 + +## 5.3.1 + +### Patch Changes + +- chore(deps): upgrade to `@vercel/nft` version 0.27.1 ([#12274](https://github.com/sveltejs/kit/pull/12274)) + +## 5.3.0 + +### Minor Changes + +- chore(deps): upgrade esbuild ([#12118](https://github.com/sveltejs/kit/pull/12118)) + +### Patch Changes + +- Updated dependencies [[`bbab296f6fcc05af6b999182798bcdedabbaa4c9`](https://github.com/sveltejs/kit/commit/bbab296f6fcc05af6b999182798bcdedabbaa4c9)]: + - @sveltejs/kit@2.5.6 + +## 5.2.0 + +### Minor Changes + +- feat: add framework metadata in Vercel build output files ([#11800](https://github.com/sveltejs/kit/pull/11800)) + +- feat: implement version skew protection ([#11987](https://github.com/sveltejs/kit/pull/11987)) + +## 5.1.1 + +### Patch Changes + +- fix: handle optional and rest routes for incremental static regeneration (ISR) correctly ([#11928](https://github.com/sveltejs/kit/pull/11928)) + +## 5.1.0 + +### Minor Changes + +- feat: allow compatible subset of Node.js built-in modules when targeting edge functions ([#11675](https://github.com/sveltejs/kit/pull/11675)) + +### Patch Changes + +- Updated dependencies [[`36dc54ac740b8b4c6a2b904a1d0aadd8923a875c`](https://github.com/sveltejs/kit/commit/36dc54ac740b8b4c6a2b904a1d0aadd8923a875c), [`5dae3676b8cc6f8ee0def57340e6a6e591bafecd`](https://github.com/sveltejs/kit/commit/5dae3676b8cc6f8ee0def57340e6a6e591bafecd), [`ada595908b5501b8f4ac30c89c0d6314f364fde3`](https://github.com/sveltejs/kit/commit/ada595908b5501b8f4ac30c89c0d6314f364fde3), [`e228f8997840b89c6248e1c5df6f3108008a06be`](https://github.com/sveltejs/kit/commit/e228f8997840b89c6248e1c5df6f3108008a06be)]: + - @sveltejs/kit@2.4.1 + +## 5.0.0 + +### Major Changes + +- breaking: update peer dependency on `@sveltejs/kit` ([#11649](https://github.com/sveltejs/kit/pull/11649)) + +### Minor Changes + +- feat: support `read` from `$app/server` ([#11649](https://github.com/sveltejs/kit/pull/11649)) + +### Patch Changes + +- Updated dependencies [[`288f731c8a5b20cadb9e219f9583f3f16bf8c7b8`](https://github.com/sveltejs/kit/commit/288f731c8a5b20cadb9e219f9583f3f16bf8c7b8)]: + - @sveltejs/kit@2.4.0 + +## 4.0.5 + +### Patch Changes + +- chore: upgrade esbuild to 0.19.11 ([#11632](https://github.com/sveltejs/kit/pull/11632)) + +## 4.0.4 + +### Patch Changes + +- fix: update @vercel/nft to 0.26.1 ([#11508](https://github.com/sveltejs/kit/pull/11508)) + +## 4.0.3 + +### Patch Changes + +- chore: update primary branch from master to main ([`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d)) + +- Updated dependencies [[`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d), [`16961e8cd3fa6a7f382153b1ff056bc2aae9b31b`](https://github.com/sveltejs/kit/commit/16961e8cd3fa6a7f382153b1ff056bc2aae9b31b), [`197e01f95652f511160f38b37b9da73a124ecd48`](https://github.com/sveltejs/kit/commit/197e01f95652f511160f38b37b9da73a124ecd48), [`102e4a5ae5b29624302163faf5a20c94a64a5b2c`](https://github.com/sveltejs/kit/commit/102e4a5ae5b29624302163faf5a20c94a64a5b2c), [`f8e3d8b9728c9f1ab63389342c31d7246b6f9db6`](https://github.com/sveltejs/kit/commit/f8e3d8b9728c9f1ab63389342c31d7246b6f9db6)]: + - @sveltejs/kit@2.0.4 + +## 4.0.2 + +### Patch Changes + +- fix: remove broken node 16 support ([#11328](https://github.com/sveltejs/kit/pull/11328)) + +## 4.0.1 + +### Patch Changes + +- chore(deps): update `@vercel/nft` ([#11281](https://github.com/sveltejs/kit/pull/11281)) + +## 4.0.0 + +### Major Changes + +- breaking: require SvelteKit 2 peer dependency ([#11289](https://github.com/sveltejs/kit/pull/11289)) + +- chore: upgrade esbuild ([#11122](https://github.com/sveltejs/kit/pull/11122)) + +### Minor Changes + +- feat: expose vercel image optimization config in adapter config ([#8667](https://github.com/sveltejs/kit/pull/8667)) + +## 3.1.0 + +### Minor Changes + +- feat: add support for nodejs20.x ([#11067](https://github.com/sveltejs/kit/pull/11067)) + ## 3.0.3 ### Patch Changes diff --git a/packages/adapter-vercel/README.md b/packages/adapter-vercel/README.md index 8a09e256d6e2..ce1a3c89595e 100644 --- a/packages/adapter-vercel/README.md +++ b/packages/adapter-vercel/README.md @@ -4,8 +4,8 @@ A SvelteKit adapter that creates a Vercel app. ## Docs -[Docs](https://kit.svelte.dev/docs/adapter-vercel) +[Docs](https://svelte.dev/docs/kit/adapter-vercel) ## Changelog -[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-vercel/CHANGELOG.md). +[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/main/packages/adapter-vercel/CHANGELOG.md). diff --git a/packages/adapter-vercel/ambient.d.ts b/packages/adapter-vercel/ambient.d.ts index a106f64e3f12..67464d3365d9 100644 --- a/packages/adapter-vercel/ambient.d.ts +++ b/packages/adapter-vercel/ambient.d.ts @@ -1,10 +1,12 @@ -import { RequestContext } from './index.js'; +import type { RequestContext } from './index.js'; declare global { namespace App { export interface Platform { /** * `context` is only available in Edge Functions + * + * @deprecated Vercel's context is deprecated. Use [`@vercel/functions`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package) instead. */ context?: RequestContext; } diff --git a/packages/adapter-vercel/files/edge.js b/packages/adapter-vercel/files/edge.js index ce603b6e3920..f87bb6e64a98 100644 --- a/packages/adapter-vercel/files/edge.js +++ b/packages/adapter-vercel/files/edge.js @@ -1,9 +1,50 @@ +/* eslint-disable n/prefer-global/process -- + Vercel Edge Runtime does not support node:process */ import { Server } from 'SERVER'; import { manifest } from 'MANIFEST'; const server = new Server(manifest); + +/** @type {HeadersInit | undefined} */ +let read_headers; +if (process.env.VERCEL_AUTOMATION_BYPASS_SECRET) { + read_headers = { + 'x-vercel-protection-bypass': process.env.VERCEL_AUTOMATION_BYPASS_SECRET + }; +} + +/** + * We don't know the origin until we receive a request, but + * that's guaranteed to happen before we call `read` + * @type {string} + */ +let origin; + const initialized = server.init({ - env: /** @type {Record<string, string>} */ (process.env) + env: /** @type {Record<string, string>} */ (process.env), + read: async (file) => { + const url = `${origin}/${file}`; + const response = await fetch(url, { + // we need to add a bypass header if the user has deployment protection enabled + // see https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/protection-bypass-automation + headers: read_headers + }); + + if (!response.ok) { + if (response.status === 401) { + throw new Error( + `Please enable Protection Bypass for Automation: https://svelte.dev/docs/kit/adapter-vercel#Troubleshooting-Deployment-protection` + ); + } + + // belt and braces — not sure how we could end up here + throw new Error( + `read(...) failed: could not fetch ${url} (${response.status} ${response.statusText})` + ); + } + + return response.body; + } }); /** @@ -11,7 +52,11 @@ const initialized = server.init({ * @param {import('../index.js').RequestContext} context */ export default async (request, context) => { - await initialized; + if (!origin) { + origin = new URL(request.url).origin; + await initialized; + } + return server.respond(request, { getClientAddress() { return /** @type {string} */ (request.headers.get('x-forwarded-for')); diff --git a/packages/adapter-vercel/files/serverless.js b/packages/adapter-vercel/files/serverless.js index 5a6c2f64e2fd..fb818ba067a5 100644 --- a/packages/adapter-vercel/files/serverless.js +++ b/packages/adapter-vercel/files/serverless.js @@ -1,53 +1,42 @@ -import { installPolyfills } from '@sveltejs/kit/node/polyfills'; -import { getRequest, setResponse } from '@sveltejs/kit/node'; +import { createReadableStream } from '@sveltejs/kit/node'; import { Server } from 'SERVER'; import { manifest } from 'MANIFEST'; - -installPolyfills(); +import process from 'node:process'; const server = new Server(manifest); await server.init({ - env: /** @type {Record<string, string>} */ (process.env) + env: /** @type {Record<string, string>} */ (process.env), + read: createReadableStream }); const DATA_SUFFIX = '/__data.json'; -/** - * @param {import('http').IncomingMessage} req - * @param {import('http').ServerResponse} res - */ -export default async (req, res) => { - if (req.url) { - const [path, search] = req.url.split('?'); - - const params = new URLSearchParams(search); - let pathname = params.get('__pathname'); +export default { + /** + * @param {Request} request + * @returns {Promise<Response>} + */ + fetch(request) { + // If this is an ISR request, the requested pathname is encoded + // as a search parameter, so we need to extract it + const url = new URL(request.url); + let pathname = url.searchParams.get('__pathname'); if (pathname) { - params.delete('__pathname'); // Optional routes' pathname replacements look like `/foo/$1/bar` which means we could end up with an url like /foo//bar pathname = pathname.replace(/\/+/g, '/'); - req.url = `${pathname}${path.endsWith(DATA_SUFFIX) ? DATA_SUFFIX : ''}?${params}`; - } - } - /** @type {Request} */ - let request; + url.pathname = pathname + (url.pathname.endsWith(DATA_SUFFIX) ? DATA_SUFFIX : ''); + url.searchParams.delete('__pathname'); - try { - request = await getRequest({ base: `https://${req.headers.host}`, request: req }); - } catch (err) { - res.statusCode = /** @type {any} */ (err).status || 400; - return res.end('Invalid request body'); - } + request = new Request(url, request); + } - setResponse( - res, - await server.respond(request, { + return server.respond(request, { getClientAddress() { return /** @type {string} */ (request.headers.get('x-forwarded-for')); } - }) - ); + }); + } }; diff --git a/packages/adapter-vercel/index.d.ts b/packages/adapter-vercel/index.d.ts index 634f90ed8db9..e3333d04a88e 100644 --- a/packages/adapter-vercel/index.d.ts +++ b/packages/adapter-vercel/index.d.ts @@ -1,14 +1,15 @@ import { Adapter } from '@sveltejs/kit'; import './ambient.js'; +import { RuntimeConfigKey } from './utils.js'; export default function plugin(config?: Config): Adapter; export interface ServerlessConfig { /** - * Whether to use [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions) or [Serverless Functions](https://vercel.com/docs/concepts/functions/serverless-functions) - * @default 'nodejs18.x' + * Whether to use [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions) (`'edge'`) or [Serverless Functions](https://vercel.com/docs/concepts/functions/serverless-functions) (`'nodejs22.x'`, `'nodejs24.x'` etc). + * @default Same as the build environment */ - runtime?: 'nodejs16.x' | 'nodejs18.x'; + runtime?: Exclude<RuntimeConfigKey, 'edge'>; /** * To which regions to deploy the app. A list of regions. * More info: https://vercel.com/docs/concepts/edge-network/regions @@ -28,6 +29,7 @@ export interface ServerlessConfig { * If `true`, this route will always be deployed as its own separate function */ split?: boolean; + /** * [Incremental Static Regeneration](https://vercel.com/docs/concepts/incremental-static-regeneration/overview) configuration. * Serverless only. @@ -37,7 +39,7 @@ export interface ServerlessConfig { /** * Expiration time (in seconds) before the cached asset will be re-generated by invoking the Serverless Function. Setting the value to `false` means it will never expire. */ - expiration: number | false; + expiration: number | string | false; /** * Random token that can be provided in the URL to bypass the cached version of the asset, by requesting the asset * with a __prerender_bypass=<token> cookie. @@ -53,9 +55,30 @@ export interface ServerlessConfig { | false; } +type ImageFormat = 'image/avif' | 'image/webp'; + +type RemotePattern = { + protocol?: 'http' | 'https'; + hostname: string; + port?: string; + pathname?: string; +}; + +type ImagesConfig = { + sizes: number[]; + domains: string[]; + remotePatterns?: RemotePattern[]; + minimumCacheTTL?: number; // seconds + formats?: ImageFormat[]; + dangerouslyAllowSVG?: boolean; + contentSecurityPolicy?: string; + contentDispositionType?: string; +}; + +/** @deprecated */ export interface EdgeConfig { /** - * Whether to use [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions) or [Serverless Functions](https://vercel.com/docs/concepts/functions/serverless-functions) + * Whether to use [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions) (`'edge'`) or [Serverless Functions](https://vercel.com/docs/concepts/functions/serverless-functions) (`'nodejs22.x'`, `'nodejs24.x'` etc). */ runtime?: 'edge'; /** @@ -74,7 +97,12 @@ export interface EdgeConfig { split?: boolean; } -export type Config = EdgeConfig | ServerlessConfig; +export type Config = (EdgeConfig | ServerlessConfig) & { + /** + * https://vercel.com/docs/build-output-api/v3/configuration#images + */ + images?: ImagesConfig; +}; // we copy the RequestContext interface from `@vercel/edge` because that package can't co-exist with `@types/node`. // see https://github.com/sveltejs/kit/pull/9280#issuecomment-1452110035 @@ -82,6 +110,8 @@ export type Config = EdgeConfig | ServerlessConfig; /** * An extension to the standard `Request` object that is passed to every Edge Function. * + * @deprecated - use [`@vercel/functions`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package) instead. + * * @example * ```ts * import type { RequestContext } from '@vercel/edge'; diff --git a/packages/adapter-vercel/index.js b/packages/adapter-vercel/index.js index a46a953dd32f..f54765d7536b 100644 --- a/packages/adapter-vercel/index.js +++ b/packages/adapter-vercel/index.js @@ -1,33 +1,41 @@ +/** @import { BuildOptions } from 'esbuild' */ import fs from 'node:fs'; import path from 'node:path'; +import process from 'node:process'; import { fileURLToPath } from 'node:url'; import { nodeFileTrace } from '@vercel/nft'; import esbuild from 'esbuild'; -import { get_pathname } from './utils.js'; +import { get_pathname, parse_isr_expiration, pattern_to_src, resolve_runtime } from './utils.js'; +import { VERSION } from '@sveltejs/kit'; -const VALID_RUNTIMES = ['edge', 'nodejs16.x', 'nodejs18.x']; +/** + * @template T + * @template {keyof T} K + * @typedef {Partial<Omit<T, K>> & Required<Pick<T, K>>} PartialExcept + */ -const DEFAULT_FUNCTION_NAME = 'fn'; +/** + * We use a custom `Builder` type here to support the minimum version of SvelteKit. + * @typedef {PartialExcept<import('@sveltejs/kit').Builder, 'log' | 'rimraf' | 'mkdirp' | 'config' | 'prerendered' | 'routes' | 'createEntries' | 'findServerAssets' | 'generateFallback' | 'generateEnvModule' | 'generateManifest' | 'getBuildDirectory' | 'getClientDirectory' | 'getServerDirectory' | 'getAppPath' | 'writeClient' | 'writePrerendered' | 'writePrerendered' | 'writeServer' | 'copy' | 'compress'>} Builder2_4_0 + */ -const get_default_runtime = () => { - const major = process.version.slice(1).split('.')[0]; - if (major === '16') return 'nodejs16.x'; - if (major === '18') return 'nodejs18.x'; +const name = '@sveltejs/adapter-vercel'; +const INTERNAL = '![-]'; // this name is guaranteed not to conflict with user routes - throw new Error( - `Unsupported Node.js version: ${process.version}. Please use Node 16 or Node 18 to build your project, or explicitly specify a runtime in your adapter configuration.` - ); -}; +const [kit_major, kit_minor] = VERSION.split('.'); + +// https://vercel.com/docs/functions/edge-functions/edge-runtime#compatible-node.js-modules +const compatible_node_modules = ['async_hooks', 'events', 'buffer', 'assert', 'util']; -/** @type {import('.').default} **/ +/** @type {import('./index.js').default} **/ const plugin = function (defaults = {}) { if ('edge' in defaults) { throw new Error("{ edge: true } has been removed in favour of { runtime: 'edge' }"); } return { - name: '@sveltejs/adapter-vercel', - + name, + /** @param {Builder2_4_0} builder */ async adapt(builder) { if (!builder.routes) { throw new Error( @@ -55,44 +63,65 @@ const plugin = function (defaults = {}) { functions: `${dir}/functions` }; - const static_config = static_vercel_config(builder); + builder.log.minor('Copying assets...'); + + builder.writeClient(dirs.static); + builder.writePrerendered(dirs.static); + + const static_config = static_vercel_config(builder, defaults, dirs.static); builder.log.minor('Generating serverless function...'); /** * @param {string} name - * @param {import('.').ServerlessConfig} config - * @param {import('@sveltejs/kit').RouteDefinition<import('.').Config>[]} routes + * @param {import('./index.js').ServerlessConfig} config + * @param {import('@sveltejs/kit').RouteDefinition<import('./index.js').Config>[]} routes */ async function generate_serverless_function(name, config, routes) { - const relativePath = path.posix.relative(tmp, builder.getServerDirectory()); + const dir = `${dirs.functions}/${name}.func`; + const relativePath = path.posix.relative(tmp, builder.getServerDirectory()); builder.copy(`${files}/serverless.js`, `${tmp}/index.js`, { replace: { SERVER: `${relativePath}/index.js`, MANIFEST: './manifest.js' } }); + if (builder.hasServerInstrumentationFile?.()) { + builder.instrument?.({ + entrypoint: `${tmp}/index.js`, + instrumentation: `${builder.getServerDirectory()}/instrumentation.server.js` + }); + } write( `${tmp}/manifest.js`, `export const manifest = ${builder.generateManifest({ relativePath, routes })};\n` ); - await create_function_bundle( - builder, - `${tmp}/index.js`, - `${dirs.functions}/${name}.func`, - config - ); + await create_function_bundle(builder, `${tmp}/index.js`, dir, config); + + for (const asset of builder.findServerAssets(routes)) { + // TODO use symlinks, once Build Output API supports doing so + builder.copy(`${builder.getServerDirectory()}/${asset}`, `${dir}/${asset}`); + } } + let warned = false; + /** * @param {string} name - * @param {import('.').EdgeConfig} config - * @param {import('@sveltejs/kit').RouteDefinition<import('.').EdgeConfig>[]} routes + * @param {import('./index.js').EdgeConfig} config + * @param {import('@sveltejs/kit').RouteDefinition<import('./index.js').EdgeConfig>[]} routes */ async function generate_edge_function(name, config, routes) { + if (!warned) { + warned = true; + builder.log.warn( + `The \`runtime: 'edge'\` option is deprecated, and will be removed in a future version of adapter-vercel` + ); + } + const tmp = builder.getBuildDirectory(`vercel-tmp/${name}`); const relativePath = path.posix.relative(tmp, builder.getServerDirectory()); @@ -108,20 +137,103 @@ const plugin = function (defaults = {}) { `export const manifest = ${builder.generateManifest({ relativePath, routes })};\n` ); - await esbuild.build({ - entryPoints: [`${tmp}/edge.js`], - outfile: `${dirs.functions}/${name}.func/index.js`, - target: 'es2020', // TODO verify what the edge runtime supports - bundle: true, - platform: 'browser', - format: 'esm', - external: config.external, - sourcemap: 'linked', - banner: { js: 'globalThis.global = globalThis;' }, - loader: { - '.wasm': 'copy' + try { + const outdir = `${dirs.functions}/${name}.func`; + /** @type {BuildOptions} */ + const esbuild_config = { + // minimum Node.js version supported is v14.6.0 that is mapped to ES2019 + // https://edge-runtime.vercel.app/features/polyfills + // TODO verify the latest ES version the edge runtime supports + target: 'es2020', + bundle: true, + platform: 'browser', + conditions: [ + // Vercel's Edge runtime key https://runtime-keys.proposal.wintercg.org/#edge-light + 'edge-light', + // re-include these since they are included by default when no conditions are specified + // https://esbuild.github.io/api/#conditions + 'module' + ], + format: 'esm', + external: [ + ...compatible_node_modules, + ...compatible_node_modules.map((id) => `node:${id}`), + ...(config.external || []) + ], + sourcemap: 'linked', + banner: { js: 'globalThis.global = globalThis;' }, + loader: { + '.wasm': 'copy', + '.woff': 'copy', + '.woff2': 'copy', + '.ttf': 'copy', + '.eot': 'copy', + '.otf': 'copy' + } + }; + const result = await esbuild.build({ + entryPoints: [`${tmp}/edge.js`], + outfile: `${outdir}/index.js`, + ...esbuild_config + }); + + let instrumentation_result; + if (builder.hasServerInstrumentationFile?.()) { + instrumentation_result = await esbuild.build({ + entryPoints: [`${builder.getServerDirectory()}/instrumentation.server.js`], + outfile: `${outdir}/instrumentation.server.js`, + ...esbuild_config + }); + + builder.instrument?.({ + entrypoint: `${outdir}/index.js`, + instrumentation: `${outdir}/instrumentation.server.js`, + module: { + generateText: generate_traced_edge_module + } + }); } - }); + + const warnings = instrumentation_result + ? [...result.warnings, ...instrumentation_result.warnings] + : result.warnings; + + if (warnings.length > 0) { + const formatted = await esbuild.formatMessages(warnings, { + kind: 'warning', + color: true + }); + + console.error(formatted.join('\n')); + } + } catch (err) { + const error = /** @type {import('esbuild').BuildFailure} */ (err); + for (const e of error.errors) { + for (const node of e.notes) { + const match = + /The package "(.+)" wasn't found on the file system but is built into node/.exec( + node.text + ); + + if (match) { + node.text = `Cannot use "${match[1]}" when deploying to Vercel Edge Functions.`; + } + } + } + + const formatted = await esbuild.formatMessages(error.errors, { + kind: 'error', + color: true + }); + + console.error(formatted.join('\n')); + + throw new Error( + `Bundling with esbuild failed with ${error.errors.length} ${ + error.errors.length === 1 ? 'error' : 'errors' + }` + ); + } write( `${dirs.functions}/${name}.func/.vc-config.json`, @@ -129,7 +241,11 @@ const plugin = function (defaults = {}) { { runtime: config.runtime, regions: config.regions, - entrypoint: 'index.js' + entrypoint: 'index.js', + framework: { + slug: 'sveltekit', + version: VERSION + } }, null, '\t' @@ -137,7 +253,7 @@ const plugin = function (defaults = {}) { ); } - /** @type {Map<string, { i: number, config: import('.').Config, routes: import('@sveltejs/kit').RouteDefinition<import('.').Config>[] }>} */ + /** @type {Map<string, { i: number, config: import('./index.js').Config, routes: import('@sveltejs/kit').RouteDefinition<import('./index.js').Config>[] }>} */ const groups = new Map(); /** @type {Map<string, { hash: string, route_id: string }>} */ @@ -146,7 +262,7 @@ const plugin = function (defaults = {}) { /** @type {Map<string, string>} */ const functions = new Map(); - /** @type {Map<import('@sveltejs/kit').RouteDefinition<import('.').Config>, { expiration: number | false, bypassToken: string | undefined, allowQuery: string[], group: number, passQuery: true }>} */ + /** @type {Map<import('@sveltejs/kit').RouteDefinition<import('./index.js').Config>, { expiration: number | false, bypassToken: string | undefined, allowQuery: string[], group: number, passQuery: true }>} */ const isr_config = new Map(); /** @type {Set<string>} */ @@ -154,8 +270,8 @@ const plugin = function (defaults = {}) { // group routes by config for (const route of builder.routes) { - const runtime = route.config?.runtime ?? defaults?.runtime ?? get_default_runtime(); - const config = { runtime, ...defaults, ...route.config }; + const runtime = resolve_runtime(defaults.runtime, route.config.runtime); + const config = { ...defaults, ...route.config, runtime }; if (is_prerendered(route)) { if (config.isr) { @@ -164,20 +280,12 @@ const plugin = function (defaults = {}) { continue; } - if (runtime && !VALID_RUNTIMES.includes(runtime)) { - throw new Error( - `Invalid runtime '${runtime}' for route ${ - route.id - }. Valid runtimes are ${VALID_RUNTIMES.join(', ')}` - ); - } - if (config.isr) { const directory = path.relative('.', builder.config.kit.files.routes + route.id); - if (runtime !== 'nodejs16.x' && runtime !== 'nodejs18.x') { + if (runtime === 'edge') { throw new Error( - `${directory}: Routes using \`isr\` must use either \`runtime: 'nodejs16.x'\` or \`runtime: 'nodejs18.x'\`` + `${directory}: Routes using \`isr\` must use a Node.js or Bun runtime (for example 'nodejs24.x' or 'experimental_bun1.x')` ); } @@ -243,7 +351,7 @@ const plugin = function (defaults = {}) { group.config.runtime === 'edge' ? generate_edge_function : generate_serverless_function; // generate one function for the group - const name = singular ? DEFAULT_FUNCTION_NAME : `fn-${group.i}`; + const name = singular ? `${INTERNAL}/catchall` : `${INTERNAL}/${group.i}`; await generate_function( name, @@ -256,23 +364,27 @@ const plugin = function (defaults = {}) { } } - for (const route of builder.routes) { - if (is_prerendered(route)) continue; + if (!singular) { + // we need to create a catch-all route so that 404s are handled + // by SvelteKit rather than Vercel - const pattern = route.pattern.toString(); + const runtime = resolve_runtime(defaults.runtime); + const generate_function = + runtime === 'edge' ? generate_edge_function : generate_serverless_function; - let src = pattern - // remove leading / and trailing $/ - .slice(1, -2) - // replace escaped \/ with / - .replace(/\\\//g, '/'); + await generate_function( + `${INTERNAL}/catchall`, + /** @type {any} */ ({ ...defaults, runtime }), + [] + ); + } - // replace the root route "^/" with "^/?" - if (src === '^/') { - src = '^/?'; - } + for (const route of builder.routes) { + if (is_prerendered(route)) continue; - const name = functions.get(pattern) ?? 'fn-0'; + const pattern = route.pattern.toString(); + const src = pattern_to_src(pattern); + const name = functions.get(pattern); const isr = isr_config.get(route); if (isr) { @@ -289,7 +401,11 @@ const plugin = function (defaults = {}) { fs.symlinkSync(`../${relative}`, `${base}/__data.json.func`); const pathname = get_pathname(route); - const json = JSON.stringify(isr, null, '\t'); + const json = JSON.stringify( + { ...isr, expiration: parse_isr_expiration(isr.expiration, route.id) }, + null, + '\t' + ); write(`${base}.prerender-config.json`, json); write(`${base}/__data.json.prerender-config.json`, json); @@ -305,43 +421,93 @@ const plugin = function (defaults = {}) { src: src + '/__data.json$', dest: `/${isr_name}/__data.json${q}` }); - } else if (!singular) { - static_config.routes.push({ src: src + '(?:/__data.json)?$', dest: `/${name}` }); - } - } + } else { + // Create a symlink for each route to the main function for better observability + // (without this, every request appears to go through `/![-]`) - if (!singular) { - // we need to create a catch-all route so that 404s are handled - // by SvelteKit rather than Vercel + // Use 'index' for the root route's filesystem representation + // Use an empty string ('') for the root route's destination name part in Vercel config + const is_root = route.id === '/'; + const route_fs_name = is_root ? 'index' : route.id.slice(1); + const route_dest_name = is_root ? '' : route.id.slice(1); - const runtime = defaults.runtime ?? get_default_runtime(); - const generate_function = - runtime === 'edge' ? generate_edge_function : generate_serverless_function; + // Define paths using path.join for safety + const base_dir = path.join(dirs.functions, route_fs_name); // e.g., .vercel/output/functions/index + // The main symlink should be named based on the route, adjacent to its potential directory + const main_symlink_path = `${base_dir}.func`; // e.g., .vercel/output/functions/index.func + // The data symlink goes inside the directory + const data_symlink_path = path.join(base_dir, '__data.json.func'); // e.g., .vercel/output/functions/index/__data.json.func - await generate_function( - DEFAULT_FUNCTION_NAME, - /** @type {any} */ ({ runtime, ...defaults }), + const target = path.join(dirs.functions, `${name}.func`); // The actual function directory e.g., .vercel/output/functions/![-].func + + // Ensure the directory for the data endpoint symlink exists (e.g., functions/index/) + builder.mkdirp(base_dir); + + // Calculate relative paths FROM the directory containing the symlink TO the target + const relative_for_main = path.relative(path.dirname(main_symlink_path), target); + const relative_for_data = path.relative(path.dirname(data_symlink_path), target); // This is path.relative(base_dir, target) + + // Create symlinks + fs.symlinkSync(relative_for_main, main_symlink_path); // Creates functions/index.func -> ![-].func + fs.symlinkSync(relative_for_data, data_symlink_path); // Creates functions/index/__data.json.func -> ../![-].func + + // Add route to the config + static_config.routes.push({ + src: src + '(?:/__data.json)?$', // Matches the incoming request path + dest: `/${route_dest_name}` // Maps to the function: '/' for root, '/about' for about, etc. + // Vercel uses this dest to find the corresponding .func dir/symlink + }); + } + } + + // optional chaining to support older versions that don't have this setting yet + if (builder.config.kit.router?.resolution === 'server') { + // Create a separate edge function just for server-side route resolution. + // By omitting all routes we're ensuring it's small (the routes will still be available + // to the route resolution, because it does not rely on the server routing manifest) + await generate_edge_function( + `${builder.config.kit.appDir}/route`, + { + external: 'external' in defaults ? defaults.external : undefined, + runtime: 'edge' + }, [] ); + + static_config.routes.push({ + src: `${builder.config.kit.paths.base}/(|.+/)__route\\.js`, + dest: `${builder.config.kit.paths.base}/${builder.config.kit.appDir}/route` + }); } // Catch-all route must come at the end, otherwise it will swallow all other routes, // including ISR aliases if there is only one function - static_config.routes.push({ src: '/.*', dest: `/${DEFAULT_FUNCTION_NAME}` }); - - builder.log.minor('Copying assets...'); - - builder.writeClient(dirs.static); - builder.writePrerendered(dirs.static); + static_config.routes.push({ src: '/.*', dest: `/${INTERNAL}/catchall` }); builder.log.minor('Writing routes...'); write(`${dir}/config.json`, JSON.stringify(static_config, null, '\t')); + }, + + supports: { + read: ({ config, route }) => { + const runtime = config.runtime ?? defaults.runtime; + + // TODO bump peer dep in next adapter major to simplify this + if (runtime === 'edge' && kit_major === '2' && kit_minor < '25') { + throw new Error( + `${name}: Cannot use \`read\` from \`$app/server\` in route \`${route.id}\` configured with \`runtime: 'edge'\` and SvelteKit < 2.25.0` + ); + } + + return true; + }, + instrumentation: () => true } }; }; -/** @param {import('.').EdgeConfig & import('.').ServerlessConfig} config */ +/** @param {import('./index.js').EdgeConfig & import('./index.js').ServerlessConfig} config */ function hash_config(config) { return [ config.runtime ?? '', @@ -368,15 +534,32 @@ function write(file, data) { } // This function is duplicated in adapter-static -/** @param {import('@sveltejs/kit').Builder} builder */ -function static_vercel_config(builder) { +/** + * @param {Builder2_4_0} builder + * @param {import('./index.js').Config} config + * @param {string} dir + */ +function static_vercel_config(builder, config, dir) { /** @type {any[]} */ const prerendered_redirects = []; /** @type {Record<string, { path: string }>} */ const overrides = {}; - for (const [src, redirect] of builder.prerendered.redirects) { + /** @type {import('./index.js').ImagesConfig | undefined} */ + const images = config.images; + + for (let [src, redirect] of builder.prerendered.redirects) { + if (src.replace(/\/$/, '') === redirect.location.replace(/\/$/, '')) { + // ignore the extreme edge case of a `/foo` -> `/foo/` redirect, + // which would only arise if the response was generated by a + // `handle` hook or outside the app altogether (since you + // can't declaratively create both routes) + } else { + // redirect both `/foo` and `/foo/` to `redirect.location` + src = src.replace(/\/?$/, '/?'); + } + prerendered_redirects.push({ src, headers: { @@ -407,29 +590,70 @@ function static_vercel_config(builder) { overrides[page.file] = { path: overrides_path }; } - return { - version: 3, - routes: [ - ...prerendered_redirects, - { - src: `/${builder.getAppPath()}/immutable/.+`, - headers: { - 'cache-control': 'public, immutable, max-age=31536000' + const routes = [ + ...prerendered_redirects, + { + src: `/${builder.getAppPath()}/immutable/.+`, + headers: { + 'cache-control': 'public, immutable, max-age=31536000' + } + } + ]; + + // https://vercel.com/docs/deployments/skew-protection + if (process.env.VERCEL_SKEW_PROTECTION_ENABLED) { + routes.push({ + src: '/.*', + has: [ + { + type: 'header', + key: 'Sec-Fetch-Dest', + value: 'document' } + ], + headers: { + 'Set-Cookie': `__vdpl=${process.env.VERCEL_DEPLOYMENT_ID}; Path=${builder.config.kit.paths.base}/; SameSite=Strict; Secure; HttpOnly` }, - { - handle: 'filesystem' - } - ], - overrides + continue: true + }); + + // this is a dreadful hack that is necessary until the Vercel Build Output API + // allows you to set multiple cookies for a single route. essentially, since we + // know that the entry file will be requested immediately, we can set the second + // cookie in _that_ response rather than the document response + const base = `${dir}/${builder.config.kit.appDir}/immutable/entry`; + const entry = fs.readdirSync(base).find((file) => file.startsWith('start.')); + + if (!entry) { + throw new Error('Could not find entry point'); + } + + routes.splice(-2, 0, { + src: `/${builder.getAppPath()}/immutable/entry/${entry}`, + headers: { + 'Set-Cookie': `__vdpl=; Path=/${builder.getAppPath()}/version.json; SameSite=Strict; Secure; HttpOnly` + }, + continue: true + }); + } + + routes.push({ + handle: 'filesystem' + }); + + return { + version: 3, + routes, + overrides, + images }; } /** - * @param {import('@sveltejs/kit').Builder} builder + * @param {Builder2_4_0} builder * @param {string} entry * @param {string} dir - * @param {import('.').ServerlessConfig} config + * @param {import('./index.js').ServerlessConfig} config */ async function create_function_bundle(builder, entry, dir, config) { fs.rmSync(dir, { force: true, recursive: true }); @@ -532,7 +756,11 @@ async function create_function_bundle(builder, entry, dir, config) { maxDuration: config.maxDuration, handler: path.relative(base + ancestor, entry), launcherType: 'Nodejs', - experimentalResponseStreaming: !config.isr + experimentalResponseStreaming: !config.isr, + framework: { + slug: 'sveltekit', + version: VERSION + } }, null, '\t' @@ -544,7 +772,7 @@ async function create_function_bundle(builder, entry, dir, config) { /** * - * @param {import('@sveltejs/kit').Builder} builder + * @param {Builder2_4_0} builder * @param {any} vercel_config */ function validate_vercel_json(builder, vercel_config) { @@ -601,4 +829,23 @@ function is_prerendered(route) { ); } +/** + * @param {{ instrumentation: string; start: string }} opts + */ +function generate_traced_edge_module({ instrumentation, start }) { + return `\ +import './${instrumentation}'; +const promise = import('./${start}'); + +/** + * @param {import('http').IncomingMessage} req + * @param {import('http').ServerResponse} res + */ +export default async (req, res) => { + const { default: handler } = await promise; + return handler(req, res); +} +`; +} + export default plugin; diff --git a/packages/adapter-vercel/package.json b/packages/adapter-vercel/package.json index e9af9d7f78f2..cd40402bc12e 100644 --- a/packages/adapter-vercel/package.json +++ b/packages/adapter-vercel/package.json @@ -1,14 +1,22 @@ { "name": "@sveltejs/adapter-vercel", - "version": "3.0.3", + "version": "6.3.0", "description": "A SvelteKit adapter that creates a Vercel app", + "keywords": [ + "adapter", + "deploy", + "hosting", + "svelte", + "sveltekit", + "vercel" + ], "repository": { "type": "git", - "url": "https://github.com/sveltejs/kit", + "url": "git+https://github.com/sveltejs/kit.git", "directory": "packages/adapter-vercel" }, "license": "MIT", - "homepage": "https://kit.svelte.dev", + "homepage": "https://svelte.dev/docs/kit/adapter-vercel", "type": "module", "exports": { ".": { @@ -22,25 +30,30 @@ "files", "index.js", "utils.js", - "index.d.ts" + "index.d.ts", + "ambient.d.ts" ], "scripts": { - "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", + "lint": "prettier --check .", "format": "pnpm lint --write", "check": "tsc", "test": "vitest run" }, "dependencies": { - "@vercel/nft": "^0.24.0", - "esbuild": "^0.18.11" + "@vercel/nft": "^1.0.0", + "esbuild": "^0.25.4" }, "devDependencies": { "@sveltejs/kit": "workspace:^", - "@types/node": "^16.18.6", - "typescript": "^4.9.4", - "vitest": "^0.34.5" + "@sveltejs/vite-plugin-svelte": "catalog:", + "@types/node": "catalog:", + "typescript": "^5.3.3", + "vitest": "catalog:" }, "peerDependencies": { - "@sveltejs/kit": "^1.5.0" + "@sveltejs/kit": "^2.4.0" + }, + "engines": { + "node": ">=20.0" } } diff --git a/packages/adapter-vercel/test/utils.spec.js b/packages/adapter-vercel/test/utils.spec.js index 077040135338..2ee4ca2030c0 100644 --- a/packages/adapter-vercel/test/utils.spec.js +++ b/packages/adapter-vercel/test/utils.spec.js @@ -1,5 +1,10 @@ -import { assert, test } from 'vitest'; -import { get_pathname } from '../utils.js'; +import { assert, test, describe } from 'vitest'; +import { get_pathname, parse_isr_expiration, pattern_to_src, resolve_runtime } from '../utils.js'; + +// workaround so that TypeScript doesn't follow that import which makes it pick up that file and then error on missing import aliases +const { parse_route_id } = await import( + new URL('../../kit/src/' + 'utils/routing.js', import.meta.url).href +); /** * @param {import('@sveltejs/kit').RouteDefinition<any>['segments']} segments @@ -14,6 +19,16 @@ test('get_pathname for simple route', () => { run_get_pathname_test([{ content: 'foo', dynamic: false, rest: false }], 'foo'); }); +test('get_pathname for simple route with multiple segments', () => { + run_get_pathname_test( + [ + { content: 'foo', dynamic: false, rest: false }, + { content: 'bar', dynamic: false, rest: false } + ], + 'foo/bar' + ); +}); + test('get_pathname for route with parameters', () => { run_get_pathname_test( [ @@ -33,3 +48,144 @@ test('get_pathname for route with parameters within segment', () => { 'foo-$1/$2-buz' ); }); + +test('get_pathname for route with optional parameters within segment', () => { + run_get_pathname_test( + [ + { content: 'foo-[[bar]]', dynamic: true, rest: false }, + { content: '[[baz]]-buz', dynamic: true, rest: false } + ], + 'foo-$1/$2-buz' + ); +}); + +test('get_pathname for route with rest parameter', () => { + run_get_pathname_test( + [ + { content: 'foo', dynamic: false, rest: false }, + { content: '[[...rest]]', dynamic: true, rest: true } + ], + 'foo$1' + ); +}); + +test('get_pathname for route with required and rest parameter', () => { + run_get_pathname_test( + [ + { content: '[foo]', dynamic: true, rest: false }, + { content: '[...rest]', dynamic: true, rest: true } + ], + '$1$2' + ); +}); + +test('get_pathname for route with required and optional parameter', () => { + run_get_pathname_test( + [ + { content: '[foo]', dynamic: true, rest: false }, + { content: '[[optional]]', dynamic: true, rest: true } + ], + '$1$2' + ); +}); + +test('get_pathname for route with required and optional parameter', () => { + run_get_pathname_test( + [ + { content: '[foo]', dynamic: true, rest: false }, + { content: '[[...rest]]', dynamic: true, rest: true }, + { content: 'bar', dynamic: false, rest: false } + ], + '$1$2/bar' + ); +}); + +/** + * @param {string} route_id + * @param {string} expected + */ +function run_pattern_to_src_test(route_id, expected) { + const { pattern } = parse_route_id(route_id); + assert.equal(pattern_to_src(pattern.toString()), expected); +} + +test('pattern_to_src for simple route', () => { + run_pattern_to_src_test('/', '^/?'); +}); + +test('pattern_to_src for route with parameters', () => { + run_pattern_to_src_test('/foo/[bar]', '^/foo/([^/]+?)/?'); +}); + +test('pattern_to_src for route with optional parameters', () => { + run_pattern_to_src_test('/foo/[[bar]]', '^/foo(/[^/]+)?/?'); +}); + +test('pattern_to_src for route with optional parameter in the middle', () => { + run_pattern_to_src_test('/foo/[[bar]]/baz', '^/foo(/[^/]+)?/baz/?'); +}); + +test('pattern_to_src for route with rest parameter', () => { + run_pattern_to_src_test('/foo/[...bar]', '^/foo(/[^]*)?/?'); +}); + +test('pattern_to_src for route with rest parameter in the middle', () => { + run_pattern_to_src_test('/foo/[...bar]/baz', '^/foo(/[^]*)?/baz/?'); +}); + +describe('parse_isr_expiration', () => { + test.each( + /** @type {const} */ ([ + [1, 1], + ['1', 1], + [false, false], + ['false', false] + ]) + )('works for valid inputs ($0)', (input, output) => { + const result = parse_isr_expiration(input, '/isr'); + assert.equal(result, output); + }); + + test('does not allow floats', () => { + assert.throws(() => parse_isr_expiration(1.5, '/isr'), /should be an integer, in \/isr/); + }); + + test('does not allow `true`', () => { + const val = /** @type {false} */ (true); + assert.throws(() => parse_isr_expiration(val, '/isr'), /should be an integer, in \/isr/); + }); + + test('does not allow negative numbers', () => { + assert.throws(() => parse_isr_expiration(-1, '/isr'), /should be non-negative, in \/isr/); + }); + + test('does not allow strings that do not parse to valid numbers', () => { + assert.throws( + () => parse_isr_expiration('foo', '/isr'), + /value was a string but could not be parsed as an integer, in \/isr/ + ); + }); + + test('does not allow strings that parse to floats', () => { + assert.throws( + () => parse_isr_expiration('1.1', '/isr'), + /value was a string but could not be parsed as an integer, in \/isr/ + ); + }); +}); + +describe('resolve_runtime', () => { + test('prefers override_key over default_key', () => { + const result = resolve_runtime('nodejs20.x', 'experimental_bun1.x'); + assert.equal(result, 'bun1.x'); + }); + + test('uses default_key when override_key is undefined', () => { + const result = resolve_runtime('experimental_bun1.x'); + assert.equal(result, 'bun1.x'); + }); + + test('throws an error when resolving to an invalid runtime', () => { + assert.throws(() => resolve_runtime('node18.x', undefined), /Unsupported runtime: node18.x/); + }); +}); diff --git a/packages/adapter-vercel/tsconfig.json b/packages/adapter-vercel/tsconfig.json index f93d6d1bbcc8..4432dae4102d 100644 --- a/packages/adapter-vercel/tsconfig.json +++ b/packages/adapter-vercel/tsconfig.json @@ -5,14 +5,15 @@ "strict": true, "noEmit": true, "noImplicitAny": true, - "module": "es2022", - "moduleResolution": "node", + "strictNullChecks": true, "target": "es2022", + "module": "node16", + "moduleResolution": "node16", "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { "@sveltejs/kit": ["../kit/types/index"] } }, - "include": ["**/*.js", "index.d.ts", "internal.d.ts"] + "include": ["*.js", "files/**/*.js", "internal.d.ts", "test/**/*.js"] } diff --git a/packages/adapter-vercel/utils.js b/packages/adapter-vercel/utils.js index de10073e8846..55fae7e358de 100644 --- a/packages/adapter-vercel/utils.js +++ b/packages/adapter-vercel/utils.js @@ -1,23 +1,163 @@ +import process from 'node:process'; + /** @param {import("@sveltejs/kit").RouteDefinition<any>} route */ export function get_pathname(route) { let i = 1; - return route.segments + const pathname = route.segments .map((segment) => { if (!segment.dynamic) { - return segment.content; + return '/' + segment.content; } const parts = segment.content.split(/\[(.+?)\](?!\])/); - return parts - .map((content, j) => { - if (j % 2) { - return `$${i++}`; - } else { - return content; - } - }) - .join(''); + let result = ''; + + if ( + parts.length === 3 && + !parts[0] && + !parts[2] && + (parts[1].startsWith('...') || parts[1][0] === '[') + ) { + // Special case: segment is a single optional or rest parameter. + // In that case we don't prepend a slash (also see comment in pattern_to_src). + result = `$${i++}`; + } else { + result = + '/' + + parts + .map((content, j) => { + if (j % 2) { + return `$${i++}`; + } else { + return content; + } + }) + .join(''); + } + + return result; }) - .join('/'); + .join(''); + + return pathname[0] === '/' ? pathname.slice(1) : pathname; +} + +/** + * Adjusts the stringified route regex for Vercel's routing system + * @param {string} pattern stringified route regex + */ +export function pattern_to_src(pattern) { + let src = pattern + // remove leading / and trailing $/ + .slice(1, -2) + // replace escaped \/ with / + .replace(/\\\//g, '/'); + + // replace the root route "^/" with "^/?" + if (src === '^/') { + src = '^/?'; + } + + // Move non-capturing groups that swallow slashes into their following capturing groups. + // This is necessary because during ISR we're using the regex to construct the __pathname + // query parameter: In case of a route like [required]/[...rest] we need to turn them + // into $1$2 and not $1/$2, because if [...rest] is empty, we don't want to have a trailing + // slash in the __pathname query parameter which wasn't there in the original URL, as that + // could result in a false trailing slash redirect in the SvelteKit runtime, leading to infinite redirects. + src = src.replace(/\(\?:\/\((.+?)\)\)/g, '(/$1)'); + + return src; } + +const integer = /^\d+$/; + +/** + * @param {false | string | number} value + * @param {string} route_id + * @returns {number | false} + */ +export function parse_isr_expiration(value, route_id) { + if (value === false || value === 'false') return false; // 1 year + + /** @param {string} desc */ + const err = (desc) => { + throw new Error( + `Invalid isr.expiration value: ${JSON.stringify(value)} (${desc}, in ${route_id})` + ); + }; + + let parsed; + if (typeof value === 'string') { + if (!integer.test(value)) { + err('value was a string but could not be parsed as an integer'); + } + parsed = Number.parseInt(value, 10); + } else { + if (!Number.isInteger(value)) { + err('should be an integer'); + } + parsed = value; + } + + if (Number.isNaN(parsed)) { + err('should be a number'); + } + if (parsed < 0) { + err('should be non-negative'); + } + return parsed; +} + +/** + * @param {string | undefined} default_key + * @param {string | undefined} [override_key] + * @returns {RuntimeKey} + */ +export function resolve_runtime(default_key, override_key) { + const key = (override_key ?? default_key ?? get_default_runtime()).replace('experimental_', ''); + assert_is_valid_runtime(key); + return key; +} + +const valid_node_versions = [20, 22, 24]; +const formatter = new Intl.ListFormat('en', { type: 'disjunction' }); + +/** @returns {RuntimeKey} */ +function get_default_runtime() { + // TODO may someday need to auto-detect Bun, but this will be complicated because you may want to run your build + // with Bun but not have your serverless runtime be in Bun. Vercel will likely have to attach something to `globalThis` or similar + // to tell us what the bun configuration is. + const major = Number(process.version.slice(1).split('.')[0]); + + if (!valid_node_versions.includes(major)) { + throw new Error( + `Unsupported Node.js version: ${process.version}. Please use Node ${formatter.format(valid_node_versions.map((v) => `${v}`))} to build your project, or explicitly specify a runtime in your adapter configuration.` + ); + } + + return `nodejs${/** @type {20 | 22 | 24} */ (major)}.x`; +} + +const valid_runtimes = /** @type {const} */ ([ + 'nodejs20.x', + 'nodejs22.x', + 'nodejs24.x', + 'bun1.x', + 'edge' +]); + +/** + * @param {string} key + * @returns {asserts key is RuntimeKey} + */ +function assert_is_valid_runtime(key) { + if (!valid_runtimes.includes(/** @type {RuntimeKey} */ (key))) { + throw new Error( + `Unsupported runtime: ${key}. Supported runtimes are: ${valid_runtimes.join(', ')}. See the Node.js Version section in your Vercel project settings for info on the currently supported versions.` + ); + } +} + +/** @typedef {Exclude<RuntimeKey, 'bun1.x'> | 'experimental_bun1.x'} RuntimeConfigKey */ +/** @typedef {typeof valid_runtimes[number]} RuntimeKey */ diff --git a/packages/amp/CHANGELOG.md b/packages/amp/CHANGELOG.md index 6e2faf9b7596..336e7c6fe82b 100644 --- a/packages/amp/CHANGELOG.md +++ b/packages/amp/CHANGELOG.md @@ -1,5 +1,56 @@ # @sveltejs/amp +## 1.1.5 +### Patch Changes + + +- chore: add `.git` to the end of `package.json` repository url ([#14134](https://github.com/sveltejs/kit/pull/14134)) + +- Updated dependencies [[`c968aef`](https://github.com/sveltejs/kit/commit/c968aef5727f978244d5160657b4a7ac651384ae)]: + - @sveltejs/kit@2.27.3 + +## 1.1.4 +### Patch Changes + + +- docs: update URLs for new svelte.dev site ([#12857](https://github.com/sveltejs/kit/pull/12857)) + +- Updated dependencies [[`dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46`](https://github.com/sveltejs/kit/commit/dcbe4222a194c5f90cfc0fc020cf065f7a4e4c46), [`4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d`](https://github.com/sveltejs/kit/commit/4cdbf76fbbf0c0ce7f574ef69c8daddcf954d39d), [`3a9b78f04786898ca93f6d4b75ab18d26bc45192`](https://github.com/sveltejs/kit/commit/3a9b78f04786898ca93f6d4b75ab18d26bc45192), [`723eb8b31e6a22c82f730c30e485386c8676b746`](https://github.com/sveltejs/kit/commit/723eb8b31e6a22c82f730c30e485386c8676b746), [`8ec471c875345b751344e67580ff1b772ef2735b`](https://github.com/sveltejs/kit/commit/8ec471c875345b751344e67580ff1b772ef2735b)]: + - @sveltejs/kit@2.7.3 + +## 1.1.3 +### Patch Changes + + +- chore: configure provenance in a simpler manner ([#12570](https://github.com/sveltejs/kit/pull/12570)) + +- Updated dependencies [[`087a43d391fc38b8c008fb39a804dc6988974101`](https://github.com/sveltejs/kit/commit/087a43d391fc38b8c008fb39a804dc6988974101)]: + - @sveltejs/kit@2.5.22 + +## 1.1.2 +### Patch Changes + + +- chore: package provenance ([#12567](https://github.com/sveltejs/kit/pull/12567)) + +- Updated dependencies [[`4930a8443caa53bcecee7b690cd28e429b1c8a20`](https://github.com/sveltejs/kit/commit/4930a8443caa53bcecee7b690cd28e429b1c8a20)]: + - @sveltejs/kit@2.5.21 + +## 1.1.1 + +### Patch Changes + +- chore: add keywords for discovery in npm search ([#12330](https://github.com/sveltejs/kit/pull/12330)) + +- Updated dependencies [[`25acb1d9fce998dccd8050b93cf4142c2b082611`](https://github.com/sveltejs/kit/commit/25acb1d9fce998dccd8050b93cf4142c2b082611), [`642c4a4aff4351b786fe6274aa2f0bf7d905faf9`](https://github.com/sveltejs/kit/commit/642c4a4aff4351b786fe6274aa2f0bf7d905faf9), [`0a0e9aa897123ebec50af08e9385b2ca4fc5bb28`](https://github.com/sveltejs/kit/commit/0a0e9aa897123ebec50af08e9385b2ca4fc5bb28)]: + - @sveltejs/kit@2.5.11 + +## 1.1.0 + +### Minor Changes + +- feat: allow SvelteKit 2 as peer dependency ([#11233](https://github.com/sveltejs/kit/pull/11233)) + ## 1.0.2 ### Patch Changes diff --git a/packages/amp/package.json b/packages/amp/package.json index c204ba87f343..64057390ddd1 100644 --- a/packages/amp/package.json +++ b/packages/amp/package.json @@ -1,14 +1,20 @@ { "name": "@sveltejs/amp", - "version": "1.0.2", + "version": "1.1.5", "description": "AMP integration for SvelteKit", + "keywords": [ + "accelerated mobile pages", + "amp", + "svelte", + "sveltekit" + ], "repository": { "type": "git", - "url": "https://github.com/sveltejs/kit", + "url": "git+https://github.com/sveltejs/kit.git", "directory": "packages/amp" }, "license": "MIT", - "homepage": "https://kit.svelte.dev", + "homepage": "https://svelte.dev", "type": "module", "exports": { ".": { @@ -23,10 +29,15 @@ "index.d.ts" ], "scripts": { - "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore", + "check": "tsc", + "lint": "prettier --check .", "format": "pnpm lint --write" }, + "devDependencies": { + "@sveltejs/kit": "workspace:^", + "typescript": "^5.3.3" + }, "peerDependencies": { - "@sveltejs/kit": "^1.0.0" + "@sveltejs/kit": "^1.0.0 || ^2.0.0" } } diff --git a/packages/amp/tsconfig.json b/packages/amp/tsconfig.json new file mode 100644 index 000000000000..26885cff272f --- /dev/null +++ b/packages/amp/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "target": "es2022", + "module": "node16", + "moduleResolution": "node16", + "allowSyntheticDefaultImports": true + } +} diff --git a/packages/create-svelte/.gitignore b/packages/create-svelte/.gitignore deleted file mode 100644 index 46c461e5b123..000000000000 --- a/packages/create-svelte/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -/dist -/.test-tmp - -# re-enable this once we're out of prerelease -/cli/versions.js - -/templates/*/.svelte -/templates/*/.svelte-kit -/templates/*/build -/templates/*/.cloudflare -/templates/*/.vercel_build_output -/templates/*/.netlify -/templates/*/dist -/templates/*/package \ No newline at end of file diff --git a/packages/create-svelte/CHANGELOG.md b/packages/create-svelte/CHANGELOG.md deleted file mode 100644 index 92a8d4a3f20d..000000000000 --- a/packages/create-svelte/CHANGELOG.md +++ /dev/null @@ -1,1418 +0,0 @@ -# create-svelte - -## 5.1.1 - -### Patch Changes - -- fix: specified `initial-scale=1` on all viewport meta tags ([#10793](https://github.com/sveltejs/kit/pull/10793)) - -- feat: make output reflect user's choice of package manager ([#10811](https://github.com/sveltejs/kit/pull/10811)) - -## 5.1.0 - -### Minor Changes - -- chore: remove `resolution-mode=highest` in template `.npmrc` ([#10635](https://github.com/sveltejs/kit/pull/10635)) - -### Patch Changes - -- feat: improve sverdle reactivity ([#10670](https://github.com/sveltejs/kit/pull/10670)) - -## 5.0.6 - -### Patch Changes - -- chore: bump typescript-eslint to v6 ([#10371](https://github.com/sveltejs/kit/pull/10371)) - -## 5.0.5 - -### Patch Changes - -- chore: upgrade `vitest` to 0.34.0 and `@clack/prompts` to 0.7.0 ([#10240](https://github.com/sveltejs/kit/pull/10240)) - -## 5.0.4 - -### Patch Changes - -- chore: upgrade @fontsource/fira-mono to v5 ([#10445](https://github.com/sveltejs/kit/pull/10445)) - -- chore: upgrade to vitest 0.33.0 ([#10445](https://github.com/sveltejs/kit/pull/10445)) - -## 5.0.3 - -### Patch Changes - -- chore: bump Vite and Svelte dependencies ([#10330](https://github.com/sveltejs/kit/pull/10330)) - -- fix: create lib folder in skeleton project ([#10341](https://github.com/sveltejs/kit/pull/10341)) - -## 5.0.2 - -### Patch Changes - -- fix: npm test should run unit tests ([#10241](https://github.com/sveltejs/kit/pull/10241)) - -- chore: upgrade vitest ([#10236](https://github.com/sveltejs/kit/pull/10236)) - -## 5.0.1 - -### Patch Changes - -- chore: use satisfies keyword in jsdocs, in create-svelte default template ([#10203](https://github.com/sveltejs/kit/pull/10203)) - -## 5.0.0 - -### Major Changes - -- breaking: install Svelte 4 ([#10217](https://github.com/sveltejs/kit/pull/10217)) - -## 4.2.0 - -### Minor Changes - -- feat: Add `data-sveltekit-preload-data` to lib template ([#9863](https://github.com/sveltejs/kit/pull/9863)) - -## 4.1.1 - -### Patch Changes - -- fix: default playwright globs ([#9795](https://github.com/sveltejs/kit/pull/9795)) - -- fix: remove obsolete `ignorePatterns: ['*.cjs']` from .eslintrc.cjs ([#9797](https://github.com/sveltejs/kit/pull/9797)) - -## 4.1.0 - -### Minor Changes - -- feat: set `resolution-mode=highest` in generated `.npmrc` ([#9781](https://github.com/sveltejs/kit/pull/9781)) - -## 4.0.0 - -### Major Changes - -- feat: switch default ESLint plugin from `eslint-plugin-svelte3` to `eslint-plugin-svelte` ([#9749](https://github.com/sveltejs/kit/pull/9749)) - -## 3.3.1 - -### Patch Changes - -- feat: upgrade to Vite 4.3 for faster build times ([#9737](https://github.com/sveltejs/kit/pull/9737)) - -- fix: generate tsconfig/jsconfig correctly for library option ([#9712](https://github.com/sveltejs/kit/pull/9712)) - -## 3.3.0 - -### Minor Changes - -- feat: ignore test files in library skeleton package.json ([#9584](https://github.com/sveltejs/kit/pull/9584)) - -## 3.2.0 - -### Minor Changes - -- feat: upgrade to TypeScript 5 ([#9435](https://github.com/sveltejs/kit/pull/9435)) - -### Patch Changes - -- chore: upgrade to Vite 4.2 ([#9434](https://github.com/sveltejs/kit/pull/9434)) - -## 3.1.2 - -### Patch Changes - -- fix: prevent duplicated lines in project creation CLI ([#9346](https://github.com/sveltejs/kit/pull/9346)) - -## 3.1.1 - -### Patch Changes - -- chore: Add hint for toggling additional options during scaffolding flow. ([#9318](https://github.com/sveltejs/kit/pull/9318)) - -## 3.1.0 - -### Minor Changes - -- feat: use `@clack/prompts` ([#9219](https://github.com/sveltejs/kit/pull/9219)) - -## 3.0.4 - -### Patch Changes - -- docs: inform about adapter-auto not supporting all environments ([#9196](https://github.com/sveltejs/kit/pull/9196)) - -## 3.0.3 - -### Patch Changes - -- chore: enhance library skeleton readme with some library instructions ([#9128](https://github.com/sveltejs/kit/pull/9128)) - -## 3.0.2 - -### Patch Changes - -- chore: avoid running publint twice in prepublish ([#9126](https://github.com/sveltejs/kit/pull/9126)) - -- fix: add `dist` to `pkg.files` and `.gitignore` ([#9124](https://github.com/sveltejs/kit/pull/9124)) - -## 3.0.1 - -### Patch Changes - -- fix: update dependency @sveltejs/package to v2.0.0 ([#9087](https://github.com/sveltejs/kit/pull/9087)) - -## 3.0.0 - -### Major Changes - -- breaking: update library scaffolding for `@sveltejs/package` version 2 ([#8922](https://github.com/sveltejs/kit/pull/8922)) - -## 2.3.4 - -### Patch Changes - -- fix: use new locator API to improve demo test ([#8988](https://github.com/sveltejs/kit/pull/8988)) - -## 2.3.3 - -### Patch Changes - -- chore: bump `@sveltejs/kit` and `@sveltejs/adapter-auto` versions ([#8740](https://github.com/sveltejs/kit/pull/8740)) - -- fix: adjust vite config type to work with vitest options ([#8871](https://github.com/sveltejs/kit/pull/8871)) - -## 2.3.2 - -### Patch Changes - -- fix: remove duplicate space around JSDoc comments removed for TypeScript demo app ([#8684](https://github.com/sveltejs/kit/pull/8684)) - -## 2.3.1 - -### Patch Changes - -- chore: remove obsolete comment from templates ([#8620](https://github.com/sveltejs/kit/pull/8620)) - -## 2.3.0 - -### Minor Changes - -- fix: adjust `app.d.ts` to diminish confusion about imports ([#8477](https://github.com/sveltejs/kit/pull/8477)) - -## 2.2.1 - -### Patch Changes - -- fix: note why TypeScript is always installed for library projects and add jsconfig ([#8484](https://github.com/sveltejs/kit/pull/8484)) - -## 2.2.0 - -### Minor Changes - -- feat: use svelte-check v3 ([#8468](https://github.com/sveltejs/kit/pull/8468)) - -## 2.1.0 - -### Minor Changes - -- feat: create vite.config.ts for TypeScript projects ([#8258](https://github.com/sveltejs/kit/pull/8258)) - -## 2.0.2 - -### Patch Changes - -- fix: address flakiness in playwright test ([#8162](https://github.com/sveltejs/kit/pull/8162)) - -## 2.0.1 - -### Patch Changes - -- chore: no code change, rerelease as 2.0.1 for technical reasons ([#8161](https://github.com/sveltejs/kit/pull/8161)) - -## 2.0.0 - -### Major Changes - -First major release, see below for the history of changes that lead up to this. -Starting from now all releases follow semver and changes will be listed as Major/Minor/Patch - -## 2.0.0-next.204 - -### Patch Changes - -- feat: remove release candidate banner ([#8109](https://github.com/sveltejs/kit/pull/8109)) - -## 2.0.0-next.203 - -### Patch Changes - -- chore: add peerDependencies, add more specific next version ([#8141](https://github.com/sveltejs/kit/pull/8141)) - -## 2.0.0-next.202 - -### Patch Changes - -- feat: vitePreprocess ([#8036](https://github.com/sveltejs/kit/pull/8036)) - -## 2.0.0-next.201 - -### Patch Changes - -- breaking: upgrade to Vite 4 ([#7543](https://github.com/sveltejs/kit/pull/7543)) - -## 2.0.0-next.200 - -### Patch Changes - -- Use `satisfies` operator ([#7861](https://github.com/sveltejs/kit/pull/7861)) - -## 2.0.0-next.199 - -### Patch Changes - -- fix: improve Sverdle a11y ([#7960](https://github.com/sveltejs/kit/pull/7960)) - -## 2.0.0-next.198 - -### Patch Changes - -- Upgrade dependencies in templates ([#7866](https://github.com/sveltejs/kit/pull/7866)) - -## 2.0.0-next.197 - -### Patch Changes - -- Upgrade dependencies ([#7852](https://github.com/sveltejs/kit/pull/7852)) - -## 2.0.0-next.196 - -### Patch Changes - -- Update app.d.ts files ([#7003](https://github.com/sveltejs/kit/pull/7003)) - -## 2.0.0-next.195 - -### Patch Changes - -- breaking: Replace `data-sveltekit-prefetch` with `-preload-code` and `-preload-data` ([#7776](https://github.com/sveltejs/kit/pull/7776)) -- Upgrade to Playwright 1.28.1 ([#7696](https://github.com/sveltejs/kit/pull/7696)) - -## 2.0.0-next.194 - -### Patch Changes - -- fix playwright glob filter ([#7826](https://github.com/sveltejs/kit/pull/7826)) - -## 2.0.0-next.193 - -### Patch Changes - -- Added the option to add Vitest to new projects ([#5708](https://github.com/sveltejs/kit/pull/5708)) - -## 2.0.0-next.192 - -### Patch Changes - -- Ignore Vite timestamp files by default in `create-svelte` templates (added to `.gitignore`) ([#7660](https://github.com/sveltejs/kit/pull/7660)) - -## 2.0.0-next.191 - -### Patch Changes - -- Add `style="display: contents"` to wrapper element by default ([#7652](https://github.com/sveltejs/kit/pull/7652)) - -## 2.0.0-next.190 - -### Patch Changes - -- fix: remove Sverdle from Stackblitz template ([#7448](https://github.com/sveltejs/kit/pull/7448)) - -## 2.0.0-next.189 - -### Patch Changes - -- update dependencies ([#7355](https://github.com/sveltejs/kit/pull/7355)) - -## 2.0.0-next.188 - -### Patch Changes - -- Use aria-current instead of active class in nav ([#7376](https://github.com/sveltejs/kit/pull/7376)) - -## 2.0.0-next.187 - -### Patch Changes - -- Add missing titles and descriptions to the Sverdle pages ([#7351](https://github.com/sveltejs/kit/pull/7351)) -- Use `justify-content: flex-start;` on Sverdle CSS to avoid compatibility issues ([#7352](https://github.com/sveltejs/kit/pull/7352)) - -## 2.0.0-next.186 - -### Patch Changes - -- feat: add reset option to update method of enhance ([#7326](https://github.com/sveltejs/kit/pull/7326)) - -## 2.0.0-next.185 - -### Patch Changes - -- Warn user when they accidentally try to publish the `./` directory ([#7280](https://github.com/sveltejs/kit/pull/7280)) - -## 2.0.0-next.184 - -### Patch Changes - -- fix sverdle guesses incorrectly cleared by form `enhance` ([#7241](https://github.com/sveltejs/kit/pull/7241)) - -## 2.0.0-next.183 - -### Patch Changes - -- feat: extract and export types from create-svelte ([#7111](https://github.com/sveltejs/kit/pull/7111)) - -## 2.0.0-next.182 - -### Patch Changes - -- Work around SSR transform bug ([#7088](https://github.com/sveltejs/kit/pull/7088)) - -## 2.0.0-next.181 - -### Patch Changes - -- Fix template description for SvelteKit demo app ([#7076](https://github.com/sveltejs/kit/pull/7076)) - -## 2.0.0-next.180 - -### Patch Changes - -- Replace /todos page in demo app with /sverdle ([#6979](https://github.com/sveltejs/kit/pull/6979)) - -## 2.0.0-next.179 - -### Patch Changes - -- breaking: rename App.PageError to App.Error ([#6963](https://github.com/sveltejs/kit/pull/6963)) - -## 2.0.0-next.178 - -### Patch Changes - -- fix: prettier not formatting svelte files ([#6866](https://github.com/sveltejs/kit/pull/6866)) - -## 2.0.0-next.177 - -### Patch Changes - -- chore: bump vite ([#6829](https://github.com/sveltejs/kit/pull/6829)) - -## 2.0.0-next.176 - -### Patch Changes - -- SvelteKit 1.0 RC ([#6707](https://github.com/sveltejs/kit/pull/6707)) - -## 2.0.0-next.175 - -### Patch Changes - -- breaking: update use:enhance usage ([#6697](https://github.com/sveltejs/kit/pull/6697)) -- breaking: hooks file rename ([#6697](https://github.com/sveltejs/kit/pull/6697)) - -## 2.0.0-next.174 - -### Patch Changes - -- Remove cookie package from demo app ([#6602](https://github.com/sveltejs/kit/pull/6602)) - -## 2.0.0-next.173 - -### Patch Changes - -- breaking: Replace `POST`/`PUT`/`PATCH`/`DELETE` in `+page.server.js` with `export const actions` ([#6469](https://github.com/sveltejs/kit/pull/6469)) - -## 2.0.0-next.172 - -### Patch Changes - -- Bump vite-plugin-svelte and required vite version ([#6583](https://github.com/sveltejs/kit/pull/6583)) - -## 2.0.0-next.171 - -### Patch Changes - -- Use `invalidateAll()` ([#6493](https://github.com/sveltejs/kit/pull/6493)) - -## 2.0.0-next.170 - -### Patch Changes - -- reorder create-svelte templates such that library is last ([#6481](https://github.com/sveltejs/kit/pull/6481)) - -## 2.0.0-next.169 - -### Patch Changes - -- Move `data-sveltekit-prefetch` to `<nav>` element ([#6442](https://github.com/sveltejs/kit/pull/6442)) - -## 2.0.0-next.168 - -### Patch Changes - -- chore: bump ts version and ensure it works with latest changes ([#6428](https://github.com/sveltejs/kit/pull/6428)) - -## 2.0.0-next.167 - -### Patch Changes - -- breaking: Replace `sveltekit:*` with valid HTML attributes like `data-sveltekit-*` ([#6170](https://github.com/sveltejs/kit/pull/6170)) - -## 2.0.0-next.166 - -### Patch Changes - -- Remove App.PrivateEnv and App.PublicEnv placeholders ([#6413](https://github.com/sveltejs/kit/pull/6413)) - -## 2.0.0-next.165 - -### Patch Changes - -- Update to Vite 3.1.0-beta.1 ([#6407](https://github.com/sveltejs/kit/pull/6407)) - -## 2.0.0-next.164 - -### Patch Changes - -- breaking: rename `$app/env` to `$app/environment`, to disambiguate with `$env/...` ([#6334](https://github.com/sveltejs/kit/pull/6334)) -- Add svelte-kit sync to check scripts in checkjs templates ([#6339](https://github.com/sveltejs/kit/pull/6339)) - -## 2.0.0-next.163 - -### Patch Changes - -- fix type definition issue that caused a svelte-check error when using TS 4.8 ([#6306](https://github.com/sveltejs/kit/pull/6306)) - -## 2.0.0-next.162 - -### Patch Changes - -- remove some unused code ([#6287](https://github.com/sveltejs/kit/pull/6287)) - -## 2.0.0-next.161 - -### Patch Changes - -- feat: add App.PageData type ([#6226](https://github.com/sveltejs/kit/pull/6226)) - -## 2.0.0-next.160 - -### Patch Changes - -- Create TypeScript/JSDoc/vanilla versions of shared template .ts files ([#6253](https://github.com/sveltejs/kit/pull/6253)) -- Create vite.config.ts when creating TypeScript project ([#6253](https://github.com/sveltejs/kit/pull/6253)) -- Invalidate data after form submission ([#6254](https://github.com/sveltejs/kit/pull/6254)) - -## 2.0.0-next.159 - -### Patch Changes - -- Update text referring to route location ([#6134](https://github.com/sveltejs/kit/pull/6134)) - -## 2.0.0-next.158 - -### Patch Changes - -- Add notes about includes/excludes and path aliases ([#6085](https://github.com/sveltejs/kit/pull/6085)) -- setup prettier plugin-search-dirs to enable use with pnpm ([#6101](https://github.com/sveltejs/kit/pull/6101)) - -## 2.0.0-next.157 - -### Patch Changes - -- Run svelte-kit sync before svelte-check in check scripts ([#6037](https://github.com/sveltejs/kit/pull/6037)) - -## 2.0.0-next.156 - -### Patch Changes - -- update `app.d.ts` for library skeleton template ([#6020](https://github.com/sveltejs/kit/pull/6020)) - -## 2.0.0-next.155 - -### Patch Changes - -- make variable names more descriptive ([#5983](https://github.com/sveltejs/kit/pull/5983)) - -## 2.0.0-next.154 - -### Patch Changes - -- remove session remnants ([#5966](https://github.com/sveltejs/kit/pull/5966)) -- remove outdated notes in `api.ts` ([#5964](https://github.com/sveltejs/kit/pull/5964)) - -## 2.0.0-next.153 - -### Patch Changes - -- Update templates ([#5778](https://github.com/sveltejs/kit/pull/5778)) - -## 2.0.0-next.152 - -### Patch Changes - -- Use @sveltejs/kit postinstall lifecycle hook to invoke 'svelte-kit sync' instead of prepare in projects created by create-svelte ([#5760](https://github.com/sveltejs/kit/pull/5760)) - -## 2.0.0-next.151 - -### Patch Changes - -- feat: include reference to `@sveltejs/kit` types in ambient file ([#5745](https://github.com/sveltejs/kit/pull/5745)) - -## 2.0.0-next.150 - -### Patch Changes - -- Remove initial-scale=1 from meta tags ([#5706](https://github.com/sveltejs/kit/pull/5706)) - -## 2.0.0-next.149 - -### Patch Changes - -- uppercase handlers ([#5513](https://github.com/sveltejs/kit/pull/5513)) - -## 2.0.0-next.148 - -### Patch Changes - -- Update dependencies ([#5005](https://github.com/sveltejs/kit/pull/5005)) - -## 2.0.0-next.147 - -### Patch Changes - -- chore: upgrade TypeScript to 4.7.4 ([#5414](https://github.com/sveltejs/kit/pull/5414)) - -## 2.0.0-next.146 - -### Patch Changes - -- Add vite.config.js to the create-svelte templates ([#5332](https://github.com/sveltejs/kit/pull/5332)) -- breaking: switch to vite CLI for dev, build, and preview commands ([#5332](https://github.com/sveltejs/kit/pull/5332)) - -## 2.0.0-next.145 - -### Patch Changes - -- fix todos fail to update in demo app ([#5354](https://github.com/sveltejs/kit/pull/5354)) - -## 2.0.0-next.144 - -### Patch Changes - -- Revert to use ESM eslint config files ([#5293](https://github.com/sveltejs/kit/pull/5293)) - -## 2.0.0-next.143 - -### Patch Changes - -- Enhance docs on importing types in app.d.ts ([#5280](https://github.com/sveltejs/kit/pull/5280)) -- Use ESM eslint config files ([#5263](https://github.com/sveltejs/kit/pull/5263)) -- fix formatting for initial package.json ([#5271](https://github.com/sveltejs/kit/pull/5271)) - -## 2.0.0-next.142 - -### Patch Changes - -- Add descriptions to templates, and make TypeScript options more self-explanatory ([#5221](https://github.com/sveltejs/kit/pull/5221)) - -## 2.0.0-next.141 - -### Patch Changes - -- fix: use inline element in heading ([#5164](https://github.com/sveltejs/kit/pull/5164)) - -## 2.0.0-next.140 - -### Patch Changes - -- Use crypto.randomUUID() instead of @lukeed/uuid ([#5042](https://github.com/sveltejs/kit/pull/5042)) - -## 2.0.0-next.139 - -### Patch Changes - -- correct default package.json format ([#5013](https://github.com/sveltejs/kit/pull/5013)) -- breaking: Replace `%svelte.body%` with `%sveltekit.body%`, etc. ([#5016](https://github.com/sveltejs/kit/pull/5016)) - -## 2.0.0-next.138 - -### Patch Changes - -- Use separate ignore files for prettier and eslint ([#5009](https://github.com/sveltejs/kit/pull/5009)) - -## 2.0.0-next.137 - -### Patch Changes - -- import generated types from `__types/index.d.ts` file ([#4705](https://github.com/sveltejs/kit/pull/4705)) - -## 2.0.0-next.136 - -### Patch Changes - -- Add README ([#4951](https://github.com/sveltejs/kit/pull/4951)) - -## 2.0.0-next.135 - -### Patch Changes - -- remove unnecessary CHANGELOG.md ([#4903](https://github.com/sveltejs/kit/pull/4903)) - -## 2.0.0-next.134 - -### Patch Changes - -- fix lib, module, and target to not override the tsconfig they generate by default ([#4893](https://github.com/sveltejs/kit/pull/4893)) - -## 2.0.0-next.133 - -### Patch Changes - -- Bump `eslint` from version 7 to 8 ([#4553](https://github.com/sveltejs/kit/pull/4553)) - -## 2.0.0-next.132 - -### Patch Changes - -- Remove default `<meta name="description">` and add separate descriptions to each page ([#4686](https://github.com/sveltejs/kit/pull/4686)) - -## 2.0.0-next.131 - -### Patch Changes - -- Make hooks file comply with TypeScript strictest mode ([#4667](https://github.com/sveltejs/kit/pull/4667)) - -## 2.0.0-next.130 - -### Patch Changes - -- Ignore .turbo directory when building templates ([#4638](https://github.com/sveltejs/kit/pull/4638)) -- Disable type checking by default for non-typescript projects. ([#4621](https://github.com/sveltejs/kit/pull/4621)) -- breaking: move non-essential TypeScript compilerOptions into user-editable config ([#4633](https://github.com/sveltejs/kit/pull/4633)) - -## 2.0.0-next.129 - -### Patch Changes - -- type check exception handling on form action ([#4532](https://github.com/sveltejs/kit/pull/4532)) -- Update broken documentation links for `App` namespaces ([#4627](https://github.com/sveltejs/kit/pull/4627)) - -## 2.0.0-next.128 - -### Patch Changes - -- chore: upgrade to Playwright 1.21.0 ([#4601](https://github.com/sveltejs/kit/pull/4601)) - -## 2.0.0-next.127 - -### Patch Changes - -- Fix iOS double-tap zoom on counter buttons ([#4390](https://github.com/sveltejs/kit/pull/4390)) - -## 2.0.0-next.126 - -### Patch Changes - -- fix: use .ts extension for tests when using TypeScript ([#4368](https://github.com/sveltejs/kit/pull/4368)) - -## 2.0.0-next.125 - -### Patch Changes - -- fix: check for app.d.ts rather than global.d.ts ([#4295](https://github.com/sveltejs/kit/pull/4295)) - -## 2.0.0-next.124 - -### Patch Changes - -- Add sync CLI command ([#4182](https://github.com/sveltejs/kit/pull/4182)) -- Upgrade to TypeScript 4.6 ([#4190](https://github.com/sveltejs/kit/pull/4190)) - -## 2.0.0-next.123 - -### Patch Changes - -- Extend user tsconfig from generated .svelte-kit/tsconfig.json ([#4118](https://github.com/sveltejs/kit/pull/4118)) - -## 2.0.0-next.122 - -### Patch Changes - -- remove unnecessary CSS units ([#4115](https://github.com/sveltejs/kit/pull/4115)) - -## 2.0.0-next.121 - -### Patch Changes - -- Add option to create integration tests with Playwright ([#4056](https://github.com/sveltejs/kit/pull/4056)) - -## 2.0.0-next.120 - -### Patch Changes - -- fix `@typescript-eslint/no-empty-interface` lint error when starting a new app with eslint ([#4077](https://github.com/sveltejs/kit/pull/4077)) - -## 2.0.0-next.119 - -### Patch Changes - -- fix: update docs URL for App namespace interfaces ([#4042](https://github.com/sveltejs/kit/pull/4042)) - -## 2.0.0-next.118 - -### Patch Changes - -- make demo app work without JS ([#3970](https://github.com/sveltejs/kit/pull/3970)) - -## 2.0.0-next.117 - -### Patch Changes - -- update comment to remove outdated reference ([#3898](https://github.com/sveltejs/kit/pull/3898)) - -## 2.0.0-next.116 - -### Patch Changes - -- use preserveValueImports flag ([#3064](https://github.com/sveltejs/kit/pull/3064)) - -## 2.0.0-next.115 - -### Patch Changes - -- fix links pointing to multi-page docs ([#3815](https://github.com/sveltejs/kit/pull/3815)) -- upgrade to TypeScript 4.5 ([#3809](https://github.com/sveltejs/kit/pull/3809)) - -## 2.0.0-next.114 - -### Patch Changes - -- Add App namespace for app-level types ([#3670](https://github.com/sveltejs/kit/pull/3670)) -- Remove target option ([#3674](https://github.com/sveltejs/kit/pull/3674)) - -## 2.0.0-next.113 - -### Patch Changes - -- Bump version ([#3610](https://github.com/sveltejs/kit/pull/3610)) - -## 2.0.0-next.112 - -### Patch Changes - -- Try this ([#3608](https://github.com/sveltejs/kit/pull/3608)) - -## 2.0.0-next.111 - -### Patch Changes - -- I think this might be it ([#3606](https://github.com/sveltejs/kit/pull/3606)) - -## 2.0.0-next.110 - -### Patch Changes - -- And again ([#3604](https://github.com/sveltejs/kit/pull/3604)) - -## 2.0.0-next.109 - -### Patch Changes - -- Bump version ([#3602](https://github.com/sveltejs/kit/pull/3602)) - -## 2.0.0-next.108 - -### Patch Changes - -- And again ([#3600](https://github.com/sveltejs/kit/pull/3600)) - -## 2.0.0-next.107 - -### Patch Changes - -- Trying something ([#3598](https://github.com/sveltejs/kit/pull/3598)) - -## 2.0.0-next.105 - -### Patch Changes - -- Another meaningless change ([#3596](https://github.com/sveltejs/kit/pull/3596)) - -## 2.0.0-next.104 - -### Patch Changes - -- typo ([#3593](https://github.com/sveltejs/kit/pull/3593)) - -## 2.0.0-next.103 - -### Patch Changes - -- Tweak README ([#3591](https://github.com/sveltejs/kit/pull/3591)) - -## 2.0.0-next.102 - -### Patch Changes - -- Fix typo ([#3583](https://github.com/sveltejs/kit/pull/3583)) - -## 2.0.0-next.101 - -### Patch Changes - -- bump eslint plugin and parser in template ([#3544](https://github.com/sveltejs/kit/pull/3544)) - -## 2.0.0-next.100 - -### Patch Changes - -- add ESLint configuration for mixed JS/TS codebase ([#3536](https://github.com/sveltejs/kit/pull/3536)) - -## 2.0.0-next.99 - -### Patch Changes - -- Respect Ctrl-C when running create-svelte ([#3472](https://github.com/sveltejs/kit/pull/3472)) -- Make project name an explicit option' ([#3472](https://github.com/sveltejs/kit/pull/3472)) -- Prompt for directory when running create-svelte without argument ([#3472](https://github.com/sveltejs/kit/pull/3472)) - -## 2.0.0-next.98 - -### Patch Changes - -- Add index.js file to `pkg.files` ([#3445](https://github.com/sveltejs/kit/pull/3445)) - -## 2.0.0-next.97 - -### Patch Changes - -- Add a programmatic interface to create-svelte ([#3437](https://github.com/sveltejs/kit/pull/3437)) - -## 2.0.0-next.96 - -### Patch Changes - -- breaking: change app.render signature to (request: Request) => Promise (#3384) ([#3430](https://github.com/sveltejs/kit/pull/3430)) - -## 2.0.0-next.95 - -### Patch Changes - -- Add methodOverrides to default configs ([#3411](https://github.com/sveltejs/kit/pull/3411)) - -## 2.0.0-next.94 - -### Patch Changes - -- Add methodOverride option for submitting PUT/PATCH/DELETE/etc with <form> elements ([#2989](https://github.com/sveltejs/kit/pull/2989)) - -## 2.0.0-next.93 - -### Patch Changes - -- Update template files to include %svelte.assets% ([#3234](https://github.com/sveltejs/kit/pull/3234)) - -## 2.0.0-next.92 - -### Patch Changes - -- Update template to use new page/request APIs ([#3146](https://github.com/sveltejs/kit/pull/3146)) - -## 2.0.0-next.91 - -### Patch Changes - -- Make demo app complies with TypeScript strict mode ([#3095](https://github.com/sveltejs/kit/pull/3095)) - -## 2.0.0-next.90 - -### Patch Changes - -- bump eslint ecmaVersion to 2020 ([#2985](https://github.com/sveltejs/kit/pull/2985)) -- include missing .npmrc in templates ([#2990](https://github.com/sveltejs/kit/pull/2990)) -- Do not gitignore `.env.example` ([#2926](https://github.com/sveltejs/kit/pull/2926)) -- update to esbuild 0.13.15 and other dependency updates ([#2957](https://github.com/sveltejs/kit/pull/2957)) - -## 2.0.0-next.89 - -### Patch Changes - -- Git ignore files with a .env. prefix ([#2905](https://github.com/sveltejs/kit/pull/2905)) - -## 2.0.0-next.88 - -### Patch Changes - -- Improve project scaffolding language ([#2895](https://github.com/sveltejs/kit/pull/2895)) - -## 2.0.0-next.87 - -### Patch Changes - -- Add adapter-auto to template configs ([#2885](https://github.com/sveltejs/kit/pull/2885)) - -## 2.0.0-next.86 - -### Patch Changes - -- Add adapter-auto ([#2867](https://github.com/sveltejs/kit/pull/2867)) -- Add an npm run package command to templates ([#2882](https://github.com/sveltejs/kit/pull/2882)) - -## 2.0.0-next.85 - -### Patch Changes - -- feat: added .env to gitignore for skeleton and default starters ([#2732](https://github.com/sveltejs/kit/pull/2732)) - -## 2.0.0-next.84 - -### Patch Changes - -- Add explicit types in `_api.ts` and `form.ts` for TypeScript and ESLint integration example ([#2657](https://github.com/sveltejs/kit/pull/2657)) - -## 2.0.0-next.83 - -### Patch Changes - -- breaking: drop Node 12 support ([#2604](https://github.com/sveltejs/kit/pull/2604)) - -## 2.0.0-next.82 - -### Patch Changes - -- chore: upgrade to Svelte 3.43.0" ([#2474](https://github.com/sveltejs/kit/pull/2474)) - -## 2.0.0-next.81 - -### Patch Changes - -- fix: provide valid value for `letter-spacing` CSS property ([#2437](https://github.com/sveltejs/kit/pull/2437)) -- update dependencies ([#2447](https://github.com/sveltejs/kit/pull/2447)) - -## 2.0.0-next.80 - -### Patch Changes - -- chore: add links to repository and homepage to package.json ([#2425](https://github.com/sveltejs/kit/pull/2425)) - -## 2.0.0-next.79 - -### Patch Changes - -- Use the name of folder as name in package.json ([#2415](https://github.com/sveltejs/kit/pull/2415)) - -## 2.0.0-next.78 - -### Patch Changes - -- Disable delete button while waiting for API response ([#2172](https://github.com/sveltejs/kit/pull/2172)) - -## 2.0.0-next.77 - -### Patch Changes - -- 94b34fa6: [breaking] standardize final output dir as /build (vs /.svelte-kit) ([#2109](https://github.com/sveltejs/kit/pull/2109)) - -## 2.0.0-next.76 - -### Patch Changes - -- b9e63381: Add DOM to lib in tsconfig ([#1956](https://github.com/sveltejs/kit/pull/1956)) - -## 2.0.0-next.75 - -### Patch Changes - -- fe68e13: Simplify component file names ([#1878](https://github.com/sveltejs/kit/pull/1878)) - -## 2.0.0-next.74 - -### Patch Changes - -- 4c7ccfd: Add \$lib alias to js/tsconfig ([#1860](https://github.com/sveltejs/kit/pull/1860)) - -## 2.0.0-next.73 - -### Patch Changes - -- 2d2fab1: Add favicon to skeleton template ([#1514](https://github.com/sveltejs/kit/pull/1514)) -- 6aa4988: Replace favicon ([#1589](https://github.com/sveltejs/kit/pull/1589)) - -## 2.0.0-next.72 - -### Patch Changes - -- 1739443: Add svelte-check to TS templates ([#1556](https://github.com/sveltejs/kit/pull/1556)) -- 6372690: gitignore package directory ([#1499](https://github.com/sveltejs/kit/pull/1499)) -- f211906: Adjust build-template script to include package.json ([#1555](https://github.com/sveltejs/kit/pull/1555)) - -## 2.0.0-next.71 - -### Patch Changes - -- dad93fc: Fix workspace dependencies ([#1434](https://github.com/sveltejs/kit/pull/1434)) - -## 2.0.0-next.70 - -### Patch Changes - -- d871213: Remove Vite dependency from apps ([#1374](https://github.com/sveltejs/kit/pull/1374)) - -## 2.0.0-next.69 - -### Patch Changes - -- 9cc2508: Ensure template files match Prettier settings ([#1364](https://github.com/sveltejs/kit/pull/1364)) -- f5e626d: Reference Vite/Svelte types inside Kit types ([#1319](https://github.com/sveltejs/kit/pull/1319)) -- 8a402a9: Exclude deploy artifacts from create-svelte package ([#1363](https://github.com/sveltejs/kit/pull/1363)) -- e8bed05: Prompt to npm install before prompting to git init ([#1362](https://github.com/sveltejs/kit/pull/1362)) -- 507e2c3: fix: Prettier not formatting .svelte files ([#1360](https://github.com/sveltejs/kit/pull/1360)) - -## 2.0.0-next.68 - -### Patch Changes - -- 5ed3ed2: Fix usage of request.locals in starter project ([#1344](https://github.com/sveltejs/kit/pull/1344)) - -## 2.0.0-next.67 - -### Patch Changes - -- d15b48a: Add renamed .svelte -> .svelte-kit directory to ignore files ([#1339](https://github.com/sveltejs/kit/pull/1339)) - -## 2.0.0-next.66 - -### Patch Changes - -- 1753987: Use request.locals - -## 2.0.0-next.65 - -### Patch Changes - -- 0befffb: Rename .svelte to .svelte-kit ([#1321](https://github.com/sveltejs/kit/pull/1321)) -- c6fde99: Switch to ESM in config files ([#1323](https://github.com/sveltejs/kit/pull/1323)) - -## 2.0.0-next.64 - -### Patch Changes - -- 3fb191c: Improved install prompts, turn confirms into toggle ([#1312](https://github.com/sveltejs/kit/pull/1312)) - -## 2.0.0-next.63 - -### Patch Changes - -- d19f3de: bump minimum required Svelte version ([#1192](https://github.com/sveltejs/kit/pull/1192)) - -## 2.0.0-next.62 - -### Patch Changes - -- c44f231: Improve a11y on to-do list in template ([#1207](https://github.com/sveltejs/kit/pull/1207)) - -## 2.0.0-next.61 - -### Patch Changes - -- 82955ec: fix: adapt to svelte ids without ?import in vite 2.2.3 - -## 2.0.0-next.60 - -### Patch Changes - -- 1b816b2: Update version of eslint-plugin-svelte3 ([#1195](https://github.com/sveltejs/kit/pull/1195)) -- 6f2b4a6: Update welcome message ([#1196](https://github.com/sveltejs/kit/pull/1196)) -- 6f2b4a6: No adapter by default ([#1196](https://github.com/sveltejs/kit/pull/1196)) - -## 2.0.0-next.59 - -### Patch Changes - -- a2f3f4b: Rename `start` to `preview` in the CLI and package scripts - -## 2.0.0-next.58 - -### Patch Changes - -- 2bf4338: Add .gitignore files to new projects ([#1167](https://github.com/sveltejs/kit/pull/1167)) - -## 2.0.0-next.57 - -### Patch Changes - -- 4645ad5: Remove obsolete vite.ssr config from template ([#1148](https://github.com/sveltejs/kit/pull/1148)) -- 872d734: Hide out-of-view counter from assistive tech ([#1150](https://github.com/sveltejs/kit/pull/1150)) - -## 2.0.0-next.56 - -### Patch Changes - -- cdf4d5b: Show git init instructions when creating new project -- 112d194: Uppercase method in template ([#1119](https://github.com/sveltejs/kit/pull/1119)) - -## 2.0.0-next.55 - -### Patch Changes - -- daf6913: Fix bootstrapping command on about page ([#1105](https://github.com/sveltejs/kit/pull/1105)) - -## 2.0.0-next.54 - -### Patch Changes - -- a84cb88: Fix global.d.ts rename, Windows build issue, missing adapter-node ([#1095](https://github.com/sveltejs/kit/pull/1095)) - -## 2.0.0-next.53 - -### Patch Changes - -- 27c2e1d: Fix CSS on demo app hero image ([#1088](https://github.com/sveltejs/kit/pull/1088)) -- bbeb58f: Include dotfiles when creating new project ([#1084](https://github.com/sveltejs/kit/pull/1084)) -- 6a8e73f: Remove large image from create-svelte ([#1085](https://github.com/sveltejs/kit/pull/1085)) - -## 2.0.0-next.52 - -### Patch Changes - -- f342372: Adding new Hello World templates (default with enhanced style and skeleton) to create-svelte ([#1014](https://github.com/sveltejs/kit/pull/1014)) - -## 2.0.0-next.51 - -### Patch Changes - -- 4cffc14: add global.d.ts to js version ([#1051](https://github.com/sveltejs/kit/pull/1051)) - -## 2.0.0-next.50 - -### Patch Changes - -- 3802c64: Fix build so that the package can be automatically published ([#1001](https://github.com/sveltejs/kit/pull/1001)) - -## 2.0.0-next.49 - -### Patch Changes - -- 3c41d07: Fix preprocess option in template -- 9bb747f: Remove CSS option and simplify ([#989](https://github.com/sveltejs/kit/pull/989)) - -## 2.0.0-next.48 - -### Patch Changes - -- 4c45784: Add ambient types to published files ([#980](https://github.com/sveltejs/kit/pull/980)) - -## 2.0.0-next.47 - -### Patch Changes - -- 59a1e06: Add button:focus CSS styles to index page of default app ([#957](https://github.com/sveltejs/kit/pull/957)) -- 39b6967: Add ambient type definitions for \$app imports ([#917](https://github.com/sveltejs/kit/pull/917)) -- dfbe62b: Add title tag to index page of default app ([#954](https://github.com/sveltejs/kit/pull/954)) - -## 2.0.0-next.46 - -### Patch Changes - -- 570f90c: Update tsconfig to use module and lib es2020 ([#817](https://github.com/sveltejs/kit/pull/817)) -- 8d453c8: Specify minimum Node version number in @sveltejs/kit and add .npmrc to enforce it ([#787](https://github.com/sveltejs/kit/pull/787)) - -## 2.0.0-next.45 - -### Patch Changes - -- dac29c5: allow importing JSON modules ([#792](https://github.com/sveltejs/kit/pull/792)) -- 8dc89ba: Set target to es2019 in default tsconfig.json ([#772](https://github.com/sveltejs/kit/pull/772)) - -## 2.0.0-next.44 - -### Patch Changes - -- 7e51473: fix eslint error in ts starter template, add eslint and prettier ignore config -- 7d42f72: Add a global stylesheet during create-svelte depending on the chosen CSS preprocessor ([#726](https://github.com/sveltejs/kit/pull/726)) - -## 2.0.0-next.43 - -### Patch Changes - -- bdf4ed9: Fix typo in `ignorePatterns` for the `.eslintrc.cjs` generated for TypeScript projects so that `.eslintrc.cjs` correctly ignores itself. ([#701](https://github.com/sveltejs/kit/pull/701)) -- f7badf1: Add '\$service-worker' to paths in tsconfig.json ([#716](https://github.com/sveltejs/kit/pull/716)) -- 9a664e1: Set `.eslintrc.cjs` to ignore all `.cjs` files. ([#707](https://github.com/sveltejs/kit/pull/707)) -- df380e6: Add env options to eslint config ([#722](https://github.com/sveltejs/kit/pull/722)) - -## 2.0.0-next.42 - -### Patch Changes - -- a52cf82: add eslint and prettier setup options ([#632](https://github.com/sveltejs/kit/pull/632)) - -## 2.0.0-next.41 - -### Patch Changes - -- 8024178: remove @sveltejs/app-utils ([#600](https://github.com/sveltejs/kit/pull/600)) - -## 2.0.0-next.40 - -### Patch Changes - -- 8805c6d: Pass adapters directly to svelte.config.cjs ([#579](https://github.com/sveltejs/kit/pull/579)) - -## 2.0.0-next.39 - -### Patch Changes - -- ac3669e: Move Vite config into svelte.config.cjs ([#569](https://github.com/sveltejs/kit/pull/569)) - -## 2.0.0-next.38 - -### Patch Changes - -- c04887c: create-svelte: Include globals.d.ts in tsconfig ([#549](https://github.com/sveltejs/kit/pull/549)) - -## 2.0.0-next.37 - -### Patch Changes - -- c76c9bf: Upgrade Vite ([#544](https://github.com/sveltejs/kit/pull/544)) -- ab28c0a: create-svelte: Remove duplicate types ([#538](https://github.com/sveltejs/kit/pull/538)) - -## 2.0.0-next.36 - -### Patch Changes - -- 0da62eb: create-svelte: Include missing ts-template ([#535](https://github.com/sveltejs/kit/pull/535)) - -## 2.0.0-next.35 - -### Patch Changes - -- bb01514: Actually fix $component => $lib transition ([#529](https://github.com/sveltejs/kit/pull/529)) - -## 2.0.0-next.34 - -### Patch Changes - -- 848687c: Fix location of example `Counter.svelte` component ([#522](https://github.com/sveltejs/kit/pull/522)) - -## 2.0.0-next.33 - -### Patch Changes - -- f7dc6ad: Fix typo in template app -- 5554acc: Add \$lib alias ([#511](https://github.com/sveltejs/kit/pull/511)) -- c0ed7a8: create-svelte: globals.d.ts TSDoc fixes, add vite/client types to js/tsconfig ([#517](https://github.com/sveltejs/kit/pull/517)) - -## 2.0.0-next.32 - -### Patch Changes - -- 97b7ea4: jsconfig for js projects ([#510](https://github.com/sveltejs/kit/pull/510)) - -## 2.0.0-next.31 - -### Patch Changes - -- c3cf3f3: Bump deps ([#492](https://github.com/sveltejs/kit/pull/492)) -- 625747d: create-svelte: bundle production dependencies for SSR ([#486](https://github.com/sveltejs/kit/pull/486)) - -## 2.0.0-next.30 - -### Patch Changes - -- b800049: Include type declarations ([#442](https://github.com/sveltejs/kit/pull/442)) - -## 2.0.0-next.29 - -### Patch Changes - -- 15dd6d6: Fix setup to include vite ([#415](https://github.com/sveltejs/kit/pull/415)) - -## 2.0.0-next.28 - -### Patch Changes - -- Use Vite - -## 2.0.0-next.27 - -### Patch Changes - -- Convert everything to ESM - -## 2.0.0-next.26 - -### Patch Changes - -- 00cbaf6: Rename `_.config.js` to `_.config.cjs` ([#356](https://github.com/sveltejs/kit/pull/356)) - -## 2.0.0-next.25 - -### Patch Changes - -- c9d8d4f: Render to #svelte by default - -## 2.0.0-next.24 - -### Patch Changes - -- 0e45255: Move options behind kit namespace, change paths -> kit.files ([#236](https://github.com/sveltejs/kit/pull/236)) - -## 2.0.0-next.23 - -### Patch Changes - -- Use next tag for all packages - -## 2.0.0-next.22 - -### Patch Changes - -- Bump kit version - -## 2.0.0-next.21 - -### Patch Changes - -- Show create-svelte version when starting a new project - -## 2.0.0-alpha.19 - -### Patch Changes - -- Add svelte-kit start command - -## 2.0.0-alpha.18 - -### Patch Changes - -- rename CLI to svelte-kit -- 0904e22: rename svelte CLI to svelte-kit ([#186](https://github.com/sveltejs/kit/pull/186)) - -## 2.0.0-alpha.16 - -### Patch Changes - -- 5fbc475: Add TypeScript support at project init - -## 2.0.0-alpha.14-15 - -### Patch Changes - -- Add 'here be dragons' warning - -## 2.0.0-alpha.13 - -### Patch Changes - -- d936573: Give newly created app a name based on current directory -- 5ca907c: Use shared mkdirp helper diff --git a/packages/create-svelte/README.md b/packages/create-svelte/README.md deleted file mode 100644 index 4a55cf05b021..000000000000 --- a/packages/create-svelte/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# create-svelte - -A CLI for creating new [SvelteKit](https://kit.svelte.dev) projects. Just run... - -```bash -npm create svelte@latest -``` - -...and follow the prompts. - -## API - -You can also use `create-svelte` programmatically: - -```js -import { create } from 'create-svelte'; - -await create('my-new-app', { - name: 'my-new-app', - template: 'default', // or 'skeleton' or 'skeletonlib' - types: 'checkjs', // or 'typescript' or null; - prettier: false, - eslint: false, - playwright: false, - vitest: false -}); -``` - -`checkjs` means your project will use TypeScript to typecheck JavaScript via [JSDoc comments](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html). - -## License - -[MIT](../../LICENSE). diff --git a/packages/create-svelte/bin.js b/packages/create-svelte/bin.js deleted file mode 100755 index fa5fc87cfb84..000000000000 --- a/packages/create-svelte/bin.js +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env node -import fs from 'node:fs'; -import path from 'node:path'; -import * as p from '@clack/prompts'; -import { bold, cyan, grey, yellow } from 'kleur/colors'; -import { create } from './index.js'; -import { dist, package_manager } from './utils.js'; - -const { version } = JSON.parse(fs.readFileSync(new URL('package.json', import.meta.url), 'utf-8')); -let cwd = process.argv[2] || '.'; - -console.log(` -${grey(`create-svelte version ${version}`)} -`); - -p.intro('Welcome to SvelteKit!'); - -if (cwd === '.') { - const dir = await p.text({ - message: 'Where should we create your project?', - placeholder: ' (hit Enter to use current directory)' - }); - - if (p.isCancel(dir)) process.exit(1); - - if (dir) { - cwd = /** @type {string} */ (dir); - } -} - -if (fs.existsSync(cwd)) { - if (fs.readdirSync(cwd).length > 0) { - const force = await p.confirm({ - message: 'Directory not empty. Continue?', - initialValue: false - }); - - // bail if `force` is `false` or the user cancelled with Ctrl-C - if (force !== true) { - process.exit(1); - } - } -} - -const options = await p.group( - { - template: () => - p.select({ - message: 'Which Svelte app template?', - // @ts-expect-error i have no idea what is going on here - options: fs.readdirSync(dist('templates')).map((dir) => { - const meta_file = dist(`templates/${dir}/meta.json`); - const { title, description } = JSON.parse(fs.readFileSync(meta_file, 'utf8')); - - return { - label: title, - hint: description, - value: dir - }; - }) - }), - - types: () => - p.select({ - message: 'Add type checking with TypeScript?', - initialValue: /** @type {'checkjs' | 'typescript' | null} */ ('checkjs'), - options: [ - { - label: 'Yes, using JavaScript with JSDoc comments', - value: 'checkjs' - }, - { - label: 'Yes, using TypeScript syntax', - value: 'typescript' - }, - { label: 'No', value: null } - ] - }), - - features: () => - p.multiselect({ - message: 'Select additional options (use arrow keys/space bar)', - required: false, - options: [ - { - value: 'eslint', - label: 'Add ESLint for code linting' - }, - { - value: 'prettier', - label: 'Add Prettier for code formatting' - }, - { - value: 'playwright', - label: 'Add Playwright for browser testing' - }, - { - value: 'vitest', - label: 'Add Vitest for unit testing' - } - ] - }) - }, - { onCancel: () => process.exit(1) } -); - -await create(cwd, { - name: path.basename(path.resolve(cwd)), - template: /** @type {'default' | 'skeleton' | 'skeletonlib'} */ (options.template), - types: options.types, - prettier: options.features.includes('prettier'), - eslint: options.features.includes('eslint'), - playwright: options.features.includes('playwright'), - vitest: options.features.includes('vitest') -}); - -p.outro('Your project is ready!'); - -if (options.types === 'typescript') { - console.log(bold('✔ Typescript')); - console.log(' Inside Svelte components, use <script lang="ts">\n'); -} else if (options.types === 'checkjs') { - console.log(bold('✔ Type-checked JavaScript')); - console.log(cyan(' https://www.typescriptlang.org/tsconfig#checkJs\n')); -} else if (options.template === 'skeletonlib') { - const warning = yellow('▲'); - console.log( - `${warning} You chose to not add type checking, but TypeScript will still be installed in order to generate type definitions when building the library\n` - ); -} - -if (options.features.includes('eslint')) { - console.log(bold('✔ ESLint')); - console.log(cyan(' https://github.com/sveltejs/eslint-plugin-svelte\n')); -} - -if (options.features.includes('prettier')) { - console.log(bold('✔ Prettier')); - console.log(cyan(' https://prettier.io/docs/en/options.html')); - console.log(cyan(' https://github.com/sveltejs/prettier-plugin-svelte#options\n')); -} - -if (options.features.includes('playwright')) { - console.log(bold('✔ Playwright')); - console.log(cyan(' https://playwright.dev\n')); -} - -if (options.features.includes('vitest')) { - console.log(bold('✔ Vitest')); - console.log(cyan(' https://vitest.dev\n')); -} - -console.log('Install community-maintained integrations:'); -console.log(cyan(' https://github.com/svelte-add/svelte-add')); - -console.log('\nNext steps:'); -let i = 1; - -const relative = path.relative(process.cwd(), cwd); -if (relative !== '') { - console.log(` ${i++}: ${bold(cyan(`cd ${relative}`))}`); -} - -console.log(` ${i++}: ${bold(cyan(`${package_manager} install`))}`); -// prettier-ignore -console.log(` ${i++}: ${bold(cyan('git init && git add -A && git commit -m "Initial commit"'))} (optional)`); -console.log(` ${i++}: ${bold(cyan(`${package_manager} run dev -- --open`))}`); - -console.log(`\nTo close the dev server, hit ${bold(cyan('Ctrl-C'))}`); -console.log(`\nStuck? Visit us at ${cyan('https://svelte.dev/chat')}`); diff --git a/packages/create-svelte/index.js b/packages/create-svelte/index.js deleted file mode 100755 index d6888950c337..000000000000 --- a/packages/create-svelte/index.js +++ /dev/null @@ -1,161 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { mkdirp, copy, dist } from './utils.js'; - -/** @type {import('./types/index').create} */ -export async function create(cwd, options) { - mkdirp(cwd); - - write_template_files(options.template, options.types, options.name, cwd); - write_common_files(cwd, options, options.name); -} - -/** - * @param {string} template - * @param {'typescript' | 'checkjs' | null} types - * @param {string} name - * @param {string} cwd - */ -function write_template_files(template, types, name, cwd) { - const dir = dist(`templates/${template}`); - copy(`${dir}/assets`, cwd, (name) => name.replace('DOT-', '.')); - copy(`${dir}/package.json`, `${cwd}/package.json`); - - const manifest = `${dir}/files.types=${types}.json`; - const files = /** @type {import('./types/internal').File[]} */ ( - JSON.parse(fs.readFileSync(manifest, 'utf-8')) - ); - - files.forEach((file) => { - const dest = path.join(cwd, file.name); - mkdirp(path.dirname(dest)); - - fs.writeFileSync(dest, file.contents.replace(/~TODO~/g, name)); - }); -} - -/** - * - * @param {string} cwd - * @param {import('./types/internal').Options} options - * @param {string} name - */ -function write_common_files(cwd, options, name) { - const shared = dist('shared.json'); - const { files } = /** @type {import('./types/internal').Common} */ ( - JSON.parse(fs.readFileSync(shared, 'utf-8')) - ); - - const pkg_file = path.join(cwd, 'package.json'); - const pkg = /** @type {any} */ (JSON.parse(fs.readFileSync(pkg_file, 'utf-8'))); - - sort_files(files).forEach((file) => { - const include = file.include.every((condition) => matches_condition(condition, options)); - const exclude = file.exclude.some((condition) => matches_condition(condition, options)); - - if (exclude || !include) return; - - if (file.name === 'package.json') { - const new_pkg = JSON.parse(file.contents); - merge(pkg, new_pkg); - } else { - const dest = path.join(cwd, file.name); - mkdirp(path.dirname(dest)); - fs.writeFileSync(dest, file.contents); - } - }); - - pkg.dependencies = sort_keys(pkg.dependencies); - pkg.devDependencies = sort_keys(pkg.devDependencies); - pkg.name = to_valid_package_name(name); - - fs.writeFileSync(pkg_file, JSON.stringify(pkg, null, '\t') + '\n'); -} - -/** - * @param {import('./types/internal').Condition} condition - * @param {import('./types/internal').Options} options - * @returns {boolean} - */ -function matches_condition(condition, options) { - if (condition === 'default' || condition === 'skeleton' || condition === 'skeletonlib') { - return options.template === condition; - } - if (condition === 'typescript' || condition === 'checkjs') { - return options.types === condition; - } - return options[condition]; -} - -/** - * @param {any} target - * @param {any} source - */ -function merge(target, source) { - for (const key in source) { - if (key in target) { - const target_value = target[key]; - const source_value = source[key]; - - if ( - typeof source_value !== typeof target_value || - Array.isArray(source_value) !== Array.isArray(target_value) - ) { - throw new Error('Mismatched values'); - } - - if (typeof source_value === 'object') { - merge(target_value, source_value); - } else { - target[key] = source_value; - } - } else { - target[key] = source[key]; - } - } -} - -/** @param {Record<string, any>} obj */ -function sort_keys(obj) { - if (!obj) return; - - /** @type {Record<string, any>} */ - const sorted = {}; - Object.keys(obj) - .sort() - .forEach((key) => { - sorted[key] = obj[key]; - }); - - return sorted; -} - -/** - * Sort files so that those which apply more generically come first so they - * can be overwritten by files for more precise cases later. - * - * @param files {import('./types/internal').Common['files']} - * */ -function sort_files(files) { - return files.sort((f1, f2) => { - const f1_more_generic = - f1.include.every((include) => f2.include.includes(include)) && - f1.exclude.every((exclude) => f2.exclude.includes(exclude)); - const f2_more_generic = - f2.include.every((include) => f1.include.includes(include)) && - f2.exclude.every((exclude) => f1.exclude.includes(exclude)); - const same = f1_more_generic && f2_more_generic; - const different = !f1_more_generic && !f2_more_generic; - return same || different ? 0 : f1_more_generic ? -1 : 1; - }); -} - -/** @param {string} name */ -function to_valid_package_name(name) { - return name - .trim() - .toLowerCase() - .replace(/\s+/g, '-') - .replace(/^[._]/, '') - .replace(/[^a-z0-9~.-]+/g, '-'); -} diff --git a/packages/create-svelte/package.json b/packages/create-svelte/package.json deleted file mode 100644 index 28d4636f3390..000000000000 --- a/packages/create-svelte/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "create-svelte", - "version": "5.1.1", - "description": "A CLI for creating new SvelteKit projects", - "repository": { - "type": "git", - "url": "https://github.com/sveltejs/kit", - "directory": "packages/create-svelte" - }, - "license": "MIT", - "homepage": "https://kit.svelte.dev", - "bin": "./bin.js", - "main": "./index.js", - "dependencies": { - "@clack/prompts": "^0.7.0", - "kleur": "^4.1.5" - }, - "devDependencies": { - "@playwright/test": "1.30.0", - "@types/gitignore-parser": "^0.0.2", - "@types/prettier": "^2.7.1", - "gitignore-parser": "^0.0.2", - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.10.1", - "sucrase": "^3.29.0", - "svelte": "^4.0.5", - "tiny-glob": "^0.2.9", - "vitest": "^0.34.5" - }, - "scripts": { - "build": "node scripts/build-templates", - "test": "pnpm build && vitest run", - "check": "tsc", - "lint": "prettier --check . --config ../../.prettierrc --ignore-path ../../.gitignore --ignore-path .gitignore --plugin prettier-plugin-svelte --plugin-search-dir=.", - "format": "pnpm lint --write", - "prepublishOnly": "pnpm build", - "postpublish": "echo \"Updating template repo\" && bash ./scripts/update-template-repo.sh" - }, - "files": [ - "index.js", - "dist", - "bin.js", - "utils.js", - "types" - ], - "types": "types/index.d.ts", - "type": "module" -} diff --git a/packages/create-svelte/scripts/build-templates.js b/packages/create-svelte/scripts/build-templates.js deleted file mode 100644 index b1181243e3e1..000000000000 --- a/packages/create-svelte/scripts/build-templates.js +++ /dev/null @@ -1,286 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import parser from 'gitignore-parser'; -import prettier from 'prettier'; -import { transform } from 'sucrase'; -import glob from 'tiny-glob/sync.js'; -import { mkdirp, rimraf } from '../utils.js'; - -/** @param {string} content */ -function convert_typescript(content) { - let { code } = transform(content, { - transforms: ['typescript'], - disableESTransforms: true - }); - - // sucrase leaves invalid class fields intact - code = code.replace(/^\s*[a-z]+;$/gm, ''); - - // Prettier strips 'unnecessary' parens from .ts files, we need to hack them back in - code = code.replace(/(\/\*\* @type.+? \*\/) (.+?) \/\*\*\*\//g, '$1($2)'); - - return prettier.format(code, { - parser: 'babel', - useTabs: true, - singleQuote: true, - trailingComma: 'none', - printWidth: 100 - }); -} - -/** @param {string} content */ -function strip_jsdoc(content) { - return content - .replace(/ \/\*\*\*\//g, '') - .replace( - /\/\*\*([\s\S]+?)(@[\s\S]+?)?\*\/([\s\n]+)/g, - (match, description, tags, whitespace) => { - if (/^\s+(\*\s*)?$/.test(description)) { - return ''; - } - - return `/**${description.replace(/\* $/, '')}*/${whitespace}`; - } - ); -} - -/** @param {Set<string>} shared */ -async function generate_templates(shared) { - const templates = fs.readdirSync('templates'); - - for (const template of templates) { - if (template[0] === '.') continue; - - const dir = `dist/templates/${template}`; - const assets = `${dir}/assets`; - mkdirp(assets); - - const cwd = path.resolve('templates', template); - - const gitignore_file = path.join(cwd, '.gitignore'); - if (!fs.existsSync(gitignore_file)) { - throw new Error(`"${template}" template must have a .gitignore file`); - } - - const gitignore = parser.compile(fs.readFileSync(gitignore_file, 'utf-8')); - - const ignore_file = path.join(cwd, '.ignore'); - if (!fs.existsSync(ignore_file)) throw new Error('Template must have a .ignore file'); - const ignore = parser.compile(fs.readFileSync(ignore_file, 'utf-8')); - - const meta_file = path.join(cwd, '.meta.json'); - if (!fs.existsSync(meta_file)) throw new Error('Template must have a .meta.json file'); - - /** @type {Record<string, import('../types/internal.js').File[]>} */ - const types = { - typescript: [], - checkjs: [], - null: [] - }; - - glob('**/*', { cwd, filesOnly: true, dot: true }).forEach((name) => { - // the package.template.json thing is a bit annoying — basically we want - // to be able to develop and deploy the app from here, but have a different - // package.json in newly created projects (based on package.template.json) - if (name === 'package.template.json') { - let contents = fs.readFileSync(path.join(cwd, name), 'utf8'); - // TODO package-specific versions - contents = contents.replace(/workspace:\*/g, 'next'); - fs.writeFileSync(`${dir}/package.json`, contents); - return; - } - - // ignore files that are written conditionally - if (shared.has(name)) return; - - // ignore contents of .gitignore or .ignore - if (!gitignore.accepts(name) || !ignore.accepts(name) || name === '.ignore') return; - - if (/\.(ts|svelte)$/.test(name)) { - const contents = fs.readFileSync(path.join(cwd, name), 'utf8'); - - if (name.endsWith('.d.ts')) { - if (name.endsWith('app.d.ts')) types.checkjs.push({ name, contents }); - types.typescript.push({ name, contents }); - } else if (name.endsWith('.ts')) { - const js = convert_typescript(contents); - - types.typescript.push({ - name, - contents: strip_jsdoc(contents) - }); - - types.checkjs.push({ - name: name.replace(/\.ts$/, '.js'), - contents: js - }); - - types.null.push({ - name: name.replace(/\.ts$/, '.js'), - contents: strip_jsdoc(js) - }); - } else { - // we jump through some hoops, rather than just using svelte.preprocess, - // so that the output preserves the original formatting to the extent - // possible (e.g. preserving double line breaks). Sucrase is the best - // tool for the job because it just removes the types; Prettier then - // tidies up the end result - const js_contents = contents.replace( - /<script([^>]+)>([\s\S]+?)<\/script>/g, - (m, attrs, typescript) => { - // Sucrase assumes 'unused' imports (which _are_ used, but only - // in the markup) are type imports, and strips them. This step - // prevents it from drawing that conclusion - const imports = []; - const import_pattern = /import (.+?) from/g; - let import_match; - while ((import_match = import_pattern.exec(typescript))) { - const word_pattern = /[a-z_$][a-z0-9_$]*/gi; - let word_match; - while ((word_match = word_pattern.exec(import_match[1]))) { - imports.push(word_match[0]); - } - } - - const suffix = `\n${imports.join(',')}`; - - const transformed = transform(typescript + suffix, { - transforms: ['typescript'], - disableESTransforms: true - }).code.slice(0, -suffix.length); - - const contents = prettier - .format(transformed, { - parser: 'babel', - useTabs: true, - singleQuote: true, - trailingComma: 'none', - printWidth: 100 - }) - .trim() - .replace(/^(.)/gm, '\t$1'); - - return `<script${attrs.replace(' lang="ts"', '')}>\n${contents}\n</script>`; - } - ); - - types.typescript.push({ - name, - contents: strip_jsdoc(contents) - }); - - types.checkjs.push({ - name, - contents: js_contents - }); - - types.null.push({ - name, - contents: strip_jsdoc(js_contents) - }); - } - } else { - const dest = path.join(assets, name.replace(/^\./, 'DOT-')); - mkdirp(path.dirname(dest)); - fs.copyFileSync(path.join(cwd, name), dest); - } - }); - - fs.copyFileSync(meta_file, `${dir}/meta.json`); - fs.writeFileSync( - `${dir}/files.types=typescript.json`, - JSON.stringify(types.typescript, null, '\t') - ); - fs.writeFileSync(`${dir}/files.types=checkjs.json`, JSON.stringify(types.checkjs, null, '\t')); - fs.writeFileSync(`${dir}/files.types=null.json`, JSON.stringify(types.null, null, '\t')); - } -} - -async function generate_shared() { - const cwd = path.resolve('shared'); - - /** @type {Set<string>} */ - const shared = new Set(); - - /** @type {Array<{ name: string, include: string[], exclude: string[], contents: string }>} */ - const files = []; - - glob('**/*', { cwd, filesOnly: true, dot: true }).forEach((file) => { - const contents = fs.readFileSync(path.join(cwd, file), 'utf8'); - - /** @type {string[]} */ - const include = []; - - /** @type {string[]} */ - const exclude = []; - - let name = file; - - if (file.startsWith('+') || file.startsWith('-')) { - const [conditions, ...rest] = file.split(path.sep); - - const pattern = /([+-])([a-z]+)/g; - let match; - while ((match = pattern.exec(conditions))) { - const set = match[1] === '+' ? include : exclude; - set.push(match[2]); - } - - name = rest.join('/'); - } - - if (name.endsWith('.ts') && !include.includes('typescript')) { - // file includes types in TypeScript and JSDoc — - // create .js file, with and without JSDoc - const js = convert_typescript(contents); - const js_name = name.replace(/\.ts$/, '.js'); - - // typescript - files.push({ - name, - include: [...include, 'typescript'], - exclude, - contents: strip_jsdoc(contents) - }); - - // checkjs - files.push({ - name: js_name, - include: [...include, 'checkjs'], - exclude, - contents: js - }); - - // no typechecking - files.push({ - name: js_name, - include, - exclude: [...exclude, 'typescript', 'checkjs'], - contents: strip_jsdoc(js) - }); - - shared.add(name); - shared.add(js_name); - } else { - shared.add(name); - files.push({ name, include, exclude, contents }); - } - }); - - files.sort((a, b) => a.include.length + a.exclude.length - (b.include.length + b.exclude.length)); - - fs.writeFileSync('dist/shared.json', JSON.stringify({ files }, null, '\t')); - - shared.delete('package.json'); - return shared; -} - -async function main() { - rimraf('dist'); - mkdirp('dist'); - - const shared = await generate_shared(); - await generate_templates(shared); -} - -main(); diff --git a/packages/create-svelte/scripts/update-template-repo-contents.js b/packages/create-svelte/scripts/update-template-repo-contents.js deleted file mode 100644 index 78ec904d6a66..000000000000 --- a/packages/create-svelte/scripts/update-template-repo-contents.js +++ /dev/null @@ -1,43 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { create } from '../index.js'; - -const repo = process.argv[2]; - -fs.readdirSync(repo).forEach((file) => { - if (file !== '.git') { - fs.rmSync(path.join(repo, file), { - recursive: true, - force: true - }); - } -}); - -await create(repo, { - name: 'kit-template-default', - template: 'default', - eslint: false, - types: 'checkjs', - prettier: true, - playwright: false, - vitest: false -}); - -// Remove the Sverdle from the template because it doesn't work within Stackblitz (cookies not set) -fs.rmSync(path.join(repo, 'src', 'routes', 'sverdle'), { force: true, recursive: true }); - -const header_file = path.join(repo, 'src', 'routes', 'Header.svelte'); -const header = fs.readFileSync(header_file, 'utf-8'); -fs.writeFileSync( - header_file, - // Remove the Sverdle link from the header - header.replace(/<\/li>\s+<li.+?'\/sverdle'[\s\S]+?<\/li>/, '</li>') -); - -const about_file = path.join(repo, 'src', 'routes', 'about', '+page.svelte'); -const about = fs.readFileSync(about_file, 'utf-8'); -fs.writeFileSync( - about_file, - // Remove the Sverdle paragraph from the about page - about.replace(/<\/p>\s+<p>\s+?[\s\S]+?Sverdle[\s\S]+?<\/p>/, '</p>') -); diff --git a/packages/create-svelte/scripts/update-template-repo.sh b/packages/create-svelte/scripts/update-template-repo.sh deleted file mode 100755 index f3997e29e43e..000000000000 --- a/packages/create-svelte/scripts/update-template-repo.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -get_abs_filename() { - # $1 : relative filename - echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" -} - -DIR=$(get_abs_filename $(dirname "$0")) -TMP=$(get_abs_filename "$DIR/../node_modules/.tmp") - -mkdir -p $TMP -cd $TMP - -if [ "$CI" ]; then - (umask 0077; echo "$UPDATE_TEMPLATE_SSH_KEY" > ~/ssh_key;) - export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=accept-new -i ~/ssh_key' -fi - -# clone the template repo -rm -rf kit-template-default -git clone --depth 1 --single-branch --branch main git@github.com:sveltejs/kit-template-default.git kit-template-default - -# empty out the repo -cd kit-template-default -node $DIR/update-template-repo-contents.js $TMP/kit-template-default - -if [ "$CI" ]; then - git config user.email 'noreply@svelte.dev' - git config user.name '[bot]' -fi - -# commit the new files -git add -A -git commit -m "version $npm_package_version" - -git push git@github.com:sveltejs/kit-template-default.git main -f \ No newline at end of file diff --git a/packages/create-svelte/shared/+checkjs/jsconfig.json b/packages/create-svelte/shared/+checkjs/jsconfig.json deleted file mode 100644 index fe45e13fdd06..000000000000 --- a/packages/create-svelte/shared/+checkjs/jsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true - } - // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files - // - // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes - // from the referenced tsconfig.json - TypeScript does not merge them in -} diff --git a/packages/create-svelte/shared/+checkjs/package.json b/packages/create-svelte/shared/+checkjs/package.json deleted file mode 100644 index dd9befe46702..000000000000 --- a/packages/create-svelte/shared/+checkjs/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "scripts": { - "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch" - }, - "devDependencies": { - "typescript": "^5.0.0", - "svelte-check": "^3.4.3" - } -} diff --git a/packages/create-svelte/shared/+default+checkjs/package.json b/packages/create-svelte/shared/+default+checkjs/package.json deleted file mode 100644 index c06981203bab..000000000000 --- a/packages/create-svelte/shared/+default+checkjs/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "devDependencies": { - "@types/cookie": "^0.5.1" - } -} diff --git a/packages/create-svelte/shared/+default+checkjs/svelte.config.js b/packages/create-svelte/shared/+default+checkjs/svelte.config.js deleted file mode 100644 index 1cf26a00dfea..000000000000 --- a/packages/create-svelte/shared/+default+checkjs/svelte.config.js +++ /dev/null @@ -1,18 +0,0 @@ -import adapter from '@sveltejs/adapter-auto'; -import { vitePreprocess } from '@sveltejs/kit/vite'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - // Consult https://kit.svelte.dev/docs/integrations#preprocessors - // for more information about preprocessors - preprocess: vitePreprocess(), - - kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter() - } -}; - -export default config; diff --git a/packages/create-svelte/shared/+default+typescript/package.json b/packages/create-svelte/shared/+default+typescript/package.json deleted file mode 100644 index c06981203bab..000000000000 --- a/packages/create-svelte/shared/+default+typescript/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "devDependencies": { - "@types/cookie": "^0.5.1" - } -} diff --git a/packages/create-svelte/shared/+default+typescript/svelte.config.js b/packages/create-svelte/shared/+default+typescript/svelte.config.js deleted file mode 100644 index 1cf26a00dfea..000000000000 --- a/packages/create-svelte/shared/+default+typescript/svelte.config.js +++ /dev/null @@ -1,18 +0,0 @@ -import adapter from '@sveltejs/adapter-auto'; -import { vitePreprocess } from '@sveltejs/kit/vite'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - // Consult https://kit.svelte.dev/docs/integrations#preprocessors - // for more information about preprocessors - preprocess: vitePreprocess(), - - kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter() - } -}; - -export default config; diff --git a/packages/create-svelte/shared/+default+vitest/src/routes/sverdle/game.test.ts b/packages/create-svelte/shared/+default+vitest/src/routes/sverdle/game.test.ts deleted file mode 100644 index 99028b6f561e..000000000000 --- a/packages/create-svelte/shared/+default+vitest/src/routes/sverdle/game.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { Game } from './game'; - -describe('game test', () => { - it('returns true when a valid word is entered', () => { - const game = new Game(); - expect(game.enter('zorro'.split(''))).toBe(true); - }); -}); diff --git a/packages/create-svelte/shared/+default-typescript/svelte.config.js b/packages/create-svelte/shared/+default-typescript/svelte.config.js deleted file mode 100644 index 348fa324acaa..000000000000 --- a/packages/create-svelte/shared/+default-typescript/svelte.config.js +++ /dev/null @@ -1,13 +0,0 @@ -import adapter from '@sveltejs/adapter-auto'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter() - } -}; - -export default config; diff --git a/packages/create-svelte/shared/+eslint+prettier+typescript/.eslintrc.cjs b/packages/create-svelte/shared/+eslint+prettier+typescript/.eslintrc.cjs deleted file mode 100644 index ebc19589fa57..000000000000 --- a/packages/create-svelte/shared/+eslint+prettier+typescript/.eslintrc.cjs +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - root: true, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:svelte/recommended', - 'prettier' - ], - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], - parserOptions: { - sourceType: 'module', - ecmaVersion: 2020, - extraFileExtensions: ['.svelte'] - }, - env: { - browser: true, - es2017: true, - node: true - }, - overrides: [ - { - files: ['*.svelte'], - parser: 'svelte-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser' - } - } - ] -}; diff --git a/packages/create-svelte/shared/+eslint+prettier-typescript/.eslintrc.cjs b/packages/create-svelte/shared/+eslint+prettier-typescript/.eslintrc.cjs deleted file mode 100644 index 29c1d31bc5e5..000000000000 --- a/packages/create-svelte/shared/+eslint+prettier-typescript/.eslintrc.cjs +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - root: true, - extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'], - parserOptions: { - sourceType: 'module', - ecmaVersion: 2020, - extraFileExtensions: ['.svelte'] - }, - env: { - browser: true, - es2017: true, - node: true - } -}; diff --git a/packages/create-svelte/shared/+eslint+prettier/package.json b/packages/create-svelte/shared/+eslint+prettier/package.json deleted file mode 100644 index 808ff280d4b8..000000000000 --- a/packages/create-svelte/shared/+eslint+prettier/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "devDependencies": { - "eslint-config-prettier": "^8.5.0" - }, - "scripts": { - "lint": "prettier --plugin-search-dir . --check . && eslint .", - "format": "prettier --plugin-search-dir . --write ." - } -} diff --git a/packages/create-svelte/shared/+eslint+typescript/.eslintrc.cjs b/packages/create-svelte/shared/+eslint+typescript/.eslintrc.cjs deleted file mode 100644 index 9586e74c9f38..000000000000 --- a/packages/create-svelte/shared/+eslint+typescript/.eslintrc.cjs +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = { - root: true, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:svelte/recommended' - ], - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], - parserOptions: { - sourceType: 'module', - ecmaVersion: 2020, - extraFileExtensions: ['.svelte'] - }, - env: { - browser: true, - es2017: true, - node: true - }, - overrides: [ - { - files: ['*.svelte'], - parser: 'svelte-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser' - } - } - ] -}; diff --git a/packages/create-svelte/shared/+eslint+typescript/package.json b/packages/create-svelte/shared/+eslint+typescript/package.json deleted file mode 100644 index d2072f21cd2b..000000000000 --- a/packages/create-svelte/shared/+eslint+typescript/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "devDependencies": { - "@typescript-eslint/eslint-plugin": "^6.0.0", - "@typescript-eslint/parser": "^6.0.0" - } -} diff --git a/packages/create-svelte/shared/+eslint-prettier/package.json b/packages/create-svelte/shared/+eslint-prettier/package.json deleted file mode 100644 index d19da8e92a5e..000000000000 --- a/packages/create-svelte/shared/+eslint-prettier/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "scripts": { - "lint": "eslint ." - } -} diff --git a/packages/create-svelte/shared/+eslint-typescript/.eslintrc.cjs b/packages/create-svelte/shared/+eslint-typescript/.eslintrc.cjs deleted file mode 100644 index e52c4810ae6d..000000000000 --- a/packages/create-svelte/shared/+eslint-typescript/.eslintrc.cjs +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - root: true, - extends: ['eslint:recommended', 'plugin:svelte/recommended'], - parserOptions: { - sourceType: 'module', - ecmaVersion: 2020, - extraFileExtensions: ['.svelte'] - }, - env: { - browser: true, - es2017: true, - node: true - } -}; diff --git a/packages/create-svelte/shared/+eslint/.eslintignore b/packages/create-svelte/shared/+eslint/.eslintignore deleted file mode 100644 index 38972655faff..000000000000 --- a/packages/create-svelte/shared/+eslint/.eslintignore +++ /dev/null @@ -1,13 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example - -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock diff --git a/packages/create-svelte/shared/+eslint/package.json b/packages/create-svelte/shared/+eslint/package.json deleted file mode 100644 index 375357e2deae..000000000000 --- a/packages/create-svelte/shared/+eslint/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "devDependencies": { - "eslint": "^8.28.0", - "eslint-plugin-svelte": "^2.30.0" - } -} diff --git a/packages/create-svelte/shared/+playwright+default/tests/test.ts b/packages/create-svelte/shared/+playwright+default/tests/test.ts deleted file mode 100644 index 09d2c0346e57..000000000000 --- a/packages/create-svelte/shared/+playwright+default/tests/test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from '@playwright/test'; - -test('about page has expected h1', async ({ page }) => { - await page.goto('/about'); - await expect(page.getByRole('heading', { name: 'About this app' })).toBeVisible(); -}); diff --git a/packages/create-svelte/shared/+playwright+skeleton/tests/test.ts b/packages/create-svelte/shared/+playwright+skeleton/tests/test.ts deleted file mode 100644 index 5816be4132cb..000000000000 --- a/packages/create-svelte/shared/+playwright+skeleton/tests/test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from '@playwright/test'; - -test('index page has expected h1', async ({ page }) => { - await page.goto('/'); - await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible(); -}); diff --git a/packages/create-svelte/shared/+playwright+skeletonlib/tests/test.ts b/packages/create-svelte/shared/+playwright+skeletonlib/tests/test.ts deleted file mode 100644 index 5816be4132cb..000000000000 --- a/packages/create-svelte/shared/+playwright+skeletonlib/tests/test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from '@playwright/test'; - -test('index page has expected h1', async ({ page }) => { - await page.goto('/'); - await expect(page.getByRole('heading', { name: 'Welcome to SvelteKit' })).toBeVisible(); -}); diff --git a/packages/create-svelte/shared/+playwright+typescript/playwright.config.ts b/packages/create-svelte/shared/+playwright+typescript/playwright.config.ts deleted file mode 100644 index 1c5d7a1fd310..000000000000 --- a/packages/create-svelte/shared/+playwright+typescript/playwright.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { PlaywrightTestConfig } from '@playwright/test'; - -const config: PlaywrightTestConfig = { - webServer: { - command: 'npm run build && npm run preview', - port: 4173 - }, - testDir: 'tests', - testMatch: /(.+\.)?(test|spec)\.[jt]s/ -}; - -export default config; diff --git a/packages/create-svelte/shared/+playwright+vitest/package.json b/packages/create-svelte/shared/+playwright+vitest/package.json deleted file mode 100644 index 0e69c1babe7a..000000000000 --- a/packages/create-svelte/shared/+playwright+vitest/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "devDependencies": { - "@playwright/test": "^1.28.1", - "vitest": "^0.32.2" - }, - "scripts": { - "test:integration": "playwright test", - "test:unit": "vitest", - "test": "npm run test:integration && npm run test:unit" - } -} diff --git a/packages/create-svelte/shared/+playwright-typescript/playwright.config.js b/packages/create-svelte/shared/+playwright-typescript/playwright.config.js deleted file mode 100644 index a43c8c02d8cb..000000000000 --- a/packages/create-svelte/shared/+playwright-typescript/playwright.config.js +++ /dev/null @@ -1,11 +0,0 @@ -/** @type {import('@playwright/test').PlaywrightTestConfig} */ -const config = { - webServer: { - command: 'npm run build && npm run preview', - port: 4173 - }, - testDir: 'tests', - testMatch: /(.+\.)?(test|spec)\.[jt]s/ -}; - -export default config; diff --git a/packages/create-svelte/shared/+playwright/package.json b/packages/create-svelte/shared/+playwright/package.json deleted file mode 100644 index 4094202b8631..000000000000 --- a/packages/create-svelte/shared/+playwright/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "devDependencies": { - "@playwright/test": "^1.28.1" - }, - "scripts": { - "test": "playwright test" - } -} diff --git a/packages/create-svelte/shared/+prettier/.prettierignore b/packages/create-svelte/shared/+prettier/.prettierignore deleted file mode 100644 index 38972655faff..000000000000 --- a/packages/create-svelte/shared/+prettier/.prettierignore +++ /dev/null @@ -1,13 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example - -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock diff --git a/packages/create-svelte/shared/+prettier/.prettierrc b/packages/create-svelte/shared/+prettier/.prettierrc deleted file mode 100644 index a77fddea9097..000000000000 --- a/packages/create-svelte/shared/+prettier/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "useTabs": true, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "plugins": ["prettier-plugin-svelte"], - "pluginSearchDirs": ["."], - "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] -} diff --git a/packages/create-svelte/shared/+prettier/package.json b/packages/create-svelte/shared/+prettier/package.json deleted file mode 100644 index e368023fb43e..000000000000 --- a/packages/create-svelte/shared/+prettier/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "devDependencies": { - "prettier": "^2.8.0", - "prettier-plugin-svelte": "^2.10.1" - } -} diff --git a/packages/create-svelte/shared/+skeletonlib+checkjs/jsconfig.json b/packages/create-svelte/shared/+skeletonlib+checkjs/jsconfig.json deleted file mode 100644 index f56aae6ff5c8..000000000000 --- a/packages/create-svelte/shared/+skeletonlib+checkjs/jsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "moduleResolution": "NodeNext" - } -} diff --git a/packages/create-svelte/shared/+skeletonlib+typescript/tsconfig.json b/packages/create-svelte/shared/+skeletonlib+typescript/tsconfig.json deleted file mode 100644 index f56aae6ff5c8..000000000000 --- a/packages/create-svelte/shared/+skeletonlib+typescript/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "moduleResolution": "NodeNext" - } -} diff --git a/packages/create-svelte/shared/+skeletonlib-typescript/jsconfig.json b/packages/create-svelte/shared/+skeletonlib-typescript/jsconfig.json deleted file mode 100644 index 7125447cb881..000000000000 --- a/packages/create-svelte/shared/+skeletonlib-typescript/jsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "moduleResolution": "NodeNext" - } -} diff --git a/packages/create-svelte/shared/+skeletonlib/README.md b/packages/create-svelte/shared/+skeletonlib/README.md deleted file mode 100644 index 4fee31f6f8e6..000000000000 --- a/packages/create-svelte/shared/+skeletonlib/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# create-svelte - -Everything you need to build a Svelte library, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). - -Read more about creating a library [in the docs](https://kit.svelte.dev/docs/packaging). - -## Creating a project - -If you're seeing this, you've probably already done this step. Congrats! - -```bash -# create a new project in the current directory -npm create svelte@latest - -# create a new project in my-app -npm create svelte@latest my-app -``` - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```bash -npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` - -Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app. - -## Building - -To build your library: - -```bash -npm run package -``` - -To create a production version of your showcase app: - -```bash -npm run build -``` - -You can preview the production build with `npm run preview`. - -> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. - -## Publishing - -Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)). - -To publish your library to [npm](https://www.npmjs.com): - -```bash -npm publish -``` diff --git a/packages/create-svelte/shared/+typescript/package.json b/packages/create-svelte/shared/+typescript/package.json deleted file mode 100644 index f7fe142530a8..000000000000 --- a/packages/create-svelte/shared/+typescript/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "scripts": { - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" - }, - "devDependencies": { - "typescript": "^5.0.0", - "tslib": "^2.4.1", - "svelte-check": "^3.4.3" - } -} diff --git a/packages/create-svelte/shared/+typescript/svelte.config.js b/packages/create-svelte/shared/+typescript/svelte.config.js deleted file mode 100644 index 1cf26a00dfea..000000000000 --- a/packages/create-svelte/shared/+typescript/svelte.config.js +++ /dev/null @@ -1,18 +0,0 @@ -import adapter from '@sveltejs/adapter-auto'; -import { vitePreprocess } from '@sveltejs/kit/vite'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - // Consult https://kit.svelte.dev/docs/integrations#preprocessors - // for more information about preprocessors - preprocess: vitePreprocess(), - - kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter() - } -}; - -export default config; diff --git a/packages/create-svelte/shared/+typescript/tsconfig.json b/packages/create-svelte/shared/+typescript/tsconfig.json deleted file mode 100644 index 6ae0c8c44d08..000000000000 --- a/packages/create-svelte/shared/+typescript/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "sourceMap": true, - "strict": true - } - // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias - // - // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes - // from the referenced tsconfig.json - TypeScript does not merge them in -} diff --git a/packages/create-svelte/shared/+vitest+skeleton/src/index.test.ts b/packages/create-svelte/shared/+vitest+skeleton/src/index.test.ts deleted file mode 100644 index e07cbbd72507..000000000000 --- a/packages/create-svelte/shared/+vitest+skeleton/src/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from 'vitest'; - -describe('sum test', () => { - it('adds 1 + 2 to equal 3', () => { - expect(1 + 2).toBe(3); - }); -}); diff --git a/packages/create-svelte/shared/+vitest+skeletonlib/src/index.test.ts b/packages/create-svelte/shared/+vitest+skeletonlib/src/index.test.ts deleted file mode 100644 index e07cbbd72507..000000000000 --- a/packages/create-svelte/shared/+vitest+skeletonlib/src/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from 'vitest'; - -describe('sum test', () => { - it('adds 1 + 2 to equal 3', () => { - expect(1 + 2).toBe(3); - }); -}); diff --git a/packages/create-svelte/shared/+vitest/package.json b/packages/create-svelte/shared/+vitest/package.json deleted file mode 100644 index 58c77018dcf5..000000000000 --- a/packages/create-svelte/shared/+vitest/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "devDependencies": { - "vitest": "^0.34.0" - }, - "scripts": { - "test": "vitest" - } -} diff --git a/packages/create-svelte/shared/+vitest/vite.config.ts b/packages/create-svelte/shared/+vitest/vite.config.ts deleted file mode 100644 index 37b6a84bc3c4..000000000000 --- a/packages/create-svelte/shared/+vitest/vite.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - plugins: [sveltekit()], - test: { - include: ['src/**/*.{test,spec}.{js,ts}'] - } -}); diff --git a/packages/create-svelte/shared/-eslint+prettier/package.json b/packages/create-svelte/shared/-eslint+prettier/package.json deleted file mode 100644 index 0193de97cbb4..000000000000 --- a/packages/create-svelte/shared/-eslint+prettier/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "scripts": { - "lint": "prettier --plugin-search-dir . --check .", - "format": "prettier --plugin-search-dir . --write ." - } -} diff --git a/packages/create-svelte/shared/-typescript/svelte.config.js b/packages/create-svelte/shared/-typescript/svelte.config.js deleted file mode 100644 index 348fa324acaa..000000000000 --- a/packages/create-svelte/shared/-typescript/svelte.config.js +++ /dev/null @@ -1,13 +0,0 @@ -import adapter from '@sveltejs/adapter-auto'; - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter() - } -}; - -export default config; diff --git a/packages/create-svelte/shared/README.md b/packages/create-svelte/shared/README.md deleted file mode 100644 index 5c91169b0ca6..000000000000 --- a/packages/create-svelte/shared/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# create-svelte - -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte). - -## Creating a project - -If you're seeing this, you've probably already done this step. Congrats! - -```bash -# create a new project in the current directory -npm create svelte@latest - -# create a new project in my-app -npm create svelte@latest my-app -``` - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```bash -npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` - -## Building - -To create a production version of your app: - -```bash -npm run build -``` - -You can preview the production build with `npm run preview`. - -> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. diff --git a/packages/create-svelte/shared/vite.config.ts b/packages/create-svelte/shared/vite.config.ts deleted file mode 100644 index bbf8c7da43f0..000000000000 --- a/packages/create-svelte/shared/vite.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - plugins: [sveltekit()] -}); diff --git a/packages/create-svelte/templates/default/.gitignore b/packages/create-svelte/templates/default/.gitignore deleted file mode 100644 index 8f6c617ecf16..000000000000 --- a/packages/create-svelte/templates/default/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example -.vercel -.output -vite.config.js.timestamp-* -vite.config.ts.timestamp-* diff --git a/packages/create-svelte/templates/default/.ignore b/packages/create-svelte/templates/default/.ignore deleted file mode 100644 index 9b5d703a4b8a..000000000000 --- a/packages/create-svelte/templates/default/.ignore +++ /dev/null @@ -1,9 +0,0 @@ -.meta.json -package.json -netlify.toml -wrangler.toml -vercel.json -.cloudflare -.netlify -.turbo -README.md \ No newline at end of file diff --git a/packages/create-svelte/templates/default/.meta.json b/packages/create-svelte/templates/default/.meta.json deleted file mode 100644 index e26f2d89a1ff..000000000000 --- a/packages/create-svelte/templates/default/.meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "SvelteKit demo app", - "description": "A demo app showcasing some of the features of SvelteKit - play a word guessing game that works without JavaScript!" -} diff --git a/packages/create-svelte/templates/default/README.md b/packages/create-svelte/templates/default/README.md deleted file mode 100644 index 9c8dc797a0be..000000000000 --- a/packages/create-svelte/templates/default/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Default SvelteKit template - -This README isn't part of the template; it is ignored, and replaced with [the shared README](../../shared/README.md) when a project is created. - -The default template is automatically deployed to Cloudflare Pages, Netlify and Vercel on every commit. Commits to `master` will update production deployments: - -- https://cloudflare.demo.svelte.dev -- https://kit-demo.netlify.app -- https://kit-demo.vercel.app - -## Cloudflare Pages settings - -### Build command - -``` -npm i -g pnpm && pnpm i && pnpm build -r -``` - -### Build output directory - -``` -/packages/create-svelte/templates/default/.svelte-kit/cloudflare -``` - -### Environment variables - -``` -NPM_FLAGS=--version -``` - -## Netlify settings - -Most settings are contained in [netlify.toml](netlify.toml). - -### Build directory - -``` -packages/create-svelte/templates/default -``` - -## Vercel settings - -### Install command - -``` -npm install -g pnpm && pnpm install && pnpm build -r -``` - -### Root directory - -``` -packages/create-svelte/templates/default -``` diff --git a/packages/create-svelte/templates/default/netlify.toml b/packages/create-svelte/templates/default/netlify.toml deleted file mode 100644 index 7dca1fcad039..000000000000 --- a/packages/create-svelte/templates/default/netlify.toml +++ /dev/null @@ -1,9 +0,0 @@ -[build] - command = "npx pnpm install --store=node_modules/.pnpm-store && npx pnpm build -r" - publish = "build" - -[build.environment] - NPM_FLAGS = "--version" # this prevents npm install from happening - -[functions] - node_bundler = "esbuild" \ No newline at end of file diff --git a/packages/create-svelte/templates/default/package.json b/packages/create-svelte/templates/default/package.json deleted file mode 100644 index b46793b54717..000000000000 --- a/packages/create-svelte/templates/default/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "default-template", - "version": "0.0.2-next.0", - "private": true, - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview" - }, - "devDependencies": { - "@neoconfetti/svelte": "^1.0.0", - "@sveltejs/adapter-auto": "workspace:*", - "@sveltejs/kit": "workspace:*", - "svelte": "^4.0.5", - "typescript": "^5.0.0", - "vite": "^4.4.9" - }, - "type": "module", - "dependencies": { - "@fontsource/fira-mono": "^5.0.5" - } -} diff --git a/packages/create-svelte/templates/default/package.template.json b/packages/create-svelte/templates/default/package.template.json deleted file mode 100644 index 6e96dfb86f88..000000000000 --- a/packages/create-svelte/templates/default/package.template.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "~TODO~", - "version": "0.0.1", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview" - }, - "devDependencies": { - "@fontsource/fira-mono": "^4.5.10", - "@neoconfetti/svelte": "^1.0.0", - "@sveltejs/adapter-auto": "^2.0.0", - "@sveltejs/kit": "^1.20.4", - "svelte": "^4.0.5", - "vite": "^4.4.2" - }, - "type": "module" -} diff --git a/packages/create-svelte/templates/default/src/app.d.ts b/packages/create-svelte/templates/default/src/app.d.ts deleted file mode 100644 index f59b884c51ed..000000000000 --- a/packages/create-svelte/templates/default/src/app.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -// See https://kit.svelte.dev/docs/types#app -// for information about these interfaces -declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface Platform {} - } -} - -export {}; diff --git a/packages/create-svelte/templates/default/src/app.html b/packages/create-svelte/templates/default/src/app.html deleted file mode 100644 index 6769ed5e89c5..000000000000 --- a/packages/create-svelte/templates/default/src/app.html +++ /dev/null @@ -1,12 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <link rel="icon" href="%sveltekit.assets%/favicon.png" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - %sveltekit.head% - </head> - <body data-sveltekit-preload-data="hover"> - <div style="display: contents">%sveltekit.body%</div> - </body> -</html> diff --git a/packages/create-svelte/templates/default/src/lib/images/github.svg b/packages/create-svelte/templates/default/src/lib/images/github.svg deleted file mode 100644 index bc5d249d333e..000000000000 --- a/packages/create-svelte/templates/default/src/lib/images/github.svg +++ /dev/null @@ -1,16 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -3 30 30"> - <path - fill-rule="evenodd" - clip-rule="evenodd" - d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5229 6.47715 22 12 22C17.5229 22 22 17.5229 22 12C22 6.47715 17.5229 2 12 2ZM0 12C0 5.3726 5.3726 0 12 0C18.6274 0 24 5.3726 24 12C24 18.6274 18.6274 24 12 24C5.3726 24 0 18.6274 0 12Z" - fill="rgba(0,0,0,0.7)" - stroke="none" - /> - <path - fill-rule="evenodd" - clip-rule="evenodd" - d="M9.59162 22.7357C9.49492 22.6109 9.49492 21.4986 9.59162 19.399C8.55572 19.4347 7.90122 19.3628 7.62812 19.1833C7.21852 18.9139 6.80842 18.0833 6.44457 17.4979C6.08072 16.9125 5.27312 16.8199 4.94702 16.6891C4.62091 16.5582 4.53905 16.0247 5.84562 16.4282C7.15222 16.8316 7.21592 17.9303 7.62812 18.1872C8.04032 18.4441 9.02572 18.3317 9.47242 18.1259C9.91907 17.9201 9.88622 17.1538 9.96587 16.8503C10.0666 16.5669 9.71162 16.5041 9.70382 16.5018C9.26777 16.5018 6.97697 16.0036 6.34772 13.7852C5.71852 11.5669 6.52907 10.117 6.96147 9.49369C7.24972 9.07814 7.22422 8.19254 6.88497 6.83679C8.11677 6.67939 9.06732 7.06709 9.73672 7.99999C9.73737 8.00534 10.6143 7.47854 12.0001 7.47854C13.386 7.47854 13.8777 7.90764 14.2571 7.99999C14.6365 8.09234 14.94 6.36699 17.2834 6.83679C16.7942 7.79839 16.3844 8.99999 16.6972 9.49369C17.0099 9.98739 18.2372 11.5573 17.4833 13.7852C16.9807 15.2706 15.9927 16.1761 14.5192 16.5018C14.3502 16.5557 14.2658 16.6427 14.2658 16.7627C14.2658 16.9427 14.4942 16.9624 14.8233 17.8058C15.0426 18.368 15.0585 19.9739 14.8708 22.6234C14.3953 22.7445 14.0254 22.8257 13.7611 22.8673C13.2924 22.9409 12.7835 22.9822 12.2834 22.9982C11.7834 23.0141 11.6098 23.0123 10.9185 22.948C10.4577 22.9051 10.0154 22.8343 9.59162 22.7357Z" - fill="rgba(0,0,0,0.7)" - stroke="none" - /> -</svg> \ No newline at end of file diff --git a/packages/create-svelte/templates/default/src/lib/images/svelte-logo.svg b/packages/create-svelte/templates/default/src/lib/images/svelte-logo.svg deleted file mode 100644 index 49492a83cc2f..000000000000 --- a/packages/create-svelte/templates/default/src/lib/images/svelte-logo.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo \ No newline at end of file diff --git a/packages/create-svelte/templates/default/src/lib/images/svelte-welcome.png b/packages/create-svelte/templates/default/src/lib/images/svelte-welcome.png deleted file mode 100644 index fe7d2d6b50ce..000000000000 Binary files a/packages/create-svelte/templates/default/src/lib/images/svelte-welcome.png and /dev/null differ diff --git a/packages/create-svelte/templates/default/src/lib/images/svelte-welcome.webp b/packages/create-svelte/templates/default/src/lib/images/svelte-welcome.webp deleted file mode 100644 index 6ec1a28d63b4..000000000000 Binary files a/packages/create-svelte/templates/default/src/lib/images/svelte-welcome.webp and /dev/null differ diff --git a/packages/create-svelte/templates/default/src/routes/+layout.svelte b/packages/create-svelte/templates/default/src/routes/+layout.svelte deleted file mode 100644 index 9c7445f55ec8..000000000000 --- a/packages/create-svelte/templates/default/src/routes/+layout.svelte +++ /dev/null @@ -1,53 +0,0 @@ - - - - - diff --git a/packages/create-svelte/templates/default/src/routes/+page.svelte b/packages/create-svelte/templates/default/src/routes/+page.svelte deleted file mode 100644 index 97201ef364c8..000000000000 --- a/packages/create-svelte/templates/default/src/routes/+page.svelte +++ /dev/null @@ -1,59 +0,0 @@ - - - - Home - - - -
-

- - - - Welcome - - - - to your new
SvelteKit app -

- -

- try editing src/routes/+page.svelte -

- - -
- - diff --git a/packages/create-svelte/templates/default/src/routes/+page.ts b/packages/create-svelte/templates/default/src/routes/+page.ts deleted file mode 100644 index a72419a63f41..000000000000 --- a/packages/create-svelte/templates/default/src/routes/+page.ts +++ /dev/null @@ -1,3 +0,0 @@ -// since there's no dynamic data here, we can prerender -// it so that it gets served as a static asset in production -export const prerender = true; diff --git a/packages/create-svelte/templates/default/src/routes/Counter.svelte b/packages/create-svelte/templates/default/src/routes/Counter.svelte deleted file mode 100644 index e40221d44b60..000000000000 --- a/packages/create-svelte/templates/default/src/routes/Counter.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - -
- - -
-
- - {Math.floor($displayed_count)} -
-
- - -
- - diff --git a/packages/create-svelte/templates/default/src/routes/Header.svelte b/packages/create-svelte/templates/default/src/routes/Header.svelte deleted file mode 100644 index 368b721e891b..000000000000 --- a/packages/create-svelte/templates/default/src/routes/Header.svelte +++ /dev/null @@ -1,129 +0,0 @@ - - -
-
- - SvelteKit - -
- - - -
- - GitHub - -
-
- - diff --git a/packages/create-svelte/templates/default/src/routes/about/+page.svelte b/packages/create-svelte/templates/default/src/routes/about/+page.svelte deleted file mode 100644 index 7f7946d23050..000000000000 --- a/packages/create-svelte/templates/default/src/routes/about/+page.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - About - - - -
-

About this app

- -

- This is a SvelteKit app. You can make your own by typing the - following into your command line and following the prompts: -

- -
npm create svelte@latest
- -

- The page you're looking at is purely static HTML, with no client-side interactivity needed. - Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening - the devtools network panel and reloading. -

- -

- The Sverdle page illustrates SvelteKit's data loading and form handling. Try - using it with JavaScript disabled! -

-
diff --git a/packages/create-svelte/templates/default/src/routes/about/+page.ts b/packages/create-svelte/templates/default/src/routes/about/+page.ts deleted file mode 100644 index e739ef4b6c11..000000000000 --- a/packages/create-svelte/templates/default/src/routes/about/+page.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { dev } from '$app/environment'; - -// we don't need any JS on this page, though we'll load -// it in dev so that we get hot module replacement -export const csr = dev; - -// since there's no dynamic data here, we can prerender -// it so that it gets served as a static asset in production -export const prerender = true; diff --git a/packages/create-svelte/templates/default/src/routes/styles.css b/packages/create-svelte/templates/default/src/routes/styles.css deleted file mode 100644 index 1441d9408f3a..000000000000 --- a/packages/create-svelte/templates/default/src/routes/styles.css +++ /dev/null @@ -1,107 +0,0 @@ -@import '@fontsource/fira-mono'; - -:root { - --font-body: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, - Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - --font-mono: 'Fira Mono', monospace; - --color-bg-0: rgb(202, 216, 228); - --color-bg-1: hsl(209, 36%, 86%); - --color-bg-2: hsl(224, 44%, 95%); - --color-theme-1: #ff3e00; - --color-theme-2: #4075a6; - --color-text: rgba(0, 0, 0, 0.7); - --column-width: 42rem; - --column-margin-top: 4rem; - font-family: var(--font-body); - color: var(--color-text); -} - -body { - min-height: 100vh; - margin: 0; - background-attachment: fixed; - background-color: var(--color-bg-1); - background-size: 100vw 100vh; - background-image: radial-gradient( - 50% 50% at 50% 50%, - rgba(255, 255, 255, 0.75) 0%, - rgba(255, 255, 255, 0) 100% - ), - linear-gradient(180deg, var(--color-bg-0) 0%, var(--color-bg-1) 15%, var(--color-bg-2) 50%); -} - -h1, -h2, -p { - font-weight: 400; -} - -p { - line-height: 1.5; -} - -a { - color: var(--color-theme-1); - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -h1 { - font-size: 2rem; - text-align: center; -} - -h2 { - font-size: 1rem; -} - -pre { - font-size: 16px; - font-family: var(--font-mono); - background-color: rgba(255, 255, 255, 0.45); - border-radius: 3px; - box-shadow: 2px 2px 6px rgb(255 255 255 / 25%); - padding: 0.5em; - overflow-x: auto; - color: var(--color-text); -} - -.text-column { - display: flex; - max-width: 48rem; - flex: 0.6; - flex-direction: column; - justify-content: center; - margin: 0 auto; -} - -input, -button { - font-size: inherit; - font-family: inherit; -} - -button:focus:not(:focus-visible) { - outline: none; -} - -@media (min-width: 720px) { - h1 { - font-size: 2.4rem; - } -} - -.visually-hidden { - border: 0; - clip: rect(0 0 0 0); - height: auto; - margin: 0; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; - white-space: nowrap; -} diff --git a/packages/create-svelte/templates/default/src/routes/sverdle/+page.server.ts b/packages/create-svelte/templates/default/src/routes/sverdle/+page.server.ts deleted file mode 100644 index 0bd1db1f264f..000000000000 --- a/packages/create-svelte/templates/default/src/routes/sverdle/+page.server.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { fail } from '@sveltejs/kit'; -import { Game } from './game'; -import type { PageServerLoad, Actions } from './$types'; - -/** @satisfies {import('./$types').PageServerLoad} */ -export const load = (({ cookies }) => { - const game = new Game(cookies.get('sverdle')); - - return { - /** - * The player's guessed words so far - */ - guesses: game.guesses, - - /** - * An array of strings like '__x_c' corresponding to the guesses, where 'x' means - * an exact match, and 'c' means a close match (right letter, wrong place) - */ - answers: game.answers, - - /** - * The correct answer, revealed if the game is over - */ - answer: game.answers.length >= 6 ? game.answer : null - }; -}) satisfies PageServerLoad; - -/** @satisfies {import('./$types').Actions} */ -export const actions = { - /** - * Modify game state in reaction to a keypress. If client-side JavaScript - * is available, this will happen in the browser instead of here - */ - update: async ({ request, cookies }) => { - const game = new Game(cookies.get('sverdle')); - - const data = await request.formData(); - const key = data.get('key'); - - const i = game.answers.length; - - if (key === 'backspace') { - game.guesses[i] = game.guesses[i].slice(0, -1); - } else { - game.guesses[i] += key; - } - - cookies.set('sverdle', game.toString()); - }, - - /** - * Modify game state in reaction to a guessed word. This logic always runs on - * the server, so that people can't cheat by peeking at the JavaScript - */ - enter: async ({ request, cookies }) => { - const game = new Game(cookies.get('sverdle')); - - const data = await request.formData(); - const guess = /** @type {string[]} */ data.getAll('guess') /***/ as string[]; - - if (!game.enter(guess)) { - return fail(400, { badGuess: true }); - } - - cookies.set('sverdle', game.toString()); - }, - - restart: async ({ cookies }) => { - cookies.delete('sverdle'); - } -} satisfies Actions; diff --git a/packages/create-svelte/templates/default/src/routes/sverdle/+page.svelte b/packages/create-svelte/templates/default/src/routes/sverdle/+page.svelte deleted file mode 100644 index 68ab6400de82..000000000000 --- a/packages/create-svelte/templates/default/src/routes/sverdle/+page.svelte +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - Sverdle - - - -

Sverdle

- - { - // prevent default callback from resetting the form - return ({ update }) => { - update({ reset: false }); - }; - }} -> - How to play - -
- {#each Array.from(Array(6).keys()) as row (row)} - {@const current = row === i} -

Row {row + 1}

-
- {#each Array.from(Array(5).keys()) as column (column)} - {@const guess = current ? currentGuess : data.guesses[row]} - {@const answer = data.answers[row]?.[column]} - {@const value = guess?.[column] ?? ''} - {@const selected = current && column === guess.length} - {@const exact = answer === 'x'} - {@const close = answer === 'c'} - {@const missing = answer === '_'} -
- {value} - - {#if exact} - (correct) - {:else if close} - (present) - {:else if missing} - (absent) - {:else} - empty - {/if} - - -
- {/each} -
- {/each} -
- -
- {#if won || data.answers.length >= 6} - {#if !won && data.answer} -

the answer was "{data.answer}"

- {/if} - - {:else} -
- - - - - {#each ['qwertyuiop', 'asdfghjkl', 'zxcvbnm'] as row} -
- {#each row as letter} - - {/each} -
- {/each} -
- {/if} -
- - -{#if won} -
-{/if} - - diff --git a/packages/create-svelte/templates/default/src/routes/sverdle/game.ts b/packages/create-svelte/templates/default/src/routes/sverdle/game.ts deleted file mode 100644 index d5cbe932e4cb..000000000000 --- a/packages/create-svelte/templates/default/src/routes/sverdle/game.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { words, allowed } from './words.server'; - -export class Game { - index: number; - guesses: string[]; - answers: string[]; - answer: string; - - /** - * Create a game object from the player's cookie, or initialise a new game - * @param {string | undefined} serialized - */ - constructor(serialized: string | undefined = undefined) { - if (serialized) { - const [index, guesses, answers] = serialized.split('-'); - - this.index = +index; - this.guesses = guesses ? guesses.split(' ') : []; - this.answers = answers ? answers.split(' ') : []; - } else { - this.index = Math.floor(Math.random() * words.length); - this.guesses = ['', '', '', '', '', '']; - this.answers = /** @type {string[]} */ [] /***/; - } - - this.answer = words[this.index]; - } - - /** - * Update game state based on a guess of a five-letter word. Returns - * true if the guess was valid, false otherwise - * @param {string[]} letters - */ - enter(letters: string[]) { - const word = letters.join(''); - const valid = allowed.has(word); - - if (!valid) return false; - - this.guesses[this.answers.length] = word; - - const available = Array.from(this.answer); - const answer = Array(5).fill('_'); - - // first, find exact matches - for (let i = 0; i < 5; i += 1) { - if (letters[i] === available[i]) { - answer[i] = 'x'; - available[i] = ' '; - } - } - - // then find close matches (this has to happen - // in a second step, otherwise an early close - // match can prevent a later exact match) - for (let i = 0; i < 5; i += 1) { - if (answer[i] === '_') { - const index = available.indexOf(letters[i]); - if (index !== -1) { - answer[i] = 'c'; - available[index] = ' '; - } - } - } - - this.answers.push(answer.join('')); - - return true; - } - - /** - * Serialize game state so it can be set as a cookie - */ - toString() { - return `${this.index}-${this.guesses.join(' ')}-${this.answers.join(' ')}`; - } -} diff --git a/packages/create-svelte/templates/default/src/routes/sverdle/how-to-play/+page.svelte b/packages/create-svelte/templates/default/src/routes/sverdle/how-to-play/+page.svelte deleted file mode 100644 index e8e2cec3198e..000000000000 --- a/packages/create-svelte/templates/default/src/routes/sverdle/how-to-play/+page.svelte +++ /dev/null @@ -1,95 +0,0 @@ - - How to play Sverdle - - - -
-

How to play Sverdle

- -

- Sverdle is a clone of Wordle, the - word guessing game. To play, enter a five-letter English word. For example: -

- -
- r - i - t - z - y -
- -

- The y is in the right place. r and - t - are the right letters, but in the wrong place. The other letters are wrong, and can be discarded. - Let's make another guess: -

- -
- p - a - r - t - y -
- -

This time we guessed right! You have six guesses to get the word.

- -

- Unlike the original Wordle, Sverdle runs on the server instead of in the browser, making it - impossible to cheat. It uses <form> and cookies to submit data, meaning you can - even play with JavaScript disabled! -

-
- - diff --git a/packages/create-svelte/templates/default/src/routes/sverdle/how-to-play/+page.ts b/packages/create-svelte/templates/default/src/routes/sverdle/how-to-play/+page.ts deleted file mode 100644 index e739ef4b6c11..000000000000 --- a/packages/create-svelte/templates/default/src/routes/sverdle/how-to-play/+page.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { dev } from '$app/environment'; - -// we don't need any JS on this page, though we'll load -// it in dev so that we get hot module replacement -export const csr = dev; - -// since there's no dynamic data here, we can prerender -// it so that it gets served as a static asset in production -export const prerender = true; diff --git a/packages/create-svelte/templates/default/src/routes/sverdle/reduced-motion.ts b/packages/create-svelte/templates/default/src/routes/sverdle/reduced-motion.ts deleted file mode 100644 index 27b9d51161d0..000000000000 --- a/packages/create-svelte/templates/default/src/routes/sverdle/reduced-motion.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { readable } from 'svelte/store'; -import { browser } from '$app/environment'; - -const reduced_motion_query = '(prefers-reduced-motion: reduce)'; - -const get_initial_motion_preference = () => { - if (!browser) return false; - return window.matchMedia(reduced_motion_query).matches; -}; - -export const reduced_motion = readable(get_initial_motion_preference(), (set) => { - if (browser) { - /** - * @param {MediaQueryListEvent} event - */ - const set_reduced_motion = (event: MediaQueryListEvent) => { - set(event.matches); - }; - const media_query_list = window.matchMedia(reduced_motion_query); - media_query_list.addEventListener('change', set_reduced_motion); - - return () => { - media_query_list.removeEventListener('change', set_reduced_motion); - }; - } -}); diff --git a/packages/create-svelte/templates/default/src/routes/sverdle/words.server.ts b/packages/create-svelte/templates/default/src/routes/sverdle/words.server.ts deleted file mode 100644 index 56082a33115d..000000000000 --- a/packages/create-svelte/templates/default/src/routes/sverdle/words.server.ts +++ /dev/null @@ -1,12980 +0,0 @@ -/** The list of possible words */ -export const words = [ - 'aback', - 'abase', - 'abate', - 'abbey', - 'abbot', - 'abhor', - 'abide', - 'abled', - 'abode', - 'abort', - 'about', - 'above', - 'abuse', - 'abyss', - 'acorn', - 'acrid', - 'actor', - 'acute', - 'adage', - 'adapt', - 'adept', - 'admin', - 'admit', - 'adobe', - 'adopt', - 'adore', - 'adorn', - 'adult', - 'affix', - 'afire', - 'afoot', - 'afoul', - 'after', - 'again', - 'agape', - 'agate', - 'agent', - 'agile', - 'aging', - 'aglow', - 'agony', - 'agora', - 'agree', - 'ahead', - 'aider', - 'aisle', - 'alarm', - 'album', - 'alert', - 'algae', - 'alibi', - 'alien', - 'align', - 'alike', - 'alive', - 'allay', - 'alley', - 'allot', - 'allow', - 'alloy', - 'aloft', - 'alone', - 'along', - 'aloof', - 'aloud', - 'alpha', - 'altar', - 'alter', - 'amass', - 'amaze', - 'amber', - 'amble', - 'amend', - 'amiss', - 'amity', - 'among', - 'ample', - 'amply', - 'amuse', - 'angel', - 'anger', - 'angle', - 'angry', - 'angst', - 'anime', - 'ankle', - 'annex', - 'annoy', - 'annul', - 'anode', - 'antic', - 'anvil', - 'aorta', - 'apart', - 'aphid', - 'aping', - 'apnea', - 'apple', - 'apply', - 'apron', - 'aptly', - 'arbor', - 'ardor', - 'arena', - 'argue', - 'arise', - 'armor', - 'aroma', - 'arose', - 'array', - 'arrow', - 'arson', - 'artsy', - 'ascot', - 'ashen', - 'aside', - 'askew', - 'assay', - 'asset', - 'atoll', - 'atone', - 'attic', - 'audio', - 'audit', - 'augur', - 'aunty', - 'avail', - 'avert', - 'avian', - 'avoid', - 'await', - 'awake', - 'award', - 'aware', - 'awash', - 'awful', - 'awoke', - 'axial', - 'axiom', - 'axion', - 'azure', - 'bacon', - 'badge', - 'badly', - 'bagel', - 'baggy', - 'baker', - 'baler', - 'balmy', - 'banal', - 'banjo', - 'barge', - 'baron', - 'basal', - 'basic', - 'basil', - 'basin', - 'basis', - 'baste', - 'batch', - 'bathe', - 'baton', - 'batty', - 'bawdy', - 'bayou', - 'beach', - 'beady', - 'beard', - 'beast', - 'beech', - 'beefy', - 'befit', - 'began', - 'begat', - 'beget', - 'begin', - 'begun', - 'being', - 'belch', - 'belie', - 'belle', - 'belly', - 'below', - 'bench', - 'beret', - 'berry', - 'berth', - 'beset', - 'betel', - 'bevel', - 'bezel', - 'bible', - 'bicep', - 'biddy', - 'bigot', - 'bilge', - 'billy', - 'binge', - 'bingo', - 'biome', - 'birch', - 'birth', - 'bison', - 'bitty', - 'black', - 'blade', - 'blame', - 'bland', - 'blank', - 'blare', - 'blast', - 'blaze', - 'bleak', - 'bleat', - 'bleed', - 'bleep', - 'blend', - 'bless', - 'blimp', - 'blind', - 'blink', - 'bliss', - 'blitz', - 'bloat', - 'block', - 'bloke', - 'blond', - 'blood', - 'bloom', - 'blown', - 'bluer', - 'bluff', - 'blunt', - 'blurb', - 'blurt', - 'blush', - 'board', - 'boast', - 'bobby', - 'boney', - 'bongo', - 'bonus', - 'booby', - 'boost', - 'booth', - 'booty', - 'booze', - 'boozy', - 'borax', - 'borne', - 'bosom', - 'bossy', - 'botch', - 'bough', - 'boule', - 'bound', - 'bowel', - 'boxer', - 'brace', - 'braid', - 'brain', - 'brake', - 'brand', - 'brash', - 'brass', - 'brave', - 'bravo', - 'brawl', - 'brawn', - 'bread', - 'break', - 'breed', - 'briar', - 'bribe', - 'brick', - 'bride', - 'brief', - 'brine', - 'bring', - 'brink', - 'briny', - 'brisk', - 'broad', - 'broil', - 'broke', - 'brood', - 'brook', - 'broom', - 'broth', - 'brown', - 'brunt', - 'brush', - 'brute', - 'buddy', - 'budge', - 'buggy', - 'bugle', - 'build', - 'built', - 'bulge', - 'bulky', - 'bully', - 'bunch', - 'bunny', - 'burly', - 'burnt', - 'burst', - 'bused', - 'bushy', - 'butch', - 'butte', - 'buxom', - 'buyer', - 'bylaw', - 'cabal', - 'cabby', - 'cabin', - 'cable', - 'cacao', - 'cache', - 'cacti', - 'caddy', - 'cadet', - 'cagey', - 'cairn', - 'camel', - 'cameo', - 'canal', - 'candy', - 'canny', - 'canoe', - 'canon', - 'caper', - 'caput', - 'carat', - 'cargo', - 'carol', - 'carry', - 'carve', - 'caste', - 'catch', - 'cater', - 'catty', - 'caulk', - 'cause', - 'cavil', - 'cease', - 'cedar', - 'cello', - 'chafe', - 'chaff', - 'chain', - 'chair', - 'chalk', - 'champ', - 'chant', - 'chaos', - 'chard', - 'charm', - 'chart', - 'chase', - 'chasm', - 'cheap', - 'cheat', - 'check', - 'cheek', - 'cheer', - 'chess', - 'chest', - 'chick', - 'chide', - 'chief', - 'child', - 'chili', - 'chill', - 'chime', - 'china', - 'chirp', - 'chock', - 'choir', - 'choke', - 'chord', - 'chore', - 'chose', - 'chuck', - 'chump', - 'chunk', - 'churn', - 'chute', - 'cider', - 'cigar', - 'cinch', - 'circa', - 'civic', - 'civil', - 'clack', - 'claim', - 'clamp', - 'clang', - 'clank', - 'clash', - 'clasp', - 'class', - 'clean', - 'clear', - 'cleat', - 'cleft', - 'clerk', - 'click', - 'cliff', - 'climb', - 'cling', - 'clink', - 'cloak', - 'clock', - 'clone', - 'close', - 'cloth', - 'cloud', - 'clout', - 'clove', - 'clown', - 'cluck', - 'clued', - 'clump', - 'clung', - 'coach', - 'coast', - 'cobra', - 'cocoa', - 'colon', - 'color', - 'comet', - 'comfy', - 'comic', - 'comma', - 'conch', - 'condo', - 'conic', - 'copse', - 'coral', - 'corer', - 'corny', - 'couch', - 'cough', - 'could', - 'count', - 'coupe', - 'court', - 'coven', - 'cover', - 'covet', - 'covey', - 'cower', - 'coyly', - 'crack', - 'craft', - 'cramp', - 'crane', - 'crank', - 'crash', - 'crass', - 'crate', - 'crave', - 'crawl', - 'craze', - 'crazy', - 'creak', - 'cream', - 'credo', - 'creed', - 'creek', - 'creep', - 'creme', - 'crepe', - 'crept', - 'cress', - 'crest', - 'crick', - 'cried', - 'crier', - 'crime', - 'crimp', - 'crisp', - 'croak', - 'crock', - 'crone', - 'crony', - 'crook', - 'cross', - 'croup', - 'crowd', - 'crown', - 'crude', - 'cruel', - 'crumb', - 'crump', - 'crush', - 'crust', - 'crypt', - 'cubic', - 'cumin', - 'curio', - 'curly', - 'curry', - 'curse', - 'curve', - 'curvy', - 'cutie', - 'cyber', - 'cycle', - 'cynic', - 'daddy', - 'daily', - 'dairy', - 'daisy', - 'dally', - 'dance', - 'dandy', - 'datum', - 'daunt', - 'dealt', - 'death', - 'debar', - 'debit', - 'debug', - 'debut', - 'decal', - 'decay', - 'decor', - 'decoy', - 'decry', - 'defer', - 'deign', - 'deity', - 'delay', - 'delta', - 'delve', - 'demon', - 'demur', - 'denim', - 'dense', - 'depot', - 'depth', - 'derby', - 'deter', - 'detox', - 'deuce', - 'devil', - 'diary', - 'dicey', - 'digit', - 'dilly', - 'dimly', - 'diner', - 'dingo', - 'dingy', - 'diode', - 'dirge', - 'dirty', - 'disco', - 'ditch', - 'ditto', - 'ditty', - 'diver', - 'dizzy', - 'dodge', - 'dodgy', - 'dogma', - 'doing', - 'dolly', - 'donor', - 'donut', - 'dopey', - 'doubt', - 'dough', - 'dowdy', - 'dowel', - 'downy', - 'dowry', - 'dozen', - 'draft', - 'drain', - 'drake', - 'drama', - 'drank', - 'drape', - 'drawl', - 'drawn', - 'dread', - 'dream', - 'dress', - 'dried', - 'drier', - 'drift', - 'drill', - 'drink', - 'drive', - 'droit', - 'droll', - 'drone', - 'drool', - 'droop', - 'dross', - 'drove', - 'drown', - 'druid', - 'drunk', - 'dryer', - 'dryly', - 'duchy', - 'dully', - 'dummy', - 'dumpy', - 'dunce', - 'dusky', - 'dusty', - 'dutch', - 'duvet', - 'dwarf', - 'dwell', - 'dwelt', - 'dying', - 'eager', - 'eagle', - 'early', - 'earth', - 'easel', - 'eaten', - 'eater', - 'ebony', - 'eclat', - 'edict', - 'edify', - 'eerie', - 'egret', - 'eight', - 'eject', - 'eking', - 'elate', - 'elbow', - 'elder', - 'elect', - 'elegy', - 'elfin', - 'elide', - 'elite', - 'elope', - 'elude', - 'email', - 'embed', - 'ember', - 'emcee', - 'empty', - 'enact', - 'endow', - 'enema', - 'enemy', - 'enjoy', - 'ennui', - 'ensue', - 'enter', - 'entry', - 'envoy', - 'epoch', - 'epoxy', - 'equal', - 'equip', - 'erase', - 'erect', - 'erode', - 'error', - 'erupt', - 'essay', - 'ester', - 'ether', - 'ethic', - 'ethos', - 'etude', - 'evade', - 'event', - 'every', - 'evict', - 'evoke', - 'exact', - 'exalt', - 'excel', - 'exert', - 'exile', - 'exist', - 'expel', - 'extol', - 'extra', - 'exult', - 'eying', - 'fable', - 'facet', - 'faint', - 'fairy', - 'faith', - 'false', - 'fancy', - 'fanny', - 'farce', - 'fatal', - 'fatty', - 'fault', - 'fauna', - 'favor', - 'feast', - 'fecal', - 'feign', - 'fella', - 'felon', - 'femme', - 'femur', - 'fence', - 'feral', - 'ferry', - 'fetal', - 'fetch', - 'fetid', - 'fetus', - 'fever', - 'fewer', - 'fiber', - 'fibre', - 'ficus', - 'field', - 'fiend', - 'fiery', - 'fifth', - 'fifty', - 'fight', - 'filer', - 'filet', - 'filly', - 'filmy', - 'filth', - 'final', - 'finch', - 'finer', - 'first', - 'fishy', - 'fixer', - 'fizzy', - 'fjord', - 'flack', - 'flail', - 'flair', - 'flake', - 'flaky', - 'flame', - 'flank', - 'flare', - 'flash', - 'flask', - 'fleck', - 'fleet', - 'flesh', - 'flick', - 'flier', - 'fling', - 'flint', - 'flirt', - 'float', - 'flock', - 'flood', - 'floor', - 'flora', - 'floss', - 'flour', - 'flout', - 'flown', - 'fluff', - 'fluid', - 'fluke', - 'flume', - 'flung', - 'flunk', - 'flush', - 'flute', - 'flyer', - 'foamy', - 'focal', - 'focus', - 'foggy', - 'foist', - 'folio', - 'folly', - 'foray', - 'force', - 'forge', - 'forgo', - 'forte', - 'forth', - 'forty', - 'forum', - 'found', - 'foyer', - 'frail', - 'frame', - 'frank', - 'fraud', - 'freak', - 'freed', - 'freer', - 'fresh', - 'friar', - 'fried', - 'frill', - 'frisk', - 'fritz', - 'frock', - 'frond', - 'front', - 'frost', - 'froth', - 'frown', - 'froze', - 'fruit', - 'fudge', - 'fugue', - 'fully', - 'fungi', - 'funky', - 'funny', - 'furor', - 'furry', - 'fussy', - 'fuzzy', - 'gaffe', - 'gaily', - 'gamer', - 'gamma', - 'gamut', - 'gassy', - 'gaudy', - 'gauge', - 'gaunt', - 'gauze', - 'gavel', - 'gawky', - 'gayer', - 'gayly', - 'gazer', - 'gecko', - 'geeky', - 'geese', - 'genie', - 'genre', - 'ghost', - 'ghoul', - 'giant', - 'giddy', - 'gipsy', - 'girly', - 'girth', - 'given', - 'giver', - 'glade', - 'gland', - 'glare', - 'glass', - 'glaze', - 'gleam', - 'glean', - 'glide', - 'glint', - 'gloat', - 'globe', - 'gloom', - 'glory', - 'gloss', - 'glove', - 'glyph', - 'gnash', - 'gnome', - 'godly', - 'going', - 'golem', - 'golly', - 'gonad', - 'goner', - 'goody', - 'gooey', - 'goofy', - 'goose', - 'gorge', - 'gouge', - 'gourd', - 'grace', - 'grade', - 'graft', - 'grail', - 'grain', - 'grand', - 'grant', - 'grape', - 'graph', - 'grasp', - 'grass', - 'grate', - 'grave', - 'gravy', - 'graze', - 'great', - 'greed', - 'green', - 'greet', - 'grief', - 'grill', - 'grime', - 'grimy', - 'grind', - 'gripe', - 'groan', - 'groin', - 'groom', - 'grope', - 'gross', - 'group', - 'grout', - 'grove', - 'growl', - 'grown', - 'gruel', - 'gruff', - 'grunt', - 'guard', - 'guava', - 'guess', - 'guest', - 'guide', - 'guild', - 'guile', - 'guilt', - 'guise', - 'gulch', - 'gully', - 'gumbo', - 'gummy', - 'guppy', - 'gusto', - 'gusty', - 'gypsy', - 'habit', - 'hairy', - 'halve', - 'handy', - 'happy', - 'hardy', - 'harem', - 'harpy', - 'harry', - 'harsh', - 'haste', - 'hasty', - 'hatch', - 'hater', - 'haunt', - 'haute', - 'haven', - 'havoc', - 'hazel', - 'heady', - 'heard', - 'heart', - 'heath', - 'heave', - 'heavy', - 'hedge', - 'hefty', - 'heist', - 'helix', - 'hello', - 'hence', - 'heron', - 'hilly', - 'hinge', - 'hippo', - 'hippy', - 'hitch', - 'hoard', - 'hobby', - 'hoist', - 'holly', - 'homer', - 'honey', - 'honor', - 'horde', - 'horny', - 'horse', - 'hotel', - 'hotly', - 'hound', - 'house', - 'hovel', - 'hover', - 'howdy', - 'human', - 'humid', - 'humor', - 'humph', - 'humus', - 'hunch', - 'hunky', - 'hurry', - 'husky', - 'hussy', - 'hutch', - 'hydro', - 'hyena', - 'hymen', - 'hyper', - 'icily', - 'icing', - 'ideal', - 'idiom', - 'idiot', - 'idler', - 'idyll', - 'igloo', - 'iliac', - 'image', - 'imbue', - 'impel', - 'imply', - 'inane', - 'inbox', - 'incur', - 'index', - 'inept', - 'inert', - 'infer', - 'ingot', - 'inlay', - 'inlet', - 'inner', - 'input', - 'inter', - 'intro', - 'ionic', - 'irate', - 'irony', - 'islet', - 'issue', - 'itchy', - 'ivory', - 'jaunt', - 'jazzy', - 'jelly', - 'jerky', - 'jetty', - 'jewel', - 'jiffy', - 'joint', - 'joist', - 'joker', - 'jolly', - 'joust', - 'judge', - 'juice', - 'juicy', - 'jumbo', - 'jumpy', - 'junta', - 'junto', - 'juror', - 'kappa', - 'karma', - 'kayak', - 'kebab', - 'khaki', - 'kinky', - 'kiosk', - 'kitty', - 'knack', - 'knave', - 'knead', - 'kneed', - 'kneel', - 'knelt', - 'knife', - 'knock', - 'knoll', - 'known', - 'koala', - 'krill', - 'label', - 'labor', - 'laden', - 'ladle', - 'lager', - 'lance', - 'lanky', - 'lapel', - 'lapse', - 'large', - 'larva', - 'lasso', - 'latch', - 'later', - 'lathe', - 'latte', - 'laugh', - 'layer', - 'leach', - 'leafy', - 'leaky', - 'leant', - 'leapt', - 'learn', - 'lease', - 'leash', - 'least', - 'leave', - 'ledge', - 'leech', - 'leery', - 'lefty', - 'legal', - 'leggy', - 'lemon', - 'lemur', - 'leper', - 'level', - 'lever', - 'libel', - 'liege', - 'light', - 'liken', - 'lilac', - 'limbo', - 'limit', - 'linen', - 'liner', - 'lingo', - 'lipid', - 'lithe', - 'liver', - 'livid', - 'llama', - 'loamy', - 'loath', - 'lobby', - 'local', - 'locus', - 'lodge', - 'lofty', - 'logic', - 'login', - 'loopy', - 'loose', - 'lorry', - 'loser', - 'louse', - 'lousy', - 'lover', - 'lower', - 'lowly', - 'loyal', - 'lucid', - 'lucky', - 'lumen', - 'lumpy', - 'lunar', - 'lunch', - 'lunge', - 'lupus', - 'lurch', - 'lurid', - 'lusty', - 'lying', - 'lymph', - 'lynch', - 'lyric', - 'macaw', - 'macho', - 'macro', - 'madam', - 'madly', - 'mafia', - 'magic', - 'magma', - 'maize', - 'major', - 'maker', - 'mambo', - 'mamma', - 'mammy', - 'manga', - 'mange', - 'mango', - 'mangy', - 'mania', - 'manic', - 'manly', - 'manor', - 'maple', - 'march', - 'marry', - 'marsh', - 'mason', - 'masse', - 'match', - 'matey', - 'mauve', - 'maxim', - 'maybe', - 'mayor', - 'mealy', - 'meant', - 'meaty', - 'mecca', - 'medal', - 'media', - 'medic', - 'melee', - 'melon', - 'mercy', - 'merge', - 'merit', - 'merry', - 'metal', - 'meter', - 'metro', - 'micro', - 'midge', - 'midst', - 'might', - 'milky', - 'mimic', - 'mince', - 'miner', - 'minim', - 'minor', - 'minty', - 'minus', - 'mirth', - 'miser', - 'missy', - 'mocha', - 'modal', - 'model', - 'modem', - 'mogul', - 'moist', - 'molar', - 'moldy', - 'money', - 'month', - 'moody', - 'moose', - 'moral', - 'moron', - 'morph', - 'mossy', - 'motel', - 'motif', - 'motor', - 'motto', - 'moult', - 'mound', - 'mount', - 'mourn', - 'mouse', - 'mouth', - 'mover', - 'movie', - 'mower', - 'mucky', - 'mucus', - 'muddy', - 'mulch', - 'mummy', - 'munch', - 'mural', - 'murky', - 'mushy', - 'music', - 'musky', - 'musty', - 'myrrh', - 'nadir', - 'naive', - 'nanny', - 'nasal', - 'nasty', - 'natal', - 'naval', - 'navel', - 'needy', - 'neigh', - 'nerdy', - 'nerve', - 'never', - 'newer', - 'newly', - 'nicer', - 'niche', - 'niece', - 'night', - 'ninja', - 'ninny', - 'ninth', - 'noble', - 'nobly', - 'noise', - 'noisy', - 'nomad', - 'noose', - 'north', - 'nosey', - 'notch', - 'novel', - 'nudge', - 'nurse', - 'nutty', - 'nylon', - 'nymph', - 'oaken', - 'obese', - 'occur', - 'ocean', - 'octal', - 'octet', - 'odder', - 'oddly', - 'offal', - 'offer', - 'often', - 'olden', - 'older', - 'olive', - 'ombre', - 'omega', - 'onion', - 'onset', - 'opera', - 'opine', - 'opium', - 'optic', - 'orbit', - 'order', - 'organ', - 'other', - 'otter', - 'ought', - 'ounce', - 'outdo', - 'outer', - 'outgo', - 'ovary', - 'ovate', - 'overt', - 'ovine', - 'ovoid', - 'owing', - 'owner', - 'oxide', - 'ozone', - 'paddy', - 'pagan', - 'paint', - 'paler', - 'palsy', - 'panel', - 'panic', - 'pansy', - 'papal', - 'paper', - 'parer', - 'parka', - 'parry', - 'parse', - 'party', - 'pasta', - 'paste', - 'pasty', - 'patch', - 'patio', - 'patsy', - 'patty', - 'pause', - 'payee', - 'payer', - 'peace', - 'peach', - 'pearl', - 'pecan', - 'pedal', - 'penal', - 'pence', - 'penne', - 'penny', - 'perch', - 'peril', - 'perky', - 'pesky', - 'pesto', - 'petal', - 'petty', - 'phase', - 'phone', - 'phony', - 'photo', - 'piano', - 'picky', - 'piece', - 'piety', - 'piggy', - 'pilot', - 'pinch', - 'piney', - 'pinky', - 'pinto', - 'piper', - 'pique', - 'pitch', - 'pithy', - 'pivot', - 'pixel', - 'pixie', - 'pizza', - 'place', - 'plaid', - 'plain', - 'plait', - 'plane', - 'plank', - 'plant', - 'plate', - 'plaza', - 'plead', - 'pleat', - 'plied', - 'plier', - 'pluck', - 'plumb', - 'plume', - 'plump', - 'plunk', - 'plush', - 'poesy', - 'point', - 'poise', - 'poker', - 'polar', - 'polka', - 'polyp', - 'pooch', - 'poppy', - 'porch', - 'poser', - 'posit', - 'posse', - 'pouch', - 'pound', - 'pouty', - 'power', - 'prank', - 'prawn', - 'preen', - 'press', - 'price', - 'prick', - 'pride', - 'pried', - 'prime', - 'primo', - 'print', - 'prior', - 'prism', - 'privy', - 'prize', - 'probe', - 'prone', - 'prong', - 'proof', - 'prose', - 'proud', - 'prove', - 'prowl', - 'proxy', - 'prude', - 'prune', - 'psalm', - 'pubic', - 'pudgy', - 'puffy', - 'pulpy', - 'pulse', - 'punch', - 'pupal', - 'pupil', - 'puppy', - 'puree', - 'purer', - 'purge', - 'purse', - 'pushy', - 'putty', - 'pygmy', - 'quack', - 'quail', - 'quake', - 'qualm', - 'quark', - 'quart', - 'quash', - 'quasi', - 'queen', - 'queer', - 'quell', - 'query', - 'quest', - 'queue', - 'quick', - 'quiet', - 'quill', - 'quilt', - 'quirk', - 'quite', - 'quota', - 'quote', - 'quoth', - 'rabbi', - 'rabid', - 'racer', - 'radar', - 'radii', - 'radio', - 'rainy', - 'raise', - 'rajah', - 'rally', - 'ralph', - 'ramen', - 'ranch', - 'randy', - 'range', - 'rapid', - 'rarer', - 'raspy', - 'ratio', - 'ratty', - 'raven', - 'rayon', - 'razor', - 'reach', - 'react', - 'ready', - 'realm', - 'rearm', - 'rebar', - 'rebel', - 'rebus', - 'rebut', - 'recap', - 'recur', - 'recut', - 'reedy', - 'refer', - 'refit', - 'regal', - 'rehab', - 'reign', - 'relax', - 'relay', - 'relic', - 'remit', - 'renal', - 'renew', - 'repay', - 'repel', - 'reply', - 'rerun', - 'reset', - 'resin', - 'retch', - 'retro', - 'retry', - 'reuse', - 'revel', - 'revue', - 'rhino', - 'rhyme', - 'rider', - 'ridge', - 'rifle', - 'right', - 'rigid', - 'rigor', - 'rinse', - 'ripen', - 'riper', - 'risen', - 'riser', - 'risky', - 'rival', - 'river', - 'rivet', - 'roach', - 'roast', - 'robin', - 'robot', - 'rocky', - 'rodeo', - 'roger', - 'rogue', - 'roomy', - 'roost', - 'rotor', - 'rouge', - 'rough', - 'round', - 'rouse', - 'route', - 'rover', - 'rowdy', - 'rower', - 'royal', - 'ruddy', - 'ruder', - 'rugby', - 'ruler', - 'rumba', - 'rumor', - 'rupee', - 'rural', - 'rusty', - 'sadly', - 'safer', - 'saint', - 'salad', - 'sally', - 'salon', - 'salsa', - 'salty', - 'salve', - 'salvo', - 'sandy', - 'saner', - 'sappy', - 'sassy', - 'satin', - 'satyr', - 'sauce', - 'saucy', - 'sauna', - 'saute', - 'savor', - 'savoy', - 'savvy', - 'scald', - 'scale', - 'scalp', - 'scaly', - 'scamp', - 'scant', - 'scare', - 'scarf', - 'scary', - 'scene', - 'scent', - 'scion', - 'scoff', - 'scold', - 'scone', - 'scoop', - 'scope', - 'score', - 'scorn', - 'scour', - 'scout', - 'scowl', - 'scram', - 'scrap', - 'scree', - 'screw', - 'scrub', - 'scrum', - 'scuba', - 'sedan', - 'seedy', - 'segue', - 'seize', - 'semen', - 'sense', - 'sepia', - 'serif', - 'serum', - 'serve', - 'setup', - 'seven', - 'sever', - 'sewer', - 'shack', - 'shade', - 'shady', - 'shaft', - 'shake', - 'shaky', - 'shale', - 'shall', - 'shalt', - 'shame', - 'shank', - 'shape', - 'shard', - 'share', - 'shark', - 'sharp', - 'shave', - 'shawl', - 'shear', - 'sheen', - 'sheep', - 'sheer', - 'sheet', - 'sheik', - 'shelf', - 'shell', - 'shied', - 'shift', - 'shine', - 'shiny', - 'shire', - 'shirk', - 'shirt', - 'shoal', - 'shock', - 'shone', - 'shook', - 'shoot', - 'shore', - 'shorn', - 'short', - 'shout', - 'shove', - 'shown', - 'showy', - 'shrew', - 'shrub', - 'shrug', - 'shuck', - 'shunt', - 'shush', - 'shyly', - 'siege', - 'sieve', - 'sight', - 'sigma', - 'silky', - 'silly', - 'since', - 'sinew', - 'singe', - 'siren', - 'sissy', - 'sixth', - 'sixty', - 'skate', - 'skier', - 'skiff', - 'skill', - 'skimp', - 'skirt', - 'skulk', - 'skull', - 'skunk', - 'slack', - 'slain', - 'slang', - 'slant', - 'slash', - 'slate', - 'slave', - 'sleek', - 'sleep', - 'sleet', - 'slept', - 'slice', - 'slick', - 'slide', - 'slime', - 'slimy', - 'sling', - 'slink', - 'sloop', - 'slope', - 'slosh', - 'sloth', - 'slump', - 'slung', - 'slunk', - 'slurp', - 'slush', - 'slyly', - 'smack', - 'small', - 'smart', - 'smash', - 'smear', - 'smell', - 'smelt', - 'smile', - 'smirk', - 'smite', - 'smith', - 'smock', - 'smoke', - 'smoky', - 'smote', - 'snack', - 'snail', - 'snake', - 'snaky', - 'snare', - 'snarl', - 'sneak', - 'sneer', - 'snide', - 'sniff', - 'snipe', - 'snoop', - 'snore', - 'snort', - 'snout', - 'snowy', - 'snuck', - 'snuff', - 'soapy', - 'sober', - 'soggy', - 'solar', - 'solid', - 'solve', - 'sonar', - 'sonic', - 'sooth', - 'sooty', - 'sorry', - 'sound', - 'south', - 'sower', - 'space', - 'spade', - 'spank', - 'spare', - 'spark', - 'spasm', - 'spawn', - 'speak', - 'spear', - 'speck', - 'speed', - 'spell', - 'spelt', - 'spend', - 'spent', - 'sperm', - 'spice', - 'spicy', - 'spied', - 'spiel', - 'spike', - 'spiky', - 'spill', - 'spilt', - 'spine', - 'spiny', - 'spire', - 'spite', - 'splat', - 'split', - 'spoil', - 'spoke', - 'spoof', - 'spook', - 'spool', - 'spoon', - 'spore', - 'sport', - 'spout', - 'spray', - 'spree', - 'sprig', - 'spunk', - 'spurn', - 'spurt', - 'squad', - 'squat', - 'squib', - 'stack', - 'staff', - 'stage', - 'staid', - 'stain', - 'stair', - 'stake', - 'stale', - 'stalk', - 'stall', - 'stamp', - 'stand', - 'stank', - 'stare', - 'stark', - 'start', - 'stash', - 'state', - 'stave', - 'stead', - 'steak', - 'steal', - 'steam', - 'steed', - 'steel', - 'steep', - 'steer', - 'stein', - 'stern', - 'stick', - 'stiff', - 'still', - 'stilt', - 'sting', - 'stink', - 'stint', - 'stock', - 'stoic', - 'stoke', - 'stole', - 'stomp', - 'stone', - 'stony', - 'stood', - 'stool', - 'stoop', - 'store', - 'stork', - 'storm', - 'story', - 'stout', - 'stove', - 'strap', - 'straw', - 'stray', - 'strip', - 'strut', - 'stuck', - 'study', - 'stuff', - 'stump', - 'stung', - 'stunk', - 'stunt', - 'style', - 'suave', - 'sugar', - 'suing', - 'suite', - 'sulky', - 'sully', - 'sumac', - 'sunny', - 'super', - 'surer', - 'surge', - 'surly', - 'sushi', - 'swami', - 'swamp', - 'swarm', - 'swash', - 'swath', - 'swear', - 'sweat', - 'sweep', - 'sweet', - 'swell', - 'swept', - 'swift', - 'swill', - 'swine', - 'swing', - 'swirl', - 'swish', - 'swoon', - 'swoop', - 'sword', - 'swore', - 'sworn', - 'swung', - 'synod', - 'syrup', - 'tabby', - 'table', - 'taboo', - 'tacit', - 'tacky', - 'taffy', - 'taint', - 'taken', - 'taker', - 'tally', - 'talon', - 'tamer', - 'tango', - 'tangy', - 'taper', - 'tapir', - 'tardy', - 'tarot', - 'taste', - 'tasty', - 'tatty', - 'taunt', - 'tawny', - 'teach', - 'teary', - 'tease', - 'teddy', - 'teeth', - 'tempo', - 'tenet', - 'tenor', - 'tense', - 'tenth', - 'tepee', - 'tepid', - 'terra', - 'terse', - 'testy', - 'thank', - 'theft', - 'their', - 'theme', - 'there', - 'these', - 'theta', - 'thick', - 'thief', - 'thigh', - 'thing', - 'think', - 'third', - 'thong', - 'thorn', - 'those', - 'three', - 'threw', - 'throb', - 'throw', - 'thrum', - 'thumb', - 'thump', - 'thyme', - 'tiara', - 'tibia', - 'tidal', - 'tiger', - 'tight', - 'tilde', - 'timer', - 'timid', - 'tipsy', - 'titan', - 'tithe', - 'title', - 'toast', - 'today', - 'toddy', - 'token', - 'tonal', - 'tonga', - 'tonic', - 'tooth', - 'topaz', - 'topic', - 'torch', - 'torso', - 'torus', - 'total', - 'totem', - 'touch', - 'tough', - 'towel', - 'tower', - 'toxic', - 'toxin', - 'trace', - 'track', - 'tract', - 'trade', - 'trail', - 'train', - 'trait', - 'tramp', - 'trash', - 'trawl', - 'tread', - 'treat', - 'trend', - 'triad', - 'trial', - 'tribe', - 'trice', - 'trick', - 'tried', - 'tripe', - 'trite', - 'troll', - 'troop', - 'trope', - 'trout', - 'trove', - 'truce', - 'truck', - 'truer', - 'truly', - 'trump', - 'trunk', - 'truss', - 'trust', - 'truth', - 'tryst', - 'tubal', - 'tuber', - 'tulip', - 'tulle', - 'tumor', - 'tunic', - 'turbo', - 'tutor', - 'twang', - 'tweak', - 'tweed', - 'tweet', - 'twice', - 'twine', - 'twirl', - 'twist', - 'twixt', - 'tying', - 'udder', - 'ulcer', - 'ultra', - 'umbra', - 'uncle', - 'uncut', - 'under', - 'undid', - 'undue', - 'unfed', - 'unfit', - 'unify', - 'union', - 'unite', - 'unity', - 'unlit', - 'unmet', - 'unset', - 'untie', - 'until', - 'unwed', - 'unzip', - 'upper', - 'upset', - 'urban', - 'urine', - 'usage', - 'usher', - 'using', - 'usual', - 'usurp', - 'utile', - 'utter', - 'vague', - 'valet', - 'valid', - 'valor', - 'value', - 'valve', - 'vapid', - 'vapor', - 'vault', - 'vaunt', - 'vegan', - 'venom', - 'venue', - 'verge', - 'verse', - 'verso', - 'verve', - 'vicar', - 'video', - 'vigil', - 'vigor', - 'villa', - 'vinyl', - 'viola', - 'viper', - 'viral', - 'virus', - 'visit', - 'visor', - 'vista', - 'vital', - 'vivid', - 'vixen', - 'vocal', - 'vodka', - 'vogue', - 'voice', - 'voila', - 'vomit', - 'voter', - 'vouch', - 'vowel', - 'vying', - 'wacky', - 'wafer', - 'wager', - 'wagon', - 'waist', - 'waive', - 'waltz', - 'warty', - 'waste', - 'watch', - 'water', - 'waver', - 'waxen', - 'weary', - 'weave', - 'wedge', - 'weedy', - 'weigh', - 'weird', - 'welch', - 'welsh', - 'wench', - 'whack', - 'whale', - 'wharf', - 'wheat', - 'wheel', - 'whelp', - 'where', - 'which', - 'whiff', - 'while', - 'whine', - 'whiny', - 'whirl', - 'whisk', - 'white', - 'whole', - 'whoop', - 'whose', - 'widen', - 'wider', - 'widow', - 'width', - 'wield', - 'wight', - 'willy', - 'wimpy', - 'wince', - 'winch', - 'windy', - 'wiser', - 'wispy', - 'witch', - 'witty', - 'woken', - 'woman', - 'women', - 'woody', - 'wooer', - 'wooly', - 'woozy', - 'wordy', - 'world', - 'worry', - 'worse', - 'worst', - 'worth', - 'would', - 'wound', - 'woven', - 'wrack', - 'wrath', - 'wreak', - 'wreck', - 'wrest', - 'wring', - 'wrist', - 'write', - 'wrong', - 'wrote', - 'wrung', - 'wryly', - 'yacht', - 'yearn', - 'yeast', - 'yield', - 'young', - 'youth', - 'zebra', - 'zesty', - 'zonal' -]; - -/** The list of valid guesses, of which the list of possible words is a subset */ -export const allowed = new Set([ - ...words, - 'aahed', - 'aalii', - 'aargh', - 'aarti', - 'abaca', - 'abaci', - 'abacs', - 'abaft', - 'abaka', - 'abamp', - 'aband', - 'abash', - 'abask', - 'abaya', - 'abbas', - 'abbed', - 'abbes', - 'abcee', - 'abeam', - 'abear', - 'abele', - 'abers', - 'abets', - 'abies', - 'abler', - 'ables', - 'ablet', - 'ablow', - 'abmho', - 'abohm', - 'aboil', - 'aboma', - 'aboon', - 'abord', - 'abore', - 'abram', - 'abray', - 'abrim', - 'abrin', - 'abris', - 'absey', - 'absit', - 'abuna', - 'abune', - 'abuts', - 'abuzz', - 'abyes', - 'abysm', - 'acais', - 'acari', - 'accas', - 'accoy', - 'acerb', - 'acers', - 'aceta', - 'achar', - 'ached', - 'aches', - 'achoo', - 'acids', - 'acidy', - 'acing', - 'acini', - 'ackee', - 'acker', - 'acmes', - 'acmic', - 'acned', - 'acnes', - 'acock', - 'acold', - 'acred', - 'acres', - 'acros', - 'acted', - 'actin', - 'acton', - 'acyls', - 'adaws', - 'adays', - 'adbot', - 'addax', - 'added', - 'adder', - 'addio', - 'addle', - 'adeem', - 'adhan', - 'adieu', - 'adios', - 'adits', - 'adman', - 'admen', - 'admix', - 'adobo', - 'adown', - 'adoze', - 'adrad', - 'adred', - 'adsum', - 'aduki', - 'adunc', - 'adust', - 'advew', - 'adyta', - 'adzed', - 'adzes', - 'aecia', - 'aedes', - 'aegis', - 'aeons', - 'aerie', - 'aeros', - 'aesir', - 'afald', - 'afara', - 'afars', - 'afear', - 'aflaj', - 'afore', - 'afrit', - 'afros', - 'agama', - 'agami', - 'agars', - 'agast', - 'agave', - 'agaze', - 'agene', - 'agers', - 'agger', - 'aggie', - 'aggri', - 'aggro', - 'aggry', - 'aghas', - 'agila', - 'agios', - 'agism', - 'agist', - 'agita', - 'aglee', - 'aglet', - 'agley', - 'agloo', - 'aglus', - 'agmas', - 'agoge', - 'agone', - 'agons', - 'agood', - 'agria', - 'agrin', - 'agros', - 'agued', - 'agues', - 'aguna', - 'aguti', - 'aheap', - 'ahent', - 'ahigh', - 'ahind', - 'ahing', - 'ahint', - 'ahold', - 'ahull', - 'ahuru', - 'aidas', - 'aided', - 'aides', - 'aidoi', - 'aidos', - 'aiery', - 'aigas', - 'aight', - 'ailed', - 'aimed', - 'aimer', - 'ainee', - 'ainga', - 'aioli', - 'aired', - 'airer', - 'airns', - 'airth', - 'airts', - 'aitch', - 'aitus', - 'aiver', - 'aiyee', - 'aizle', - 'ajies', - 'ajiva', - 'ajuga', - 'ajwan', - 'akees', - 'akela', - 'akene', - 'aking', - 'akita', - 'akkas', - 'alaap', - 'alack', - 'alamo', - 'aland', - 'alane', - 'alang', - 'alans', - 'alant', - 'alapa', - 'alaps', - 'alary', - 'alate', - 'alays', - 'albas', - 'albee', - 'alcid', - 'alcos', - 'aldea', - 'alder', - 'aldol', - 'aleck', - 'alecs', - 'alefs', - 'aleft', - 'aleph', - 'alews', - 'aleye', - 'alfas', - 'algal', - 'algas', - 'algid', - 'algin', - 'algor', - 'algum', - 'alias', - 'alifs', - 'aline', - 'alist', - 'aliya', - 'alkie', - 'alkos', - 'alkyd', - 'alkyl', - 'allee', - 'allel', - 'allis', - 'allod', - 'allyl', - 'almah', - 'almas', - 'almeh', - 'almes', - 'almud', - 'almug', - 'alods', - 'aloed', - 'aloes', - 'aloha', - 'aloin', - 'aloos', - 'alowe', - 'altho', - 'altos', - 'alula', - 'alums', - 'alure', - 'alvar', - 'alway', - 'amahs', - 'amain', - 'amate', - 'amaut', - 'amban', - 'ambit', - 'ambos', - 'ambry', - 'ameba', - 'ameer', - 'amene', - 'amens', - 'ament', - 'amias', - 'amice', - 'amici', - 'amide', - 'amido', - 'amids', - 'amies', - 'amiga', - 'amigo', - 'amine', - 'amino', - 'amins', - 'amirs', - 'amlas', - 'amman', - 'ammon', - 'ammos', - 'amnia', - 'amnic', - 'amnio', - 'amoks', - 'amole', - 'amort', - 'amour', - 'amove', - 'amowt', - 'amped', - 'ampul', - 'amrit', - 'amuck', - 'amyls', - 'anana', - 'anata', - 'ancho', - 'ancle', - 'ancon', - 'andro', - 'anear', - 'anele', - 'anent', - 'angas', - 'anglo', - 'anigh', - 'anile', - 'anils', - 'anima', - 'animi', - 'anion', - 'anise', - 'anker', - 'ankhs', - 'ankus', - 'anlas', - 'annal', - 'annas', - 'annat', - 'anoas', - 'anole', - 'anomy', - 'ansae', - 'antae', - 'antar', - 'antas', - 'anted', - 'antes', - 'antis', - 'antra', - 'antre', - 'antsy', - 'anura', - 'anyon', - 'apace', - 'apage', - 'apaid', - 'apayd', - 'apays', - 'apeak', - 'apeek', - 'apers', - 'apert', - 'apery', - 'apgar', - 'aphis', - 'apian', - 'apiol', - 'apish', - 'apism', - 'apode', - 'apods', - 'apoop', - 'aport', - 'appal', - 'appay', - 'appel', - 'appro', - 'appui', - 'appuy', - 'apres', - 'apses', - 'apsis', - 'apsos', - 'apted', - 'apter', - 'aquae', - 'aquas', - 'araba', - 'araks', - 'arame', - 'arars', - 'arbas', - 'arced', - 'archi', - 'arcos', - 'arcus', - 'ardeb', - 'ardri', - 'aread', - 'areae', - 'areal', - 'arear', - 'areas', - 'areca', - 'aredd', - 'arede', - 'arefy', - 'areic', - 'arene', - 'arepa', - 'arere', - 'arete', - 'arets', - 'arett', - 'argal', - 'argan', - 'argil', - 'argle', - 'argol', - 'argon', - 'argot', - 'argus', - 'arhat', - 'arias', - 'ariel', - 'ariki', - 'arils', - 'ariot', - 'arish', - 'arked', - 'arled', - 'arles', - 'armed', - 'armer', - 'armet', - 'armil', - 'arnas', - 'arnut', - 'aroba', - 'aroha', - 'aroid', - 'arpas', - 'arpen', - 'arrah', - 'arras', - 'arret', - 'arris', - 'arroz', - 'arsed', - 'arses', - 'arsey', - 'arsis', - 'artal', - 'artel', - 'artic', - 'artis', - 'aruhe', - 'arums', - 'arval', - 'arvee', - 'arvos', - 'aryls', - 'asana', - 'ascon', - 'ascus', - 'asdic', - 'ashed', - 'ashes', - 'ashet', - 'asked', - 'asker', - 'askoi', - 'askos', - 'aspen', - 'asper', - 'aspic', - 'aspie', - 'aspis', - 'aspro', - 'assai', - 'assam', - 'asses', - 'assez', - 'assot', - 'aster', - 'astir', - 'astun', - 'asura', - 'asway', - 'aswim', - 'asyla', - 'ataps', - 'ataxy', - 'atigi', - 'atilt', - 'atimy', - 'atlas', - 'atman', - 'atmas', - 'atmos', - 'atocs', - 'atoke', - 'atoks', - 'atoms', - 'atomy', - 'atony', - 'atopy', - 'atria', - 'atrip', - 'attap', - 'attar', - 'atuas', - 'audad', - 'auger', - 'aught', - 'aulas', - 'aulic', - 'auloi', - 'aulos', - 'aumil', - 'aunes', - 'aunts', - 'aurae', - 'aural', - 'aurar', - 'auras', - 'aurei', - 'aures', - 'auric', - 'auris', - 'aurum', - 'autos', - 'auxin', - 'avale', - 'avant', - 'avast', - 'avels', - 'avens', - 'avers', - 'avgas', - 'avine', - 'avion', - 'avise', - 'aviso', - 'avize', - 'avows', - 'avyze', - 'awarn', - 'awato', - 'awave', - 'aways', - 'awdls', - 'aweel', - 'aweto', - 'awing', - 'awmry', - 'awned', - 'awner', - 'awols', - 'awork', - 'axels', - 'axile', - 'axils', - 'axing', - 'axite', - 'axled', - 'axles', - 'axman', - 'axmen', - 'axoid', - 'axone', - 'axons', - 'ayahs', - 'ayaya', - 'ayelp', - 'aygre', - 'ayins', - 'ayont', - 'ayres', - 'ayrie', - 'azans', - 'azide', - 'azido', - 'azine', - 'azlon', - 'azoic', - 'azole', - 'azons', - 'azote', - 'azoth', - 'azuki', - 'azurn', - 'azury', - 'azygy', - 'azyme', - 'azyms', - 'baaed', - 'baals', - 'babas', - 'babel', - 'babes', - 'babka', - 'baboo', - 'babul', - 'babus', - 'bacca', - 'bacco', - 'baccy', - 'bacha', - 'bachs', - 'backs', - 'baddy', - 'baels', - 'baffs', - 'baffy', - 'bafts', - 'baghs', - 'bagie', - 'bahts', - 'bahus', - 'bahut', - 'bails', - 'bairn', - 'baisa', - 'baith', - 'baits', - 'baiza', - 'baize', - 'bajan', - 'bajra', - 'bajri', - 'bajus', - 'baked', - 'baken', - 'bakes', - 'bakra', - 'balas', - 'balds', - 'baldy', - 'baled', - 'bales', - 'balks', - 'balky', - 'balls', - 'bally', - 'balms', - 'baloo', - 'balsa', - 'balti', - 'balun', - 'balus', - 'bambi', - 'banak', - 'banco', - 'bancs', - 'banda', - 'bandh', - 'bands', - 'bandy', - 'baned', - 'banes', - 'bangs', - 'bania', - 'banks', - 'banns', - 'bants', - 'bantu', - 'banty', - 'banya', - 'bapus', - 'barbe', - 'barbs', - 'barby', - 'barca', - 'barde', - 'bardo', - 'bards', - 'bardy', - 'bared', - 'barer', - 'bares', - 'barfi', - 'barfs', - 'baric', - 'barks', - 'barky', - 'barms', - 'barmy', - 'barns', - 'barny', - 'barps', - 'barra', - 'barre', - 'barro', - 'barry', - 'barye', - 'basan', - 'based', - 'basen', - 'baser', - 'bases', - 'basho', - 'basij', - 'basks', - 'bason', - 'basse', - 'bassi', - 'basso', - 'bassy', - 'basta', - 'basti', - 'basto', - 'basts', - 'bated', - 'bates', - 'baths', - 'batik', - 'batta', - 'batts', - 'battu', - 'bauds', - 'bauks', - 'baulk', - 'baurs', - 'bavin', - 'bawds', - 'bawks', - 'bawls', - 'bawns', - 'bawrs', - 'bawty', - 'bayed', - 'bayer', - 'bayes', - 'bayle', - 'bayts', - 'bazar', - 'bazoo', - 'beads', - 'beaks', - 'beaky', - 'beals', - 'beams', - 'beamy', - 'beano', - 'beans', - 'beany', - 'beare', - 'bears', - 'beath', - 'beats', - 'beaty', - 'beaus', - 'beaut', - 'beaux', - 'bebop', - 'becap', - 'becke', - 'becks', - 'bedad', - 'bedel', - 'bedes', - 'bedew', - 'bedim', - 'bedye', - 'beedi', - 'beefs', - 'beeps', - 'beers', - 'beery', - 'beets', - 'befog', - 'begad', - 'begar', - 'begem', - 'begot', - 'begum', - 'beige', - 'beigy', - 'beins', - 'bekah', - 'belah', - 'belar', - 'belay', - 'belee', - 'belga', - 'bells', - 'belon', - 'belts', - 'bemad', - 'bemas', - 'bemix', - 'bemud', - 'bends', - 'bendy', - 'benes', - 'benet', - 'benga', - 'benis', - 'benne', - 'benni', - 'benny', - 'bento', - 'bents', - 'benty', - 'bepat', - 'beray', - 'beres', - 'bergs', - 'berko', - 'berks', - 'berme', - 'berms', - 'berob', - 'beryl', - 'besat', - 'besaw', - 'besee', - 'beses', - 'besit', - 'besom', - 'besot', - 'besti', - 'bests', - 'betas', - 'beted', - 'betes', - 'beths', - 'betid', - 'beton', - 'betta', - 'betty', - 'bever', - 'bevor', - 'bevue', - 'bevvy', - 'bewet', - 'bewig', - 'bezes', - 'bezil', - 'bezzy', - 'bhais', - 'bhaji', - 'bhang', - 'bhats', - 'bhels', - 'bhoot', - 'bhuna', - 'bhuts', - 'biach', - 'biali', - 'bialy', - 'bibbs', - 'bibes', - 'biccy', - 'bices', - 'bided', - 'bider', - 'bides', - 'bidet', - 'bidis', - 'bidon', - 'bield', - 'biers', - 'biffo', - 'biffs', - 'biffy', - 'bifid', - 'bigae', - 'biggs', - 'biggy', - 'bigha', - 'bight', - 'bigly', - 'bigos', - 'bijou', - 'biked', - 'biker', - 'bikes', - 'bikie', - 'bilbo', - 'bilby', - 'biled', - 'biles', - 'bilgy', - 'bilks', - 'bills', - 'bimah', - 'bimas', - 'bimbo', - 'binal', - 'bindi', - 'binds', - 'biner', - 'bines', - 'bings', - 'bingy', - 'binit', - 'binks', - 'bints', - 'biogs', - 'biont', - 'biota', - 'biped', - 'bipod', - 'birds', - 'birks', - 'birle', - 'birls', - 'biros', - 'birrs', - 'birse', - 'birsy', - 'bises', - 'bisks', - 'bisom', - 'bitch', - 'biter', - 'bites', - 'bitos', - 'bitou', - 'bitsy', - 'bitte', - 'bitts', - 'bivia', - 'bivvy', - 'bizes', - 'bizzo', - 'bizzy', - 'blabs', - 'blads', - 'blady', - 'blaer', - 'blaes', - 'blaff', - 'blags', - 'blahs', - 'blain', - 'blams', - 'blart', - 'blase', - 'blash', - 'blate', - 'blats', - 'blatt', - 'blaud', - 'blawn', - 'blaws', - 'blays', - 'blear', - 'blebs', - 'blech', - 'blees', - 'blent', - 'blert', - 'blest', - 'blets', - 'bleys', - 'blimy', - 'bling', - 'blini', - 'blins', - 'bliny', - 'blips', - 'blist', - 'blite', - 'blits', - 'blive', - 'blobs', - 'blocs', - 'blogs', - 'blook', - 'bloop', - 'blore', - 'blots', - 'blows', - 'blowy', - 'blubs', - 'blude', - 'bluds', - 'bludy', - 'blued', - 'blues', - 'bluet', - 'bluey', - 'bluid', - 'blume', - 'blunk', - 'blurs', - 'blype', - 'boabs', - 'boaks', - 'boars', - 'boart', - 'boats', - 'bobac', - 'bobak', - 'bobas', - 'bobol', - 'bobos', - 'bocca', - 'bocce', - 'bocci', - 'boche', - 'bocks', - 'boded', - 'bodes', - 'bodge', - 'bodhi', - 'bodle', - 'boeps', - 'boets', - 'boeuf', - 'boffo', - 'boffs', - 'bogan', - 'bogey', - 'boggy', - 'bogie', - 'bogle', - 'bogue', - 'bogus', - 'bohea', - 'bohos', - 'boils', - 'boing', - 'boink', - 'boite', - 'boked', - 'bokeh', - 'bokes', - 'bokos', - 'bolar', - 'bolas', - 'bolds', - 'boles', - 'bolix', - 'bolls', - 'bolos', - 'bolts', - 'bolus', - 'bomas', - 'bombe', - 'bombo', - 'bombs', - 'bonce', - 'bonds', - 'boned', - 'boner', - 'bones', - 'bongs', - 'bonie', - 'bonks', - 'bonne', - 'bonny', - 'bonza', - 'bonze', - 'booai', - 'booay', - 'boobs', - 'boody', - 'booed', - 'boofy', - 'boogy', - 'boohs', - 'books', - 'booky', - 'bools', - 'booms', - 'boomy', - 'boong', - 'boons', - 'boord', - 'boors', - 'boose', - 'boots', - 'boppy', - 'borak', - 'boral', - 'boras', - 'borde', - 'bords', - 'bored', - 'boree', - 'borel', - 'borer', - 'bores', - 'borgo', - 'boric', - 'borks', - 'borms', - 'borna', - 'boron', - 'borts', - 'borty', - 'bortz', - 'bosie', - 'bosks', - 'bosky', - 'boson', - 'bosun', - 'botas', - 'botel', - 'botes', - 'bothy', - 'botte', - 'botts', - 'botty', - 'bouge', - 'bouks', - 'boult', - 'bouns', - 'bourd', - 'bourg', - 'bourn', - 'bouse', - 'bousy', - 'bouts', - 'bovid', - 'bowat', - 'bowed', - 'bower', - 'bowes', - 'bowet', - 'bowie', - 'bowls', - 'bowne', - 'bowrs', - 'bowse', - 'boxed', - 'boxen', - 'boxes', - 'boxla', - 'boxty', - 'boyar', - 'boyau', - 'boyed', - 'boyfs', - 'boygs', - 'boyla', - 'boyos', - 'boysy', - 'bozos', - 'braai', - 'brach', - 'brack', - 'bract', - 'brads', - 'braes', - 'brags', - 'brail', - 'braks', - 'braky', - 'brame', - 'brane', - 'brank', - 'brans', - 'brant', - 'brast', - 'brats', - 'brava', - 'bravi', - 'braws', - 'braxy', - 'brays', - 'braza', - 'braze', - 'bream', - 'brede', - 'breds', - 'breem', - 'breer', - 'brees', - 'breid', - 'breis', - 'breme', - 'brens', - 'brent', - 'brere', - 'brers', - 'breve', - 'brews', - 'breys', - 'brier', - 'bries', - 'brigs', - 'briki', - 'briks', - 'brill', - 'brims', - 'brins', - 'brios', - 'brise', - 'briss', - 'brith', - 'brits', - 'britt', - 'brize', - 'broch', - 'brock', - 'brods', - 'brogh', - 'brogs', - 'brome', - 'bromo', - 'bronc', - 'brond', - 'brool', - 'broos', - 'brose', - 'brosy', - 'brows', - 'brugh', - 'bruin', - 'bruit', - 'brule', - 'brume', - 'brung', - 'brusk', - 'brust', - 'bruts', - 'buats', - 'buaze', - 'bubal', - 'bubas', - 'bubba', - 'bubbe', - 'bubby', - 'bubus', - 'buchu', - 'bucko', - 'bucks', - 'bucku', - 'budas', - 'budis', - 'budos', - 'buffa', - 'buffe', - 'buffi', - 'buffo', - 'buffs', - 'buffy', - 'bufos', - 'bufty', - 'buhls', - 'buhrs', - 'buiks', - 'buist', - 'bukes', - 'bulbs', - 'bulgy', - 'bulks', - 'bulla', - 'bulls', - 'bulse', - 'bumbo', - 'bumfs', - 'bumph', - 'bumps', - 'bumpy', - 'bunas', - 'bunce', - 'bunco', - 'bunde', - 'bundh', - 'bunds', - 'bundt', - 'bundu', - 'bundy', - 'bungs', - 'bungy', - 'bunia', - 'bunje', - 'bunjy', - 'bunko', - 'bunks', - 'bunns', - 'bunts', - 'bunty', - 'bunya', - 'buoys', - 'buppy', - 'buran', - 'buras', - 'burbs', - 'burds', - 'buret', - 'burfi', - 'burgh', - 'burgs', - 'burin', - 'burka', - 'burke', - 'burks', - 'burls', - 'burns', - 'buroo', - 'burps', - 'burqa', - 'burro', - 'burrs', - 'burry', - 'bursa', - 'burse', - 'busby', - 'buses', - 'busks', - 'busky', - 'bussu', - 'busti', - 'busts', - 'busty', - 'buteo', - 'butes', - 'butle', - 'butoh', - 'butts', - 'butty', - 'butut', - 'butyl', - 'buzzy', - 'bwana', - 'bwazi', - 'byded', - 'bydes', - 'byked', - 'bykes', - 'byres', - 'byrls', - 'byssi', - 'bytes', - 'byway', - 'caaed', - 'cabas', - 'caber', - 'cabob', - 'caboc', - 'cabre', - 'cacas', - 'cacks', - 'cacky', - 'cadee', - 'cades', - 'cadge', - 'cadgy', - 'cadie', - 'cadis', - 'cadre', - 'caeca', - 'caese', - 'cafes', - 'caffs', - 'caged', - 'cager', - 'cages', - 'cagot', - 'cahow', - 'caids', - 'cains', - 'caird', - 'cajon', - 'cajun', - 'caked', - 'cakes', - 'cakey', - 'calfs', - 'calid', - 'calif', - 'calix', - 'calks', - 'calla', - 'calls', - 'calms', - 'calmy', - 'calos', - 'calpa', - 'calps', - 'calve', - 'calyx', - 'caman', - 'camas', - 'cames', - 'camis', - 'camos', - 'campi', - 'campo', - 'camps', - 'campy', - 'camus', - 'caned', - 'caneh', - 'caner', - 'canes', - 'cangs', - 'canid', - 'canna', - 'canns', - 'canso', - 'canst', - 'canto', - 'cants', - 'canty', - 'capas', - 'caped', - 'capes', - 'capex', - 'caphs', - 'capiz', - 'caple', - 'capon', - 'capos', - 'capot', - 'capri', - 'capul', - 'carap', - 'carbo', - 'carbs', - 'carby', - 'cardi', - 'cards', - 'cardy', - 'cared', - 'carer', - 'cares', - 'caret', - 'carex', - 'carks', - 'carle', - 'carls', - 'carns', - 'carny', - 'carob', - 'carom', - 'caron', - 'carpi', - 'carps', - 'carrs', - 'carse', - 'carta', - 'carte', - 'carts', - 'carvy', - 'casas', - 'casco', - 'cased', - 'cases', - 'casks', - 'casky', - 'casts', - 'casus', - 'cates', - 'cauda', - 'cauks', - 'cauld', - 'cauls', - 'caums', - 'caups', - 'cauri', - 'causa', - 'cavas', - 'caved', - 'cavel', - 'caver', - 'caves', - 'cavie', - 'cawed', - 'cawks', - 'caxon', - 'ceaze', - 'cebid', - 'cecal', - 'cecum', - 'ceded', - 'ceder', - 'cedes', - 'cedis', - 'ceiba', - 'ceili', - 'ceils', - 'celeb', - 'cella', - 'celli', - 'cells', - 'celom', - 'celts', - 'cense', - 'cento', - 'cents', - 'centu', - 'ceorl', - 'cepes', - 'cerci', - 'cered', - 'ceres', - 'cerge', - 'ceria', - 'ceric', - 'cerne', - 'ceroc', - 'ceros', - 'certs', - 'certy', - 'cesse', - 'cesta', - 'cesti', - 'cetes', - 'cetyl', - 'cezve', - 'chace', - 'chack', - 'chaco', - 'chado', - 'chads', - 'chaft', - 'chais', - 'chals', - 'chams', - 'chana', - 'chang', - 'chank', - 'chape', - 'chaps', - 'chapt', - 'chara', - 'chare', - 'chark', - 'charr', - 'chars', - 'chary', - 'chats', - 'chave', - 'chavs', - 'chawk', - 'chaws', - 'chaya', - 'chays', - 'cheep', - 'chefs', - 'cheka', - 'chela', - 'chelp', - 'chemo', - 'chems', - 'chere', - 'chert', - 'cheth', - 'chevy', - 'chews', - 'chewy', - 'chiao', - 'chias', - 'chibs', - 'chica', - 'chich', - 'chico', - 'chics', - 'chiel', - 'chiks', - 'chile', - 'chimb', - 'chimo', - 'chimp', - 'chine', - 'ching', - 'chink', - 'chino', - 'chins', - 'chips', - 'chirk', - 'chirl', - 'chirm', - 'chiro', - 'chirr', - 'chirt', - 'chiru', - 'chits', - 'chive', - 'chivs', - 'chivy', - 'chizz', - 'choco', - 'chocs', - 'chode', - 'chogs', - 'choil', - 'choko', - 'choky', - 'chola', - 'choli', - 'cholo', - 'chomp', - 'chons', - 'choof', - 'chook', - 'choom', - 'choon', - 'chops', - 'chota', - 'chott', - 'chout', - 'choux', - 'chowk', - 'chows', - 'chubs', - 'chufa', - 'chuff', - 'chugs', - 'chums', - 'churl', - 'churr', - 'chuse', - 'chuts', - 'chyle', - 'chyme', - 'chynd', - 'cibol', - 'cided', - 'cides', - 'ciels', - 'ciggy', - 'cilia', - 'cills', - 'cimar', - 'cimex', - 'cinct', - 'cines', - 'cinqs', - 'cions', - 'cippi', - 'circs', - 'cires', - 'cirls', - 'cirri', - 'cisco', - 'cissy', - 'cists', - 'cital', - 'cited', - 'citer', - 'cites', - 'cives', - 'civet', - 'civie', - 'civvy', - 'clach', - 'clade', - 'clads', - 'claes', - 'clags', - 'clame', - 'clams', - 'clans', - 'claps', - 'clapt', - 'claro', - 'clart', - 'clary', - 'clast', - 'clats', - 'claut', - 'clave', - 'clavi', - 'claws', - 'clays', - 'cleck', - 'cleek', - 'cleep', - 'clefs', - 'clegs', - 'cleik', - 'clems', - 'clepe', - 'clept', - 'cleve', - 'clews', - 'clied', - 'clies', - 'clift', - 'clime', - 'cline', - 'clint', - 'clipe', - 'clips', - 'clipt', - 'clits', - 'cloam', - 'clods', - 'cloff', - 'clogs', - 'cloke', - 'clomb', - 'clomp', - 'clonk', - 'clons', - 'cloop', - 'cloot', - 'clops', - 'clote', - 'clots', - 'clour', - 'clous', - 'clows', - 'cloye', - 'cloys', - 'cloze', - 'clubs', - 'clues', - 'cluey', - 'clunk', - 'clype', - 'cnida', - 'coact', - 'coady', - 'coala', - 'coals', - 'coaly', - 'coapt', - 'coarb', - 'coate', - 'coati', - 'coats', - 'cobbs', - 'cobby', - 'cobia', - 'coble', - 'cobza', - 'cocas', - 'cocci', - 'cocco', - 'cocks', - 'cocky', - 'cocos', - 'codas', - 'codec', - 'coded', - 'coden', - 'coder', - 'codes', - 'codex', - 'codon', - 'coeds', - 'coffs', - 'cogie', - 'cogon', - 'cogue', - 'cohab', - 'cohen', - 'cohoe', - 'cohog', - 'cohos', - 'coifs', - 'coign', - 'coils', - 'coins', - 'coirs', - 'coits', - 'coked', - 'cokes', - 'colas', - 'colby', - 'colds', - 'coled', - 'coles', - 'coley', - 'colic', - 'colin', - 'colls', - 'colly', - 'colog', - 'colts', - 'colza', - 'comae', - 'comal', - 'comas', - 'combe', - 'combi', - 'combo', - 'combs', - 'comby', - 'comer', - 'comes', - 'comix', - 'commo', - 'comms', - 'commy', - 'compo', - 'comps', - 'compt', - 'comte', - 'comus', - 'coned', - 'cones', - 'coney', - 'confs', - 'conga', - 'conge', - 'congo', - 'conia', - 'conin', - 'conks', - 'conky', - 'conne', - 'conns', - 'conte', - 'conto', - 'conus', - 'convo', - 'cooch', - 'cooed', - 'cooee', - 'cooer', - 'cooey', - 'coofs', - 'cooks', - 'cooky', - 'cools', - 'cooly', - 'coomb', - 'cooms', - 'coomy', - 'coons', - 'coops', - 'coopt', - 'coost', - 'coots', - 'cooze', - 'copal', - 'copay', - 'coped', - 'copen', - 'coper', - 'copes', - 'coppy', - 'copra', - 'copsy', - 'coqui', - 'coram', - 'corbe', - 'corby', - 'cords', - 'cored', - 'cores', - 'corey', - 'corgi', - 'coria', - 'corks', - 'corky', - 'corms', - 'corni', - 'corno', - 'corns', - 'cornu', - 'corps', - 'corse', - 'corso', - 'cosec', - 'cosed', - 'coses', - 'coset', - 'cosey', - 'cosie', - 'costa', - 'coste', - 'costs', - 'cotan', - 'coted', - 'cotes', - 'coths', - 'cotta', - 'cotts', - 'coude', - 'coups', - 'courb', - 'courd', - 'coure', - 'cours', - 'couta', - 'couth', - 'coved', - 'coves', - 'covin', - 'cowal', - 'cowan', - 'cowed', - 'cowks', - 'cowls', - 'cowps', - 'cowry', - 'coxae', - 'coxal', - 'coxed', - 'coxes', - 'coxib', - 'coyau', - 'coyed', - 'coyer', - 'coypu', - 'cozed', - 'cozen', - 'cozes', - 'cozey', - 'cozie', - 'craal', - 'crabs', - 'crags', - 'craic', - 'craig', - 'crake', - 'crame', - 'crams', - 'crans', - 'crape', - 'craps', - 'crapy', - 'crare', - 'craws', - 'crays', - 'creds', - 'creel', - 'crees', - 'crems', - 'crena', - 'creps', - 'crepy', - 'crewe', - 'crews', - 'crias', - 'cribs', - 'cries', - 'crims', - 'crine', - 'crios', - 'cripe', - 'crips', - 'crise', - 'crith', - 'crits', - 'croci', - 'crocs', - 'croft', - 'crogs', - 'cromb', - 'crome', - 'cronk', - 'crons', - 'crool', - 'croon', - 'crops', - 'crore', - 'crost', - 'crout', - 'crows', - 'croze', - 'cruck', - 'crudo', - 'cruds', - 'crudy', - 'crues', - 'cruet', - 'cruft', - 'crunk', - 'cruor', - 'crura', - 'cruse', - 'crusy', - 'cruve', - 'crwth', - 'cryer', - 'ctene', - 'cubby', - 'cubeb', - 'cubed', - 'cuber', - 'cubes', - 'cubit', - 'cuddy', - 'cuffo', - 'cuffs', - 'cuifs', - 'cuing', - 'cuish', - 'cuits', - 'cukes', - 'culch', - 'culet', - 'culex', - 'culls', - 'cully', - 'culms', - 'culpa', - 'culti', - 'cults', - 'culty', - 'cumec', - 'cundy', - 'cunei', - 'cunit', - 'cunts', - 'cupel', - 'cupid', - 'cuppa', - 'cuppy', - 'curat', - 'curbs', - 'curch', - 'curds', - 'curdy', - 'cured', - 'curer', - 'cures', - 'curet', - 'curfs', - 'curia', - 'curie', - 'curli', - 'curls', - 'curns', - 'curny', - 'currs', - 'cursi', - 'curst', - 'cusec', - 'cushy', - 'cusks', - 'cusps', - 'cuspy', - 'cusso', - 'cusum', - 'cutch', - 'cuter', - 'cutes', - 'cutey', - 'cutin', - 'cutis', - 'cutto', - 'cutty', - 'cutup', - 'cuvee', - 'cuzes', - 'cwtch', - 'cyano', - 'cyans', - 'cycad', - 'cycas', - 'cyclo', - 'cyder', - 'cylix', - 'cymae', - 'cymar', - 'cymas', - 'cymes', - 'cymol', - 'cysts', - 'cytes', - 'cyton', - 'czars', - 'daals', - 'dabba', - 'daces', - 'dacha', - 'dacks', - 'dadah', - 'dadas', - 'dados', - 'daffs', - 'daffy', - 'dagga', - 'daggy', - 'dagos', - 'dahls', - 'daiko', - 'daine', - 'daint', - 'daker', - 'daled', - 'dales', - 'dalis', - 'dalle', - 'dalts', - 'daman', - 'damar', - 'dames', - 'damme', - 'damns', - 'damps', - 'dampy', - 'dancy', - 'dangs', - 'danio', - 'danks', - 'danny', - 'dants', - 'daraf', - 'darbs', - 'darcy', - 'dared', - 'darer', - 'dares', - 'darga', - 'dargs', - 'daric', - 'daris', - 'darks', - 'darky', - 'darns', - 'darre', - 'darts', - 'darzi', - 'dashi', - 'dashy', - 'datal', - 'dated', - 'dater', - 'dates', - 'datos', - 'datto', - 'daube', - 'daubs', - 'dauby', - 'dauds', - 'dault', - 'daurs', - 'dauts', - 'daven', - 'davit', - 'dawah', - 'dawds', - 'dawed', - 'dawen', - 'dawks', - 'dawns', - 'dawts', - 'dayan', - 'daych', - 'daynt', - 'dazed', - 'dazer', - 'dazes', - 'deads', - 'deair', - 'deals', - 'deans', - 'deare', - 'dearn', - 'dears', - 'deary', - 'deash', - 'deave', - 'deaws', - 'deawy', - 'debag', - 'debby', - 'debel', - 'debes', - 'debts', - 'debud', - 'debur', - 'debus', - 'debye', - 'decad', - 'decaf', - 'decan', - 'decko', - 'decks', - 'decos', - 'dedal', - 'deeds', - 'deedy', - 'deely', - 'deems', - 'deens', - 'deeps', - 'deere', - 'deers', - 'deets', - 'deeve', - 'deevs', - 'defat', - 'deffo', - 'defis', - 'defog', - 'degas', - 'degum', - 'degus', - 'deice', - 'deids', - 'deify', - 'deils', - 'deism', - 'deist', - 'deked', - 'dekes', - 'dekko', - 'deled', - 'deles', - 'delfs', - 'delft', - 'delis', - 'dells', - 'delly', - 'delos', - 'delph', - 'delts', - 'deman', - 'demes', - 'demic', - 'demit', - 'demob', - 'demoi', - 'demos', - 'dempt', - 'denar', - 'denay', - 'dench', - 'denes', - 'denet', - 'denis', - 'dents', - 'deoxy', - 'derat', - 'deray', - 'dered', - 'deres', - 'derig', - 'derma', - 'derms', - 'derns', - 'derny', - 'deros', - 'derro', - 'derry', - 'derth', - 'dervs', - 'desex', - 'deshi', - 'desis', - 'desks', - 'desse', - 'devas', - 'devel', - 'devis', - 'devon', - 'devos', - 'devot', - 'dewan', - 'dewar', - 'dewax', - 'dewed', - 'dexes', - 'dexie', - 'dhaba', - 'dhaks', - 'dhals', - 'dhikr', - 'dhobi', - 'dhole', - 'dholl', - 'dhols', - 'dhoti', - 'dhows', - 'dhuti', - 'diact', - 'dials', - 'diane', - 'diazo', - 'dibbs', - 'diced', - 'dicer', - 'dices', - 'dicht', - 'dicks', - 'dicky', - 'dicot', - 'dicta', - 'dicts', - 'dicty', - 'diddy', - 'didie', - 'didos', - 'didst', - 'diebs', - 'diels', - 'diene', - 'diets', - 'diffs', - 'dight', - 'dikas', - 'diked', - 'diker', - 'dikes', - 'dikey', - 'dildo', - 'dilli', - 'dills', - 'dimbo', - 'dimer', - 'dimes', - 'dimps', - 'dinar', - 'dined', - 'dines', - 'dinge', - 'dings', - 'dinic', - 'dinks', - 'dinky', - 'dinna', - 'dinos', - 'dints', - 'diols', - 'diota', - 'dippy', - 'dipso', - 'diram', - 'direr', - 'dirke', - 'dirks', - 'dirls', - 'dirts', - 'disas', - 'disci', - 'discs', - 'dishy', - 'disks', - 'disme', - 'dital', - 'ditas', - 'dited', - 'dites', - 'ditsy', - 'ditts', - 'ditzy', - 'divan', - 'divas', - 'dived', - 'dives', - 'divis', - 'divna', - 'divos', - 'divot', - 'divvy', - 'diwan', - 'dixie', - 'dixit', - 'diyas', - 'dizen', - 'djinn', - 'djins', - 'doabs', - 'doats', - 'dobby', - 'dobes', - 'dobie', - 'dobla', - 'dobra', - 'dobro', - 'docht', - 'docks', - 'docos', - 'docus', - 'doddy', - 'dodos', - 'doeks', - 'doers', - 'doest', - 'doeth', - 'doffs', - 'dogan', - 'doges', - 'dogey', - 'doggo', - 'doggy', - 'dogie', - 'dohyo', - 'doilt', - 'doily', - 'doits', - 'dojos', - 'dolce', - 'dolci', - 'doled', - 'doles', - 'dolia', - 'dolls', - 'dolma', - 'dolor', - 'dolos', - 'dolts', - 'domal', - 'domed', - 'domes', - 'domic', - 'donah', - 'donas', - 'donee', - 'doner', - 'donga', - 'dongs', - 'donko', - 'donna', - 'donne', - 'donny', - 'donsy', - 'doobs', - 'dooce', - 'doody', - 'dooks', - 'doole', - 'dools', - 'dooly', - 'dooms', - 'doomy', - 'doona', - 'doorn', - 'doors', - 'doozy', - 'dopas', - 'doped', - 'doper', - 'dopes', - 'dorad', - 'dorba', - 'dorbs', - 'doree', - 'dores', - 'doric', - 'doris', - 'dorks', - 'dorky', - 'dorms', - 'dormy', - 'dorps', - 'dorrs', - 'dorsa', - 'dorse', - 'dorts', - 'dorty', - 'dosai', - 'dosas', - 'dosed', - 'doseh', - 'doser', - 'doses', - 'dosha', - 'dotal', - 'doted', - 'doter', - 'dotes', - 'dotty', - 'douar', - 'douce', - 'doucs', - 'douks', - 'doula', - 'douma', - 'doums', - 'doups', - 'doura', - 'douse', - 'douts', - 'doved', - 'doven', - 'dover', - 'doves', - 'dovie', - 'dowar', - 'dowds', - 'dowed', - 'dower', - 'dowie', - 'dowle', - 'dowls', - 'dowly', - 'downa', - 'downs', - 'dowps', - 'dowse', - 'dowts', - 'doxed', - 'doxes', - 'doxie', - 'doyen', - 'doyly', - 'dozed', - 'dozer', - 'dozes', - 'drabs', - 'drack', - 'draco', - 'draff', - 'drags', - 'drail', - 'drams', - 'drant', - 'draps', - 'drats', - 'drave', - 'draws', - 'drays', - 'drear', - 'dreck', - 'dreed', - 'dreer', - 'drees', - 'dregs', - 'dreks', - 'drent', - 'drere', - 'drest', - 'dreys', - 'dribs', - 'drice', - 'dries', - 'drily', - 'drips', - 'dript', - 'droid', - 'droil', - 'droke', - 'drole', - 'drome', - 'drony', - 'droob', - 'droog', - 'drook', - 'drops', - 'dropt', - 'drouk', - 'drows', - 'drubs', - 'drugs', - 'drums', - 'drupe', - 'druse', - 'drusy', - 'druxy', - 'dryad', - 'dryas', - 'dsobo', - 'dsomo', - 'duads', - 'duals', - 'duans', - 'duars', - 'dubbo', - 'ducal', - 'ducat', - 'duces', - 'ducks', - 'ducky', - 'ducts', - 'duddy', - 'duded', - 'dudes', - 'duels', - 'duets', - 'duett', - 'duffs', - 'dufus', - 'duing', - 'duits', - 'dukas', - 'duked', - 'dukes', - 'dukka', - 'dulce', - 'dules', - 'dulia', - 'dulls', - 'dulse', - 'dumas', - 'dumbo', - 'dumbs', - 'dumka', - 'dumky', - 'dumps', - 'dunam', - 'dunch', - 'dunes', - 'dungs', - 'dungy', - 'dunks', - 'dunno', - 'dunny', - 'dunsh', - 'dunts', - 'duomi', - 'duomo', - 'duped', - 'duper', - 'dupes', - 'duple', - 'duply', - 'duppy', - 'dural', - 'duras', - 'dured', - 'dures', - 'durgy', - 'durns', - 'duroc', - 'duros', - 'duroy', - 'durra', - 'durrs', - 'durry', - 'durst', - 'durum', - 'durzi', - 'dusks', - 'dusts', - 'duxes', - 'dwaal', - 'dwale', - 'dwalm', - 'dwams', - 'dwang', - 'dwaum', - 'dweeb', - 'dwile', - 'dwine', - 'dyads', - 'dyers', - 'dyked', - 'dykes', - 'dykey', - 'dykon', - 'dynel', - 'dynes', - 'dzhos', - 'eagre', - 'ealed', - 'eales', - 'eaned', - 'eards', - 'eared', - 'earls', - 'earns', - 'earnt', - 'earst', - 'eased', - 'easer', - 'eases', - 'easle', - 'easts', - 'eathe', - 'eaved', - 'eaves', - 'ebbed', - 'ebbet', - 'ebons', - 'ebook', - 'ecads', - 'eched', - 'eches', - 'echos', - 'ecrus', - 'edema', - 'edged', - 'edger', - 'edges', - 'edile', - 'edits', - 'educe', - 'educt', - 'eejit', - 'eensy', - 'eeven', - 'eevns', - 'effed', - 'egads', - 'egers', - 'egest', - 'eggar', - 'egged', - 'egger', - 'egmas', - 'ehing', - 'eider', - 'eidos', - 'eigne', - 'eiked', - 'eikon', - 'eilds', - 'eisel', - 'ejido', - 'ekkas', - 'elain', - 'eland', - 'elans', - 'elchi', - 'eldin', - 'elemi', - 'elfed', - 'eliad', - 'elint', - 'elmen', - 'eloge', - 'elogy', - 'eloin', - 'elops', - 'elpee', - 'elsin', - 'elute', - 'elvan', - 'elven', - 'elver', - 'elves', - 'emacs', - 'embar', - 'embay', - 'embog', - 'embow', - 'embox', - 'embus', - 'emeer', - 'emend', - 'emerg', - 'emery', - 'emeus', - 'emics', - 'emirs', - 'emits', - 'emmas', - 'emmer', - 'emmet', - 'emmew', - 'emmys', - 'emoji', - 'emong', - 'emote', - 'emove', - 'empts', - 'emule', - 'emure', - 'emyde', - 'emyds', - 'enarm', - 'enate', - 'ended', - 'ender', - 'endew', - 'endue', - 'enews', - 'enfix', - 'eniac', - 'enlit', - 'enmew', - 'ennog', - 'enoki', - 'enols', - 'enorm', - 'enows', - 'enrol', - 'ensew', - 'ensky', - 'entia', - 'enure', - 'enurn', - 'envoi', - 'enzym', - 'eorls', - 'eosin', - 'epact', - 'epees', - 'ephah', - 'ephas', - 'ephod', - 'ephor', - 'epics', - 'epode', - 'epopt', - 'epris', - 'eques', - 'equid', - 'erbia', - 'erevs', - 'ergon', - 'ergos', - 'ergot', - 'erhus', - 'erica', - 'erick', - 'erics', - 'ering', - 'erned', - 'ernes', - 'erose', - 'erred', - 'erses', - 'eruct', - 'erugo', - 'eruvs', - 'erven', - 'ervil', - 'escar', - 'escot', - 'esile', - 'eskar', - 'esker', - 'esnes', - 'esses', - 'estoc', - 'estop', - 'estro', - 'etage', - 'etape', - 'etats', - 'etens', - 'ethal', - 'ethne', - 'ethyl', - 'etics', - 'etnas', - 'ettin', - 'ettle', - 'etuis', - 'etwee', - 'etyma', - 'eughs', - 'euked', - 'eupad', - 'euros', - 'eusol', - 'evens', - 'evert', - 'evets', - 'evhoe', - 'evils', - 'evite', - 'evohe', - 'ewers', - 'ewest', - 'ewhow', - 'ewked', - 'exams', - 'exeat', - 'execs', - 'exeem', - 'exeme', - 'exfil', - 'exies', - 'exine', - 'exing', - 'exits', - 'exode', - 'exome', - 'exons', - 'expat', - 'expos', - 'exude', - 'exuls', - 'exurb', - 'eyass', - 'eyers', - 'eyots', - 'eyras', - 'eyres', - 'eyrie', - 'eyrir', - 'ezine', - 'fabby', - 'faced', - 'facer', - 'faces', - 'facia', - 'facta', - 'facts', - 'faddy', - 'faded', - 'fader', - 'fades', - 'fadge', - 'fados', - 'faena', - 'faery', - 'faffs', - 'faffy', - 'faggy', - 'fagin', - 'fagot', - 'faiks', - 'fails', - 'faine', - 'fains', - 'fairs', - 'faked', - 'faker', - 'fakes', - 'fakey', - 'fakie', - 'fakir', - 'falaj', - 'falls', - 'famed', - 'fames', - 'fanal', - 'fands', - 'fanes', - 'fanga', - 'fango', - 'fangs', - 'fanks', - 'fanon', - 'fanos', - 'fanum', - 'faqir', - 'farad', - 'farci', - 'farcy', - 'fards', - 'fared', - 'farer', - 'fares', - 'farle', - 'farls', - 'farms', - 'faros', - 'farro', - 'farse', - 'farts', - 'fasci', - 'fasti', - 'fasts', - 'fated', - 'fates', - 'fatly', - 'fatso', - 'fatwa', - 'faugh', - 'fauld', - 'fauns', - 'faurd', - 'fauts', - 'fauve', - 'favas', - 'favel', - 'faver', - 'faves', - 'favus', - 'fawns', - 'fawny', - 'faxed', - 'faxes', - 'fayed', - 'fayer', - 'fayne', - 'fayre', - 'fazed', - 'fazes', - 'feals', - 'feare', - 'fears', - 'feart', - 'fease', - 'feats', - 'feaze', - 'feces', - 'fecht', - 'fecit', - 'fecks', - 'fedex', - 'feebs', - 'feeds', - 'feels', - 'feens', - 'feers', - 'feese', - 'feeze', - 'fehme', - 'feint', - 'feist', - 'felch', - 'felid', - 'fells', - 'felly', - 'felts', - 'felty', - 'femal', - 'femes', - 'femmy', - 'fends', - 'fendy', - 'fenis', - 'fenks', - 'fenny', - 'fents', - 'feods', - 'feoff', - 'ferer', - 'feres', - 'feria', - 'ferly', - 'fermi', - 'ferms', - 'ferns', - 'ferny', - 'fesse', - 'festa', - 'fests', - 'festy', - 'fetas', - 'feted', - 'fetes', - 'fetor', - 'fetta', - 'fetts', - 'fetwa', - 'feuar', - 'feuds', - 'feued', - 'feyed', - 'feyer', - 'feyly', - 'fezes', - 'fezzy', - 'fiars', - 'fiats', - 'fibro', - 'fices', - 'fiche', - 'fichu', - 'ficin', - 'ficos', - 'fides', - 'fidge', - 'fidos', - 'fiefs', - 'fient', - 'fiere', - 'fiers', - 'fiest', - 'fifed', - 'fifer', - 'fifes', - 'fifis', - 'figgy', - 'figos', - 'fiked', - 'fikes', - 'filar', - 'filch', - 'filed', - 'files', - 'filii', - 'filks', - 'fille', - 'fillo', - 'fills', - 'filmi', - 'films', - 'filos', - 'filum', - 'finca', - 'finds', - 'fined', - 'fines', - 'finis', - 'finks', - 'finny', - 'finos', - 'fiord', - 'fiqhs', - 'fique', - 'fired', - 'firer', - 'fires', - 'firie', - 'firks', - 'firms', - 'firns', - 'firry', - 'firth', - 'fiscs', - 'fisks', - 'fists', - 'fisty', - 'fitch', - 'fitly', - 'fitna', - 'fitte', - 'fitts', - 'fiver', - 'fives', - 'fixed', - 'fixes', - 'fixit', - 'fjeld', - 'flabs', - 'flaff', - 'flags', - 'flaks', - 'flamm', - 'flams', - 'flamy', - 'flane', - 'flans', - 'flaps', - 'flary', - 'flats', - 'flava', - 'flawn', - 'flaws', - 'flawy', - 'flaxy', - 'flays', - 'fleam', - 'fleas', - 'fleek', - 'fleer', - 'flees', - 'flegs', - 'fleme', - 'fleur', - 'flews', - 'flexi', - 'flexo', - 'fleys', - 'flics', - 'flied', - 'flies', - 'flimp', - 'flims', - 'flips', - 'flirs', - 'flisk', - 'flite', - 'flits', - 'flitt', - 'flobs', - 'flocs', - 'floes', - 'flogs', - 'flong', - 'flops', - 'flors', - 'flory', - 'flosh', - 'flota', - 'flote', - 'flows', - 'flubs', - 'flued', - 'flues', - 'fluey', - 'fluky', - 'flump', - 'fluor', - 'flurr', - 'fluty', - 'fluyt', - 'flyby', - 'flype', - 'flyte', - 'foals', - 'foams', - 'foehn', - 'fogey', - 'fogie', - 'fogle', - 'fogou', - 'fohns', - 'foids', - 'foils', - 'foins', - 'folds', - 'foley', - 'folia', - 'folic', - 'folie', - 'folks', - 'folky', - 'fomes', - 'fonda', - 'fonds', - 'fondu', - 'fones', - 'fonly', - 'fonts', - 'foods', - 'foody', - 'fools', - 'foots', - 'footy', - 'foram', - 'forbs', - 'forby', - 'fordo', - 'fords', - 'forel', - 'fores', - 'forex', - 'forks', - 'forky', - 'forme', - 'forms', - 'forts', - 'forza', - 'forze', - 'fossa', - 'fosse', - 'fouat', - 'fouds', - 'fouer', - 'fouet', - 'foule', - 'fouls', - 'fount', - 'fours', - 'fouth', - 'fovea', - 'fowls', - 'fowth', - 'foxed', - 'foxes', - 'foxie', - 'foyle', - 'foyne', - 'frabs', - 'frack', - 'fract', - 'frags', - 'fraim', - 'franc', - 'frape', - 'fraps', - 'frass', - 'frate', - 'frati', - 'frats', - 'fraus', - 'frays', - 'frees', - 'freet', - 'freit', - 'fremd', - 'frena', - 'freon', - 'frere', - 'frets', - 'fribs', - 'frier', - 'fries', - 'frigs', - 'frise', - 'frist', - 'frith', - 'frits', - 'fritt', - 'frize', - 'frizz', - 'froes', - 'frogs', - 'frons', - 'frore', - 'frorn', - 'frory', - 'frosh', - 'frows', - 'frowy', - 'frugs', - 'frump', - 'frush', - 'frust', - 'fryer', - 'fubar', - 'fubby', - 'fubsy', - 'fucks', - 'fucus', - 'fuddy', - 'fudgy', - 'fuels', - 'fuero', - 'fuffs', - 'fuffy', - 'fugal', - 'fuggy', - 'fugie', - 'fugio', - 'fugle', - 'fugly', - 'fugus', - 'fujis', - 'fulls', - 'fumed', - 'fumer', - 'fumes', - 'fumet', - 'fundi', - 'funds', - 'fundy', - 'fungo', - 'fungs', - 'funks', - 'fural', - 'furan', - 'furca', - 'furls', - 'furol', - 'furrs', - 'furth', - 'furze', - 'furzy', - 'fused', - 'fusee', - 'fusel', - 'fuses', - 'fusil', - 'fusks', - 'fusts', - 'fusty', - 'futon', - 'fuzed', - 'fuzee', - 'fuzes', - 'fuzil', - 'fyces', - 'fyked', - 'fykes', - 'fyles', - 'fyrds', - 'fytte', - 'gabba', - 'gabby', - 'gable', - 'gaddi', - 'gades', - 'gadge', - 'gadid', - 'gadis', - 'gadje', - 'gadjo', - 'gadso', - 'gaffs', - 'gaged', - 'gager', - 'gages', - 'gaids', - 'gains', - 'gairs', - 'gaita', - 'gaits', - 'gaitt', - 'gajos', - 'galah', - 'galas', - 'galax', - 'galea', - 'galed', - 'gales', - 'galls', - 'gally', - 'galop', - 'galut', - 'galvo', - 'gamas', - 'gamay', - 'gamba', - 'gambe', - 'gambo', - 'gambs', - 'gamed', - 'games', - 'gamey', - 'gamic', - 'gamin', - 'gamme', - 'gammy', - 'gamps', - 'ganch', - 'gandy', - 'ganef', - 'ganev', - 'gangs', - 'ganja', - 'ganof', - 'gants', - 'gaols', - 'gaped', - 'gaper', - 'gapes', - 'gapos', - 'gappy', - 'garbe', - 'garbo', - 'garbs', - 'garda', - 'gares', - 'garis', - 'garms', - 'garni', - 'garre', - 'garth', - 'garum', - 'gases', - 'gasps', - 'gaspy', - 'gasts', - 'gatch', - 'gated', - 'gater', - 'gates', - 'gaths', - 'gator', - 'gauch', - 'gaucy', - 'gauds', - 'gauje', - 'gault', - 'gaums', - 'gaumy', - 'gaups', - 'gaurs', - 'gauss', - 'gauzy', - 'gavot', - 'gawcy', - 'gawds', - 'gawks', - 'gawps', - 'gawsy', - 'gayal', - 'gazal', - 'gazar', - 'gazed', - 'gazes', - 'gazon', - 'gazoo', - 'geals', - 'geans', - 'geare', - 'gears', - 'geats', - 'gebur', - 'gecks', - 'geeks', - 'geeps', - 'geest', - 'geist', - 'geits', - 'gelds', - 'gelee', - 'gelid', - 'gelly', - 'gelts', - 'gemel', - 'gemma', - 'gemmy', - 'gemot', - 'genal', - 'genas', - 'genes', - 'genet', - 'genic', - 'genii', - 'genip', - 'genny', - 'genoa', - 'genom', - 'genro', - 'gents', - 'genty', - 'genua', - 'genus', - 'geode', - 'geoid', - 'gerah', - 'gerbe', - 'geres', - 'gerle', - 'germs', - 'germy', - 'gerne', - 'gesse', - 'gesso', - 'geste', - 'gests', - 'getas', - 'getup', - 'geums', - 'geyan', - 'geyer', - 'ghast', - 'ghats', - 'ghaut', - 'ghazi', - 'ghees', - 'ghest', - 'ghyll', - 'gibed', - 'gibel', - 'giber', - 'gibes', - 'gibli', - 'gibus', - 'gifts', - 'gigas', - 'gighe', - 'gigot', - 'gigue', - 'gilas', - 'gilds', - 'gilet', - 'gills', - 'gilly', - 'gilpy', - 'gilts', - 'gimel', - 'gimme', - 'gimps', - 'gimpy', - 'ginch', - 'ginge', - 'gings', - 'ginks', - 'ginny', - 'ginzo', - 'gipon', - 'gippo', - 'gippy', - 'girds', - 'girls', - 'girns', - 'giron', - 'giros', - 'girrs', - 'girsh', - 'girts', - 'gismo', - 'gisms', - 'gists', - 'gitch', - 'gites', - 'giust', - 'gived', - 'gives', - 'gizmo', - 'glace', - 'glads', - 'glady', - 'glaik', - 'glair', - 'glams', - 'glans', - 'glary', - 'glaum', - 'glaur', - 'glazy', - 'gleba', - 'glebe', - 'gleby', - 'glede', - 'gleds', - 'gleed', - 'gleek', - 'glees', - 'gleet', - 'gleis', - 'glens', - 'glent', - 'gleys', - 'glial', - 'glias', - 'glibs', - 'gliff', - 'glift', - 'glike', - 'glime', - 'glims', - 'glisk', - 'glits', - 'glitz', - 'gloam', - 'globi', - 'globs', - 'globy', - 'glode', - 'glogg', - 'gloms', - 'gloop', - 'glops', - 'glost', - 'glout', - 'glows', - 'gloze', - 'glued', - 'gluer', - 'glues', - 'gluey', - 'glugs', - 'glume', - 'glums', - 'gluon', - 'glute', - 'gluts', - 'gnarl', - 'gnarr', - 'gnars', - 'gnats', - 'gnawn', - 'gnaws', - 'gnows', - 'goads', - 'goafs', - 'goals', - 'goary', - 'goats', - 'goaty', - 'goban', - 'gobar', - 'gobbi', - 'gobbo', - 'gobby', - 'gobis', - 'gobos', - 'godet', - 'godso', - 'goels', - 'goers', - 'goest', - 'goeth', - 'goety', - 'gofer', - 'goffs', - 'gogga', - 'gogos', - 'goier', - 'gojis', - 'golds', - 'goldy', - 'goles', - 'golfs', - 'golpe', - 'golps', - 'gombo', - 'gomer', - 'gompa', - 'gonch', - 'gonef', - 'gongs', - 'gonia', - 'gonif', - 'gonks', - 'gonna', - 'gonof', - 'gonys', - 'gonzo', - 'gooby', - 'goods', - 'goofs', - 'googs', - 'gooks', - 'gooky', - 'goold', - 'gools', - 'gooly', - 'goons', - 'goony', - 'goops', - 'goopy', - 'goors', - 'goory', - 'goosy', - 'gopak', - 'gopik', - 'goral', - 'goras', - 'gored', - 'gores', - 'goris', - 'gorms', - 'gormy', - 'gorps', - 'gorse', - 'gorsy', - 'gosht', - 'gosse', - 'gotch', - 'goths', - 'gothy', - 'gotta', - 'gouch', - 'gouks', - 'goura', - 'gouts', - 'gouty', - 'gowan', - 'gowds', - 'gowfs', - 'gowks', - 'gowls', - 'gowns', - 'goxes', - 'goyim', - 'goyle', - 'graal', - 'grabs', - 'grads', - 'graff', - 'graip', - 'grama', - 'grame', - 'gramp', - 'grams', - 'grana', - 'grans', - 'grapy', - 'gravs', - 'grays', - 'grebe', - 'grebo', - 'grece', - 'greek', - 'grees', - 'grege', - 'grego', - 'grein', - 'grens', - 'grese', - 'greve', - 'grews', - 'greys', - 'grice', - 'gride', - 'grids', - 'griff', - 'grift', - 'grigs', - 'grike', - 'grins', - 'griot', - 'grips', - 'gript', - 'gripy', - 'grise', - 'grist', - 'grisy', - 'grith', - 'grits', - 'grize', - 'groat', - 'grody', - 'grogs', - 'groks', - 'groma', - 'grone', - 'groof', - 'grosz', - 'grots', - 'grouf', - 'grovy', - 'grows', - 'grrls', - 'grrrl', - 'grubs', - 'grued', - 'grues', - 'grufe', - 'grume', - 'grump', - 'grund', - 'gryce', - 'gryde', - 'gryke', - 'grype', - 'grypt', - 'guaco', - 'guana', - 'guano', - 'guans', - 'guars', - 'gucks', - 'gucky', - 'gudes', - 'guffs', - 'gugas', - 'guids', - 'guimp', - 'guiro', - 'gulag', - 'gular', - 'gulas', - 'gules', - 'gulet', - 'gulfs', - 'gulfy', - 'gulls', - 'gulph', - 'gulps', - 'gulpy', - 'gumma', - 'gummi', - 'gumps', - 'gundy', - 'gunge', - 'gungy', - 'gunks', - 'gunky', - 'gunny', - 'guqin', - 'gurdy', - 'gurge', - 'gurls', - 'gurly', - 'gurns', - 'gurry', - 'gursh', - 'gurus', - 'gushy', - 'gusla', - 'gusle', - 'gusli', - 'gussy', - 'gusts', - 'gutsy', - 'gutta', - 'gutty', - 'guyed', - 'guyle', - 'guyot', - 'guyse', - 'gwine', - 'gyals', - 'gyans', - 'gybed', - 'gybes', - 'gyeld', - 'gymps', - 'gynae', - 'gynie', - 'gynny', - 'gynos', - 'gyoza', - 'gypos', - 'gyppo', - 'gyppy', - 'gyral', - 'gyred', - 'gyres', - 'gyron', - 'gyros', - 'gyrus', - 'gytes', - 'gyved', - 'gyves', - 'haafs', - 'haars', - 'hable', - 'habus', - 'hacek', - 'hacks', - 'hadal', - 'haded', - 'hades', - 'hadji', - 'hadst', - 'haems', - 'haets', - 'haffs', - 'hafiz', - 'hafts', - 'haggs', - 'hahas', - 'haick', - 'haika', - 'haiks', - 'haiku', - 'hails', - 'haily', - 'hains', - 'haint', - 'hairs', - 'haith', - 'hajes', - 'hajis', - 'hajji', - 'hakam', - 'hakas', - 'hakea', - 'hakes', - 'hakim', - 'hakus', - 'halal', - 'haled', - 'haler', - 'hales', - 'halfa', - 'halfs', - 'halid', - 'hallo', - 'halls', - 'halma', - 'halms', - 'halon', - 'halos', - 'halse', - 'halts', - 'halva', - 'halwa', - 'hamal', - 'hamba', - 'hamed', - 'hames', - 'hammy', - 'hamza', - 'hanap', - 'hance', - 'hanch', - 'hands', - 'hangi', - 'hangs', - 'hanks', - 'hanky', - 'hansa', - 'hanse', - 'hants', - 'haole', - 'haoma', - 'hapax', - 'haply', - 'happi', - 'hapus', - 'haram', - 'hards', - 'hared', - 'hares', - 'harim', - 'harks', - 'harls', - 'harms', - 'harns', - 'haros', - 'harps', - 'harts', - 'hashy', - 'hasks', - 'hasps', - 'hasta', - 'hated', - 'hates', - 'hatha', - 'hauds', - 'haufs', - 'haugh', - 'hauld', - 'haulm', - 'hauls', - 'hault', - 'hauns', - 'hause', - 'haver', - 'haves', - 'hawed', - 'hawks', - 'hawms', - 'hawse', - 'hayed', - 'hayer', - 'hayey', - 'hayle', - 'hazan', - 'hazed', - 'hazer', - 'hazes', - 'heads', - 'heald', - 'heals', - 'heame', - 'heaps', - 'heapy', - 'heare', - 'hears', - 'heast', - 'heats', - 'heben', - 'hebes', - 'hecht', - 'hecks', - 'heder', - 'hedgy', - 'heeds', - 'heedy', - 'heels', - 'heeze', - 'hefte', - 'hefts', - 'heids', - 'heigh', - 'heils', - 'heirs', - 'hejab', - 'hejra', - 'heled', - 'heles', - 'helio', - 'hells', - 'helms', - 'helos', - 'helot', - 'helps', - 'helve', - 'hemal', - 'hemes', - 'hemic', - 'hemin', - 'hemps', - 'hempy', - 'hench', - 'hends', - 'henge', - 'henna', - 'henny', - 'henry', - 'hents', - 'hepar', - 'herbs', - 'herby', - 'herds', - 'heres', - 'herls', - 'herma', - 'herms', - 'herns', - 'heros', - 'herry', - 'herse', - 'hertz', - 'herye', - 'hesps', - 'hests', - 'hetes', - 'heths', - 'heuch', - 'heugh', - 'hevea', - 'hewed', - 'hewer', - 'hewgh', - 'hexad', - 'hexed', - 'hexer', - 'hexes', - 'hexyl', - 'heyed', - 'hiant', - 'hicks', - 'hided', - 'hider', - 'hides', - 'hiems', - 'highs', - 'hight', - 'hijab', - 'hijra', - 'hiked', - 'hiker', - 'hikes', - 'hikoi', - 'hilar', - 'hilch', - 'hillo', - 'hills', - 'hilts', - 'hilum', - 'hilus', - 'himbo', - 'hinau', - 'hinds', - 'hings', - 'hinky', - 'hinny', - 'hints', - 'hiois', - 'hiply', - 'hired', - 'hiree', - 'hirer', - 'hires', - 'hissy', - 'hists', - 'hithe', - 'hived', - 'hiver', - 'hives', - 'hizen', - 'hoaed', - 'hoagy', - 'hoars', - 'hoary', - 'hoast', - 'hobos', - 'hocks', - 'hocus', - 'hodad', - 'hodja', - 'hoers', - 'hogan', - 'hogen', - 'hoggs', - 'hoghs', - 'hohed', - 'hoick', - 'hoied', - 'hoiks', - 'hoing', - 'hoise', - 'hokas', - 'hoked', - 'hokes', - 'hokey', - 'hokis', - 'hokku', - 'hokum', - 'holds', - 'holed', - 'holes', - 'holey', - 'holks', - 'holla', - 'hollo', - 'holme', - 'holms', - 'holon', - 'holos', - 'holts', - 'homas', - 'homed', - 'homes', - 'homey', - 'homie', - 'homme', - 'homos', - 'honan', - 'honda', - 'honds', - 'honed', - 'honer', - 'hones', - 'hongi', - 'hongs', - 'honks', - 'honky', - 'hooch', - 'hoods', - 'hoody', - 'hooey', - 'hoofs', - 'hooka', - 'hooks', - 'hooky', - 'hooly', - 'hoons', - 'hoops', - 'hoord', - 'hoors', - 'hoosh', - 'hoots', - 'hooty', - 'hoove', - 'hopak', - 'hoped', - 'hoper', - 'hopes', - 'hoppy', - 'horah', - 'horal', - 'horas', - 'horis', - 'horks', - 'horme', - 'horns', - 'horst', - 'horsy', - 'hosed', - 'hosel', - 'hosen', - 'hoser', - 'hoses', - 'hosey', - 'hosta', - 'hosts', - 'hotch', - 'hoten', - 'hotty', - 'houff', - 'houfs', - 'hough', - 'houri', - 'hours', - 'houts', - 'hovea', - 'hoved', - 'hoven', - 'hoves', - 'howbe', - 'howes', - 'howff', - 'howfs', - 'howks', - 'howls', - 'howre', - 'howso', - 'hoxed', - 'hoxes', - 'hoyas', - 'hoyed', - 'hoyle', - 'hubby', - 'hucks', - 'hudna', - 'hudud', - 'huers', - 'huffs', - 'huffy', - 'huger', - 'huggy', - 'huhus', - 'huias', - 'hulas', - 'hules', - 'hulks', - 'hulky', - 'hullo', - 'hulls', - 'hully', - 'humas', - 'humfs', - 'humic', - 'humps', - 'humpy', - 'hunks', - 'hunts', - 'hurds', - 'hurls', - 'hurly', - 'hurra', - 'hurst', - 'hurts', - 'hushy', - 'husks', - 'husos', - 'hutia', - 'huzza', - 'huzzy', - 'hwyls', - 'hydra', - 'hyens', - 'hygge', - 'hying', - 'hykes', - 'hylas', - 'hyleg', - 'hyles', - 'hylic', - 'hymns', - 'hynde', - 'hyoid', - 'hyped', - 'hypes', - 'hypha', - 'hyphy', - 'hypos', - 'hyrax', - 'hyson', - 'hythe', - 'iambi', - 'iambs', - 'ibrik', - 'icers', - 'iched', - 'iches', - 'ichor', - 'icier', - 'icker', - 'ickle', - 'icons', - 'ictal', - 'ictic', - 'ictus', - 'idant', - 'ideas', - 'idees', - 'ident', - 'idled', - 'idles', - 'idola', - 'idols', - 'idyls', - 'iftar', - 'igapo', - 'igged', - 'iglus', - 'ihram', - 'ikans', - 'ikats', - 'ikons', - 'ileac', - 'ileal', - 'ileum', - 'ileus', - 'iliad', - 'ilial', - 'ilium', - 'iller', - 'illth', - 'imago', - 'imams', - 'imari', - 'imaum', - 'imbar', - 'imbed', - 'imide', - 'imido', - 'imids', - 'imine', - 'imino', - 'immew', - 'immit', - 'immix', - 'imped', - 'impis', - 'impot', - 'impro', - 'imshi', - 'imshy', - 'inapt', - 'inarm', - 'inbye', - 'incel', - 'incle', - 'incog', - 'incus', - 'incut', - 'indew', - 'india', - 'indie', - 'indol', - 'indow', - 'indri', - 'indue', - 'inerm', - 'infix', - 'infos', - 'infra', - 'ingan', - 'ingle', - 'inion', - 'inked', - 'inker', - 'inkle', - 'inned', - 'innit', - 'inorb', - 'inrun', - 'inset', - 'inspo', - 'intel', - 'intil', - 'intis', - 'intra', - 'inula', - 'inure', - 'inurn', - 'inust', - 'invar', - 'inwit', - 'iodic', - 'iodid', - 'iodin', - 'iotas', - 'ippon', - 'irade', - 'irids', - 'iring', - 'irked', - 'iroko', - 'irone', - 'irons', - 'isbas', - 'ishes', - 'isled', - 'isles', - 'isnae', - 'issei', - 'istle', - 'items', - 'ither', - 'ivied', - 'ivies', - 'ixias', - 'ixnay', - 'ixora', - 'ixtle', - 'izard', - 'izars', - 'izzat', - 'jaaps', - 'jabot', - 'jacal', - 'jacks', - 'jacky', - 'jaded', - 'jades', - 'jafas', - 'jaffa', - 'jagas', - 'jager', - 'jaggs', - 'jaggy', - 'jagir', - 'jagra', - 'jails', - 'jaker', - 'jakes', - 'jakey', - 'jalap', - 'jalop', - 'jambe', - 'jambo', - 'jambs', - 'jambu', - 'james', - 'jammy', - 'jamon', - 'janes', - 'janns', - 'janny', - 'janty', - 'japan', - 'japed', - 'japer', - 'japes', - 'jarks', - 'jarls', - 'jarps', - 'jarta', - 'jarul', - 'jasey', - 'jaspe', - 'jasps', - 'jatos', - 'jauks', - 'jaups', - 'javas', - 'javel', - 'jawan', - 'jawed', - 'jaxie', - 'jeans', - 'jeats', - 'jebel', - 'jedis', - 'jeels', - 'jeely', - 'jeeps', - 'jeers', - 'jeeze', - 'jefes', - 'jeffs', - 'jehad', - 'jehus', - 'jelab', - 'jello', - 'jells', - 'jembe', - 'jemmy', - 'jenny', - 'jeons', - 'jerid', - 'jerks', - 'jerry', - 'jesse', - 'jests', - 'jesus', - 'jetes', - 'jeton', - 'jeune', - 'jewed', - 'jewie', - 'jhala', - 'jiaos', - 'jibba', - 'jibbs', - 'jibed', - 'jiber', - 'jibes', - 'jiffs', - 'jiggy', - 'jigot', - 'jihad', - 'jills', - 'jilts', - 'jimmy', - 'jimpy', - 'jingo', - 'jinks', - 'jinne', - 'jinni', - 'jinns', - 'jirds', - 'jirga', - 'jirre', - 'jisms', - 'jived', - 'jiver', - 'jives', - 'jivey', - 'jnana', - 'jobed', - 'jobes', - 'jocko', - 'jocks', - 'jocky', - 'jocos', - 'jodel', - 'joeys', - 'johns', - 'joins', - 'joked', - 'jokes', - 'jokey', - 'jokol', - 'joled', - 'joles', - 'jolls', - 'jolts', - 'jolty', - 'jomon', - 'jomos', - 'jones', - 'jongs', - 'jonty', - 'jooks', - 'joram', - 'jorum', - 'jotas', - 'jotty', - 'jotun', - 'joual', - 'jougs', - 'jouks', - 'joule', - 'jours', - 'jowar', - 'jowed', - 'jowls', - 'jowly', - 'joyed', - 'jubas', - 'jubes', - 'jucos', - 'judas', - 'judgy', - 'judos', - 'jugal', - 'jugum', - 'jujus', - 'juked', - 'jukes', - 'jukus', - 'julep', - 'jumar', - 'jumby', - 'jumps', - 'junco', - 'junks', - 'junky', - 'jupes', - 'jupon', - 'jural', - 'jurat', - 'jurel', - 'jures', - 'justs', - 'jutes', - 'jutty', - 'juves', - 'juvie', - 'kaama', - 'kabab', - 'kabar', - 'kabob', - 'kacha', - 'kacks', - 'kadai', - 'kades', - 'kadis', - 'kafir', - 'kagos', - 'kagus', - 'kahal', - 'kaiak', - 'kaids', - 'kaies', - 'kaifs', - 'kaika', - 'kaiks', - 'kails', - 'kaims', - 'kaing', - 'kains', - 'kakas', - 'kakis', - 'kalam', - 'kales', - 'kalif', - 'kalis', - 'kalpa', - 'kamas', - 'kames', - 'kamik', - 'kamis', - 'kamme', - 'kanae', - 'kanas', - 'kandy', - 'kaneh', - 'kanes', - 'kanga', - 'kangs', - 'kanji', - 'kants', - 'kanzu', - 'kaons', - 'kapas', - 'kaphs', - 'kapok', - 'kapow', - 'kapus', - 'kaput', - 'karas', - 'karat', - 'karks', - 'karns', - 'karoo', - 'karos', - 'karri', - 'karst', - 'karsy', - 'karts', - 'karzy', - 'kasha', - 'kasme', - 'katal', - 'katas', - 'katis', - 'katti', - 'kaugh', - 'kauri', - 'kauru', - 'kaury', - 'kaval', - 'kavas', - 'kawas', - 'kawau', - 'kawed', - 'kayle', - 'kayos', - 'kazis', - 'kazoo', - 'kbars', - 'kebar', - 'kebob', - 'kecks', - 'kedge', - 'kedgy', - 'keech', - 'keefs', - 'keeks', - 'keels', - 'keema', - 'keeno', - 'keens', - 'keeps', - 'keets', - 'keeve', - 'kefir', - 'kehua', - 'keirs', - 'kelep', - 'kelim', - 'kells', - 'kelly', - 'kelps', - 'kelpy', - 'kelts', - 'kelty', - 'kembo', - 'kembs', - 'kemps', - 'kempt', - 'kempy', - 'kenaf', - 'kench', - 'kendo', - 'kenos', - 'kente', - 'kents', - 'kepis', - 'kerbs', - 'kerel', - 'kerfs', - 'kerky', - 'kerma', - 'kerne', - 'kerns', - 'keros', - 'kerry', - 'kerve', - 'kesar', - 'kests', - 'ketas', - 'ketch', - 'ketes', - 'ketol', - 'kevel', - 'kevil', - 'kexes', - 'keyed', - 'keyer', - 'khadi', - 'khafs', - 'khans', - 'khaph', - 'khats', - 'khaya', - 'khazi', - 'kheda', - 'kheth', - 'khets', - 'khoja', - 'khors', - 'khoum', - 'khuds', - 'kiaat', - 'kiack', - 'kiang', - 'kibbe', - 'kibbi', - 'kibei', - 'kibes', - 'kibla', - 'kicks', - 'kicky', - 'kiddo', - 'kiddy', - 'kidel', - 'kidge', - 'kiefs', - 'kiers', - 'kieve', - 'kievs', - 'kight', - 'kikes', - 'kikoi', - 'kiley', - 'kilim', - 'kills', - 'kilns', - 'kilos', - 'kilps', - 'kilts', - 'kilty', - 'kimbo', - 'kinas', - 'kinda', - 'kinds', - 'kindy', - 'kines', - 'kings', - 'kinin', - 'kinks', - 'kinos', - 'kiore', - 'kipes', - 'kippa', - 'kipps', - 'kirby', - 'kirks', - 'kirns', - 'kirri', - 'kisan', - 'kissy', - 'kists', - 'kited', - 'kiter', - 'kites', - 'kithe', - 'kiths', - 'kitul', - 'kivas', - 'kiwis', - 'klang', - 'klaps', - 'klett', - 'klick', - 'klieg', - 'kliks', - 'klong', - 'kloof', - 'kluge', - 'klutz', - 'knags', - 'knaps', - 'knarl', - 'knars', - 'knaur', - 'knawe', - 'knees', - 'knell', - 'knish', - 'knits', - 'knive', - 'knobs', - 'knops', - 'knosp', - 'knots', - 'knout', - 'knowe', - 'knows', - 'knubs', - 'knurl', - 'knurr', - 'knurs', - 'knuts', - 'koans', - 'koaps', - 'koban', - 'kobos', - 'koels', - 'koffs', - 'kofta', - 'kogal', - 'kohas', - 'kohen', - 'kohls', - 'koine', - 'kojis', - 'kokam', - 'kokas', - 'koker', - 'kokra', - 'kokum', - 'kolas', - 'kolos', - 'kombu', - 'konbu', - 'kondo', - 'konks', - 'kooks', - 'kooky', - 'koori', - 'kopek', - 'kophs', - 'kopje', - 'koppa', - 'korai', - 'koras', - 'korat', - 'kores', - 'korma', - 'koros', - 'korun', - 'korus', - 'koses', - 'kotch', - 'kotos', - 'kotow', - 'koura', - 'kraal', - 'krabs', - 'kraft', - 'krais', - 'krait', - 'krang', - 'krans', - 'kranz', - 'kraut', - 'krays', - 'kreep', - 'kreng', - 'krewe', - 'krona', - 'krone', - 'kroon', - 'krubi', - 'krunk', - 'ksars', - 'kubie', - 'kudos', - 'kudus', - 'kudzu', - 'kufis', - 'kugel', - 'kuias', - 'kukri', - 'kukus', - 'kulak', - 'kulan', - 'kulas', - 'kulfi', - 'kumis', - 'kumys', - 'kuris', - 'kurre', - 'kurta', - 'kurus', - 'kusso', - 'kutas', - 'kutch', - 'kutis', - 'kutus', - 'kuzus', - 'kvass', - 'kvell', - 'kwela', - 'kyack', - 'kyaks', - 'kyang', - 'kyars', - 'kyats', - 'kybos', - 'kydst', - 'kyles', - 'kylie', - 'kylin', - 'kylix', - 'kyloe', - 'kynde', - 'kynds', - 'kypes', - 'kyrie', - 'kytes', - 'kythe', - 'laari', - 'labda', - 'labia', - 'labis', - 'labra', - 'laced', - 'lacer', - 'laces', - 'lacet', - 'lacey', - 'lacks', - 'laddy', - 'laded', - 'lader', - 'lades', - 'laers', - 'laevo', - 'lagan', - 'lahal', - 'lahar', - 'laich', - 'laics', - 'laids', - 'laigh', - 'laika', - 'laiks', - 'laird', - 'lairs', - 'lairy', - 'laith', - 'laity', - 'laked', - 'laker', - 'lakes', - 'lakhs', - 'lakin', - 'laksa', - 'laldy', - 'lalls', - 'lamas', - 'lambs', - 'lamby', - 'lamed', - 'lamer', - 'lames', - 'lamia', - 'lammy', - 'lamps', - 'lanai', - 'lanas', - 'lanch', - 'lande', - 'lands', - 'lanes', - 'lanks', - 'lants', - 'lapin', - 'lapis', - 'lapje', - 'larch', - 'lards', - 'lardy', - 'laree', - 'lares', - 'largo', - 'laris', - 'larks', - 'larky', - 'larns', - 'larnt', - 'larum', - 'lased', - 'laser', - 'lases', - 'lassi', - 'lassu', - 'lassy', - 'lasts', - 'latah', - 'lated', - 'laten', - 'latex', - 'lathi', - 'laths', - 'lathy', - 'latke', - 'latus', - 'lauan', - 'lauch', - 'lauds', - 'laufs', - 'laund', - 'laura', - 'laval', - 'lavas', - 'laved', - 'laver', - 'laves', - 'lavra', - 'lavvy', - 'lawed', - 'lawer', - 'lawin', - 'lawks', - 'lawns', - 'lawny', - 'laxed', - 'laxer', - 'laxes', - 'laxly', - 'layed', - 'layin', - 'layup', - 'lazar', - 'lazed', - 'lazes', - 'lazos', - 'lazzi', - 'lazzo', - 'leads', - 'leady', - 'leafs', - 'leaks', - 'leams', - 'leans', - 'leany', - 'leaps', - 'leare', - 'lears', - 'leary', - 'leats', - 'leavy', - 'leaze', - 'leben', - 'leccy', - 'ledes', - 'ledgy', - 'ledum', - 'leear', - 'leeks', - 'leeps', - 'leers', - 'leese', - 'leets', - 'leeze', - 'lefte', - 'lefts', - 'leger', - 'leges', - 'legge', - 'leggo', - 'legit', - 'lehrs', - 'lehua', - 'leirs', - 'leish', - 'leman', - 'lemed', - 'lemel', - 'lemes', - 'lemma', - 'lemme', - 'lends', - 'lenes', - 'lengs', - 'lenis', - 'lenos', - 'lense', - 'lenti', - 'lento', - 'leone', - 'lepid', - 'lepra', - 'lepta', - 'lered', - 'leres', - 'lerps', - 'lesbo', - 'leses', - 'lests', - 'letch', - 'lethe', - 'letup', - 'leuch', - 'leuco', - 'leuds', - 'leugh', - 'levas', - 'levee', - 'leves', - 'levin', - 'levis', - 'lewis', - 'lexes', - 'lexis', - 'lezes', - 'lezza', - 'lezzy', - 'liana', - 'liane', - 'liang', - 'liard', - 'liars', - 'liart', - 'liber', - 'libra', - 'libri', - 'lichi', - 'licht', - 'licit', - 'licks', - 'lidar', - 'lidos', - 'liefs', - 'liens', - 'liers', - 'lieus', - 'lieve', - 'lifer', - 'lifes', - 'lifts', - 'ligan', - 'liger', - 'ligge', - 'ligne', - 'liked', - 'liker', - 'likes', - 'likin', - 'lills', - 'lilos', - 'lilts', - 'liman', - 'limas', - 'limax', - 'limba', - 'limbi', - 'limbs', - 'limby', - 'limed', - 'limen', - 'limes', - 'limey', - 'limma', - 'limns', - 'limos', - 'limpa', - 'limps', - 'linac', - 'linch', - 'linds', - 'lindy', - 'lined', - 'lines', - 'liney', - 'linga', - 'lings', - 'lingy', - 'linin', - 'links', - 'linky', - 'linns', - 'linny', - 'linos', - 'lints', - 'linty', - 'linum', - 'linux', - 'lions', - 'lipas', - 'lipes', - 'lipin', - 'lipos', - 'lippy', - 'liras', - 'lirks', - 'lirot', - 'lisks', - 'lisle', - 'lisps', - 'lists', - 'litai', - 'litas', - 'lited', - 'liter', - 'lites', - 'litho', - 'liths', - 'litre', - 'lived', - 'liven', - 'lives', - 'livor', - 'livre', - 'llano', - 'loach', - 'loads', - 'loafs', - 'loams', - 'loans', - 'loast', - 'loave', - 'lobar', - 'lobed', - 'lobes', - 'lobos', - 'lobus', - 'loche', - 'lochs', - 'locie', - 'locis', - 'locks', - 'locos', - 'locum', - 'loden', - 'lodes', - 'loess', - 'lofts', - 'logan', - 'loges', - 'loggy', - 'logia', - 'logie', - 'logoi', - 'logon', - 'logos', - 'lohan', - 'loids', - 'loins', - 'loipe', - 'loirs', - 'lokes', - 'lolls', - 'lolly', - 'lolog', - 'lomas', - 'lomed', - 'lomes', - 'loner', - 'longa', - 'longe', - 'longs', - 'looby', - 'looed', - 'looey', - 'loofa', - 'loofs', - 'looie', - 'looks', - 'looky', - 'looms', - 'loons', - 'loony', - 'loops', - 'loord', - 'loots', - 'loped', - 'loper', - 'lopes', - 'loppy', - 'loral', - 'loran', - 'lords', - 'lordy', - 'lorel', - 'lores', - 'loric', - 'loris', - 'losed', - 'losel', - 'losen', - 'loses', - 'lossy', - 'lotah', - 'lotas', - 'lotes', - 'lotic', - 'lotos', - 'lotsa', - 'lotta', - 'lotte', - 'lotto', - 'lotus', - 'loued', - 'lough', - 'louie', - 'louis', - 'louma', - 'lound', - 'louns', - 'loupe', - 'loups', - 'loure', - 'lours', - 'loury', - 'louts', - 'lovat', - 'loved', - 'loves', - 'lovey', - 'lovie', - 'lowan', - 'lowed', - 'lowes', - 'lownd', - 'lowne', - 'lowns', - 'lowps', - 'lowry', - 'lowse', - 'lowts', - 'loxed', - 'loxes', - 'lozen', - 'luach', - 'luaus', - 'lubed', - 'lubes', - 'lubra', - 'luces', - 'lucks', - 'lucre', - 'ludes', - 'ludic', - 'ludos', - 'luffa', - 'luffs', - 'luged', - 'luger', - 'luges', - 'lulls', - 'lulus', - 'lumas', - 'lumbi', - 'lumme', - 'lummy', - 'lumps', - 'lunas', - 'lunes', - 'lunet', - 'lungi', - 'lungs', - 'lunks', - 'lunts', - 'lupin', - 'lured', - 'lurer', - 'lures', - 'lurex', - 'lurgi', - 'lurgy', - 'lurks', - 'lurry', - 'lurve', - 'luser', - 'lushy', - 'lusks', - 'lusts', - 'lusus', - 'lutea', - 'luted', - 'luter', - 'lutes', - 'luvvy', - 'luxed', - 'luxer', - 'luxes', - 'lweis', - 'lyams', - 'lyard', - 'lyart', - 'lyase', - 'lycea', - 'lycee', - 'lycra', - 'lymes', - 'lynes', - 'lyres', - 'lysed', - 'lyses', - 'lysin', - 'lysis', - 'lysol', - 'lyssa', - 'lyted', - 'lytes', - 'lythe', - 'lytic', - 'lytta', - 'maaed', - 'maare', - 'maars', - 'mabes', - 'macas', - 'maced', - 'macer', - 'maces', - 'mache', - 'machi', - 'machs', - 'macks', - 'macle', - 'macon', - 'madge', - 'madid', - 'madre', - 'maerl', - 'mafic', - 'mages', - 'maggs', - 'magot', - 'magus', - 'mahoe', - 'mahua', - 'mahwa', - 'maids', - 'maiko', - 'maiks', - 'maile', - 'maill', - 'mails', - 'maims', - 'mains', - 'maire', - 'mairs', - 'maise', - 'maist', - 'makar', - 'makes', - 'makis', - 'makos', - 'malam', - 'malar', - 'malas', - 'malax', - 'males', - 'malic', - 'malik', - 'malis', - 'malls', - 'malms', - 'malmy', - 'malts', - 'malty', - 'malus', - 'malva', - 'malwa', - 'mamas', - 'mamba', - 'mamee', - 'mamey', - 'mamie', - 'manas', - 'manat', - 'mandi', - 'maneb', - 'maned', - 'maneh', - 'manes', - 'manet', - 'mangs', - 'manis', - 'manky', - 'manna', - 'manos', - 'manse', - 'manta', - 'manto', - 'manty', - 'manul', - 'manus', - 'mapau', - 'maqui', - 'marae', - 'marah', - 'maras', - 'marcs', - 'mardy', - 'mares', - 'marge', - 'margs', - 'maria', - 'marid', - 'marka', - 'marks', - 'marle', - 'marls', - 'marly', - 'marms', - 'maron', - 'maror', - 'marra', - 'marri', - 'marse', - 'marts', - 'marvy', - 'masas', - 'mased', - 'maser', - 'mases', - 'mashy', - 'masks', - 'massa', - 'massy', - 'masts', - 'masty', - 'masus', - 'matai', - 'mated', - 'mater', - 'mates', - 'maths', - 'matin', - 'matlo', - 'matte', - 'matts', - 'matza', - 'matzo', - 'mauby', - 'mauds', - 'mauls', - 'maund', - 'mauri', - 'mausy', - 'mauts', - 'mauzy', - 'maven', - 'mavie', - 'mavin', - 'mavis', - 'mawed', - 'mawks', - 'mawky', - 'mawns', - 'mawrs', - 'maxed', - 'maxes', - 'maxis', - 'mayan', - 'mayas', - 'mayed', - 'mayos', - 'mayst', - 'mazed', - 'mazer', - 'mazes', - 'mazey', - 'mazut', - 'mbira', - 'meads', - 'meals', - 'meane', - 'means', - 'meany', - 'meare', - 'mease', - 'meath', - 'meats', - 'mebos', - 'mechs', - 'mecks', - 'medii', - 'medle', - 'meeds', - 'meers', - 'meets', - 'meffs', - 'meins', - 'meint', - 'meiny', - 'meith', - 'mekka', - 'melas', - 'melba', - 'melds', - 'melic', - 'melik', - 'mells', - 'melts', - 'melty', - 'memes', - 'memos', - 'menad', - 'mends', - 'mened', - 'menes', - 'menge', - 'mengs', - 'mensa', - 'mense', - 'mensh', - 'menta', - 'mento', - 'menus', - 'meous', - 'meows', - 'merch', - 'mercs', - 'merde', - 'mered', - 'merel', - 'merer', - 'meres', - 'meril', - 'meris', - 'merks', - 'merle', - 'merls', - 'merse', - 'mesal', - 'mesas', - 'mesel', - 'meses', - 'meshy', - 'mesic', - 'mesne', - 'meson', - 'messy', - 'mesto', - 'meted', - 'metes', - 'metho', - 'meths', - 'metic', - 'metif', - 'metis', - 'metol', - 'metre', - 'meuse', - 'meved', - 'meves', - 'mewed', - 'mewls', - 'meynt', - 'mezes', - 'mezze', - 'mezzo', - 'mhorr', - 'miaou', - 'miaow', - 'miasm', - 'miaul', - 'micas', - 'miche', - 'micht', - 'micks', - 'micky', - 'micos', - 'micra', - 'middy', - 'midgy', - 'midis', - 'miens', - 'mieve', - 'miffs', - 'miffy', - 'mifty', - 'miggs', - 'mihas', - 'mihis', - 'miked', - 'mikes', - 'mikra', - 'mikva', - 'milch', - 'milds', - 'miler', - 'miles', - 'milfs', - 'milia', - 'milko', - 'milks', - 'mille', - 'mills', - 'milor', - 'milos', - 'milpa', - 'milts', - 'milty', - 'miltz', - 'mimed', - 'mimeo', - 'mimer', - 'mimes', - 'mimsy', - 'minae', - 'minar', - 'minas', - 'mincy', - 'minds', - 'mined', - 'mines', - 'minge', - 'mings', - 'mingy', - 'minis', - 'minke', - 'minks', - 'minny', - 'minos', - 'mints', - 'mired', - 'mires', - 'mirex', - 'mirid', - 'mirin', - 'mirks', - 'mirky', - 'mirly', - 'miros', - 'mirvs', - 'mirza', - 'misch', - 'misdo', - 'mises', - 'misgo', - 'misos', - 'missa', - 'mists', - 'misty', - 'mitch', - 'miter', - 'mites', - 'mitis', - 'mitre', - 'mitts', - 'mixed', - 'mixen', - 'mixer', - 'mixes', - 'mixte', - 'mixup', - 'mizen', - 'mizzy', - 'mneme', - 'moans', - 'moats', - 'mobby', - 'mobes', - 'mobey', - 'mobie', - 'moble', - 'mochi', - 'mochs', - 'mochy', - 'mocks', - 'moder', - 'modes', - 'modge', - 'modii', - 'modus', - 'moers', - 'mofos', - 'moggy', - 'mohel', - 'mohos', - 'mohrs', - 'mohua', - 'mohur', - 'moile', - 'moils', - 'moira', - 'moire', - 'moits', - 'mojos', - 'mokes', - 'mokis', - 'mokos', - 'molal', - 'molas', - 'molds', - 'moled', - 'moles', - 'molla', - 'molls', - 'molly', - 'molto', - 'molts', - 'molys', - 'momes', - 'momma', - 'mommy', - 'momus', - 'monad', - 'monal', - 'monas', - 'monde', - 'mondo', - 'moner', - 'mongo', - 'mongs', - 'monic', - 'monie', - 'monks', - 'monos', - 'monte', - 'monty', - 'moobs', - 'mooch', - 'moods', - 'mooed', - 'mooks', - 'moola', - 'mooli', - 'mools', - 'mooly', - 'moong', - 'moons', - 'moony', - 'moops', - 'moors', - 'moory', - 'moots', - 'moove', - 'moped', - 'moper', - 'mopes', - 'mopey', - 'moppy', - 'mopsy', - 'mopus', - 'morae', - 'moras', - 'morat', - 'moray', - 'morel', - 'mores', - 'moria', - 'morne', - 'morns', - 'morra', - 'morro', - 'morse', - 'morts', - 'mosed', - 'moses', - 'mosey', - 'mosks', - 'mosso', - 'moste', - 'mosts', - 'moted', - 'moten', - 'motes', - 'motet', - 'motey', - 'moths', - 'mothy', - 'motis', - 'motte', - 'motts', - 'motty', - 'motus', - 'motza', - 'mouch', - 'moues', - 'mould', - 'mouls', - 'moups', - 'moust', - 'mousy', - 'moved', - 'moves', - 'mowas', - 'mowed', - 'mowra', - 'moxas', - 'moxie', - 'moyas', - 'moyle', - 'moyls', - 'mozed', - 'mozes', - 'mozos', - 'mpret', - 'mucho', - 'mucic', - 'mucid', - 'mucin', - 'mucks', - 'mucor', - 'mucro', - 'mudge', - 'mudir', - 'mudra', - 'muffs', - 'mufti', - 'mugga', - 'muggs', - 'muggy', - 'muhly', - 'muids', - 'muils', - 'muirs', - 'muist', - 'mujik', - 'mulct', - 'muled', - 'mules', - 'muley', - 'mulga', - 'mulie', - 'mulla', - 'mulls', - 'mulse', - 'mulsh', - 'mumms', - 'mumps', - 'mumsy', - 'mumus', - 'munga', - 'munge', - 'mungo', - 'mungs', - 'munis', - 'munts', - 'muntu', - 'muons', - 'muras', - 'mured', - 'mures', - 'murex', - 'murid', - 'murks', - 'murls', - 'murly', - 'murra', - 'murre', - 'murri', - 'murrs', - 'murry', - 'murti', - 'murva', - 'musar', - 'musca', - 'mused', - 'muser', - 'muses', - 'muset', - 'musha', - 'musit', - 'musks', - 'musos', - 'musse', - 'mussy', - 'musth', - 'musts', - 'mutch', - 'muted', - 'muter', - 'mutes', - 'mutha', - 'mutis', - 'muton', - 'mutts', - 'muxed', - 'muxes', - 'muzak', - 'muzzy', - 'mvule', - 'myall', - 'mylar', - 'mynah', - 'mynas', - 'myoid', - 'myoma', - 'myope', - 'myops', - 'myopy', - 'mysid', - 'mythi', - 'myths', - 'mythy', - 'myxos', - 'mzees', - 'naams', - 'naans', - 'nabes', - 'nabis', - 'nabks', - 'nabla', - 'nabob', - 'nache', - 'nacho', - 'nacre', - 'nadas', - 'naeve', - 'naevi', - 'naffs', - 'nagas', - 'naggy', - 'nagor', - 'nahal', - 'naiad', - 'naifs', - 'naiks', - 'nails', - 'naira', - 'nairu', - 'naked', - 'naker', - 'nakfa', - 'nalas', - 'naled', - 'nalla', - 'named', - 'namer', - 'names', - 'namma', - 'namus', - 'nanas', - 'nance', - 'nancy', - 'nandu', - 'nanna', - 'nanos', - 'nanua', - 'napas', - 'naped', - 'napes', - 'napoo', - 'nappa', - 'nappe', - 'nappy', - 'naras', - 'narco', - 'narcs', - 'nards', - 'nares', - 'naric', - 'naris', - 'narks', - 'narky', - 'narre', - 'nashi', - 'natch', - 'nates', - 'natis', - 'natty', - 'nauch', - 'naunt', - 'navar', - 'naves', - 'navew', - 'navvy', - 'nawab', - 'nazes', - 'nazir', - 'nazis', - 'nduja', - 'neafe', - 'neals', - 'neaps', - 'nears', - 'neath', - 'neats', - 'nebek', - 'nebel', - 'necks', - 'neddy', - 'needs', - 'neeld', - 'neele', - 'neemb', - 'neems', - 'neeps', - 'neese', - 'neeze', - 'negro', - 'negus', - 'neifs', - 'neist', - 'neive', - 'nelis', - 'nelly', - 'nemas', - 'nemns', - 'nempt', - 'nenes', - 'neons', - 'neper', - 'nepit', - 'neral', - 'nerds', - 'nerka', - 'nerks', - 'nerol', - 'nerts', - 'nertz', - 'nervy', - 'nests', - 'netes', - 'netop', - 'netts', - 'netty', - 'neuks', - 'neume', - 'neums', - 'nevel', - 'neves', - 'nevus', - 'newbs', - 'newed', - 'newel', - 'newie', - 'newsy', - 'newts', - 'nexts', - 'nexus', - 'ngaio', - 'ngana', - 'ngati', - 'ngoma', - 'ngwee', - 'nicad', - 'nicht', - 'nicks', - 'nicol', - 'nidal', - 'nided', - 'nides', - 'nidor', - 'nidus', - 'niefs', - 'nieve', - 'nifes', - 'niffs', - 'niffy', - 'nifty', - 'niger', - 'nighs', - 'nihil', - 'nikab', - 'nikah', - 'nikau', - 'nills', - 'nimbi', - 'nimbs', - 'nimps', - 'niner', - 'nines', - 'ninon', - 'nipas', - 'nippy', - 'niqab', - 'nirls', - 'nirly', - 'nisei', - 'nisse', - 'nisus', - 'niter', - 'nites', - 'nitid', - 'niton', - 'nitre', - 'nitro', - 'nitry', - 'nitty', - 'nival', - 'nixed', - 'nixer', - 'nixes', - 'nixie', - 'nizam', - 'nkosi', - 'noahs', - 'nobby', - 'nocks', - 'nodal', - 'noddy', - 'nodes', - 'nodus', - 'noels', - 'noggs', - 'nohow', - 'noils', - 'noily', - 'noint', - 'noirs', - 'noles', - 'nolls', - 'nolos', - 'nomas', - 'nomen', - 'nomes', - 'nomic', - 'nomoi', - 'nomos', - 'nonas', - 'nonce', - 'nones', - 'nonet', - 'nongs', - 'nonis', - 'nonny', - 'nonyl', - 'noobs', - 'nooit', - 'nooks', - 'nooky', - 'noons', - 'noops', - 'nopal', - 'noria', - 'noris', - 'norks', - 'norma', - 'norms', - 'nosed', - 'noser', - 'noses', - 'notal', - 'noted', - 'noter', - 'notes', - 'notum', - 'nould', - 'noule', - 'nouls', - 'nouns', - 'nouny', - 'noups', - 'novae', - 'novas', - 'novum', - 'noway', - 'nowed', - 'nowls', - 'nowts', - 'nowty', - 'noxal', - 'noxes', - 'noyau', - 'noyed', - 'noyes', - 'nubby', - 'nubia', - 'nucha', - 'nuddy', - 'nuder', - 'nudes', - 'nudie', - 'nudzh', - 'nuffs', - 'nugae', - 'nuked', - 'nukes', - 'nulla', - 'nulls', - 'numbs', - 'numen', - 'nummy', - 'nunny', - 'nurds', - 'nurdy', - 'nurls', - 'nurrs', - 'nutso', - 'nutsy', - 'nyaff', - 'nyala', - 'nying', - 'nyssa', - 'oaked', - 'oaker', - 'oakum', - 'oared', - 'oases', - 'oasis', - 'oasts', - 'oaten', - 'oater', - 'oaths', - 'oaves', - 'obang', - 'obeah', - 'obeli', - 'obeys', - 'obias', - 'obied', - 'obiit', - 'obits', - 'objet', - 'oboes', - 'obole', - 'oboli', - 'obols', - 'occam', - 'ocher', - 'oches', - 'ochre', - 'ochry', - 'ocker', - 'ocrea', - 'octad', - 'octan', - 'octas', - 'octyl', - 'oculi', - 'odahs', - 'odals', - 'odeon', - 'odeum', - 'odism', - 'odist', - 'odium', - 'odors', - 'odour', - 'odyle', - 'odyls', - 'ofays', - 'offed', - 'offie', - 'oflag', - 'ofter', - 'ogams', - 'ogeed', - 'ogees', - 'oggin', - 'ogham', - 'ogive', - 'ogled', - 'ogler', - 'ogles', - 'ogmic', - 'ogres', - 'ohias', - 'ohing', - 'ohmic', - 'ohone', - 'oidia', - 'oiled', - 'oiler', - 'oinks', - 'oints', - 'ojime', - 'okapi', - 'okays', - 'okehs', - 'okras', - 'oktas', - 'oldie', - 'oleic', - 'olein', - 'olent', - 'oleos', - 'oleum', - 'olios', - 'ollas', - 'ollav', - 'oller', - 'ollie', - 'ology', - 'olpae', - 'olpes', - 'omasa', - 'omber', - 'ombus', - 'omens', - 'omers', - 'omits', - 'omlah', - 'omovs', - 'omrah', - 'oncer', - 'onces', - 'oncet', - 'oncus', - 'onely', - 'oners', - 'onery', - 'onium', - 'onkus', - 'onlay', - 'onned', - 'ontic', - 'oobit', - 'oohed', - 'oomph', - 'oonts', - 'ooped', - 'oorie', - 'ooses', - 'ootid', - 'oozed', - 'oozes', - 'opahs', - 'opals', - 'opens', - 'opepe', - 'oping', - 'oppos', - 'opsin', - 'opted', - 'opter', - 'orach', - 'oracy', - 'orals', - 'orang', - 'orant', - 'orate', - 'orbed', - 'orcas', - 'orcin', - 'ordos', - 'oread', - 'orfes', - 'orgia', - 'orgic', - 'orgue', - 'oribi', - 'oriel', - 'orixa', - 'orles', - 'orlon', - 'orlop', - 'ormer', - 'ornis', - 'orpin', - 'orris', - 'ortho', - 'orval', - 'orzos', - 'oscar', - 'oshac', - 'osier', - 'osmic', - 'osmol', - 'ossia', - 'ostia', - 'otaku', - 'otary', - 'ottar', - 'ottos', - 'oubit', - 'oucht', - 'ouens', - 'ouija', - 'oulks', - 'oumas', - 'oundy', - 'oupas', - 'ouped', - 'ouphe', - 'ouphs', - 'ourie', - 'ousel', - 'ousts', - 'outby', - 'outed', - 'outre', - 'outro', - 'outta', - 'ouzel', - 'ouzos', - 'ovals', - 'ovels', - 'ovens', - 'overs', - 'ovist', - 'ovoli', - 'ovolo', - 'ovule', - 'owche', - 'owies', - 'owled', - 'owler', - 'owlet', - 'owned', - 'owres', - 'owrie', - 'owsen', - 'oxbow', - 'oxers', - 'oxeye', - 'oxids', - 'oxies', - 'oxime', - 'oxims', - 'oxlip', - 'oxter', - 'oyers', - 'ozeki', - 'ozzie', - 'paals', - 'paans', - 'pacas', - 'paced', - 'pacer', - 'paces', - 'pacey', - 'pacha', - 'packs', - 'pacos', - 'pacta', - 'pacts', - 'padis', - 'padle', - 'padma', - 'padre', - 'padri', - 'paean', - 'paedo', - 'paeon', - 'paged', - 'pager', - 'pages', - 'pagle', - 'pagod', - 'pagri', - 'paiks', - 'pails', - 'pains', - 'paire', - 'pairs', - 'paisa', - 'paise', - 'pakka', - 'palas', - 'palay', - 'palea', - 'paled', - 'pales', - 'palet', - 'palis', - 'palki', - 'palla', - 'palls', - 'pally', - 'palms', - 'palmy', - 'palpi', - 'palps', - 'palsa', - 'pampa', - 'panax', - 'pance', - 'panda', - 'pands', - 'pandy', - 'paned', - 'panes', - 'panga', - 'pangs', - 'panim', - 'panko', - 'panne', - 'panni', - 'panto', - 'pants', - 'panty', - 'paoli', - 'paolo', - 'papas', - 'papaw', - 'papes', - 'pappi', - 'pappy', - 'parae', - 'paras', - 'parch', - 'pardi', - 'pards', - 'pardy', - 'pared', - 'paren', - 'pareo', - 'pares', - 'pareu', - 'parev', - 'parge', - 'pargo', - 'paris', - 'parki', - 'parks', - 'parky', - 'parle', - 'parly', - 'parma', - 'parol', - 'parps', - 'parra', - 'parrs', - 'parti', - 'parts', - 'parve', - 'parvo', - 'paseo', - 'pases', - 'pasha', - 'pashm', - 'paska', - 'paspy', - 'passe', - 'pasts', - 'pated', - 'paten', - 'pater', - 'pates', - 'paths', - 'patin', - 'patka', - 'patly', - 'patte', - 'patus', - 'pauas', - 'pauls', - 'pavan', - 'paved', - 'paven', - 'paver', - 'paves', - 'pavid', - 'pavin', - 'pavis', - 'pawas', - 'pawaw', - 'pawed', - 'pawer', - 'pawks', - 'pawky', - 'pawls', - 'pawns', - 'paxes', - 'payed', - 'payor', - 'paysd', - 'peage', - 'peags', - 'peaks', - 'peaky', - 'peals', - 'peans', - 'peare', - 'pears', - 'peart', - 'pease', - 'peats', - 'peaty', - 'peavy', - 'peaze', - 'pebas', - 'pechs', - 'pecke', - 'pecks', - 'pecky', - 'pedes', - 'pedis', - 'pedro', - 'peece', - 'peeks', - 'peels', - 'peens', - 'peeoy', - 'peepe', - 'peeps', - 'peers', - 'peery', - 'peeve', - 'peggy', - 'peghs', - 'peins', - 'peise', - 'peize', - 'pekan', - 'pekes', - 'pekin', - 'pekoe', - 'pelas', - 'pelau', - 'peles', - 'pelfs', - 'pells', - 'pelma', - 'pelon', - 'pelta', - 'pelts', - 'pends', - 'pendu', - 'pened', - 'penes', - 'pengo', - 'penie', - 'penis', - 'penks', - 'penna', - 'penni', - 'pents', - 'peons', - 'peony', - 'pepla', - 'pepos', - 'peppy', - 'pepsi', - 'perai', - 'perce', - 'percs', - 'perdu', - 'perdy', - 'perea', - 'peres', - 'peris', - 'perks', - 'perms', - 'perns', - 'perog', - 'perps', - 'perry', - 'perse', - 'perst', - 'perts', - 'perve', - 'pervo', - 'pervs', - 'pervy', - 'pesos', - 'pests', - 'pesty', - 'petar', - 'peter', - 'petit', - 'petre', - 'petri', - 'petti', - 'petto', - 'pewee', - 'pewit', - 'peyse', - 'phage', - 'phang', - 'phare', - 'pharm', - 'pheer', - 'phene', - 'pheon', - 'phese', - 'phial', - 'phish', - 'phizz', - 'phlox', - 'phoca', - 'phono', - 'phons', - 'phots', - 'phpht', - 'phuts', - 'phyla', - 'phyle', - 'piani', - 'pians', - 'pibal', - 'pical', - 'picas', - 'piccy', - 'picks', - 'picot', - 'picra', - 'picul', - 'piend', - 'piers', - 'piert', - 'pieta', - 'piets', - 'piezo', - 'pight', - 'pigmy', - 'piing', - 'pikas', - 'pikau', - 'piked', - 'piker', - 'pikes', - 'pikey', - 'pikis', - 'pikul', - 'pilae', - 'pilaf', - 'pilao', - 'pilar', - 'pilau', - 'pilaw', - 'pilch', - 'pilea', - 'piled', - 'pilei', - 'piler', - 'piles', - 'pilis', - 'pills', - 'pilow', - 'pilum', - 'pilus', - 'pimas', - 'pimps', - 'pinas', - 'pined', - 'pines', - 'pingo', - 'pings', - 'pinko', - 'pinks', - 'pinna', - 'pinny', - 'pinon', - 'pinot', - 'pinta', - 'pints', - 'pinup', - 'pions', - 'piony', - 'pious', - 'pioye', - 'pioys', - 'pipal', - 'pipas', - 'piped', - 'pipes', - 'pipet', - 'pipis', - 'pipit', - 'pippy', - 'pipul', - 'pirai', - 'pirls', - 'pirns', - 'pirog', - 'pisco', - 'pises', - 'pisky', - 'pisos', - 'pissy', - 'piste', - 'pitas', - 'piths', - 'piton', - 'pitot', - 'pitta', - 'piums', - 'pixes', - 'pized', - 'pizes', - 'plaas', - 'plack', - 'plage', - 'plans', - 'plaps', - 'plash', - 'plasm', - 'plast', - 'plats', - 'platt', - 'platy', - 'playa', - 'plays', - 'pleas', - 'plebe', - 'plebs', - 'plena', - 'pleon', - 'plesh', - 'plews', - 'plica', - 'plies', - 'plims', - 'pling', - 'plink', - 'ploat', - 'plods', - 'plong', - 'plonk', - 'plook', - 'plops', - 'plots', - 'plotz', - 'plouk', - 'plows', - 'ploye', - 'ploys', - 'plues', - 'pluff', - 'plugs', - 'plums', - 'plumy', - 'pluot', - 'pluto', - 'plyer', - 'poach', - 'poaka', - 'poake', - 'poboy', - 'pocks', - 'pocky', - 'podal', - 'poddy', - 'podex', - 'podge', - 'podgy', - 'podia', - 'poems', - 'poeps', - 'poets', - 'pogey', - 'pogge', - 'pogos', - 'pohed', - 'poilu', - 'poind', - 'pokal', - 'poked', - 'pokes', - 'pokey', - 'pokie', - 'poled', - 'poler', - 'poles', - 'poley', - 'polio', - 'polis', - 'polje', - 'polks', - 'polls', - 'polly', - 'polos', - 'polts', - 'polys', - 'pombe', - 'pomes', - 'pommy', - 'pomos', - 'pomps', - 'ponce', - 'poncy', - 'ponds', - 'pones', - 'poney', - 'ponga', - 'pongo', - 'pongs', - 'pongy', - 'ponks', - 'ponts', - 'ponty', - 'ponzu', - 'poods', - 'pooed', - 'poofs', - 'poofy', - 'poohs', - 'pooja', - 'pooka', - 'pooks', - 'pools', - 'poons', - 'poops', - 'poopy', - 'poori', - 'poort', - 'poots', - 'poove', - 'poovy', - 'popes', - 'poppa', - 'popsy', - 'porae', - 'poral', - 'pored', - 'porer', - 'pores', - 'porge', - 'porgy', - 'porin', - 'porks', - 'porky', - 'porno', - 'porns', - 'porny', - 'porta', - 'ports', - 'porty', - 'posed', - 'poses', - 'posey', - 'posho', - 'posts', - 'potae', - 'potch', - 'poted', - 'potes', - 'potin', - 'potoo', - 'potsy', - 'potto', - 'potts', - 'potty', - 'pouff', - 'poufs', - 'pouke', - 'pouks', - 'poule', - 'poulp', - 'poult', - 'poupe', - 'poupt', - 'pours', - 'pouts', - 'powan', - 'powin', - 'pownd', - 'powns', - 'powny', - 'powre', - 'poxed', - 'poxes', - 'poynt', - 'poyou', - 'poyse', - 'pozzy', - 'praam', - 'prads', - 'prahu', - 'prams', - 'prana', - 'prang', - 'praos', - 'prase', - 'prate', - 'prats', - 'pratt', - 'praty', - 'praus', - 'prays', - 'predy', - 'preed', - 'prees', - 'preif', - 'prems', - 'premy', - 'prent', - 'preon', - 'preop', - 'preps', - 'presa', - 'prese', - 'prest', - 'preve', - 'prexy', - 'preys', - 'prial', - 'pricy', - 'prief', - 'prier', - 'pries', - 'prigs', - 'prill', - 'prima', - 'primi', - 'primp', - 'prims', - 'primy', - 'prink', - 'prion', - 'prise', - 'priss', - 'proas', - 'probs', - 'prods', - 'proem', - 'profs', - 'progs', - 'proin', - 'proke', - 'prole', - 'proll', - 'promo', - 'proms', - 'pronk', - 'props', - 'prore', - 'proso', - 'pross', - 'prost', - 'prosy', - 'proto', - 'proul', - 'prows', - 'proyn', - 'prunt', - 'pruta', - 'pryer', - 'pryse', - 'pseud', - 'pshaw', - 'psion', - 'psoae', - 'psoai', - 'psoas', - 'psora', - 'psych', - 'psyop', - 'pubco', - 'pubes', - 'pubis', - 'pucan', - 'pucer', - 'puces', - 'pucka', - 'pucks', - 'puddy', - 'pudge', - 'pudic', - 'pudor', - 'pudsy', - 'pudus', - 'puers', - 'puffa', - 'puffs', - 'puggy', - 'pugil', - 'puhas', - 'pujah', - 'pujas', - 'pukas', - 'puked', - 'puker', - 'pukes', - 'pukey', - 'pukka', - 'pukus', - 'pulao', - 'pulas', - 'puled', - 'puler', - 'pules', - 'pulik', - 'pulis', - 'pulka', - 'pulks', - 'pulli', - 'pulls', - 'pully', - 'pulmo', - 'pulps', - 'pulus', - 'pumas', - 'pumie', - 'pumps', - 'punas', - 'punce', - 'punga', - 'pungs', - 'punji', - 'punka', - 'punks', - 'punky', - 'punny', - 'punto', - 'punts', - 'punty', - 'pupae', - 'pupas', - 'pupus', - 'purda', - 'pured', - 'pures', - 'purin', - 'puris', - 'purls', - 'purpy', - 'purrs', - 'pursy', - 'purty', - 'puses', - 'pusle', - 'pussy', - 'putid', - 'puton', - 'putti', - 'putto', - 'putts', - 'puzel', - 'pwned', - 'pyats', - 'pyets', - 'pygal', - 'pyins', - 'pylon', - 'pyned', - 'pynes', - 'pyoid', - 'pyots', - 'pyral', - 'pyran', - 'pyres', - 'pyrex', - 'pyric', - 'pyros', - 'pyxed', - 'pyxes', - 'pyxie', - 'pyxis', - 'pzazz', - 'qadis', - 'qaids', - 'qajaq', - 'qanat', - 'qapik', - 'qibla', - 'qophs', - 'qorma', - 'quads', - 'quaff', - 'quags', - 'quair', - 'quais', - 'quaky', - 'quale', - 'quant', - 'quare', - 'quass', - 'quate', - 'quats', - 'quayd', - 'quays', - 'qubit', - 'quean', - 'queme', - 'quena', - 'quern', - 'queyn', - 'queys', - 'quich', - 'quids', - 'quiff', - 'quims', - 'quina', - 'quine', - 'quino', - 'quins', - 'quint', - 'quipo', - 'quips', - 'quipu', - 'quire', - 'quirt', - 'quist', - 'quits', - 'quoad', - 'quods', - 'quoif', - 'quoin', - 'quoit', - 'quoll', - 'quonk', - 'quops', - 'qursh', - 'quyte', - 'rabat', - 'rabic', - 'rabis', - 'raced', - 'races', - 'rache', - 'racks', - 'racon', - 'radge', - 'radix', - 'radon', - 'raffs', - 'rafts', - 'ragas', - 'ragde', - 'raged', - 'ragee', - 'rager', - 'rages', - 'ragga', - 'raggs', - 'raggy', - 'ragis', - 'ragus', - 'rahed', - 'rahui', - 'raias', - 'raids', - 'raiks', - 'raile', - 'rails', - 'raine', - 'rains', - 'raird', - 'raita', - 'raits', - 'rajas', - 'rajes', - 'raked', - 'rakee', - 'raker', - 'rakes', - 'rakia', - 'rakis', - 'rakus', - 'rales', - 'ramal', - 'ramee', - 'ramet', - 'ramie', - 'ramin', - 'ramis', - 'rammy', - 'ramps', - 'ramus', - 'ranas', - 'rance', - 'rands', - 'ranee', - 'ranga', - 'rangi', - 'rangs', - 'rangy', - 'ranid', - 'ranis', - 'ranke', - 'ranks', - 'rants', - 'raped', - 'raper', - 'rapes', - 'raphe', - 'rappe', - 'rared', - 'raree', - 'rares', - 'rarks', - 'rased', - 'raser', - 'rases', - 'rasps', - 'rasse', - 'rasta', - 'ratal', - 'ratan', - 'ratas', - 'ratch', - 'rated', - 'ratel', - 'rater', - 'rates', - 'ratha', - 'rathe', - 'raths', - 'ratoo', - 'ratos', - 'ratus', - 'rauns', - 'raupo', - 'raved', - 'ravel', - 'raver', - 'raves', - 'ravey', - 'ravin', - 'rawer', - 'rawin', - 'rawly', - 'rawns', - 'raxed', - 'raxes', - 'rayah', - 'rayas', - 'rayed', - 'rayle', - 'rayne', - 'razed', - 'razee', - 'razer', - 'razes', - 'razoo', - 'readd', - 'reads', - 'reais', - 'reaks', - 'realo', - 'reals', - 'reame', - 'reams', - 'reamy', - 'reans', - 'reaps', - 'rears', - 'reast', - 'reata', - 'reate', - 'reave', - 'rebbe', - 'rebec', - 'rebid', - 'rebit', - 'rebop', - 'rebuy', - 'recal', - 'recce', - 'recco', - 'reccy', - 'recit', - 'recks', - 'recon', - 'recta', - 'recti', - 'recto', - 'redan', - 'redds', - 'reddy', - 'reded', - 'redes', - 'redia', - 'redid', - 'redip', - 'redly', - 'redon', - 'redos', - 'redox', - 'redry', - 'redub', - 'redux', - 'redye', - 'reech', - 'reede', - 'reeds', - 'reefs', - 'reefy', - 'reeks', - 'reeky', - 'reels', - 'reens', - 'reest', - 'reeve', - 'refed', - 'refel', - 'reffo', - 'refis', - 'refix', - 'refly', - 'refry', - 'regar', - 'reges', - 'reggo', - 'regie', - 'regma', - 'regna', - 'regos', - 'regur', - 'rehem', - 'reifs', - 'reify', - 'reiki', - 'reiks', - 'reink', - 'reins', - 'reird', - 'reist', - 'reive', - 'rejig', - 'rejon', - 'reked', - 'rekes', - 'rekey', - 'relet', - 'relie', - 'relit', - 'rello', - 'reman', - 'remap', - 'remen', - 'remet', - 'remex', - 'remix', - 'renay', - 'rends', - 'reney', - 'renga', - 'renig', - 'renin', - 'renne', - 'renos', - 'rente', - 'rents', - 'reoil', - 'reorg', - 'repeg', - 'repin', - 'repla', - 'repos', - 'repot', - 'repps', - 'repro', - 'reran', - 'rerig', - 'resat', - 'resaw', - 'resay', - 'resee', - 'reses', - 'resew', - 'resid', - 'resit', - 'resod', - 'resow', - 'resto', - 'rests', - 'resty', - 'resus', - 'retag', - 'retax', - 'retem', - 'retia', - 'retie', - 'retox', - 'revet', - 'revie', - 'rewan', - 'rewax', - 'rewed', - 'rewet', - 'rewin', - 'rewon', - 'rewth', - 'rexes', - 'rezes', - 'rheas', - 'rheme', - 'rheum', - 'rhies', - 'rhime', - 'rhine', - 'rhody', - 'rhomb', - 'rhone', - 'rhumb', - 'rhyne', - 'rhyta', - 'riads', - 'rials', - 'riant', - 'riata', - 'ribas', - 'ribby', - 'ribes', - 'riced', - 'ricer', - 'rices', - 'ricey', - 'richt', - 'ricin', - 'ricks', - 'rides', - 'ridgy', - 'ridic', - 'riels', - 'riems', - 'rieve', - 'rifer', - 'riffs', - 'rifte', - 'rifts', - 'rifty', - 'riggs', - 'rigol', - 'riled', - 'riles', - 'riley', - 'rille', - 'rills', - 'rimae', - 'rimed', - 'rimer', - 'rimes', - 'rimus', - 'rinds', - 'rindy', - 'rines', - 'rings', - 'rinks', - 'rioja', - 'riots', - 'riped', - 'ripes', - 'ripps', - 'rises', - 'rishi', - 'risks', - 'risps', - 'risus', - 'rites', - 'ritts', - 'ritzy', - 'rivas', - 'rived', - 'rivel', - 'riven', - 'rives', - 'riyal', - 'rizas', - 'roads', - 'roams', - 'roans', - 'roars', - 'roary', - 'roate', - 'robed', - 'robes', - 'roble', - 'rocks', - 'roded', - 'rodes', - 'roguy', - 'rohes', - 'roids', - 'roils', - 'roily', - 'roins', - 'roist', - 'rojak', - 'rojis', - 'roked', - 'roker', - 'rokes', - 'rolag', - 'roles', - 'rolfs', - 'rolls', - 'romal', - 'roman', - 'romeo', - 'romps', - 'ronde', - 'rondo', - 'roneo', - 'rones', - 'ronin', - 'ronne', - 'ronte', - 'ronts', - 'roods', - 'roofs', - 'roofy', - 'rooks', - 'rooky', - 'rooms', - 'roons', - 'roops', - 'roopy', - 'roosa', - 'roose', - 'roots', - 'rooty', - 'roped', - 'roper', - 'ropes', - 'ropey', - 'roque', - 'roral', - 'rores', - 'roric', - 'rorid', - 'rorie', - 'rorts', - 'rorty', - 'rosed', - 'roses', - 'roset', - 'roshi', - 'rosin', - 'rosit', - 'rosti', - 'rosts', - 'rotal', - 'rotan', - 'rotas', - 'rotch', - 'roted', - 'rotes', - 'rotis', - 'rotls', - 'roton', - 'rotos', - 'rotte', - 'rouen', - 'roues', - 'roule', - 'rouls', - 'roums', - 'roups', - 'roupy', - 'roust', - 'routh', - 'routs', - 'roved', - 'roven', - 'roves', - 'rowan', - 'rowed', - 'rowel', - 'rowen', - 'rowie', - 'rowme', - 'rownd', - 'rowth', - 'rowts', - 'royne', - 'royst', - 'rozet', - 'rozit', - 'ruana', - 'rubai', - 'rubby', - 'rubel', - 'rubes', - 'rubin', - 'ruble', - 'rubli', - 'rubus', - 'ruche', - 'rucks', - 'rudas', - 'rudds', - 'rudes', - 'rudie', - 'rudis', - 'rueda', - 'ruers', - 'ruffe', - 'ruffs', - 'rugae', - 'rugal', - 'ruggy', - 'ruing', - 'ruins', - 'rukhs', - 'ruled', - 'rules', - 'rumal', - 'rumbo', - 'rumen', - 'rumes', - 'rumly', - 'rummy', - 'rumpo', - 'rumps', - 'rumpy', - 'runch', - 'runds', - 'runed', - 'runes', - 'rungs', - 'runic', - 'runny', - 'runts', - 'runty', - 'rupia', - 'rurps', - 'rurus', - 'rusas', - 'ruses', - 'rushy', - 'rusks', - 'rusma', - 'russe', - 'rusts', - 'ruths', - 'rutin', - 'rutty', - 'ryals', - 'rybat', - 'ryked', - 'rykes', - 'rymme', - 'rynds', - 'ryots', - 'ryper', - 'saags', - 'sabal', - 'sabed', - 'saber', - 'sabes', - 'sabha', - 'sabin', - 'sabir', - 'sable', - 'sabot', - 'sabra', - 'sabre', - 'sacks', - 'sacra', - 'saddo', - 'sades', - 'sadhe', - 'sadhu', - 'sadis', - 'sados', - 'sadza', - 'safed', - 'safes', - 'sagas', - 'sager', - 'sages', - 'saggy', - 'sagos', - 'sagum', - 'saheb', - 'sahib', - 'saice', - 'saick', - 'saics', - 'saids', - 'saiga', - 'sails', - 'saims', - 'saine', - 'sains', - 'sairs', - 'saist', - 'saith', - 'sajou', - 'sakai', - 'saker', - 'sakes', - 'sakia', - 'sakis', - 'sakti', - 'salal', - 'salat', - 'salep', - 'sales', - 'salet', - 'salic', - 'salix', - 'salle', - 'salmi', - 'salol', - 'salop', - 'salpa', - 'salps', - 'salse', - 'salto', - 'salts', - 'salue', - 'salut', - 'saman', - 'samas', - 'samba', - 'sambo', - 'samek', - 'samel', - 'samen', - 'sames', - 'samey', - 'samfu', - 'sammy', - 'sampi', - 'samps', - 'sands', - 'saned', - 'sanes', - 'sanga', - 'sangh', - 'sango', - 'sangs', - 'sanko', - 'sansa', - 'santo', - 'sants', - 'saola', - 'sapan', - 'sapid', - 'sapor', - 'saran', - 'sards', - 'sared', - 'saree', - 'sarge', - 'sargo', - 'sarin', - 'saris', - 'sarks', - 'sarky', - 'sarod', - 'saros', - 'sarus', - 'saser', - 'sasin', - 'sasse', - 'satai', - 'satay', - 'sated', - 'satem', - 'sates', - 'satis', - 'sauba', - 'sauch', - 'saugh', - 'sauls', - 'sault', - 'saunt', - 'saury', - 'sauts', - 'saved', - 'saver', - 'saves', - 'savey', - 'savin', - 'sawah', - 'sawed', - 'sawer', - 'saxes', - 'sayed', - 'sayer', - 'sayid', - 'sayne', - 'sayon', - 'sayst', - 'sazes', - 'scabs', - 'scads', - 'scaff', - 'scags', - 'scail', - 'scala', - 'scall', - 'scams', - 'scand', - 'scans', - 'scapa', - 'scape', - 'scapi', - 'scarp', - 'scars', - 'scart', - 'scath', - 'scats', - 'scatt', - 'scaud', - 'scaup', - 'scaur', - 'scaws', - 'sceat', - 'scena', - 'scend', - 'schav', - 'schmo', - 'schul', - 'schwa', - 'sclim', - 'scody', - 'scogs', - 'scoog', - 'scoot', - 'scopa', - 'scops', - 'scots', - 'scoug', - 'scoup', - 'scowp', - 'scows', - 'scrab', - 'scrae', - 'scrag', - 'scran', - 'scrat', - 'scraw', - 'scray', - 'scrim', - 'scrip', - 'scrob', - 'scrod', - 'scrog', - 'scrow', - 'scudi', - 'scudo', - 'scuds', - 'scuff', - 'scuft', - 'scugs', - 'sculk', - 'scull', - 'sculp', - 'sculs', - 'scums', - 'scups', - 'scurf', - 'scurs', - 'scuse', - 'scuta', - 'scute', - 'scuts', - 'scuzz', - 'scyes', - 'sdayn', - 'sdein', - 'seals', - 'seame', - 'seams', - 'seamy', - 'seans', - 'seare', - 'sears', - 'sease', - 'seats', - 'seaze', - 'sebum', - 'secco', - 'sechs', - 'sects', - 'seder', - 'sedes', - 'sedge', - 'sedgy', - 'sedum', - 'seeds', - 'seeks', - 'seeld', - 'seels', - 'seely', - 'seems', - 'seeps', - 'seepy', - 'seers', - 'sefer', - 'segar', - 'segni', - 'segno', - 'segol', - 'segos', - 'sehri', - 'seifs', - 'seils', - 'seine', - 'seirs', - 'seise', - 'seism', - 'seity', - 'seiza', - 'sekos', - 'sekts', - 'selah', - 'seles', - 'selfs', - 'sella', - 'selle', - 'sells', - 'selva', - 'semee', - 'semes', - 'semie', - 'semis', - 'senas', - 'sends', - 'senes', - 'sengi', - 'senna', - 'senor', - 'sensa', - 'sensi', - 'sente', - 'senti', - 'sents', - 'senvy', - 'senza', - 'sepad', - 'sepal', - 'sepic', - 'sepoy', - 'septa', - 'septs', - 'serac', - 'serai', - 'seral', - 'sered', - 'serer', - 'seres', - 'serfs', - 'serge', - 'seric', - 'serin', - 'serks', - 'seron', - 'serow', - 'serra', - 'serre', - 'serrs', - 'serry', - 'servo', - 'sesey', - 'sessa', - 'setae', - 'setal', - 'seton', - 'setts', - 'sewan', - 'sewar', - 'sewed', - 'sewel', - 'sewen', - 'sewin', - 'sexed', - 'sexer', - 'sexes', - 'sexto', - 'sexts', - 'seyen', - 'shads', - 'shags', - 'shahs', - 'shako', - 'shakt', - 'shalm', - 'shaly', - 'shama', - 'shams', - 'shand', - 'shans', - 'shaps', - 'sharn', - 'shash', - 'shaul', - 'shawm', - 'shawn', - 'shaws', - 'shaya', - 'shays', - 'shchi', - 'sheaf', - 'sheal', - 'sheas', - 'sheds', - 'sheel', - 'shend', - 'shent', - 'sheol', - 'sherd', - 'shere', - 'shero', - 'shets', - 'sheva', - 'shewn', - 'shews', - 'shiai', - 'shiel', - 'shier', - 'shies', - 'shill', - 'shily', - 'shims', - 'shins', - 'ships', - 'shirr', - 'shirs', - 'shish', - 'shiso', - 'shist', - 'shite', - 'shits', - 'shiur', - 'shiva', - 'shive', - 'shivs', - 'shlep', - 'shlub', - 'shmek', - 'shmoe', - 'shoat', - 'shoed', - 'shoer', - 'shoes', - 'shogi', - 'shogs', - 'shoji', - 'shojo', - 'shola', - 'shool', - 'shoon', - 'shoos', - 'shope', - 'shops', - 'shorl', - 'shote', - 'shots', - 'shott', - 'showd', - 'shows', - 'shoyu', - 'shred', - 'shris', - 'shrow', - 'shtik', - 'shtum', - 'shtup', - 'shule', - 'shuln', - 'shuls', - 'shuns', - 'shura', - 'shute', - 'shuts', - 'shwas', - 'shyer', - 'sials', - 'sibbs', - 'sibyl', - 'sices', - 'sicht', - 'sicko', - 'sicks', - 'sicky', - 'sidas', - 'sided', - 'sider', - 'sides', - 'sidha', - 'sidhe', - 'sidle', - 'sield', - 'siens', - 'sient', - 'sieth', - 'sieur', - 'sifts', - 'sighs', - 'sigil', - 'sigla', - 'signa', - 'signs', - 'sijos', - 'sikas', - 'siker', - 'sikes', - 'silds', - 'siled', - 'silen', - 'siler', - 'siles', - 'silex', - 'silks', - 'sills', - 'silos', - 'silts', - 'silty', - 'silva', - 'simar', - 'simas', - 'simba', - 'simis', - 'simps', - 'simul', - 'sinds', - 'sined', - 'sines', - 'sings', - 'sinhs', - 'sinks', - 'sinky', - 'sinus', - 'siped', - 'sipes', - 'sippy', - 'sired', - 'siree', - 'sires', - 'sirih', - 'siris', - 'siroc', - 'sirra', - 'sirup', - 'sisal', - 'sises', - 'sista', - 'sists', - 'sitar', - 'sited', - 'sites', - 'sithe', - 'sitka', - 'situp', - 'situs', - 'siver', - 'sixer', - 'sixes', - 'sixmo', - 'sixte', - 'sizar', - 'sized', - 'sizel', - 'sizer', - 'sizes', - 'skags', - 'skail', - 'skald', - 'skank', - 'skart', - 'skats', - 'skatt', - 'skaws', - 'skean', - 'skear', - 'skeds', - 'skeed', - 'skeef', - 'skeen', - 'skeer', - 'skees', - 'skeet', - 'skegg', - 'skegs', - 'skein', - 'skelf', - 'skell', - 'skelm', - 'skelp', - 'skene', - 'skens', - 'skeos', - 'skeps', - 'skers', - 'skets', - 'skews', - 'skids', - 'skied', - 'skies', - 'skiey', - 'skimo', - 'skims', - 'skink', - 'skins', - 'skint', - 'skios', - 'skips', - 'skirl', - 'skirr', - 'skite', - 'skits', - 'skive', - 'skivy', - 'sklim', - 'skoal', - 'skody', - 'skoff', - 'skogs', - 'skols', - 'skool', - 'skort', - 'skosh', - 'skran', - 'skrik', - 'skuas', - 'skugs', - 'skyed', - 'skyer', - 'skyey', - 'skyfs', - 'skyre', - 'skyrs', - 'skyte', - 'slabs', - 'slade', - 'slaes', - 'slags', - 'slaid', - 'slake', - 'slams', - 'slane', - 'slank', - 'slaps', - 'slart', - 'slats', - 'slaty', - 'slaws', - 'slays', - 'slebs', - 'sleds', - 'sleer', - 'slews', - 'sleys', - 'slier', - 'slily', - 'slims', - 'slipe', - 'slips', - 'slipt', - 'slish', - 'slits', - 'slive', - 'sloan', - 'slobs', - 'sloes', - 'slogs', - 'sloid', - 'slojd', - 'slomo', - 'sloom', - 'sloot', - 'slops', - 'slopy', - 'slorm', - 'slots', - 'slove', - 'slows', - 'sloyd', - 'slubb', - 'slubs', - 'slued', - 'slues', - 'sluff', - 'slugs', - 'sluit', - 'slums', - 'slurb', - 'slurs', - 'sluse', - 'sluts', - 'slyer', - 'slype', - 'smaak', - 'smaik', - 'smalm', - 'smalt', - 'smarm', - 'smaze', - 'smeek', - 'smees', - 'smeik', - 'smeke', - 'smerk', - 'smews', - 'smirr', - 'smirs', - 'smits', - 'smogs', - 'smoko', - 'smolt', - 'smoor', - 'smoot', - 'smore', - 'smorg', - 'smout', - 'smowt', - 'smugs', - 'smurs', - 'smush', - 'smuts', - 'snabs', - 'snafu', - 'snags', - 'snaps', - 'snarf', - 'snark', - 'snars', - 'snary', - 'snash', - 'snath', - 'snaws', - 'snead', - 'sneap', - 'snebs', - 'sneck', - 'sneds', - 'sneed', - 'snees', - 'snell', - 'snibs', - 'snick', - 'snies', - 'snift', - 'snigs', - 'snips', - 'snipy', - 'snirt', - 'snits', - 'snobs', - 'snods', - 'snoek', - 'snoep', - 'snogs', - 'snoke', - 'snood', - 'snook', - 'snool', - 'snoot', - 'snots', - 'snowk', - 'snows', - 'snubs', - 'snugs', - 'snush', - 'snyes', - 'soaks', - 'soaps', - 'soare', - 'soars', - 'soave', - 'sobas', - 'socas', - 'soces', - 'socko', - 'socks', - 'socle', - 'sodas', - 'soddy', - 'sodic', - 'sodom', - 'sofar', - 'sofas', - 'softa', - 'softs', - 'softy', - 'soger', - 'sohur', - 'soils', - 'soily', - 'sojas', - 'sojus', - 'sokah', - 'soken', - 'sokes', - 'sokol', - 'solah', - 'solan', - 'solas', - 'solde', - 'soldi', - 'soldo', - 'solds', - 'soled', - 'solei', - 'soler', - 'soles', - 'solon', - 'solos', - 'solum', - 'solus', - 'soman', - 'somas', - 'sonce', - 'sonde', - 'sones', - 'songs', - 'sonly', - 'sonne', - 'sonny', - 'sonse', - 'sonsy', - 'sooey', - 'sooks', - 'sooky', - 'soole', - 'sools', - 'sooms', - 'soops', - 'soote', - 'soots', - 'sophs', - 'sophy', - 'sopor', - 'soppy', - 'sopra', - 'soral', - 'soras', - 'sorbo', - 'sorbs', - 'sorda', - 'sordo', - 'sords', - 'sored', - 'soree', - 'sorel', - 'sorer', - 'sores', - 'sorex', - 'sorgo', - 'sorns', - 'sorra', - 'sorta', - 'sorts', - 'sorus', - 'soths', - 'sotol', - 'souce', - 'souct', - 'sough', - 'souks', - 'souls', - 'soums', - 'soups', - 'soupy', - 'sours', - 'souse', - 'souts', - 'sowar', - 'sowce', - 'sowed', - 'sowff', - 'sowfs', - 'sowle', - 'sowls', - 'sowms', - 'sownd', - 'sowne', - 'sowps', - 'sowse', - 'sowth', - 'soyas', - 'soyle', - 'soyuz', - 'sozin', - 'spacy', - 'spado', - 'spaed', - 'spaer', - 'spaes', - 'spags', - 'spahi', - 'spail', - 'spain', - 'spait', - 'spake', - 'spald', - 'spale', - 'spall', - 'spalt', - 'spams', - 'spane', - 'spang', - 'spans', - 'spard', - 'spars', - 'spart', - 'spate', - 'spats', - 'spaul', - 'spawl', - 'spaws', - 'spayd', - 'spays', - 'spaza', - 'spazz', - 'speal', - 'spean', - 'speat', - 'specs', - 'spect', - 'speel', - 'speer', - 'speil', - 'speir', - 'speks', - 'speld', - 'spelk', - 'speos', - 'spets', - 'speug', - 'spews', - 'spewy', - 'spial', - 'spica', - 'spick', - 'spics', - 'spide', - 'spier', - 'spies', - 'spiff', - 'spifs', - 'spiks', - 'spile', - 'spims', - 'spina', - 'spink', - 'spins', - 'spirt', - 'spiry', - 'spits', - 'spitz', - 'spivs', - 'splay', - 'splog', - 'spode', - 'spods', - 'spoom', - 'spoor', - 'spoot', - 'spork', - 'sposh', - 'spots', - 'sprad', - 'sprag', - 'sprat', - 'spred', - 'sprew', - 'sprit', - 'sprod', - 'sprog', - 'sprue', - 'sprug', - 'spuds', - 'spued', - 'spuer', - 'spues', - 'spugs', - 'spule', - 'spume', - 'spumy', - 'spurs', - 'sputa', - 'spyal', - 'spyre', - 'squab', - 'squaw', - 'squeg', - 'squid', - 'squit', - 'squiz', - 'stabs', - 'stade', - 'stags', - 'stagy', - 'staig', - 'stane', - 'stang', - 'staph', - 'staps', - 'starn', - 'starr', - 'stars', - 'stats', - 'staun', - 'staws', - 'stays', - 'stean', - 'stear', - 'stedd', - 'stede', - 'steds', - 'steek', - 'steem', - 'steen', - 'steil', - 'stela', - 'stele', - 'stell', - 'steme', - 'stems', - 'stend', - 'steno', - 'stens', - 'stent', - 'steps', - 'stept', - 'stere', - 'stets', - 'stews', - 'stewy', - 'steys', - 'stich', - 'stied', - 'sties', - 'stilb', - 'stile', - 'stime', - 'stims', - 'stimy', - 'stipa', - 'stipe', - 'stire', - 'stirk', - 'stirp', - 'stirs', - 'stive', - 'stivy', - 'stoae', - 'stoai', - 'stoas', - 'stoat', - 'stobs', - 'stoep', - 'stogy', - 'stoit', - 'stoln', - 'stoma', - 'stond', - 'stong', - 'stonk', - 'stonn', - 'stook', - 'stoor', - 'stope', - 'stops', - 'stopt', - 'stoss', - 'stots', - 'stott', - 'stoun', - 'stoup', - 'stour', - 'stown', - 'stowp', - 'stows', - 'strad', - 'strae', - 'strag', - 'strak', - 'strep', - 'strew', - 'stria', - 'strig', - 'strim', - 'strop', - 'strow', - 'stroy', - 'strum', - 'stubs', - 'stude', - 'studs', - 'stull', - 'stulm', - 'stumm', - 'stums', - 'stuns', - 'stupa', - 'stupe', - 'sture', - 'sturt', - 'styed', - 'styes', - 'styli', - 'stylo', - 'styme', - 'stymy', - 'styre', - 'styte', - 'subah', - 'subas', - 'subby', - 'suber', - 'subha', - 'succi', - 'sucks', - 'sucky', - 'sucre', - 'sudds', - 'sudor', - 'sudsy', - 'suede', - 'suent', - 'suers', - 'suete', - 'suets', - 'suety', - 'sugan', - 'sughs', - 'sugos', - 'suhur', - 'suids', - 'suint', - 'suits', - 'sujee', - 'sukhs', - 'sukuk', - 'sulci', - 'sulfa', - 'sulfo', - 'sulks', - 'sulph', - 'sulus', - 'sumis', - 'summa', - 'sumos', - 'sumph', - 'sumps', - 'sunis', - 'sunks', - 'sunna', - 'sunns', - 'sunup', - 'supes', - 'supra', - 'surah', - 'sural', - 'suras', - 'surat', - 'surds', - 'sured', - 'sures', - 'surfs', - 'surfy', - 'surgy', - 'surra', - 'sused', - 'suses', - 'susus', - 'sutor', - 'sutra', - 'sutta', - 'swabs', - 'swack', - 'swads', - 'swage', - 'swags', - 'swail', - 'swain', - 'swale', - 'swaly', - 'swamy', - 'swang', - 'swank', - 'swans', - 'swaps', - 'swapt', - 'sward', - 'sware', - 'swarf', - 'swart', - 'swats', - 'swayl', - 'sways', - 'sweal', - 'swede', - 'sweed', - 'sweel', - 'sweer', - 'swees', - 'sweir', - 'swelt', - 'swerf', - 'sweys', - 'swies', - 'swigs', - 'swile', - 'swims', - 'swink', - 'swipe', - 'swire', - 'swiss', - 'swith', - 'swits', - 'swive', - 'swizz', - 'swobs', - 'swole', - 'swoln', - 'swops', - 'swopt', - 'swots', - 'swoun', - 'sybbe', - 'sybil', - 'syboe', - 'sybow', - 'sycee', - 'syces', - 'sycon', - 'syens', - 'syker', - 'sykes', - 'sylis', - 'sylph', - 'sylva', - 'symar', - 'synch', - 'syncs', - 'synds', - 'syned', - 'synes', - 'synth', - 'syped', - 'sypes', - 'syphs', - 'syrah', - 'syren', - 'sysop', - 'sythe', - 'syver', - 'taals', - 'taata', - 'taber', - 'tabes', - 'tabid', - 'tabis', - 'tabla', - 'tabor', - 'tabun', - 'tabus', - 'tacan', - 'taces', - 'tacet', - 'tache', - 'tacho', - 'tachs', - 'tacks', - 'tacos', - 'tacts', - 'taels', - 'tafia', - 'taggy', - 'tagma', - 'tahas', - 'tahrs', - 'taiga', - 'taigs', - 'taiko', - 'tails', - 'tains', - 'taira', - 'taish', - 'taits', - 'tajes', - 'takas', - 'takes', - 'takhi', - 'takin', - 'takis', - 'takky', - 'talak', - 'talaq', - 'talar', - 'talas', - 'talcs', - 'talcy', - 'talea', - 'taler', - 'tales', - 'talks', - 'talky', - 'talls', - 'talma', - 'talpa', - 'taluk', - 'talus', - 'tamal', - 'tamed', - 'tames', - 'tamin', - 'tamis', - 'tammy', - 'tamps', - 'tanas', - 'tanga', - 'tangi', - 'tangs', - 'tanhs', - 'tanka', - 'tanks', - 'tanky', - 'tanna', - 'tansy', - 'tanti', - 'tanto', - 'tanty', - 'tapas', - 'taped', - 'tapen', - 'tapes', - 'tapet', - 'tapis', - 'tappa', - 'tapus', - 'taras', - 'tardo', - 'tared', - 'tares', - 'targa', - 'targe', - 'tarns', - 'taroc', - 'tarok', - 'taros', - 'tarps', - 'tarre', - 'tarry', - 'tarsi', - 'tarts', - 'tarty', - 'tasar', - 'tased', - 'taser', - 'tases', - 'tasks', - 'tassa', - 'tasse', - 'tasso', - 'tatar', - 'tater', - 'tates', - 'taths', - 'tatie', - 'tatou', - 'tatts', - 'tatus', - 'taube', - 'tauld', - 'tauon', - 'taupe', - 'tauts', - 'tavah', - 'tavas', - 'taver', - 'tawai', - 'tawas', - 'tawed', - 'tawer', - 'tawie', - 'tawse', - 'tawts', - 'taxed', - 'taxer', - 'taxes', - 'taxis', - 'taxol', - 'taxon', - 'taxor', - 'taxus', - 'tayra', - 'tazza', - 'tazze', - 'teade', - 'teads', - 'teaed', - 'teaks', - 'teals', - 'teams', - 'tears', - 'teats', - 'teaze', - 'techs', - 'techy', - 'tecta', - 'teels', - 'teems', - 'teend', - 'teene', - 'teens', - 'teeny', - 'teers', - 'teffs', - 'teggs', - 'tegua', - 'tegus', - 'tehrs', - 'teiid', - 'teils', - 'teind', - 'teins', - 'telae', - 'telco', - 'teles', - 'telex', - 'telia', - 'telic', - 'tells', - 'telly', - 'teloi', - 'telos', - 'temed', - 'temes', - 'tempi', - 'temps', - 'tempt', - 'temse', - 'tench', - 'tends', - 'tendu', - 'tenes', - 'tenge', - 'tenia', - 'tenne', - 'tenno', - 'tenny', - 'tenon', - 'tents', - 'tenty', - 'tenue', - 'tepal', - 'tepas', - 'tepoy', - 'terai', - 'teras', - 'terce', - 'terek', - 'teres', - 'terfe', - 'terfs', - 'terga', - 'terms', - 'terne', - 'terns', - 'terry', - 'terts', - 'tesla', - 'testa', - 'teste', - 'tests', - 'tetes', - 'teths', - 'tetra', - 'tetri', - 'teuch', - 'teugh', - 'tewed', - 'tewel', - 'tewit', - 'texas', - 'texes', - 'texts', - 'thack', - 'thagi', - 'thaim', - 'thale', - 'thali', - 'thana', - 'thane', - 'thang', - 'thans', - 'thanx', - 'tharm', - 'thars', - 'thaws', - 'thawy', - 'thebe', - 'theca', - 'theed', - 'theek', - 'thees', - 'thegn', - 'theic', - 'thein', - 'thelf', - 'thema', - 'thens', - 'theow', - 'therm', - 'thesp', - 'thete', - 'thews', - 'thewy', - 'thigs', - 'thilk', - 'thill', - 'thine', - 'thins', - 'thiol', - 'thirl', - 'thoft', - 'thole', - 'tholi', - 'thoro', - 'thorp', - 'thous', - 'thowl', - 'thrae', - 'thraw', - 'thrid', - 'thrip', - 'throe', - 'thuds', - 'thugs', - 'thuja', - 'thunk', - 'thurl', - 'thuya', - 'thymi', - 'thymy', - 'tians', - 'tiars', - 'tical', - 'ticca', - 'ticed', - 'tices', - 'tichy', - 'ticks', - 'ticky', - 'tiddy', - 'tided', - 'tides', - 'tiers', - 'tiffs', - 'tifos', - 'tifts', - 'tiges', - 'tigon', - 'tikas', - 'tikes', - 'tikis', - 'tikka', - 'tilak', - 'tiled', - 'tiler', - 'tiles', - 'tills', - 'tilly', - 'tilth', - 'tilts', - 'timbo', - 'timed', - 'times', - 'timon', - 'timps', - 'tinas', - 'tinct', - 'tinds', - 'tinea', - 'tined', - 'tines', - 'tinge', - 'tings', - 'tinks', - 'tinny', - 'tints', - 'tinty', - 'tipis', - 'tippy', - 'tired', - 'tires', - 'tirls', - 'tiros', - 'tirrs', - 'titch', - 'titer', - 'titis', - 'titre', - 'titty', - 'titup', - 'tiyin', - 'tiyns', - 'tizes', - 'tizzy', - 'toads', - 'toady', - 'toaze', - 'tocks', - 'tocky', - 'tocos', - 'todde', - 'toeas', - 'toffs', - 'toffy', - 'tofts', - 'tofus', - 'togae', - 'togas', - 'toged', - 'toges', - 'togue', - 'tohos', - 'toile', - 'toils', - 'toing', - 'toise', - 'toits', - 'tokay', - 'toked', - 'toker', - 'tokes', - 'tokos', - 'tolan', - 'tolar', - 'tolas', - 'toled', - 'toles', - 'tolls', - 'tolly', - 'tolts', - 'tolus', - 'tolyl', - 'toman', - 'tombs', - 'tomes', - 'tomia', - 'tommy', - 'tomos', - 'tondi', - 'tondo', - 'toned', - 'toner', - 'tones', - 'toney', - 'tongs', - 'tonka', - 'tonks', - 'tonne', - 'tonus', - 'tools', - 'tooms', - 'toons', - 'toots', - 'toped', - 'topee', - 'topek', - 'toper', - 'topes', - 'tophe', - 'tophi', - 'tophs', - 'topis', - 'topoi', - 'topos', - 'toppy', - 'toque', - 'torah', - 'toran', - 'toras', - 'torcs', - 'tores', - 'toric', - 'torii', - 'toros', - 'torot', - 'torrs', - 'torse', - 'torsi', - 'torsk', - 'torta', - 'torte', - 'torts', - 'tosas', - 'tosed', - 'toses', - 'toshy', - 'tossy', - 'toted', - 'toter', - 'totes', - 'totty', - 'touks', - 'touns', - 'tours', - 'touse', - 'tousy', - 'touts', - 'touze', - 'touzy', - 'towed', - 'towie', - 'towns', - 'towny', - 'towse', - 'towsy', - 'towts', - 'towze', - 'towzy', - 'toyed', - 'toyer', - 'toyon', - 'toyos', - 'tozed', - 'tozes', - 'tozie', - 'trabs', - 'trads', - 'tragi', - 'traik', - 'trams', - 'trank', - 'tranq', - 'trans', - 'trant', - 'trape', - 'traps', - 'trapt', - 'trass', - 'trats', - 'tratt', - 'trave', - 'trayf', - 'trays', - 'treck', - 'treed', - 'treen', - 'trees', - 'trefa', - 'treif', - 'treks', - 'trema', - 'trems', - 'tress', - 'trest', - 'trets', - 'trews', - 'treyf', - 'treys', - 'triac', - 'tride', - 'trier', - 'tries', - 'triff', - 'trigo', - 'trigs', - 'trike', - 'trild', - 'trill', - 'trims', - 'trine', - 'trins', - 'triol', - 'trior', - 'trios', - 'trips', - 'tripy', - 'trist', - 'troad', - 'troak', - 'troat', - 'trock', - 'trode', - 'trods', - 'trogs', - 'trois', - 'troke', - 'tromp', - 'trona', - 'tronc', - 'trone', - 'tronk', - 'trons', - 'trooz', - 'troth', - 'trots', - 'trows', - 'troys', - 'trued', - 'trues', - 'trugo', - 'trugs', - 'trull', - 'tryer', - 'tryke', - 'tryma', - 'tryps', - 'tsade', - 'tsadi', - 'tsars', - 'tsked', - 'tsuba', - 'tsubo', - 'tuans', - 'tuart', - 'tuath', - 'tubae', - 'tubar', - 'tubas', - 'tubby', - 'tubed', - 'tubes', - 'tucks', - 'tufas', - 'tuffe', - 'tuffs', - 'tufts', - 'tufty', - 'tugra', - 'tuile', - 'tuina', - 'tuism', - 'tuktu', - 'tules', - 'tulpa', - 'tulsi', - 'tumid', - 'tummy', - 'tumps', - 'tumpy', - 'tunas', - 'tunds', - 'tuned', - 'tuner', - 'tunes', - 'tungs', - 'tunny', - 'tupek', - 'tupik', - 'tuple', - 'tuque', - 'turds', - 'turfs', - 'turfy', - 'turks', - 'turme', - 'turms', - 'turns', - 'turnt', - 'turps', - 'turrs', - 'tushy', - 'tusks', - 'tusky', - 'tutee', - 'tutti', - 'tutty', - 'tutus', - 'tuxes', - 'tuyer', - 'twaes', - 'twain', - 'twals', - 'twank', - 'twats', - 'tways', - 'tweel', - 'tween', - 'tweep', - 'tweer', - 'twerk', - 'twerp', - 'twier', - 'twigs', - 'twill', - 'twilt', - 'twink', - 'twins', - 'twiny', - 'twire', - 'twirp', - 'twite', - 'twits', - 'twoer', - 'twyer', - 'tyees', - 'tyers', - 'tyiyn', - 'tykes', - 'tyler', - 'tymps', - 'tynde', - 'tyned', - 'tynes', - 'typal', - 'typed', - 'types', - 'typey', - 'typic', - 'typos', - 'typps', - 'typto', - 'tyran', - 'tyred', - 'tyres', - 'tyros', - 'tythe', - 'tzars', - 'udals', - 'udons', - 'ugali', - 'ugged', - 'uhlan', - 'uhuru', - 'ukase', - 'ulama', - 'ulans', - 'ulema', - 'ulmin', - 'ulnad', - 'ulnae', - 'ulnar', - 'ulnas', - 'ulpan', - 'ulvas', - 'ulyie', - 'ulzie', - 'umami', - 'umbel', - 'umber', - 'umble', - 'umbos', - 'umbre', - 'umiac', - 'umiak', - 'umiaq', - 'ummah', - 'ummas', - 'ummed', - 'umped', - 'umphs', - 'umpie', - 'umpty', - 'umrah', - 'umras', - 'unais', - 'unapt', - 'unarm', - 'unary', - 'unaus', - 'unbag', - 'unban', - 'unbar', - 'unbed', - 'unbid', - 'unbox', - 'uncap', - 'unces', - 'uncia', - 'uncos', - 'uncoy', - 'uncus', - 'undam', - 'undee', - 'undos', - 'undug', - 'uneth', - 'unfix', - 'ungag', - 'unget', - 'ungod', - 'ungot', - 'ungum', - 'unhat', - 'unhip', - 'unica', - 'units', - 'unjam', - 'unked', - 'unket', - 'unkid', - 'unlaw', - 'unlay', - 'unled', - 'unlet', - 'unlid', - 'unman', - 'unmew', - 'unmix', - 'unpay', - 'unpeg', - 'unpen', - 'unpin', - 'unred', - 'unrid', - 'unrig', - 'unrip', - 'unsaw', - 'unsay', - 'unsee', - 'unsew', - 'unsex', - 'unsod', - 'untax', - 'untin', - 'unwet', - 'unwit', - 'unwon', - 'upbow', - 'upbye', - 'updos', - 'updry', - 'upend', - 'upjet', - 'uplay', - 'upled', - 'uplit', - 'upped', - 'upran', - 'uprun', - 'upsee', - 'upsey', - 'uptak', - 'upter', - 'uptie', - 'uraei', - 'urali', - 'uraos', - 'urare', - 'urari', - 'urase', - 'urate', - 'urbex', - 'urbia', - 'urdee', - 'ureal', - 'ureas', - 'uredo', - 'ureic', - 'urena', - 'urent', - 'urged', - 'urger', - 'urges', - 'urial', - 'urite', - 'urman', - 'urnal', - 'urned', - 'urped', - 'ursae', - 'ursid', - 'urson', - 'urubu', - 'urvas', - 'users', - 'usnea', - 'usque', - 'usure', - 'usury', - 'uteri', - 'uveal', - 'uveas', - 'uvula', - 'vacua', - 'vaded', - 'vades', - 'vagal', - 'vagus', - 'vails', - 'vaire', - 'vairs', - 'vairy', - 'vakas', - 'vakil', - 'vales', - 'valis', - 'valse', - 'vamps', - 'vampy', - 'vanda', - 'vaned', - 'vanes', - 'vangs', - 'vants', - 'vaped', - 'vaper', - 'vapes', - 'varan', - 'varas', - 'vardy', - 'varec', - 'vares', - 'varia', - 'varix', - 'varna', - 'varus', - 'varve', - 'vasal', - 'vases', - 'vasts', - 'vasty', - 'vatic', - 'vatus', - 'vauch', - 'vaute', - 'vauts', - 'vawte', - 'vaxes', - 'veale', - 'veals', - 'vealy', - 'veena', - 'veeps', - 'veers', - 'veery', - 'vegas', - 'veges', - 'vegie', - 'vegos', - 'vehme', - 'veils', - 'veily', - 'veins', - 'veiny', - 'velar', - 'velds', - 'veldt', - 'veles', - 'vells', - 'velum', - 'venae', - 'venal', - 'vends', - 'vendu', - 'veney', - 'venge', - 'venin', - 'vents', - 'venus', - 'verbs', - 'verra', - 'verry', - 'verst', - 'verts', - 'vertu', - 'vespa', - 'vesta', - 'vests', - 'vetch', - 'vexed', - 'vexer', - 'vexes', - 'vexil', - 'vezir', - 'vials', - 'viand', - 'vibes', - 'vibex', - 'vibey', - 'viced', - 'vices', - 'vichy', - 'viers', - 'views', - 'viewy', - 'vifda', - 'viffs', - 'vigas', - 'vigia', - 'vilde', - 'viler', - 'villi', - 'vills', - 'vimen', - 'vinal', - 'vinas', - 'vinca', - 'vined', - 'viner', - 'vines', - 'vinew', - 'vinic', - 'vinos', - 'vints', - 'viold', - 'viols', - 'vired', - 'vireo', - 'vires', - 'virga', - 'virge', - 'virid', - 'virls', - 'virtu', - 'visas', - 'vised', - 'vises', - 'visie', - 'visne', - 'vison', - 'visto', - 'vitae', - 'vitas', - 'vitex', - 'vitro', - 'vitta', - 'vivas', - 'vivat', - 'vivda', - 'viver', - 'vives', - 'vizir', - 'vizor', - 'vleis', - 'vlies', - 'vlogs', - 'voars', - 'vocab', - 'voces', - 'voddy', - 'vodou', - 'vodun', - 'voema', - 'vogie', - 'voids', - 'voile', - 'voips', - 'volae', - 'volar', - 'voled', - 'voles', - 'volet', - 'volks', - 'volta', - 'volte', - 'volti', - 'volts', - 'volva', - 'volve', - 'vomer', - 'voted', - 'votes', - 'vouge', - 'voulu', - 'vowed', - 'vower', - 'voxel', - 'vozhd', - 'vraic', - 'vrils', - 'vroom', - 'vrous', - 'vrouw', - 'vrows', - 'vuggs', - 'vuggy', - 'vughs', - 'vughy', - 'vulgo', - 'vulns', - 'vulva', - 'vutty', - 'waacs', - 'wacke', - 'wacko', - 'wacks', - 'wadds', - 'waddy', - 'waded', - 'wader', - 'wades', - 'wadge', - 'wadis', - 'wadts', - 'waffs', - 'wafts', - 'waged', - 'wages', - 'wagga', - 'wagyu', - 'wahoo', - 'waide', - 'waifs', - 'waift', - 'wails', - 'wains', - 'wairs', - 'waite', - 'waits', - 'wakas', - 'waked', - 'waken', - 'waker', - 'wakes', - 'wakfs', - 'waldo', - 'walds', - 'waled', - 'waler', - 'wales', - 'walie', - 'walis', - 'walks', - 'walla', - 'walls', - 'wally', - 'walty', - 'wamed', - 'wames', - 'wamus', - 'wands', - 'waned', - 'wanes', - 'waney', - 'wangs', - 'wanks', - 'wanky', - 'wanle', - 'wanly', - 'wanna', - 'wants', - 'wanty', - 'wanze', - 'waqfs', - 'warbs', - 'warby', - 'wards', - 'wared', - 'wares', - 'warez', - 'warks', - 'warms', - 'warns', - 'warps', - 'warre', - 'warst', - 'warts', - 'wases', - 'washy', - 'wasms', - 'wasps', - 'waspy', - 'wasts', - 'watap', - 'watts', - 'wauff', - 'waugh', - 'wauks', - 'waulk', - 'wauls', - 'waurs', - 'waved', - 'waves', - 'wavey', - 'wawas', - 'wawes', - 'wawls', - 'waxed', - 'waxer', - 'waxes', - 'wayed', - 'wazir', - 'wazoo', - 'weald', - 'weals', - 'weamb', - 'weans', - 'wears', - 'webby', - 'weber', - 'wecht', - 'wedel', - 'wedgy', - 'weeds', - 'weeke', - 'weeks', - 'weels', - 'weems', - 'weens', - 'weeny', - 'weeps', - 'weepy', - 'weest', - 'weete', - 'weets', - 'wefte', - 'wefts', - 'weids', - 'weils', - 'weirs', - 'weise', - 'weize', - 'wekas', - 'welds', - 'welke', - 'welks', - 'welkt', - 'wells', - 'welly', - 'welts', - 'wembs', - 'wends', - 'wenge', - 'wenny', - 'wents', - 'weros', - 'wersh', - 'wests', - 'wetas', - 'wetly', - 'wexed', - 'wexes', - 'whamo', - 'whams', - 'whang', - 'whaps', - 'whare', - 'whata', - 'whats', - 'whaup', - 'whaur', - 'wheal', - 'whear', - 'wheen', - 'wheep', - 'wheft', - 'whelk', - 'whelm', - 'whens', - 'whets', - 'whews', - 'wheys', - 'whids', - 'whift', - 'whigs', - 'whilk', - 'whims', - 'whins', - 'whios', - 'whips', - 'whipt', - 'whirr', - 'whirs', - 'whish', - 'whiss', - 'whist', - 'whits', - 'whity', - 'whizz', - 'whomp', - 'whoof', - 'whoot', - 'whops', - 'whore', - 'whorl', - 'whort', - 'whoso', - 'whows', - 'whump', - 'whups', - 'whyda', - 'wicca', - 'wicks', - 'wicky', - 'widdy', - 'wides', - 'wiels', - 'wifed', - 'wifes', - 'wifey', - 'wifie', - 'wifty', - 'wigan', - 'wigga', - 'wiggy', - 'wikis', - 'wilco', - 'wilds', - 'wiled', - 'wiles', - 'wilga', - 'wilis', - 'wilja', - 'wills', - 'wilts', - 'wimps', - 'winds', - 'wined', - 'wines', - 'winey', - 'winge', - 'wings', - 'wingy', - 'winks', - 'winna', - 'winns', - 'winos', - 'winze', - 'wiped', - 'wiper', - 'wipes', - 'wired', - 'wirer', - 'wires', - 'wirra', - 'wised', - 'wises', - 'wisha', - 'wisht', - 'wisps', - 'wists', - 'witan', - 'wited', - 'wites', - 'withe', - 'withs', - 'withy', - 'wived', - 'wiver', - 'wives', - 'wizen', - 'wizes', - 'woads', - 'woald', - 'wocks', - 'wodge', - 'woful', - 'wojus', - 'woker', - 'wokka', - 'wolds', - 'wolfs', - 'wolly', - 'wolve', - 'wombs', - 'womby', - 'womyn', - 'wonga', - 'wongi', - 'wonks', - 'wonky', - 'wonts', - 'woods', - 'wooed', - 'woofs', - 'woofy', - 'woold', - 'wools', - 'woons', - 'woops', - 'woopy', - 'woose', - 'woosh', - 'wootz', - 'words', - 'works', - 'worms', - 'wormy', - 'worts', - 'wowed', - 'wowee', - 'woxen', - 'wrang', - 'wraps', - 'wrapt', - 'wrast', - 'wrate', - 'wrawl', - 'wrens', - 'wrick', - 'wried', - 'wrier', - 'wries', - 'writs', - 'wroke', - 'wroot', - 'wroth', - 'wryer', - 'wuddy', - 'wudus', - 'wulls', - 'wurst', - 'wuses', - 'wushu', - 'wussy', - 'wuxia', - 'wyled', - 'wyles', - 'wynds', - 'wynns', - 'wyted', - 'wytes', - 'xebec', - 'xenia', - 'xenic', - 'xenon', - 'xeric', - 'xerox', - 'xerus', - 'xoana', - 'xrays', - 'xylan', - 'xylem', - 'xylic', - 'xylol', - 'xylyl', - 'xysti', - 'xysts', - 'yaars', - 'yabas', - 'yabba', - 'yabby', - 'yacca', - 'yacka', - 'yacks', - 'yaffs', - 'yager', - 'yages', - 'yagis', - 'yahoo', - 'yaird', - 'yakka', - 'yakow', - 'yales', - 'yamen', - 'yampy', - 'yamun', - 'yangs', - 'yanks', - 'yapok', - 'yapon', - 'yapps', - 'yappy', - 'yarak', - 'yarco', - 'yards', - 'yarer', - 'yarfa', - 'yarks', - 'yarns', - 'yarrs', - 'yarta', - 'yarto', - 'yates', - 'yauds', - 'yauld', - 'yaups', - 'yawed', - 'yawey', - 'yawls', - 'yawns', - 'yawny', - 'yawps', - 'ybore', - 'yclad', - 'ycled', - 'ycond', - 'ydrad', - 'ydred', - 'yeads', - 'yeahs', - 'yealm', - 'yeans', - 'yeard', - 'years', - 'yecch', - 'yechs', - 'yechy', - 'yedes', - 'yeeds', - 'yeesh', - 'yeggs', - 'yelks', - 'yells', - 'yelms', - 'yelps', - 'yelts', - 'yenta', - 'yente', - 'yerba', - 'yerds', - 'yerks', - 'yeses', - 'yesks', - 'yests', - 'yesty', - 'yetis', - 'yetts', - 'yeuks', - 'yeuky', - 'yeven', - 'yeves', - 'yewen', - 'yexed', - 'yexes', - 'yfere', - 'yiked', - 'yikes', - 'yills', - 'yince', - 'yipes', - 'yippy', - 'yirds', - 'yirks', - 'yirrs', - 'yirth', - 'yites', - 'yitie', - 'ylems', - 'ylike', - 'ylkes', - 'ymolt', - 'ympes', - 'yobbo', - 'yobby', - 'yocks', - 'yodel', - 'yodhs', - 'yodle', - 'yogas', - 'yogee', - 'yoghs', - 'yogic', - 'yogin', - 'yogis', - 'yoick', - 'yojan', - 'yoked', - 'yokel', - 'yoker', - 'yokes', - 'yokul', - 'yolks', - 'yolky', - 'yomim', - 'yomps', - 'yonic', - 'yonis', - 'yonks', - 'yoofs', - 'yoops', - 'yores', - 'yorks', - 'yorps', - 'youks', - 'yourn', - 'yours', - 'yourt', - 'youse', - 'yowed', - 'yowes', - 'yowie', - 'yowls', - 'yowza', - 'yrapt', - 'yrent', - 'yrivd', - 'yrneh', - 'ysame', - 'ytost', - 'yuans', - 'yucas', - 'yucca', - 'yucch', - 'yucko', - 'yucks', - 'yucky', - 'yufts', - 'yugas', - 'yuked', - 'yukes', - 'yukky', - 'yukos', - 'yulan', - 'yules', - 'yummo', - 'yummy', - 'yumps', - 'yupon', - 'yuppy', - 'yurta', - 'yurts', - 'yuzus', - 'zabra', - 'zacks', - 'zaida', - 'zaidy', - 'zaire', - 'zakat', - 'zaman', - 'zambo', - 'zamia', - 'zanja', - 'zante', - 'zanza', - 'zanze', - 'zappy', - 'zarfs', - 'zaris', - 'zatis', - 'zaxes', - 'zayin', - 'zazen', - 'zeals', - 'zebec', - 'zebub', - 'zebus', - 'zedas', - 'zeins', - 'zendo', - 'zerda', - 'zerks', - 'zeros', - 'zests', - 'zetas', - 'zexes', - 'zezes', - 'zhomo', - 'zibet', - 'ziffs', - 'zigan', - 'zilas', - 'zilch', - 'zilla', - 'zills', - 'zimbi', - 'zimbs', - 'zinco', - 'zincs', - 'zincy', - 'zineb', - 'zines', - 'zings', - 'zingy', - 'zinke', - 'zinky', - 'zippo', - 'zippy', - 'ziram', - 'zitis', - 'zizel', - 'zizit', - 'zlote', - 'zloty', - 'zoaea', - 'zobos', - 'zobus', - 'zocco', - 'zoeae', - 'zoeal', - 'zoeas', - 'zoism', - 'zoist', - 'zombi', - 'zonae', - 'zonda', - 'zoned', - 'zoner', - 'zones', - 'zonks', - 'zooea', - 'zooey', - 'zooid', - 'zooks', - 'zooms', - 'zoons', - 'zooty', - 'zoppa', - 'zoppo', - 'zoril', - 'zoris', - 'zorro', - 'zouks', - 'zowee', - 'zowie', - 'zulus', - 'zupan', - 'zupas', - 'zuppa', - 'zurfs', - 'zuzim', - 'zygal', - 'zygon', - 'zymes', - 'zymic' -]); diff --git a/packages/create-svelte/templates/default/static/robots.txt b/packages/create-svelte/templates/default/static/robots.txt deleted file mode 100644 index e9e57dc4d41b..000000000000 --- a/packages/create-svelte/templates/default/static/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/packages/create-svelte/templates/default/svelte.config.js b/packages/create-svelte/templates/default/svelte.config.js deleted file mode 100644 index e4597b125653..000000000000 --- a/packages/create-svelte/templates/default/svelte.config.js +++ /dev/null @@ -1,17 +0,0 @@ -import adapter from '@sveltejs/adapter-auto'; -import { vitePreprocess } from '@sveltejs/kit/vite'; - -// This config is ignored and replaced with one of the configs in the shared folder when a project is created. - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - // Consult https://kit.svelte.dev/docs/integrations#preprocessors - // for more information about preprocessors - preprocess: vitePreprocess(), - - kit: { - adapter: adapter() - } -}; - -export default config; diff --git a/packages/create-svelte/templates/default/tsconfig.json b/packages/create-svelte/templates/default/tsconfig.json deleted file mode 100644 index 81ff9770cd8a..000000000000 --- a/packages/create-svelte/templates/default/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "./.svelte-kit/tsconfig.json" -} diff --git a/packages/create-svelte/templates/default/vercel.json b/packages/create-svelte/templates/default/vercel.json deleted file mode 100644 index 564eeeb2c771..000000000000 --- a/packages/create-svelte/templates/default/vercel.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "github": { - "silent": true - } -} diff --git a/packages/create-svelte/templates/default/vite.config.js b/packages/create-svelte/templates/default/vite.config.js deleted file mode 100644 index 3dc75a73b8e8..000000000000 --- a/packages/create-svelte/templates/default/vite.config.js +++ /dev/null @@ -1,13 +0,0 @@ -import path from 'node:path'; -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - plugins: [sveltekit()], - - server: { - fs: { - allow: [path.resolve('../../../kit')] - } - } -}); diff --git a/packages/create-svelte/templates/default/wrangler.toml b/packages/create-svelte/templates/default/wrangler.toml deleted file mode 100644 index 6fa14136b41c..000000000000 --- a/packages/create-svelte/templates/default/wrangler.toml +++ /dev/null @@ -1,12 +0,0 @@ -name = "svelte-kit-demo" -account_id = "32a8245cd45a24083dd0acae1d482048" -route = "cloudflare-workers-demo.svelte.dev/*" - -main = "./.cloudflare/worker.js" -site.bucket = "./.cloudflare/public" - -build.command = "npm run build" - -compatibility_date = "2021-11-12" -workers_dev = true - diff --git a/packages/create-svelte/templates/skeleton/.gitignore b/packages/create-svelte/templates/skeleton/.gitignore deleted file mode 100644 index 6635cf554275..000000000000 --- a/packages/create-svelte/templates/skeleton/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.DS_Store -node_modules -/build -/.svelte-kit -/package -.env -.env.* -!.env.example -vite.config.js.timestamp-* -vite.config.ts.timestamp-* diff --git a/packages/create-svelte/templates/skeleton/.ignore b/packages/create-svelte/templates/skeleton/.ignore deleted file mode 100644 index 449d6440cc69..000000000000 --- a/packages/create-svelte/templates/skeleton/.ignore +++ /dev/null @@ -1,3 +0,0 @@ -package.json -.meta.json -.turbo \ No newline at end of file diff --git a/packages/create-svelte/templates/skeleton/.meta.json b/packages/create-svelte/templates/skeleton/.meta.json deleted file mode 100644 index c707b6a95088..000000000000 --- a/packages/create-svelte/templates/skeleton/.meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Skeleton project", - "description": "Barebones scaffolding for your new SvelteKit app" -} diff --git a/packages/create-svelte/templates/skeleton/package.json b/packages/create-svelte/templates/skeleton/package.json deleted file mode 100644 index 1706b9d759f8..000000000000 --- a/packages/create-svelte/templates/skeleton/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "skeleton-template", - "private": true, - "version": "0.0.1-next.0", - "devDependencies": { - "@sveltejs/adapter-auto": "workspace:*" - }, - "type": "module" -} diff --git a/packages/create-svelte/templates/skeleton/package.template.json b/packages/create-svelte/templates/skeleton/package.template.json deleted file mode 100644 index 427a2face111..000000000000 --- a/packages/create-svelte/templates/skeleton/package.template.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "~TODO~", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview" - }, - "devDependencies": { - "@sveltejs/adapter-auto": "^2.0.0", - "@sveltejs/kit": "^1.20.4", - "svelte": "^4.0.5", - "vite": "^4.4.2" - }, - "type": "module" -} diff --git a/packages/create-svelte/templates/skeleton/src/app.d.ts b/packages/create-svelte/templates/skeleton/src/app.d.ts deleted file mode 100644 index f59b884c51ed..000000000000 --- a/packages/create-svelte/templates/skeleton/src/app.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -// See https://kit.svelte.dev/docs/types#app -// for information about these interfaces -declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface Platform {} - } -} - -export {}; diff --git a/packages/create-svelte/templates/skeleton/src/app.html b/packages/create-svelte/templates/skeleton/src/app.html deleted file mode 100644 index 6769ed5e89c5..000000000000 --- a/packages/create-svelte/templates/skeleton/src/app.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/packages/create-svelte/templates/skeleton/src/lib/index.ts b/packages/create-svelte/templates/skeleton/src/lib/index.ts deleted file mode 100644 index 856f2b6c38ae..000000000000 --- a/packages/create-svelte/templates/skeleton/src/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. diff --git a/packages/create-svelte/templates/skeleton/src/routes/+page.svelte b/packages/create-svelte/templates/skeleton/src/routes/+page.svelte deleted file mode 100644 index 5982b0ae37dd..000000000000 --- a/packages/create-svelte/templates/skeleton/src/routes/+page.svelte +++ /dev/null @@ -1,2 +0,0 @@ -

Welcome to SvelteKit

-

Visit kit.svelte.dev to read the documentation

diff --git a/packages/create-svelte/templates/skeleton/svelte.config.js b/packages/create-svelte/templates/skeleton/svelte.config.js deleted file mode 100644 index 7b52576cd6c6..000000000000 --- a/packages/create-svelte/templates/skeleton/svelte.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import adapter from '@sveltejs/adapter-auto'; - -// This config is ignored and replaced with one of the configs in the shared folder when a project is created. - -/** @type {import('@sveltejs/kit').Config} */ -const config = { - kit: { - adapter: adapter() - } -}; - -export default config; diff --git a/packages/create-svelte/templates/skeleton/vite.config.js b/packages/create-svelte/templates/skeleton/vite.config.js deleted file mode 100644 index bbf8c7da43f0..000000000000 --- a/packages/create-svelte/templates/skeleton/vite.config.js +++ /dev/null @@ -1,6 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - plugins: [sveltekit()] -}); diff --git a/packages/create-svelte/templates/skeletonlib/.gitignore b/packages/create-svelte/templates/skeletonlib/.gitignore deleted file mode 100644 index ac7211b4033c..000000000000 --- a/packages/create-svelte/templates/skeletonlib/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -.DS_Store -node_modules -/build -/dist -/.svelte-kit -/package -.env -.env.* -!.env.example -vite.config.js.timestamp-* -vite.config.ts.timestamp-* diff --git a/packages/create-svelte/templates/skeletonlib/.ignore b/packages/create-svelte/templates/skeletonlib/.ignore deleted file mode 100644 index 449d6440cc69..000000000000 --- a/packages/create-svelte/templates/skeletonlib/.ignore +++ /dev/null @@ -1,3 +0,0 @@ -package.json -.meta.json -.turbo \ No newline at end of file diff --git a/packages/create-svelte/templates/skeletonlib/.meta.json b/packages/create-svelte/templates/skeletonlib/.meta.json deleted file mode 100644 index aa67986ab50e..000000000000 --- a/packages/create-svelte/templates/skeletonlib/.meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Library project", - "description": "Barebones scaffolding for your new Svelte library" -} diff --git a/packages/create-svelte/templates/skeletonlib/package.template.json b/packages/create-svelte/templates/skeletonlib/package.template.json deleted file mode 100644 index ed466f930e43..000000000000 --- a/packages/create-svelte/templates/skeletonlib/package.template.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "~TODO~", - "version": "0.0.1", - "scripts": { - "dev": "vite dev", - "build": "vite build && npm run package", - "preview": "vite preview", - "package": "svelte-kit sync && svelte-package && publint", - "prepublishOnly": "npm run package" - }, - "exports": { - ".": { - "types": "./dist/index.d.ts", - "svelte": "./dist/index.js" - } - }, - "files": ["dist", "!dist/**/*.test.*", "!dist/**/*.spec.*"], - "peerDependencies": { - "svelte": "^4.0.0" - }, - "devDependencies": { - "@sveltejs/adapter-auto": "^2.0.0", - "@sveltejs/kit": "^1.20.4", - "@sveltejs/package": "^2.0.0", - "publint": "^0.1.9", - "svelte": "^4.0.5", - "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^4.4.2" - }, - "svelte": "./dist/index.js", - "types": "./dist/index.d.ts", - "type": "module" -} diff --git a/packages/create-svelte/templates/skeletonlib/src/app.d.ts b/packages/create-svelte/templates/skeletonlib/src/app.d.ts deleted file mode 100644 index f59b884c51ed..000000000000 --- a/packages/create-svelte/templates/skeletonlib/src/app.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -// See https://kit.svelte.dev/docs/types#app -// for information about these interfaces -declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface Platform {} - } -} - -export {}; diff --git a/packages/create-svelte/templates/skeletonlib/src/app.html b/packages/create-svelte/templates/skeletonlib/src/app.html deleted file mode 100644 index d2fc6b061c6a..000000000000 --- a/packages/create-svelte/templates/skeletonlib/src/app.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - %sveltekit.head% - - -
%sveltekit.body%
- - diff --git a/packages/create-svelte/templates/skeletonlib/src/lib/index.js b/packages/create-svelte/templates/skeletonlib/src/lib/index.js deleted file mode 100644 index 47d3c46fb896..000000000000 --- a/packages/create-svelte/templates/skeletonlib/src/lib/index.js +++ /dev/null @@ -1 +0,0 @@ -// Reexport your entry components here diff --git a/packages/create-svelte/templates/skeletonlib/src/routes/+page.svelte b/packages/create-svelte/templates/skeletonlib/src/routes/+page.svelte deleted file mode 100644 index 0a45b69f099b..000000000000 --- a/packages/create-svelte/templates/skeletonlib/src/routes/+page.svelte +++ /dev/null @@ -1,3 +0,0 @@ -

Welcome to your library project

-

Create your package using @sveltejs/package and preview/showcase your work with SvelteKit

-

Visit kit.svelte.dev to read the documentation

diff --git a/packages/create-svelte/templates/skeletonlib/svelte.config.js b/packages/create-svelte/templates/skeletonlib/svelte.config.js deleted file mode 100644 index a894776b5d11..000000000000 --- a/packages/create-svelte/templates/skeletonlib/svelte.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import adapter from '@sveltejs/adapter-auto'; - -// This config is ignored and replaced with one of the configs in the shared folder when a project is created. - -/** @type {import('@sveltejs/package').Config} */ -const config = { - kit: { - adapter: adapter() - } -}; - -export default config; diff --git a/packages/create-svelte/templates/skeletonlib/vite.config.js b/packages/create-svelte/templates/skeletonlib/vite.config.js deleted file mode 100644 index bbf8c7da43f0..000000000000 --- a/packages/create-svelte/templates/skeletonlib/vite.config.js +++ /dev/null @@ -1,6 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - plugins: [sveltekit()] -}); diff --git a/packages/create-svelte/test/check.js b/packages/create-svelte/test/check.js deleted file mode 100644 index d910ee12e29a..000000000000 --- a/packages/create-svelte/test/check.js +++ /dev/null @@ -1,151 +0,0 @@ -import { exec, execSync } from 'node:child_process'; -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { promisify } from 'node:util'; -import glob from 'tiny-glob/sync.js'; -import { beforeAll, describe, test } from 'vitest'; -import { create } from '../index.js'; - -/** - * Resolve the given path relative to the current file - * @param {string} path - */ -const resolve_path = (path) => fileURLToPath(new URL(path, import.meta.url)); - -// use a directory outside of packages to ensure it isn't added to the pnpm workspace -const test_workspace_dir = resolve_path('../../../.test-tmp/create-svelte/'); - -const existing_workspace_overrides = JSON.parse( - fs.readFileSync(resolve_path('../../../package.json'), 'utf-8') -).pnpm?.overrides; - -const overrides = { ...existing_workspace_overrides }; - -for (const pkg_path of glob(resolve_path('../../../packages/*/package.json'))) { - const name = JSON.parse(fs.readFileSync(pkg_path, 'utf-8')).name; - // use `file:` protocol for opting into stricter resolve logic which catches more bugs, - // but only on CI because it doesn't work locally for some reason - const protocol = process.env.CI ? 'file:' : ''; - overrides[name] = `${protocol}${path.dirname(path.resolve(pkg_path))}`; -} - -try { - const kit_dir = resolve_path('../../../packages/kit'); - const ls_vite_result = execSync('pnpm ls --json vite', { cwd: kit_dir }); - const vite_version = JSON.parse(ls_vite_result.toString())[0].devDependencies.vite.version; - overrides.vite = vite_version; -} catch (e) { - console.error('failed to parse installed vite version from packages/kit'); - throw e; -} - -// prepare test pnpm workspace -fs.rmSync(test_workspace_dir, { recursive: true, force: true }); -fs.mkdirSync(test_workspace_dir, { recursive: true }); -const workspace = { - name: 'svelte-check-test-fake-pnpm-workspace', - private: true, - version: '0.0.0', - pnpm: { overrides }, - devDependencies: overrides -}; - -fs.writeFileSync( - path.join(test_workspace_dir, 'package.json'), - JSON.stringify(workspace, null, '\t') -); - -fs.writeFileSync(path.join(test_workspace_dir, 'pnpm-workspace.yaml'), 'packages:\n - ./*\n'); - -const exec_async = promisify(exec); - -beforeAll(async () => { - await exec_async('pnpm install --no-frozen-lockfile', { - cwd: test_workspace_dir - }); -}, 60000); - -/** @param {any} pkg */ -function patch_package_json(pkg) { - Object.entries(overrides).forEach(([key, value]) => { - if (pkg.devDependencies?.[key]) { - pkg.devDependencies[key] = value; - } - - if (pkg.dependencies?.[key]) { - pkg.dependencies[key] = value; - } - - if (!pkg.pnpm) { - pkg.pnpm = {}; - } - - if (!pkg.pnpm.overrides) { - pkg.pnpm.overrides = {}; - } - - pkg.pnpm.overrides = { ...pkg.pnpm.overrides, ...overrides }; - }); - pkg.private = true; -} - -/** - * Tests in different templates can be run concurrently for a nice speedup locally, but tests within a template must be run sequentially. - * It'd be better to group tests by template, but vitest doesn't support that yet. - * @type {Map import('node:child_process').PromiseWithChild][]>} - */ -const script_test_map = new Map(); - -const templates = /** @type {Array<'default' | 'skeleton' | 'skeletonlib'>} */ ( - fs.readdirSync('templates') -); - -for (const template of templates) { - if (template[0] === '.') continue; - - for (const types of /** @type {const} */ (['checkjs', 'typescript'])) { - const cwd = path.join(test_workspace_dir, `${template}-${types}`); - fs.rmSync(cwd, { recursive: true, force: true }); - - create(cwd, { - name: `create-svelte-test-${template}-${types}`, - template, - types, - prettier: true, - eslint: true, - playwright: false, - vitest: false - }); - - const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8')); - patch_package_json(pkg); - - fs.writeFileSync(path.join(cwd, 'package.json'), JSON.stringify(pkg, null, '\t') + '\n'); - - // run provided scripts that are non-blocking. All of them should exit with 0 - // package script requires lib dir - // TODO: lint should run before format - const scripts_to_test = ['format', 'lint', 'check', 'build', 'package'].filter( - (s) => s in pkg.scripts - ); - - for (const script of scripts_to_test) { - const tests = script_test_map.get(script) ?? []; - tests.push([`${template}-${types}`, () => exec_async(`pnpm ${script}`, { cwd })]); - script_test_map.set(script, tests); - } - } -} - -for (const [script, tests] of script_test_map) { - describe.concurrent( - script, - () => { - for (const [name, task] of tests) { - test(name, task); - } - }, - { timeout: 60000 } - ); -} diff --git a/packages/create-svelte/tsconfig.json b/packages/create-svelte/tsconfig.json deleted file mode 100644 index a5d5c18dc879..000000000000 --- a/packages/create-svelte/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "checkJs": true, - "noEmit": true, - "target": "esnext", - "module": "esnext", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "strict": true, - "skipLibCheck": true - }, - "include": ["./scripts/**/*", "./test/*.js", "./*.js"] -} diff --git a/packages/create-svelte/types/index.d.ts b/packages/create-svelte/types/index.d.ts deleted file mode 100644 index ef78af4797b1..000000000000 --- a/packages/create-svelte/types/index.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Options } from './internal'; - -/** - * Create a new SvelteKit project. - * @param {string} cwd - Path to the directory to create - * @param {import('./internal').Options} options - */ -export function create(cwd: string, options: Options): Promise; diff --git a/packages/create-svelte/types/internal.d.ts b/packages/create-svelte/types/internal.d.ts deleted file mode 100644 index 612d1cd3ceba..000000000000 --- a/packages/create-svelte/types/internal.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -export type Options = { - name: string; - template: 'default' | 'skeleton' | 'skeletonlib'; - types: 'typescript' | 'checkjs' | null; - prettier: boolean; - eslint: boolean; - playwright: boolean; - vitest: boolean; -}; - -export type File = { - name: string; - contents: string; -}; - -export type Condition = - | 'eslint' - | 'prettier' - | 'typescript' - | 'checkjs' - | 'playwright' - | 'vitest' - | 'skeleton' - | 'default' - | 'skeletonlib'; - -export type Common = { - files: Array<{ - name: string; - include: Condition[]; - exclude: Condition[]; - contents: string; - }>; -}; diff --git a/packages/create-svelte/utils.js b/packages/create-svelte/utils.js deleted file mode 100644 index d2760749f6ae..000000000000 --- a/packages/create-svelte/utils.js +++ /dev/null @@ -1,70 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -/** @param {string} dir */ -export function mkdirp(dir) { - try { - fs.mkdirSync(dir, { recursive: true }); - } catch (e) { - if (/** @type {any} */ (e).code === 'EEXIST') return; - throw e; - } -} - -/** @param {string} path */ -export function rimraf(path) { - (fs.rmSync || fs.rmdirSync)(path, { recursive: true, force: true }); -} - -/** - * @template T - * @param {T} x - */ -function identity(x) { - return x; -} - -/** - * @param {string} from - * @param {string} to - * @param {(basename: string) => string} rename - */ -export function copy(from, to, rename = identity) { - if (!fs.existsSync(from)) return; - - const stats = fs.statSync(from); - - if (stats.isDirectory()) { - fs.readdirSync(from).forEach((file) => { - copy(path.join(from, file), path.join(to, rename(file))); - }); - } else { - mkdirp(path.dirname(to)); - fs.copyFileSync(from, to); - } -} - -/** @param {string} path */ -export function dist(path) { - return fileURLToPath(new URL(`./dist/${path}`, import.meta.url).href); -} - -/** @type {string} */ -export const package_manager = get_package_manager() || 'npm'; - -/** - * Supports npm, pnpm, Yarn, cnpm, bun and any other package manager that sets the - * npm_config_user_agent env variable. - * Thanks to https://github.com/zkochan/packages/tree/main/which-pm-runs for this code! - */ -function get_package_manager() { - if (!process.env.npm_config_user_agent) { - return undefined; - } - const user_agent = process.env.npm_config_user_agent; - const pm_spec = user_agent.split(' ')[0]; - const separator_pos = pm_spec.lastIndexOf('/'); - const name = pm_spec.substring(0, separator_pos); - return name === 'npminstall' ? 'cnpm' : name; -} diff --git a/packages/create-svelte/vitest.config.js b/packages/create-svelte/vitest.config.js deleted file mode 100644 index 3f5451417a27..000000000000 --- a/packages/create-svelte/vitest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { dir: './test', include: ['*.js'] } -}); diff --git a/packages/enhanced-img/.prettierignore b/packages/enhanced-img/.prettierignore new file mode 100644 index 000000000000..57a5af7f662f --- /dev/null +++ b/packages/enhanced-img/.prettierignore @@ -0,0 +1 @@ +test/Output.svelte diff --git a/packages/enhanced-img/CHANGELOG.md b/packages/enhanced-img/CHANGELOG.md new file mode 100644 index 000000000000..317bb4bec48d --- /dev/null +++ b/packages/enhanced-img/CHANGELOG.md @@ -0,0 +1,269 @@ +# @sveltejs/enhanced-img + +## 0.9.2 +### Patch Changes + + +- chore: remove duplicate caching layer ([#14988](https://github.com/sveltejs/kit/pull/14988)) + +## 0.9.1 +### Patch Changes + + +- fix: update vite-imagetools for caching fixes to avoid crashes ([#14976](https://github.com/sveltejs/kit/pull/14976)) + +## 0.9.0 +### Minor Changes + + +- chore(deps): update dependency vite-imagetools to v9 ([#14608](https://github.com/sveltejs/kit/pull/14608)) + +## 0.8.5 +### Patch Changes + + +- fix: warn rather than crash when non-enhanced image dynamically passed to `enhanced:img` ([#14845](https://github.com/sveltejs/kit/pull/14845)) + +## 0.8.4 +### Patch Changes + + +- fix: add script block only when there are imports to add ([#14604](https://github.com/sveltejs/kit/pull/14604)) + +## 0.8.3 +### Patch Changes + + +- chore: update "homepage" field in package.json ([#14579](https://github.com/sveltejs/kit/pull/14579)) + +## 0.8.2 +### Patch Changes + + +- chore: upgrade sharp to 0.34.4 ([#14478](https://github.com/sveltejs/kit/pull/14478)) + +## 0.8.1 +### Patch Changes + + +- fix(perf): correctly apply id filter from vite-plugin-svelte ([#14248](https://github.com/sveltejs/kit/pull/14248)) + +## 0.8.0 +### Minor Changes + + +- breaking: upgrade to vite-imagetools 8 to auto-rotate images ([#14172](https://github.com/sveltejs/kit/pull/14172)) + +## 0.7.1 +### Patch Changes + + +- chore: add `.git` to the end of `package.json` repository url ([#14134](https://github.com/sveltejs/kit/pull/14134)) + +## 0.7.0 +### Minor Changes + + +- breaking: use new filters and enhancements from `vite-plugin-svelte`. Requires `vite >= 6.3` and `vite-plugin-svelte >= 6.0`. ([#13967](https://github.com/sveltejs/kit/pull/13967)) + +## 0.6.1 +### Patch Changes + + +- feat: allow `vite-plugin-svelte` 6 peer dependency ([#13921](https://github.com/sveltejs/kit/pull/13921)) + +## 0.6.0 +### Minor Changes + + +- feat: add validation to ensure plugin occurs in correct order ([`1c3f36dc10265fb79c64643c3d7a91469d34e697`](https://github.com/sveltejs/kit/commit/1c3f36dc10265fb79c64643c3d7a91469d34e697)) + +## 0.5.1 +### Patch Changes + + +- chore(deps): upgrade sharp to 0.34.1 ([#13611](https://github.com/sveltejs/kit/pull/13611)) + +## 0.5.0 +### Minor Changes + + +- feat: add support for targeting `enhanced\:img` in CSS ([#13617](https://github.com/sveltejs/kit/pull/13617)) + +## 0.4.4 +### Patch Changes + + +- fix: handle duplicate SVG images ([`8073d7c7dcc391d406c658729221a18ac6f18102`](https://github.com/sveltejs/kit/commit/8073d7c7dcc391d406c658729221a18ac6f18102)) + +## 0.4.3 +### Patch Changes + + +- fix: properly handle multiple SVGs ([#13127](https://github.com/sveltejs/kit/pull/13127)) + +## 0.4.2 +### Patch Changes + + +- feat: set intrinsic width and height for SVGs ([#13126](https://github.com/sveltejs/kit/pull/13126)) + + +- perf: directly inline values since Svelte no longer inlines variables into template ([#13035](https://github.com/sveltejs/kit/pull/13035)) + +## 0.4.1 +### Patch Changes + + +- fix: correctly handle `` elements nested in other DOM elements ([#12945](https://github.com/sveltejs/kit/pull/12945)) + +## 0.4.0 +### Minor Changes + + +- breaking: require Svelte 5 ([#12822](https://github.com/sveltejs/kit/pull/12822)) + +## 0.3.10 +### Patch Changes + + +- docs: update URLs for new svelte.dev site ([#12857](https://github.com/sveltejs/kit/pull/12857)) + +## 0.3.9 +### Patch Changes + + +- chore: upgrade svelte-parse-markup ([#12793](https://github.com/sveltejs/kit/pull/12793)) + +## 0.3.8 +### Patch Changes + + +- fix: import `node:process` instead of using globals ([#12641](https://github.com/sveltejs/kit/pull/12641)) + +## 0.3.7 +### Patch Changes + + +- fix: avoid duplicating width/height attributes ([#12673](https://github.com/sveltejs/kit/pull/12673)) + +## 0.3.6 +### Patch Changes + + +- fix: address Svelte 5 warning ([`ec04dae73702c99652e4972d2b7363f2c11ccf5a`](https://github.com/sveltejs/kit/commit/ec04dae73702c99652e4972d2b7363f2c11ccf5a)) + +## 0.3.5 +### Patch Changes + + +- perf: hoist vite asset declarations to module block ([#12627](https://github.com/sveltejs/kit/pull/12627)) + +## 0.3.4 +### Patch Changes + + +- perf: apply performance optimization to dev srcset ([#12621](https://github.com/sveltejs/kit/pull/12621)) + +## 0.3.3 +### Patch Changes + + +- chore: configure provenance in a simpler manner ([#12570](https://github.com/sveltejs/kit/pull/12570)) + +## 0.3.2 +### Patch Changes + + +- chore: package provenance ([#12567](https://github.com/sveltejs/kit/pull/12567)) + + +- fix: ensure src attribute is properly formed ([`65931f276ac2102032e3032c864a472eee19b7bb`](https://github.com/sveltejs/kit/commit/65931f276ac2102032e3032c864a472eee19b7bb)) + +## 0.3.1 +### Patch Changes + + +- fix: make `*?enhanced` imports available in the ambient context ([#12363](https://github.com/sveltejs/kit/pull/12363)) + +## 0.3.0 + +### Minor Changes + +- breaking: return plugin synchronously from `enhancedImages()` ([#12297](https://github.com/sveltejs/kit/pull/12297)) + +### Patch Changes + +- chore: add keywords for discovery in npm search ([#12330](https://github.com/sveltejs/kit/pull/12330)) + +## 0.2.1 + +### Patch Changes + +- fix: use correct type for `*?enhanced` imports ([#12224](https://github.com/sveltejs/kit/pull/12224)) + +## 0.2.0 + +### Minor Changes + +- feat: upgrade vite-imagetools to v7. caches build output by default ([#12055](https://github.com/sveltejs/kit/pull/12055)) + +## 0.1.9 + +### Patch Changes + +- fix: support shorthand attribute syntax ([#11884](https://github.com/sveltejs/kit/pull/11884)) + +## 0.1.8 + +### Patch Changes + +- fix: correct images cache key to avoid collisions when images have same name ([#11602](https://github.com/sveltejs/kit/pull/11602)) + +## 0.1.7 + +### Patch Changes + +- chore: update primary branch from master to main ([`47779436c5f6c4d50011d0ef8b2709a07c0fec5d`](https://github.com/sveltejs/kit/commit/47779436c5f6c4d50011d0ef8b2709a07c0fec5d)) + +- fix: throw an error if image cannot be resolved ([#11346](https://github.com/sveltejs/kit/pull/11346)) + +- fix: attempt to address issues accessing images on filesystem ([#11403](https://github.com/sveltejs/kit/pull/11403)) + +## 0.1.6 + +### Patch Changes + +- chore: upgrade vite-imagetools ([#11122](https://github.com/sveltejs/kit/pull/11122)) + +## 0.1.5 + +### Patch Changes + +- fix: correctly generate client-side code ([#11059](https://github.com/sveltejs/kit/pull/11059)) + +## 0.1.4 + +### Patch Changes + +- fix: avoid creating conflicting import statements ([#11047](https://github.com/sveltejs/kit/pull/11047)) + +## 0.1.3 + +### Patch Changes + +- fix: only resolve images if optimizable ([#11041](https://github.com/sveltejs/kit/pull/11041)) + +## 0.1.2 + +### Patch Changes + +- fix: refresh in dev mode when an image changes ([#11033](https://github.com/sveltejs/kit/pull/11033)) + +- fix: auto-import of svg images ([`4426daebe`](https://github.com/sveltejs/kit/commit/4426daebe1d345f60554225e3f12ea932b0110e4)) + +## 0.1.1 + +### Patch Changes + +- feat: add experimental `@sveltejs/enhanced-img` package ([#10788](https://github.com/sveltejs/kit/pull/10788)) diff --git a/packages/enhanced-img/README.md b/packages/enhanced-img/README.md new file mode 100644 index 000000000000..ac87975c1f95 --- /dev/null +++ b/packages/enhanced-img/README.md @@ -0,0 +1,17 @@ +# `@sveltejs/enhanced-img` + +A Vite plugin which runs a Svelte preprocessor to locate images and then transform them at build-time. + +**WARNING**: This package is experimental. It uses pre-1.0 versioning and may introduce breaking changes with every minor version release. + +## Docs + +[Docs](https://svelte.dev/docs/kit/images) + +## Changelog + +[The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/main/packages/enhanced-img/CHANGELOG.md). + +## Acknowledgements + +We'd like to thank the author of `svelte-preprocess-import-assets`, which this code is partially based off of. We'd also like to thank the authors of `vite-imagetools` which is used in `@sveltejs/enhanced-img`. diff --git a/packages/enhanced-img/package.json b/packages/enhanced-img/package.json new file mode 100644 index 000000000000..711a0dd537d6 --- /dev/null +++ b/packages/enhanced-img/package.json @@ -0,0 +1,62 @@ +{ + "name": "@sveltejs/enhanced-img", + "version": "0.9.2", + "description": "Image optimization for your Svelte apps", + "repository": { + "type": "git", + "url": "git+https://github.com/sveltejs/kit.git", + "directory": "packages/enhanced-img" + }, + "keywords": [ + "component", + "enhanced", + "image", + "preprocessor", + "plugin", + "svelte", + "sveltekit", + "vite" + ], + "license": "MIT", + "homepage": "https://svelte.dev/docs/kit/images#sveltejs-enhanced-img", + "type": "module", + "scripts": { + "lint": "prettier --check .", + "check": "tsc", + "format": "prettier --write .", + "test": "pnpm test:unit && pnpm test:integration", + "test:unit": "vitest run", + "test:integration": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test" + }, + "files": [ + "src", + "types" + ], + "exports": { + "types": "./types/index.d.ts", + "import": "./src/index.js" + }, + "types": "types/index.d.ts", + "dependencies": { + "magic-string": "^0.30.5", + "sharp": "^0.34.1", + "svelte-parse-markup": "^0.1.5", + "vite-imagetools": "^9.0.2", + "zimmerframe": "^1.1.2" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "catalog:", + "@types/estree": "catalog:", + "@types/node": "catalog:", + "rollup": "^4.27.4", + "svelte": "catalog:", + "typescript": "^5.6.3", + "vite": "catalog:", + "vitest": "catalog:" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.0.0", + "svelte": "^5.0.0", + "vite": "^6.3.0 || >=7.0.0" + } +} diff --git a/packages/enhanced-img/src/index.js b/packages/enhanced-img/src/index.js new file mode 100644 index 000000000000..485a8a3f1015 --- /dev/null +++ b/packages/enhanced-img/src/index.js @@ -0,0 +1,94 @@ +import path from 'node:path'; +import process from 'node:process'; +import { imagetools } from 'vite-imagetools'; +import { image_plugin } from './vite-plugin.js'; + +/** + * @returns {import('vite').Plugin[]} + */ +export function enhancedImages() { + const imagetools_instance = imagetools_plugin(); + return !process.versions.webcontainer + ? [image_plugin(imagetools_instance), imagetools_instance] + : []; +} + +/** @type {Record} */ +const fallback = { + '.avif': 'png', + '.gif': 'gif', + '.heif': 'jpg', + '.jpeg': 'jpg', + '.jpg': 'jpg', + '.png': 'png', + '.tiff': 'jpg', + '.webp': 'png' +}; + +function imagetools_plugin() { + /** @type {Partial} */ + const imagetools_opts = { + defaultDirectives: async ({ pathname, searchParams: qs }, metadata) => { + if (!qs.has('enhanced')) return new URLSearchParams(); + + const img_width = qs.get('imgWidth'); + const width = img_width ? parseInt(img_width) : (await metadata()).width; + if (!width) { + console.warn(`Could not determine width of image ${pathname}`); + return new URLSearchParams(); + } + + const { widths, kind } = get_widths(width, qs.get('imgSizes')); + return new URLSearchParams({ + as: 'picture', + format: `avif;webp;${fallback[path.extname(pathname)] ?? 'png'}`, + w: widths.join(';'), + ...(kind === 'x' && !qs.has('w') && { basePixels: widths[0].toString() }) + }); + }, + namedExports: false + }; + + // TODO: should we make formats or sizes configurable besides just letting people override defaultDirectives? + // TODO: generate img rather than picture if only a single format is provided + // by resolving the directives for the URL in the preprocessor + return imagetools(imagetools_opts); +} + +/** + * @param {number} width + * @param {string | null} sizes + * @returns {{ widths: number[]; kind: 'w' | 'x' }} + */ +function get_widths(width, sizes) { + // We don't really know what the user wants here. But if they have an image that's really big + // then we can probably assume they're always displaying it full viewport/breakpoint. + // If the user is displaying a responsive image then the size usually doesn't change that much + // Instead, the number of columns in the design may reduce and the image may take a greater + // fraction of the screen. + // Assume if they're bothering to specify sizes that it's going to take most of the screen + // as that's the case where an image may be rendered at very different sizes. Otherwise, it's + // probably a responsive image and a single size is okay (two when accounting for HiDPI). + if (sizes) { + // Use common device sizes. Doesn't hurt to include larger sizes as the user will rarely + // provide an image that large. + // https://screensiz.es/ + // https://gs.statcounter.com/screen-resolution-stats (note: logical. we want physical) + // Include 1080 because lighthouse uses a moto g4 with 360 logical pixels and 3x pixel ratio. + const widths = [540, 768, 1080, 1366, 1536, 1920, 2560, 3000, 4096, 5120]; + widths.push(width); + return { widths, kind: 'w' }; + } + + // Don't need more than 2x resolution. Note that due to this optimization, pixel density + // descriptors will often end up being cheaper as many mobile devices have pixel density ratios + // near 3 which would cause larger images to be chosen on mobile when using sizes. + + // Most OLED screens that say they are 3x resolution, are actually 3x in the green color, but + // only 1.5x in the red and blue colors. Showing a 3x resolution image in the app vs a 2x + // resolution image will be visually the same, though the 3x image takes significantly more + // data. Even true 3x resolution screens are wasteful as the human eye cannot see that level of + // detail without something like a magnifying glass. + // https://blog.twitter.com/engineering/en_us/topics/infrastructure/2019/capping-image-fidelity-on-ultra-high-resolution-devices.html + return { widths: [Math.round(width / 2), width], kind: 'x' }; +} diff --git a/packages/enhanced-img/src/vite-plugin.js b/packages/enhanced-img/src/vite-plugin.js new file mode 100644 index 000000000000..3812242225f8 --- /dev/null +++ b/packages/enhanced-img/src/vite-plugin.js @@ -0,0 +1,393 @@ +/** @import { AST } from 'svelte/compiler' */ +import { existsSync } from 'node:fs'; +import path from 'node:path'; +import MagicString from 'magic-string'; +import sharp from 'sharp'; +import { parse } from 'svelte-parse-markup'; +import { walk } from 'zimmerframe'; + +// TODO: expose this in vite-imagetools rather than duplicating it +const OPTIMIZABLE = /^[^?]+\.(avif|heif|gif|jpeg|jpg|png|tiff|webp)(\?.*)?$/; + +/** + * Creates the Svelte image plugin. + * @param {import('vite').Plugin} imagetools_plugin + * @returns {import('vite').Plugin} + */ +export function image_plugin(imagetools_plugin) { + /** @type {import('vite').ResolvedConfig} */ + let vite_config; + + const name = 'vite-plugin-enhanced-img-markup'; + + /** @type {import('vite').Plugin} */ + const plugin = { + name, + configResolved(config) { + vite_config = config; + const svelteConfigPlugin = config.plugins.find((p) => p.name === 'vite-plugin-svelte:config'); + if (!svelteConfigPlugin) { + throw new Error( + '@sveltejs/enhanced-img requires @sveltejs/vite-plugin-svelte 6 or higher to be installed' + ); + } + // @ts-expect-error plugin.transform is defined below before configResolved is called + plugin.transform.filter.id = svelteConfigPlugin.api.idFilter.id; + }, + transform: { + order: 'pre', // puts it before vite-plugin-svelte:compile + filter: { + code: / __IMPORTED_ASSET_0__ + * @type {Map} + */ + const imports = new Map(); + + /** + * @param {import('svelte/compiler').AST.RegularElement} node + * @param {AST.Text | AST.ExpressionTag} src_attribute + * @returns {Promise} + */ + async function update_element(node, src_attribute) { + if (src_attribute.type === 'ExpressionTag') { + const start = + 'end' in src_attribute.expression + ? src_attribute.expression.end + : src_attribute.expression.range?.[0]; + const end = + 'start' in src_attribute.expression + ? src_attribute.expression.start + : src_attribute.expression.range?.[1]; + + if (typeof start !== 'number' || typeof end !== 'number') { + throw new Error('ExpressionTag has no range'); + } + const src_var_name = content.substring(start, end).trim(); + + s.update(node.start, node.end, dynamic_img_to_picture(content, node, src_var_name)); + return; + } + + const original_url = src_attribute.raw.trim(); + let url = original_url; + + if (OPTIMIZABLE.test(url)) { + const sizes = get_attr_value(node, 'sizes'); + const width = get_attr_value(node, 'width'); + url += url.includes('?') ? '&' : '?'; + if (sizes && 'raw' in sizes) { + url += 'imgSizes=' + encodeURIComponent(sizes.raw) + '&'; + } + if (width && 'raw' in width) { + url += 'imgWidth=' + encodeURIComponent(width.raw) + '&'; + } + url += 'enhanced'; + } + + // resolves the import so that we can build the entire picture template string and don't + // need any logic blocks + const resolved_id = (await plugin_context.resolve(url, filename))?.id; + if (!resolved_id) { + const query_index = url.indexOf('?'); + const file_path = query_index >= 0 ? url.substring(0, query_index) : url; + if (existsSync(path.resolve(vite_config.publicDir, file_path))) { + throw new Error( + `Could not locate ${file_path}. Please move it to be located relative to the page in the routes directory or reference it beginning with /static/. See https://vitejs.dev/guide/assets for more details on referencing assets.` + ); + } + throw new Error( + `Could not locate ${file_path}. See https://vitejs.dev/guide/assets for more details on referencing assets.` + ); + } + + if (OPTIMIZABLE.test(url)) { + const image = await process_id(resolved_id, plugin_context, imagetools_plugin); + s.update(node.start, node.end, img_to_picture(content, node, image)); + } else { + const metadata = await sharp(resolved_id).metadata(); + // this must come after the await so that we don't hand off processing between getting + // the imports.size and incrementing the imports.size + const name = imports.get(original_url) || '__IMPORTED_ASSET_' + imports.size + '__'; + if (!metadata.width || !metadata.height) { + console.warn(`Could not determine intrinsic dimensions for ${resolved_id}`); + } + const new_markup = ``; + s.update(node.start, node.end, new_markup); + imports.set(original_url, name); + } + } + + /** + * @type {Array>} + */ + const pending_ast_updates = []; + + walk(/** @type {import('svelte/compiler').AST.TemplateNode} */ (ast), null, { + RegularElement(node, { next }) { + if ('name' in node && node.name === 'enhanced:img') { + // Compare node tag match + const src = get_attr_value(node, 'src'); + + if (!src || typeof src === 'boolean') return; + + pending_ast_updates.push(update_element(node, src)); + + return; + } + + next(); + } + }); + + await Promise.all(pending_ast_updates); + + // add imports + if (imports.size) { + let text = ''; + for (const [path, import_name] of imports.entries()) { + text += `\timport ${import_name} from "${path}";\n`; + } + + if (ast.instance) { + // @ts-ignore + s.appendLeft(ast.instance.content.start, text); + } else { + s.prepend(`\n`); + } + } + + if (ast.css) { + const css = content.substring(ast.css.start, ast.css.end); + const modified = css.replaceAll('enhanced\\:img', 'img'); + if (modified !== css) { + s.update(ast.css.start, ast.css.end, modified); + } + } + + return { + code: s.toString(), + map: s.generateMap({ hires: 'boundary' }) + }; + } + } + }; + return plugin; +} + +/** + * @param {string} resolved_id + * @param {import('vite').Rollup.PluginContext} plugin_context + * @param {import('vite').Plugin} imagetools_plugin + * @returns {Promise} + */ +async function process_id(resolved_id, plugin_context, imagetools_plugin) { + if (!imagetools_plugin.load) { + throw new Error('Invalid instance of vite-imagetools. Could not find load method.'); + } + const hook = imagetools_plugin.load; + const handler = typeof hook === 'object' ? hook.handler : hook; + const module_info = await handler.call(plugin_context, resolved_id); + if (!module_info) { + throw new Error(`Could not load ${resolved_id}`); + } + const code = typeof module_info === 'string' ? module_info : module_info.code; + return parse_object(code.replace('export default', '').replace(/;$/, '').trim()); +} + +/** + * @param {string} str + */ +export function parse_object(str) { + const updated = str + .replaceAll(/{(\n\s*)?/gm, '{"') + .replaceAll(':', '":') + .replaceAll(/,(\n\s*)?([^ ])/g, ',"$2'); + try { + return JSON.parse(updated); + } catch { + throw new Error(`Failed parsing string to object: ${str}`); + } +} + +/** + * @param {import('../types/internal.js').TemplateNode} node + * @param {string} attr + * @returns {AST.Text | AST.ExpressionTag | undefined} + */ +function get_attr_value(node, attr) { + if (!('type' in node) || !('attributes' in node)) return; + const attribute = node.attributes.find( + /** @param {any} v */ (v) => v.type === 'Attribute' && v.name === attr + ); + + if (!attribute || !('value' in attribute) || typeof attribute.value === 'boolean') return; + + // Check if value is an array and has at least one element + if (Array.isArray(attribute.value)) { + if (attribute.value.length > 0) return attribute.value[0]; + return; + } + + // If it's not an array or is empty, return the value as is + return attribute.value; +} + +/** + * @param {string} content + * @param {import('../types/internal.js').Attribute[]} attributes + * @param {{ + * src: string, + * width?: string | number, + * height?: string | number + * }} details + */ +function serialize_img_attributes(content, attributes, details) { + const attribute_strings = attributes.map((attribute) => { + if ('name' in attribute && attribute.name === 'src') { + return `src=${details.src}`; + } + return content.substring(attribute.start, attribute.end); + }); + + /** @type {number | undefined} */ + let user_width; + /** @type {number | undefined} */ + let user_height; + for (const attribute of attributes) { + if ('name' in attribute && 'value' in attribute) { + const value = Array.isArray(attribute.value) ? attribute.value[0] : attribute.value; + if (typeof value === 'object' && 'raw' in value) { + if (attribute.name === 'width') user_width = parseInt(value.raw); + if (attribute.name === 'height') user_height = parseInt(value.raw); + } + } + } + if (details.width && details.height) { + if (!user_width && !user_height) { + attribute_strings.push(`width=${details.width}`); + attribute_strings.push(`height=${details.height}`); + } else if (!user_width && user_height) { + attribute_strings.push( + `width=${Math.round( + (stringToNumber(details.width) * user_height) / stringToNumber(details.height) + )}` + ); + } else if (!user_height && user_width) { + attribute_strings.push( + `height=${Math.round( + (stringToNumber(details.height) * user_width) / stringToNumber(details.width) + )}` + ); + } + } + + return attribute_strings.join(' '); +} + +/** + * @param {string|number} param + */ +function stringToNumber(param) { + return typeof param === 'string' ? parseInt(param) : param; +} + +/** + * @param {string} content + * @param {import('svelte/compiler').AST.RegularElement} node + * @param {import('vite-imagetools').Picture} image + */ +function img_to_picture(content, node, image) { + /** @type {import('../types/internal.js').Attribute[]} */ + const attributes = node.attributes; + const index = attributes.findIndex( + (attribute) => 'name' in attribute && attribute.name === 'sizes' + ); + let sizes_string = ''; + if (index >= 0) { + sizes_string = ' ' + content.substring(attributes[index].start, attributes[index].end); + attributes.splice(index, 1); + } + + let res = ''; + + for (const [format, srcset] of Object.entries(image.sources)) { + res += ``; + } + + res += ``; + + return (res += ''); +} + +/** + * @param {string} src + */ +function to_value(src) { + // __VITE_ASSET__ needs to be contained in double quotes to work with Vite asset plugin + return src.startsWith('__VITE_ASSET__') ? `{"${src}"}` : `"${src}"`; +} + +/** + * For images like `` + * @param {string} content + * @param {import('svelte/compiler').AST.RegularElement} node + * @param {string} src_var_name + */ +function dynamic_img_to_picture(content, node, src_var_name) { + const attributes = node.attributes; + /** + * @param attribute_name {string} + */ + function index(attribute_name) { + return attributes.findIndex( + (attribute) => 'name' in attribute && attribute.name === attribute_name + ); + } + const size_index = index('sizes'); + const width_index = index('width'); + const height_index = index('height'); + let sizes_string = ''; + if (size_index >= 0) { + sizes_string = + ' ' + content.substring(attributes[size_index].start, attributes[size_index].end); + attributes.splice(size_index, 1); + } + + return `{#if typeof ${src_var_name} === 'string'} + {#if import.meta.DEV && ${!width_index && !height_index}} + {${src_var_name}} was not enhanced. Cannot determine dimensions. + {:else} + + {/if} +{:else} + + {#each Object.entries(${src_var_name}.sources) as [format, srcset]} + + {/each} + + +{/if}`; +} diff --git a/packages/enhanced-img/test/Input.svelte b/packages/enhanced-img/test/Input.svelte new file mode 100644 index 000000000000..35fd9c59901d --- /dev/null +++ b/packages/enhanced-img/test/Input.svelte @@ -0,0 +1,60 @@ + + +{foo} + +non-enhanced test + + + +
+ +
+ + + + + + + + + + + + (foo = 'clicked an image!')} + alt="event handler test" +/> + + + + + + + +{#each images as image} + +{/each} + +{#each images as _, i} + +{/each} + + + + + + diff --git a/packages/enhanced-img/test/Output.svelte b/packages/enhanced-img/test/Output.svelte new file mode 100644 index 000000000000..431adaf72d27 --- /dev/null +++ b/packages/enhanced-img/test/Output.svelte @@ -0,0 +1,96 @@ + + +{foo} + +non-enhanced test + +dev test + +
+ nested test +
+ +production test + +dimensions test + +directive test + +spread attributes test + +sizes test + + (foo = 'clicked an image!')} alt="event handler test" width=1440 height=1440 /> + +alias test + +absolute path test + +{#if typeof src === 'string'} + {#if + import.meta.DEV && false} + {src} was not enhanced. Cannot determine dimensions. + {:else} + attribute shorthand test + {/if} +{:else} + + {#each Object.entries(src.sources) as [format, srcset]} + + {/each} + attribute shorthand test + +{/if} + +{#each images as image} + {#if typeof image === 'string'} + {#if + import.meta.DEV && false} + {image} was not enhanced. Cannot determine dimensions. + {:else} + opt-in test + {/if} +{:else} + + {#each Object.entries(image.sources) as [format, srcset]} + + {/each} + opt-in test + +{/if} +{/each} + +{#each images as _, i} + {#if typeof get_image(i) === 'string'} + {#if + import.meta.DEV && false} + {get_image(i)} was not enhanced. Cannot determine dimensions. + {:else} + opt-in test + {/if} +{:else} + + {#each Object.entries(get_image(i).sources) as [format, srcset]} + + {/each} + opt-in test + +{/if} +{/each} + + + + + + diff --git a/packages/create-svelte/templates/default/.npmrc b/packages/enhanced-img/test/apps/basics/.npmrc similarity index 100% rename from packages/create-svelte/templates/default/.npmrc rename to packages/enhanced-img/test/apps/basics/.npmrc diff --git a/packages/enhanced-img/test/apps/basics/jsconfig.json b/packages/enhanced-img/test/apps/basics/jsconfig.json new file mode 100644 index 000000000000..e0733f11157e --- /dev/null +++ b/packages/enhanced-img/test/apps/basics/jsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/packages/enhanced-img/test/apps/basics/package.json b/packages/enhanced-img/test/apps/basics/package.json new file mode 100644 index 000000000000..7593534e8a7a --- /dev/null +++ b/packages/enhanced-img/test/apps/basics/package.json @@ -0,0 +1,19 @@ +{ + "name": "enhanced-img-basics", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "test": "playwright test" + }, + "devDependencies": { + "@sveltejs/enhanced-img": "workspace:^", + "@sveltejs/kit": "workspace:^", + "@sveltejs/vite-plugin-svelte": "catalog:", + "svelte": "catalog:", + "vite": "catalog:" + }, + "type": "module" +} diff --git a/packages/enhanced-img/test/apps/basics/playwright.config.js b/packages/enhanced-img/test/apps/basics/playwright.config.js new file mode 100644 index 000000000000..33d36b651014 --- /dev/null +++ b/packages/enhanced-img/test/apps/basics/playwright.config.js @@ -0,0 +1 @@ +export { config as default } from '../../utils.js'; diff --git a/packages/enhanced-img/test/apps/basics/src/app.html b/packages/enhanced-img/test/apps/basics/src/app.html new file mode 100644 index 000000000000..d533c5e31716 --- /dev/null +++ b/packages/enhanced-img/test/apps/basics/src/app.html @@ -0,0 +1,11 @@ + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/packages/enhanced-img/test/apps/basics/src/routes/+error.svelte b/packages/enhanced-img/test/apps/basics/src/routes/+error.svelte new file mode 100644 index 000000000000..fe313e063894 --- /dev/null +++ b/packages/enhanced-img/test/apps/basics/src/routes/+error.svelte @@ -0,0 +1,5 @@ + + +

{page.status}

diff --git a/packages/enhanced-img/test/apps/basics/src/routes/+layout.svelte b/packages/enhanced-img/test/apps/basics/src/routes/+layout.svelte new file mode 100644 index 000000000000..0dcd5f448604 --- /dev/null +++ b/packages/enhanced-img/test/apps/basics/src/routes/+layout.svelte @@ -0,0 +1,6 @@ + + +{@render children?.()} diff --git a/packages/enhanced-img/test/apps/basics/src/routes/+page.svelte b/packages/enhanced-img/test/apps/basics/src/routes/+page.svelte new file mode 100644 index 000000000000..c1b83b304a84 --- /dev/null +++ b/packages/enhanced-img/test/apps/basics/src/routes/+page.svelte @@ -0,0 +1,12 @@ + + + + + + + + + +