diff --git a/.eslintrc b/.eslintrc index 93ac8338aa..01f1e369c8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,10 +1,19 @@ { - "extends": [ - "@nuxtjs/eslint-config-typescript" - ], + "extends": ["eslint-config-unjs"], "rules": { - "import/no-named-as-default-member": "off", - "import/default": "off", - "no-console": "off" + "space-before-function-paren": 0, + "arrow-parens": 0, + "comma-dangle": 0, + "semi": 0, + "unicorn/prevent-abbreviations": 0, + "quotes": 0, + "keyword-spacing": 0, + "no-undef": 0, + "unicorn/catch-error-name": 0, + "unicorn/no-null": 0, + "unicorn/no-useless-undefined": 0, + "unicorn/no-await-expression-member": 0, + "unicorn/no-array-push-push": 0, + "unicorn/filename-case": 0 } } diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index bcad0c0fc8..883c5354f0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -35,4 +35,3 @@ Examples: - [ ] I have linked an issue or discussion. - [ ] I have updated the documentation accordingly. - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2317e2c463..8875ddc1ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - run: corepack enable - uses: actions/setup-node@v3 with: - node-version: '16' + node-version: "16" cache: pnpm - run: pnpm install - run: pnpm lint diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/README.md b/README.md index 2c63f161ed..9661d0b775 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ - [![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] [![npm-edge version][npm-edge-version-src]][npm-edge-version-href] [![npm-edge downloads][npm-edge-downloads-src]][npm-edge-downloads-href] +

⚗️ Nitro

@@ -12,18 +12,18 @@ Nitro provides a powerful toolchain and a runtime framework from the [UnJS](https://github.com/unjs) ecosystem to build and deploy **any JavaScript server, anywhere!** - ❯ 🐇 **Rapid development** experience with hot module replacement
- ❯ 😌 **Multi-provider** deployments with a single codebase and zero-configuration
- ❯ 💼 **Portable and compact** deployments without `node_modules` dependency
- ❯ 📁 **Directory structure** aware to register API routes and more with zero configuration
- ❯ 🤏 **Minimal Design** to fit into any solution with minimum overhead
- ❯ 🚀 **Code-splitting** and async chunk loading for fast server startup time
- ❯ 👕 **TypeScript** fully supported
- ❯ 💾 **Multi-driver storage** and caching layer
- ❯ 💰 **Route caching** and static **pre-rendering** with built-in crawler
- ❯ 🐱 **Hackable** to extend almost any part of nitro using options
- ❯ ✨ **Auto imports** for lazy folks and a tidy minimal codebase
- ❯ 🏛️ **Best-effort compatibility** for using legacy npm packages and mocking Node.js modules
+❯ 🐇 **Rapid development** experience with hot module replacement
+❯ 😌 **Multi-provider** deployments with a single codebase and zero-configuration
+❯ 💼 **Portable and compact** deployments without `node_modules` dependency
+❯ 📁 **Directory structure** aware to register API routes and more with zero configuration
+❯ 🤏 **Minimal Design** to fit into any solution with minimum overhead
+❯ 🚀 **Code-splitting** and async chunk loading for fast server startup time
+❯ 👕 **TypeScript** fully supported
+❯ 💾 **Multi-driver storage** and caching layer
+❯ 💰 **Route caching** and static **pre-rendering** with built-in crawler
+❯ 🐱 **Hackable** to extend almost any part of nitro using options
+❯ ✨ **Auto imports** for lazy folks and a tidy minimal codebase
+❯ 🏛️ **Best-effort compatibility** for using legacy npm packages and mocking Node.js modules
## Who is using Nitro? @@ -49,7 +49,7 @@ cd nitro-app 1️⃣ Create `routes/index.ts`: ```ts [routes/index.ts] -export default defineEventHandler(() => 'nitro is amazing!') +export default defineEventHandler(() => "nitro is amazing!"); ``` 2️⃣ Start development server: @@ -66,7 +66,7 @@ Check `.nitro/dev/index.mjs` if want to know what is happening ```bash npx nitropack build -```` +``` 4️⃣ Output is in the `.output` directory and ready to be deployed on almost any VPS with no dependencies. You can locally try it too: @@ -76,34 +76,28 @@ node .output/server/index.mjs That's it you got it! Read the [documentation](https://nitro.unjs.io) to learn more. - -

🌱 nitro is young and under development

-Check [🐛 open issues](https://github.com/unjs/nitro/issues) for the known issues and roadmap and tell us [💡your ideas](https://github.com/unjs/nitro/discussions/new)! -
+Check [🐛 open issues](https://github.com/unjs/nitro/issues) for the known issues and roadmap and tell us [💡your ideas](https://github.com/unjs/nitro/discussions/new)! +
## License Made with 💛 Published under [MIT](./LICENSE). + [npm-version-src]: https://flat.badgen.net/npm/v/nitropack?style=flat-square&label=stable [npm-version-href]: https://npmjs.com/package/nitropack - [npm-downloads-src]: https://flat.badgen.net/npm/dm/nitropack?style=flat-square&label=stable [npm-downloads-href]: https://npmjs.com/package/nitropack - [npm-edge-version-src]: https://flat.badgen.net/npm/v/nitropack-edge?style=flat-square&label=edge [npm-edge-version-href]: https://npmjs.com/package/nitropack-edge - [npm-edge-downloads-src]: https://flat.badgen.net/npm/dm/nitropack-edge?style=flat-square&label=edge [npm-edge-downloads-href]: https://npmjs.com/package/nitropack-edge - [github-actions-src]: https://flat.badgen.net/github/status/unjs/nitro?style=flat-square [github-actions-href]: https://github.com/unjs/nitro/actions?query=workflow%3Aci - [codecov-src]: https://flat.badgen.net/codecov/c/gh/unjs/nitro/main?style=flat-square [codecov-href]: https://codecov.io/gh/unjs/nitro diff --git a/build.config.ts b/build.config.ts index be5082eb0a..113bb9985c 100644 --- a/build.config.ts +++ b/build.config.ts @@ -1,22 +1,20 @@ -import { defineBuildConfig } from 'unbuild' +import { defineBuildConfig } from "unbuild"; export default defineBuildConfig({ declaration: true, - name: 'nitro', + name: "nitro", entries: [ - 'src/index', - 'src/cli', - { input: 'src/runtime/', outDir: 'dist/runtime', format: 'esm' } + "src/index", + "src/cli", + { input: "src/runtime/", outDir: "dist/runtime", format: "esm" }, ], dependencies: [ - '@cloudflare/kv-asset-handler', - '@netlify/functions', - '@nuxt/devalue', - 'destr', - 'ofetch', - 'ora' + "@cloudflare/kv-asset-handler", + "@netlify/functions", + "@nuxt/devalue", + "destr", + "ofetch", + "ora", ], - externals: [ - '@nuxt/schema' - ] -}) + externals: ["@nuxt/schema"], +}); diff --git a/examples/api-routes/api/hello.ts b/examples/api-routes/api/hello.ts index ec2770b4bc..21fe2776d8 100644 --- a/examples/api-routes/api/hello.ts +++ b/examples/api-routes/api/hello.ts @@ -1 +1 @@ -export default defineEventHandler(() => 'Nitro is amazing!') +export default defineEventHandler(() => "Nitro is amazing!"); diff --git a/examples/api-routes/api/hello/[name].ts b/examples/api-routes/api/hello/[name].ts index bfee85aa0f..ee43aab612 100644 --- a/examples/api-routes/api/hello/[name].ts +++ b/examples/api-routes/api/hello/[name].ts @@ -1 +1,3 @@ -export default defineEventHandler(event => `Hello ${event.context.params.name}!`) +export default defineEventHandler( + (event) => `Hello ${event.context.params.name}!` +); diff --git a/examples/api-routes/api/test.get.ts b/examples/api-routes/api/test.get.ts index 84e39cefcf..927dfd195e 100644 --- a/examples/api-routes/api/test.get.ts +++ b/examples/api-routes/api/test.get.ts @@ -1 +1 @@ -export default defineEventHandler(() => 'Test get handler') +export default defineEventHandler(() => "Test get handler"); diff --git a/examples/api-routes/api/test.post.ts b/examples/api-routes/api/test.post.ts index 79a8cc07f5..710f745b69 100644 --- a/examples/api-routes/api/test.post.ts +++ b/examples/api-routes/api/test.post.ts @@ -1 +1 @@ -export default defineEventHandler(() => 'Test post handler') +export default defineEventHandler(() => "Test post handler"); diff --git a/examples/api-routes/nitro.config.ts b/examples/api-routes/nitro.config.ts index a61d1d8e09..0d2ca51b59 100644 --- a/examples/api-routes/nitro.config.ts +++ b/examples/api-routes/nitro.config.ts @@ -1,4 +1,3 @@ -import { defineNitroConfig } from 'nitropack' +import { defineNitroConfig } from "nitropack"; -export default defineNitroConfig({ -}) +export default defineNitroConfig({}); diff --git a/examples/api-routes/routes/[...].ts b/examples/api-routes/routes/[...].ts index a58441c887..22a7d27ec0 100644 --- a/examples/api-routes/routes/[...].ts +++ b/examples/api-routes/routes/[...].ts @@ -6,5 +6,5 @@ export default defineEventHandler(() => {
  • /api/hello/world
  • /api/test
  • -` -}) +`; +}); diff --git a/examples/cached-handler/nitro.config.ts b/examples/cached-handler/nitro.config.ts index a61d1d8e09..0d2ca51b59 100644 --- a/examples/cached-handler/nitro.config.ts +++ b/examples/cached-handler/nitro.config.ts @@ -1,4 +1,3 @@ -import { defineNitroConfig } from 'nitropack' +import { defineNitroConfig } from "nitropack"; -export default defineNitroConfig({ -}) +export default defineNitroConfig({}); diff --git a/examples/cached-handler/routes/index.ts b/examples/cached-handler/routes/index.ts index 3254a64f53..235e8977e0 100644 --- a/examples/cached-handler/routes/index.ts +++ b/examples/cached-handler/routes/index.ts @@ -1,4 +1,4 @@ export default defineCachedEventHandler(async () => { - await new Promise(resolve => setTimeout(resolve, 1000)) - return `Response generated at ${new Date().toISOString()} (took 1 second)` -}) + await new Promise((resolve) => setTimeout(resolve, 1000)); + return `Response generated at ${new Date().toISOString()} (took 1 second)`; +}); diff --git a/examples/custom-error-handler/error.ts b/examples/custom-error-handler/error.ts index a37a68a08c..3ad9cfb172 100644 --- a/examples/custom-error-handler/error.ts +++ b/examples/custom-error-handler/error.ts @@ -1,5 +1,5 @@ -import type { NitroErrorHandler } from 'nitropack' +import type { NitroErrorHandler } from "nitropack"; -export default function (error, event) { - event.res.end('[custom error handler] ' + error.stack) -} +export default function (error, event) { + event.res.end("[custom error handler] " + error.stack); +}; diff --git a/examples/custom-error-handler/nitro.config.ts b/examples/custom-error-handler/nitro.config.ts index 01d9ec5abf..ba0adbda20 100644 --- a/examples/custom-error-handler/nitro.config.ts +++ b/examples/custom-error-handler/nitro.config.ts @@ -1,7 +1,7 @@ -import { defineNitroConfig } from 'nitropack' -import errorHandler from './error' +import { defineNitroConfig } from "nitropack"; +import errorHandler from "./error"; export default defineNitroConfig({ - errorHandler: '~/error', - devErrorHandler: errorHandler -}) + errorHandler: "~/error", + devErrorHandler: errorHandler, +}); diff --git a/examples/custom-error-handler/routes/index.ts b/examples/custom-error-handler/routes/index.ts index f747912de9..820c4b6596 100644 --- a/examples/custom-error-handler/routes/index.ts +++ b/examples/custom-error-handler/routes/index.ts @@ -1,3 +1,3 @@ export default defineEventHandler(() => { - throw new Error('Example Error!') -}) + throw new Error("Example Error!"); +}); diff --git a/examples/hello-world/nitro.config.ts b/examples/hello-world/nitro.config.ts index a61d1d8e09..0d2ca51b59 100644 --- a/examples/hello-world/nitro.config.ts +++ b/examples/hello-world/nitro.config.ts @@ -1,4 +1,3 @@ -import { defineNitroConfig } from 'nitropack' +import { defineNitroConfig } from "nitropack"; -export default defineNitroConfig({ -}) +export default defineNitroConfig({}); diff --git a/examples/hello-world/routes/index.ts b/examples/hello-world/routes/index.ts index db740e4bcd..815b6c7183 100644 --- a/examples/hello-world/routes/index.ts +++ b/examples/hello-world/routes/index.ts @@ -1 +1 @@ -export default defineEventHandler(() => '

    nitro is amazing!

    ') +export default defineEventHandler(() => "

    nitro is amazing!

    "); diff --git a/examples/middleware/middleware/auth.ts b/examples/middleware/middleware/auth.ts index 5035d76a83..00f610ce10 100644 --- a/examples/middleware/middleware/auth.ts +++ b/examples/middleware/middleware/auth.ts @@ -1,3 +1,3 @@ export default defineEventHandler((event) => { - event.context.auth = { name: 'User ' + Math.round(Math.random() * 100) } -}) + event.context.auth = { name: "User " + Math.round(Math.random() * 100) }; +}); diff --git a/examples/middleware/nitro.config.ts b/examples/middleware/nitro.config.ts index a61d1d8e09..0d2ca51b59 100644 --- a/examples/middleware/nitro.config.ts +++ b/examples/middleware/nitro.config.ts @@ -1,4 +1,3 @@ -import { defineNitroConfig } from 'nitropack' +import { defineNitroConfig } from "nitropack"; -export default defineNitroConfig({ -}) +export default defineNitroConfig({}); diff --git a/examples/middleware/routes/index.ts b/examples/middleware/routes/index.ts index 301814d72e..e7961fa36d 100644 --- a/examples/middleware/routes/index.ts +++ b/examples/middleware/routes/index.ts @@ -1,3 +1,3 @@ -export default defineEventHandler(event => ({ - auth: event.context.auth -})) +export default defineEventHandler((event) => ({ + auth: event.context.auth, +})); diff --git a/examples/plugins/nitro.config.ts b/examples/plugins/nitro.config.ts index 5b8e964d56..05e210f7fe 100644 --- a/examples/plugins/nitro.config.ts +++ b/examples/plugins/nitro.config.ts @@ -1,7 +1,5 @@ -import { defineNitroConfig } from 'nitropack' +import { defineNitroConfig } from "nitropack"; export default defineNitroConfig({ - plugins: [ - '~/plugins/test' - ] -}) + plugins: ["~/plugins/test"], +}); diff --git a/examples/plugins/plugins/test.ts b/examples/plugins/plugins/test.ts index 430ecb90cb..d6f5a24e88 100644 --- a/examples/plugins/plugins/test.ts +++ b/examples/plugins/plugins/test.ts @@ -1,3 +1,3 @@ export default defineNitroPlugin((_nitroApp) => { - console.log('Nitro plugin!') -}) + console.log("Nitro plugin!"); +}); diff --git a/examples/plugins/routes/index.ts b/examples/plugins/routes/index.ts index 1cc398b615..2b5a032a89 100644 --- a/examples/plugins/routes/index.ts +++ b/examples/plugins/routes/index.ts @@ -1,3 +1,3 @@ -import { eventHandler } from 'h3' +import { eventHandler } from "h3"; -export default eventHandler(() => '

    Hello Nitro!

    ') +export default eventHandler(() => "

    Hello Nitro!

    "); diff --git a/examples/tsconfig.json b/examples/tsconfig.json index 3ae2a24af8..379a994d81 100644 --- a/examples/tsconfig.json +++ b/examples/tsconfig.json @@ -1,4 +1,4 @@ { - "extends": "../tsconfig.json", - "include": ["."] + "extends": "../tsconfig.json", + "include": ["."] } diff --git a/package.json b/package.json index 6ed50e7d89..6788422ab2 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "docs:build": "vitepress build docs", "docs:dev": "vitepress dev docs", "docs:serve": "vitepress serve docs", - "lint": "eslint --ext .ts,.mjs,.cjs .", + "lint": "eslint --ext .ts,.mjs,.cjs . && prettier -c src test", "nitro": "jiti ./src/cli.ts", "prepack": "pnpm build", "release": "pnpm test && pnpm build && changelogen --release && pnpm publish && git push --follow-tags", @@ -80,8 +80,8 @@ "mlly": "^1.0.0", "mri": "^1.2.0", "node-fetch-native": "^1.0.1", - "ohash": "^1.0.0", "ofetch": "^1.0.0", + "ohash": "^1.0.0", "pathe": "^1.0.0", "perfect-debounce": "^0.1.3", "pkg-types": "^1.0.1", @@ -102,16 +102,14 @@ "unstorage": "^1.0.1" }, "devDependencies": { - "@nuxtjs/eslint-config-typescript": "^11.0.0", "@types/aws-lambda": "^8.10.109", "@types/etag": "^1.8.1", "@types/fs-extra": "^9.0.13", "@types/http-proxy": "^1.17.9", "@types/node-fetch": "^2.6.2", - "@types/object-hash": "^2.2.1", "@types/semver": "^7.3.13", "@types/serve-static": "^1.15.0", - "@vitest/coverage-c8": "^0.25.5", + "@vitest/coverage-c8": "^0.25.6", "c8": "^7.12.0", "changelogen": "^0.4.0", "edge-runtime": "2.0.2", @@ -120,9 +118,10 @@ "execa": "^6.1.0", "expect-type": "^0.15.0", "miniflare": "^2.11.0", + "prettier": "^2.8.1", "typescript": "^4.9.4", "unbuild": "^1.0.2", - "vitest": "^0.25.5" + "vitest": "^0.25.6" }, "packageManager": "pnpm@7.18.1", "engines": { diff --git a/playground/nitro.config.ts b/playground/nitro.config.ts index 14ed31af57..8185335729 100644 --- a/playground/nitro.config.ts +++ b/playground/nitro.config.ts @@ -1,4 +1,3 @@ -import { defineNitroConfig } from '../src' +import { defineNitroConfig } from "../src"; -export default defineNitroConfig({ -}) +export default defineNitroConfig({}); diff --git a/playground/routes/index.ts b/playground/routes/index.ts index 1cc398b615..2b5a032a89 100644 --- a/playground/routes/index.ts +++ b/playground/routes/index.ts @@ -1,3 +1,3 @@ -import { eventHandler } from 'h3' +import { eventHandler } from "h3"; -export default eventHandler(() => '

    Hello Nitro!

    ') +export default eventHandler(() => "

    Hello Nitro!

    "); diff --git a/playground/tsconfig.json b/playground/tsconfig.json index dd14e743b0..a0e3fbad2e 100644 --- a/playground/tsconfig.json +++ b/playground/tsconfig.json @@ -2,15 +2,9 @@ "extends": "./.nitro/types/tsconfig.json", "compilerOptions": { "paths": { - "nitropack": [ - "../src/index" - ], - "#internal/nitro": [ - "../src/runtime/index" - ], - "#internal/nitro/*": [ - "../src/runtime/*" - ] - }, + "nitropack": ["../src/index"], + "#internal/nitro": ["../src/runtime/index"], + "#internal/nitro/*": ["../src/runtime/*"] + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec9aaa53d3..0cbdd2e43f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,6 @@ importers: specifiers: '@cloudflare/kv-asset-handler': ^0.2.0 '@netlify/functions': ^1.3.0 - '@nuxtjs/eslint-config-typescript': ^11.0.0 '@rollup/plugin-alias': ^4.0.2 '@rollup/plugin-commonjs': ^23.0.4 '@rollup/plugin-inject': ^5.0.2 @@ -23,11 +22,10 @@ importers: '@types/fs-extra': ^9.0.13 '@types/http-proxy': ^1.17.9 '@types/node-fetch': ^2.6.2 - '@types/object-hash': ^2.2.1 '@types/semver': ^7.3.13 '@types/serve-static': ^1.15.0 '@vercel/nft': ^0.22.5 - '@vitest/coverage-c8': ^0.25.5 + '@vitest/coverage-c8': ^0.25.6 archiver: ^5.3.1 c12: ^1.1.0 c8: ^7.12.0 @@ -68,6 +66,7 @@ importers: pathe: ^1.0.0 perfect-debounce: ^0.1.3 pkg-types: ^1.0.1 + prettier: ^2.8.1 pretty-bytes: ^6.0.0 radix3: ^1.0.0 rollup: ^2.79.1 @@ -85,7 +84,7 @@ importers: unenv: ^1.0.0 unimport: ^1.0.1 unstorage: ^1.0.1 - vitest: ^0.25.5 + vitest: ^0.25.6 dependencies: '@cloudflare/kv-asset-handler': 0.2.0 '@netlify/functions': 1.3.0 @@ -146,16 +145,14 @@ importers: unimport: 1.0.1_rollup@2.79.1 unstorage: 1.0.1 devDependencies: - '@nuxtjs/eslint-config-typescript': 11.0.0_ha6vam6werchizxrnqvarmz2zu '@types/aws-lambda': 8.10.109 '@types/etag': 1.8.1 '@types/fs-extra': 9.0.13 '@types/http-proxy': 1.17.9 '@types/node-fetch': 2.6.2 - '@types/object-hash': 2.2.1 '@types/semver': 7.3.13 '@types/serve-static': 1.15.0 - '@vitest/coverage-c8': 0.25.5 + '@vitest/coverage-c8': 0.25.6 c8: 7.12.0 changelogen: 0.4.0 edge-runtime: 2.0.2 @@ -164,9 +161,10 @@ importers: execa: 6.1.0 expect-type: 0.15.0 miniflare: 2.11.0 + prettier: 2.8.1 typescript: 4.9.4 unbuild: 1.0.2 - vitest: 0.25.5 + vitest: 0.25.6 examples/api-routes: specifiers: @@ -964,43 +962,6 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.13.0 - /@nuxtjs/eslint-config-typescript/11.0.0_ha6vam6werchizxrnqvarmz2zu: - resolution: {integrity: sha512-hmFjGtXT524ql8eTbK8BaRkamcXB6Z8YOW8nSQhosTP6oBw9WtOFUeWr7holyE278UhOmx+wDFG90BnyM9D+UA==} - peerDependencies: - eslint: ^8.23.0 - dependencies: - '@nuxtjs/eslint-config': 11.0.0_xjely6t5wgwgvqnbbgk52l5je4 - '@typescript-eslint/eslint-plugin': 5.36.1_ujfdmtdxmqxe4a5dut33px3yhi - '@typescript-eslint/parser': 5.36.1_ha6vam6werchizxrnqvarmz2zu - eslint: 8.29.0 - eslint-import-resolver-typescript: 3.5.0_lt3hqehuojhfcbzgzqfngbtmrq - eslint-plugin-import: 2.26.0_xjely6t5wgwgvqnbbgk52l5je4 - transitivePeerDependencies: - - eslint-import-resolver-webpack - - supports-color - - typescript - dev: true - - /@nuxtjs/eslint-config/11.0.0_xjely6t5wgwgvqnbbgk52l5je4: - resolution: {integrity: sha512-o4zFOpU8gJgwrC/gLE7c2E0XEjkv2fEixCGG1y+dZYzBPyzTorkQmfxskSF3WRXcZkpkS9uUYlRkeOSdYB7z0w==} - peerDependencies: - eslint: ^8.23.0 - dependencies: - eslint: 8.29.0 - eslint-config-standard: 17.0.0_l24xu3wzfjzwv252bimf7eavfq - eslint-plugin-import: 2.26.0_xjely6t5wgwgvqnbbgk52l5je4 - eslint-plugin-n: 15.2.5_eslint@8.29.0 - eslint-plugin-node: 11.1.0_eslint@8.29.0 - eslint-plugin-promise: 6.0.1_eslint@8.29.0 - eslint-plugin-unicorn: 43.0.2_eslint@8.29.0 - eslint-plugin-vue: 9.4.0_eslint@8.29.0 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: true - /@pkgr/utils/2.3.1: resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -1301,10 +1262,6 @@ packages: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true - /@types/object-hash/2.2.1: - resolution: {integrity: sha512-i/rtaJFCsPljrZvP/akBqEwUP2y5cZLOmvO+JaYnz01aPknrQ+hB5MRcO7iqCUsFaYfTG8kGfKUyboA07xeDHQ==} - dev: true - /@types/resolve/1.20.2: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -1471,11 +1428,11 @@ packages: - supports-color dev: false - /@vitest/coverage-c8/0.25.5: - resolution: {integrity: sha512-tXlg0QVkTQ+7dqhsuwRFovP5vjgYGyBrRREFS55IQ20IsiSaRug9sRblRsckTXmGtz1OMJy/Lxv1gFVo24/ipA==} + /@vitest/coverage-c8/0.25.6: + resolution: {integrity: sha512-+Bcs1XZ8CuEe52QSOgzqmqiV82CXZRRj3yclPQqtLkUV4S6+kdOdfRTtMHOilwhZH70giDjBQx3jgiZoYgFVMA==} dependencies: c8: 7.12.0 - vitest: 0.25.5 + vitest: 0.25.6 transitivePeerDependencies: - '@edge-runtime/vm' - '@vitest/browser' @@ -1670,10 +1627,6 @@ packages: readable-stream: 3.6.0 dev: false - /boolbase/1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - dev: true - /brace-expansion/1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -2018,12 +1971,6 @@ packages: shebang-command: 2.0.0 which: 2.0.2 - /cssesc/3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - dev: true - /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -2737,24 +2684,6 @@ packages: strip-indent: 3.0.0 dev: true - /eslint-plugin-vue/9.4.0_eslint@8.29.0: - resolution: {integrity: sha512-Nzz2QIJ8FG+rtJaqT/7/ru5ie2XgT9KCudkbN0y3uFYhQ41nuHEaboLAiqwMcK006hZPQv/rVMRhUIwEGhIvfQ==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 - dependencies: - eslint: 8.29.0 - eslint-utils: 3.0.0_eslint@8.29.0 - natural-compare: 1.4.0 - nth-check: 2.0.1 - postcss-selector-parser: 6.0.10 - semver: 7.3.8 - vue-eslint-parser: 9.0.3_eslint@8.29.0 - xml-name-validator: 4.0.0 - transitivePeerDependencies: - - supports-color - dev: true - /eslint-scope/5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -4184,12 +4113,6 @@ packages: validate-npm-package-name: 4.0.0 dev: true - /nth-check/2.0.1: - resolution: {integrity: sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==} - dependencies: - boolbase: 1.0.0 - dev: true - /object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -4437,14 +4360,6 @@ packages: engines: {node: '>=4'} dev: true - /postcss-selector-parser/6.0.10: - resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} - engines: {node: '>=4'} - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - dev: true - /postcss/8.4.17: resolution: {integrity: sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==} engines: {node: ^10 || ^12 || >=14} @@ -4459,6 +4374,12 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prettier/2.8.1: + resolution: {integrity: sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /pretty-bytes/5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} @@ -5336,6 +5257,7 @@ packages: /util-deprecate/1.0.2: resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} + dev: false /v8-to-istanbul/9.0.0: resolution: {integrity: sha512-HcvgY/xaRm7isYmyx+lFKA4uQmfUbN0J4M0nNItvzTvH/iQ9kW5j/t4YSR+Ge323/lrgDAWJoF46tzGQHwBHFw==} @@ -5387,8 +5309,8 @@ packages: fsevents: 2.3.2 dev: true - /vitest/0.25.5: - resolution: {integrity: sha512-lFKSTZV+AjuL44/yNC0aDPKTiasYSqOQ97Gg2G4P2LnjyzQ21ZUs4rQOscHK3lrSnVuir3+1QavHzbDgH4tWQA==} + /vitest/0.25.6: + resolution: {integrity: sha512-jdPgmZ7BcDnm1+hmMPIl9BZjSy+b8Y8V0tQMsv7ECO90Qic7EZ5/+traILXLpsXgqK5KgVrUJmchevAUuKL/1w==} engines: {node: '>=v14.16.0'} hasBin: true peerDependencies: @@ -5431,24 +5353,6 @@ packages: - terser dev: true - /vue-eslint-parser/9.0.3_eslint@8.29.0: - resolution: {integrity: sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og==} - engines: {node: ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '>=6.0.0' - dependencies: - debug: 4.3.4 - eslint: 8.29.0 - eslint-scope: 7.1.1 - eslint-visitor-keys: 3.3.0 - espree: 9.4.0 - esquery: 1.4.0 - lodash: 4.17.21 - semver: 7.3.8 - transitivePeerDependencies: - - supports-color - dev: true - /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false @@ -5546,11 +5450,6 @@ packages: optional: true dev: true - /xml-name-validator/4.0.0: - resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} - engines: {node: '>=12'} - dev: true - /y18n/5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 444db3ff2c..c8506e68dc 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,2 @@ packages: - - 'examples/**' + - "examples/**" diff --git a/renovate.json b/renovate.json index a9971c80d6..57fe916b5c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,3 @@ { - "extends": [ - "github>unjs/renovate-config" - ] + "extends": ["github>unjs/renovate-config"] } diff --git a/scripts/bump-edge.ts b/scripts/bump-edge.ts index f29a792cda..3bc50968b3 100755 --- a/scripts/bump-edge.ts +++ b/scripts/bump-edge.ts @@ -1,82 +1,96 @@ -import { promises as fsp } from 'fs' -import { execaCommand } from 'execa' -import { resolve } from 'pathe' -import { globby } from 'globby' +import { promises as fsp } from "node:fs"; +import { execaCommand } from "execa"; +import { resolve } from "pathe"; +import { globby } from "globby"; // Temporary forked from nuxt/framework -async function loadPackage (dir: string) { - const pkgPath = resolve(dir, 'package.json') - const data = JSON.parse(await fsp.readFile(pkgPath, 'utf-8').catch(() => '{}')) - const save = () => fsp.writeFile(pkgPath, JSON.stringify(data, null, 2) + '\n') +async function loadPackage(dir: string) { + const pkgPath = resolve(dir, "package.json"); + const data = JSON.parse( + await fsp.readFile(pkgPath, "utf8").catch(() => "{}") + ); + const save = () => + fsp.writeFile(pkgPath, JSON.stringify(data, null, 2) + "\n"); const updateDeps = (reviver: Function) => { - for (const type of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) { - if (!data[type]) { continue } + for (const type of [ + "dependencies", + "devDependencies", + "optionalDependencies", + "peerDependencies", + ]) { + if (!data[type]) { + continue; + } for (const e of Object.entries(data[type])) { - const dep = { name: e[0], range: e[1], type } - delete data[type][dep.name] - const updated = reviver(dep) || dep - data[updated.type] = data[updated.type] || {} - data[updated.type][updated.name] = updated.range + const dep = { name: e[0], range: e[1], type }; + delete data[type][dep.name]; + const updated = reviver(dep) || dep; + data[updated.type] = data[updated.type] || {}; + data[updated.type][updated.name] = updated.range; } } - } + }; return { dir, data, save, - updateDeps - } + updateDeps, + }; } -type ThenArg = T extends PromiseLike ? U : T -type Package = ThenArg> +type ThenArg = T extends PromiseLike ? U : T; +type Package = ThenArg>; -async function loadWorkspace (dir: string) { - const workspacePkg = await loadPackage(dir) - const pkgDirs = await globby(workspacePkg.data.workspaces || [], { onlyDirectories: true }) +async function loadWorkspace(dir: string) { + const workspacePkg = await loadPackage(dir); + const pkgDirs = await globby(workspacePkg.data.workspaces || [], { + onlyDirectories: true, + }); - const packages: Package[] = [workspacePkg] + const packages: Package[] = [workspacePkg]; for (const pkgDir of pkgDirs) { - const pkg = await loadPackage(pkgDir) - if (!pkg.data.name) { continue } - packages.push(pkg) + const pkg = await loadPackage(pkgDir); + if (!pkg.data.name) { + continue; + } + packages.push(pkg); } const find = (name: string) => { - const pkg = packages.find(pkg => pkg.data.name === name) + const pkg = packages.find((pkg) => pkg.data.name === name); if (!pkg) { - throw new Error('Workspace package not found: ' + name) + throw new Error("Workspace package not found: " + name); } - return pkg - } + return pkg; + }; const rename = (from: string, to: string) => { - find(from).data.name = to + find(from).data.name = to; for (const pkg of packages) { pkg.updateDeps((dep) => { - if (dep.name === from && !dep.range.startsWith('npm:')) { - dep.range = 'npm:' + to + '@' + dep.range + if (dep.name === from && !dep.range.startsWith("npm:")) { + dep.range = "npm:" + to + "@" + dep.range; } - }) + }); } - } + }; const setVersion = (name: string, newVersion: string) => { - find(name).data.version = newVersion + find(name).data.version = newVersion; for (const pkg of packages) { pkg.updateDeps((dep) => { if (dep.name === name) { - dep.range = newVersion + dep.range = newVersion; } - }) + }); } - } + }; - const save = () => Promise.all(packages.map(pkg => pkg.save())) + const save = () => Promise.all(packages.map((pkg) => pkg.save())); return { dir, @@ -85,26 +99,33 @@ async function loadWorkspace (dir: string) { save, find, rename, - setVersion - } + setVersion, + }; } -async function main () { - const workspace = await loadWorkspace(process.cwd()) +async function main() { + const workspace = await loadWorkspace(process.cwd()); - const commit = await execaCommand('git rev-parse --short HEAD').then(r => r.stdout.trim()) - const date = Math.round(Date.now() / (1000 * 60)) + const commit = await execaCommand("git rev-parse --short HEAD").then((r) => + r.stdout.trim() + ); + const date = Math.round(Date.now() / (1000 * 60)); - for (const pkg of workspace.packages.filter(p => !p.data.private)) { - workspace.setVersion(pkg.data.name, `${pkg.data.version}-${date}.${commit}`) - workspace.rename(pkg.data.name, pkg.data.name + '-edge') + for (const pkg of workspace.packages.filter((p) => !p.data.private)) { + workspace.setVersion( + pkg.data.name, + `${pkg.data.version}-${date}.${commit}` + ); + workspace.rename(pkg.data.name, pkg.data.name + "-edge"); } - await workspace.save() + await workspace.save(); } +// eslint-disable-next-line unicorn/prefer-top-level-await main().catch((err) => { // eslint-disable-next-line no-console - console.error(err) - process.exit(1) -}) + console.error(err); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); +}); diff --git a/src/build.ts b/src/build.ts index d729ae30af..28b40ac0c9 100644 --- a/src/build.ts +++ b/src/build.ts @@ -1,278 +1,342 @@ -import { promises as fsp } from 'fs' -import { relative, resolve, join, dirname, isAbsolute } from 'pathe' -import { resolveAlias } from 'pathe/utils' -import * as rollup from 'rollup' -import fse from 'fs-extra' -import { defu } from 'defu' -import { watch } from 'chokidar' -import { debounce } from 'perfect-debounce' -import type { TSConfig } from 'pkg-types' -import type { RollupError } from 'rollup' -import type { OnResolveResult, PartialMessage } from 'esbuild' -import { printFSTree } from './utils/tree' -import { getRollupConfig, RollupConfig } from './rollup/config' -import { prettyPath, writeFile, isDirectory } from './utils' -import { GLOB_SCAN_PATTERN, scanHandlers } from './scan' -import type { Nitro } from './types' -import { runtimeDir } from './dirs' -import { snapshotStorage } from './storage' -import { compressPublicAssets } from './compress' - -export async function prepare (nitro: Nitro) { - await prepareDir(nitro.options.output.dir) +import { promises as fsp } from "node:fs"; +import { relative, resolve, join, dirname, isAbsolute } from "pathe"; +import { resolveAlias } from "pathe/utils"; +import * as rollup from "rollup"; +import fse from "fs-extra"; +import { defu } from "defu"; +import { watch } from "chokidar"; +import { debounce } from "perfect-debounce"; +import type { TSConfig } from "pkg-types"; +import type { RollupError } from "rollup"; +import type { OnResolveResult, PartialMessage } from "esbuild"; +import { printFSTree } from "./utils/tree"; +import { getRollupConfig, RollupConfig } from "./rollup/config"; +import { prettyPath, writeFile, isDirectory } from "./utils"; +import { GLOB_SCAN_PATTERN, scanHandlers } from "./scan"; +import type { Nitro } from "./types"; +import { runtimeDir } from "./dirs"; +import { snapshotStorage } from "./storage"; +import { compressPublicAssets } from "./compress"; + +export async function prepare(nitro: Nitro) { + await prepareDir(nitro.options.output.dir); if (!nitro.options.noPublicDir) { - await prepareDir(nitro.options.output.publicDir) + await prepareDir(nitro.options.output.publicDir); } - await prepareDir(nitro.options.output.serverDir) + await prepareDir(nitro.options.output.serverDir); } -async function prepareDir (dir: string) { - await fsp.mkdir(dir, { recursive: true }) - await fse.emptyDir(dir) +async function prepareDir(dir: string) { + await fsp.mkdir(dir, { recursive: true }); + await fse.emptyDir(dir); } -export async function copyPublicAssets (nitro: Nitro) { - if (nitro.options.noPublicDir) { return } +export async function copyPublicAssets(nitro: Nitro) { + if (nitro.options.noPublicDir) { + return; + } for (const asset of nitro.options.publicAssets) { if (await isDirectory(asset.dir)) { - await fse.copy(asset.dir, join(nitro.options.output.publicDir, asset.baseURL!)) + await fse.copy( + asset.dir, + join(nitro.options.output.publicDir, asset.baseURL!) + ); } } if (nitro.options.compressPublicAssets) { - await compressPublicAssets(nitro) + await compressPublicAssets(nitro); } - nitro.logger.success('Generated public ' + prettyPath(nitro.options.output.publicDir)) + nitro.logger.success( + "Generated public " + prettyPath(nitro.options.output.publicDir) + ); } -export async function build (nitro: Nitro) { - const rollupConfig = getRollupConfig(nitro) - await nitro.hooks.callHook('rollup:before', nitro) - return nitro.options.dev ? _watch(nitro, rollupConfig) : _build(nitro, rollupConfig) +export async function build(nitro: Nitro) { + const rollupConfig = getRollupConfig(nitro); + await nitro.hooks.callHook("rollup:before", nitro); + return nitro.options.dev + ? _watch(nitro, rollupConfig) + : _build(nitro, rollupConfig); } -export async function writeTypes (nitro: Nitro) { - const routeTypes: Record = {} +export async function writeTypes(nitro: Nitro) { + const routeTypes: Record = {}; - const middleware = [ - ...nitro.scannedHandlers, - ...nitro.options.handlers - ] + const middleware = [...nitro.scannedHandlers, ...nitro.options.handlers]; for (const mw of middleware) { - if (typeof mw.handler !== 'string' || !mw.route) { continue } - const relativePath = relative(join(nitro.options.buildDir, 'types'), mw.handler).replace(/\.[a-z]+$/, '') - routeTypes[mw.route] = routeTypes[mw.route] || [] - routeTypes[mw.route].push(`Awaited>`) + if (typeof mw.handler !== "string" || !mw.route) { + continue; + } + const relativePath = relative( + join(nitro.options.buildDir, "types"), + mw.handler + ).replace(/\.[a-z]+$/, ""); + routeTypes[mw.route] = routeTypes[mw.route] || []; + routeTypes[mw.route].push( + `Awaited>` + ); } - let autoImportedTypes: string[] = [] + let autoImportedTypes: string[] = []; if (nitro.unimport) { autoImportedTypes = [ - (await nitro.unimport - .generateTypeDeclarations({ + ( + await nitro.unimport.generateTypeDeclarations({ exportHelper: false, resolvePath: (i) => { - if (i.from.startsWith('#internal/nitro')) { - return resolveAlias(i.from, nitro.options.alias) + if (i.from.startsWith("#internal/nitro")) { + return resolveAlias(i.from, nitro.options.alias); } - return i.from - } - })) - .trim() - ] + return i.from; + }, + }) + ).trim(), + ]; } const lines = [ - '// Generated by nitro', - 'declare module \'nitropack\' {', - ' type Awaited = T extends PromiseLike ? Awaited : T', - ' interface InternalApi {', - ...Object.entries(routeTypes).map(([path, types]) => ` '${path}': ${types.join(' | ')}`), - ' }', - '}', + "// Generated by nitro", + "declare module 'nitropack' {", + " type Awaited = T extends PromiseLike ? Awaited : T", + " interface InternalApi {", + ...Object.entries(routeTypes).map( + ([path, types]) => ` '${path}': ${types.join(" | ")}` + ), + " }", + "}", ...autoImportedTypes, // Makes this a module for augmentation purposes - 'export {}' - ] + "export {}", + ]; - await writeFile(join(nitro.options.buildDir, 'types/nitro.d.ts'), lines.join('\n')) + await writeFile( + join(nitro.options.buildDir, "types/nitro.d.ts"), + lines.join("\n") + ); if (nitro.options.typescript.generateTsConfig) { const tsConfig: TSConfig = { compilerOptions: { - target: 'ESNext', - module: 'ESNext', - moduleResolution: 'Node', + target: "ESNext", + module: "ESNext", + moduleResolution: "Node", allowJs: true, resolveJsonModule: true, paths: nitro.options.typescript.internalPaths ? { - '#internal/nitro': [ - join(runtimeDir, 'index') - ], - '#internal/nitro/*': [ - join(runtimeDir, '*') - ] + "#internal/nitro": [join(runtimeDir, "index")], + "#internal/nitro/*": [join(runtimeDir, "*")], } - : {} + : {}, }, include: [ - './nitro.d.ts', - join(relative(join(nitro.options.buildDir, 'types'), nitro.options.rootDir), '**/*'), - ...nitro.options.srcDir !== nitro.options.rootDir ? [join(relative(join(nitro.options.buildDir, 'types'), nitro.options.srcDir), '**/*')] : [] - ] - } - await writeFile(join(nitro.options.buildDir, 'types/tsconfig.json'), JSON.stringify(tsConfig, null, 2)) + "./nitro.d.ts", + join( + relative( + join(nitro.options.buildDir, "types"), + nitro.options.rootDir + ), + "**/*" + ), + ...(nitro.options.srcDir !== nitro.options.rootDir + ? [ + join( + relative( + join(nitro.options.buildDir, "types"), + nitro.options.srcDir + ), + "**/*" + ), + ] + : []), + ], + }; + await writeFile( + join(nitro.options.buildDir, "types/tsconfig.json"), + JSON.stringify(tsConfig, null, 2) + ); } } -async function _snapshot (nitro: Nitro) { - if (!nitro.options.bundledStorage.length || - nitro.options.preset === 'nitro-prerender' +async function _snapshot(nitro: Nitro) { + if ( + nitro.options.bundledStorage.length === 0 || + nitro.options.preset === "nitro-prerender" ) { - return + return; } // TODO: Use virtual storage for server assets - const storageDir = resolve(nitro.options.buildDir, 'snapshot') + const storageDir = resolve(nitro.options.buildDir, "snapshot"); nitro.options.serverAssets.push({ - baseName: 'nitro:bundled', - dir: storageDir - }) - - const data = await snapshotStorage(nitro) - await Promise.all(Object.entries(data).map(async ([path, contents]) => { - if (typeof contents !== 'string') { contents = JSON.stringify(contents) } - const fsPath = join(storageDir, path.replace(/:/g, '/')) - await fsp.mkdir(dirname(fsPath), { recursive: true }) - await fsp.writeFile(fsPath, contents, 'utf8') - })) + baseName: "nitro:bundled", + dir: storageDir, + }); + + const data = await snapshotStorage(nitro); + await Promise.all( + Object.entries(data).map(async ([path, contents]) => { + if (typeof contents !== "string") { + contents = JSON.stringify(contents); + } + const fsPath = join(storageDir, path.replace(/:/g, "/")); + await fsp.mkdir(dirname(fsPath), { recursive: true }); + await fsp.writeFile(fsPath, contents, "utf8"); + }) + ); } -async function _build (nitro: Nitro, rollupConfig: RollupConfig) { - await scanHandlers(nitro) - await writeTypes(nitro) - await _snapshot(nitro) +async function _build(nitro: Nitro, rollupConfig: RollupConfig) { + await scanHandlers(nitro); + await writeTypes(nitro); + await _snapshot(nitro); - nitro.logger.info(`Building Nitro Server (preset: \`${nitro.options.preset}\`)`) + nitro.logger.info( + `Building Nitro Server (preset: \`${nitro.options.preset}\`)` + ); const build = await rollup.rollup(rollupConfig).catch((error) => { - nitro.logger.error(formatRollupError(error)) - throw error - }) + nitro.logger.error(formatRollupError(error)); + throw error; + }); - await build.write(rollupConfig.output) + await build.write(rollupConfig.output); // Write build info - const nitroConfigPath = resolve(nitro.options.output.dir, 'nitro.json') + const nitroConfigPath = resolve(nitro.options.output.dir, "nitro.json"); const buildInfo = { date: new Date(), preset: nitro.options.preset, commands: { preview: nitro.options.commands.preview, - deploy: nitro.options.commands.deploy - } - } - await writeFile(nitroConfigPath, JSON.stringify(buildInfo, null, 2)) + deploy: nitro.options.commands.deploy, + }, + }; + await writeFile(nitroConfigPath, JSON.stringify(buildInfo, null, 2)); - nitro.logger.success('Nitro server built') + nitro.logger.success("Nitro server built"); if (nitro.options.logLevel > 1) { - await printFSTree(nitro.options.output.serverDir) + await printFSTree(nitro.options.output.serverDir); } - await nitro.hooks.callHook('compiled', nitro) + await nitro.hooks.callHook("compiled", nitro); // Show deploy and preview hints - const rOutput = relative(process.cwd(), nitro.options.output.dir) + const rOutput = relative(process.cwd(), nitro.options.output.dir); const rewriteRelativePaths = (input: string) => { - return input.replace(/\s\.\/([^\s]*)/g, ` ${rOutput}/$1`) - } + return input.replace(/\s\.\/(\S*)/g, ` ${rOutput}/$1`); + }; if (buildInfo.commands.preview) { - nitro.logger.success(`You can preview this build using \`${rewriteRelativePaths(buildInfo.commands.preview)}\``) + nitro.logger.success( + `You can preview this build using \`${rewriteRelativePaths( + buildInfo.commands.preview + )}\`` + ); } if (buildInfo.commands.deploy) { - nitro.logger.success(`You can deploy this build using \`${rewriteRelativePaths(buildInfo.commands.deploy)}\``) + nitro.logger.success( + `You can deploy this build using \`${rewriteRelativePaths( + buildInfo.commands.deploy + )}\`` + ); } } -function startRollupWatcher (nitro: Nitro, rollupConfig: RollupConfig) { - const watcher = rollup.watch(defu(rollupConfig, { - watch: { - chokidar: nitro.options.watchOptions - } - })) - let start: number +function startRollupWatcher(nitro: Nitro, rollupConfig: RollupConfig) { + const watcher = rollup.watch( + defu(rollupConfig, { + watch: { + chokidar: nitro.options.watchOptions, + }, + }) + ); + let start: number; - watcher.on('event', (event) => { + watcher.on("event", (event) => { switch (event.code) { // The watcher is (re)starting - case 'START': - return + case "START": + return; // Building an individual bundle - case 'BUNDLE_START': - start = Date.now() - return + case "BUNDLE_START": + start = Date.now(); + return; // Finished building all bundles - case 'END': - nitro.hooks.callHook('compiled', nitro) - nitro.logger.success('Nitro built', start ? `in ${Date.now() - start} ms` : '') - nitro.hooks.callHook('dev:reload') - return + case "END": + nitro.hooks.callHook("compiled", nitro); + nitro.logger.success( + "Nitro built", + start ? `in ${Date.now() - start} ms` : "" + ); + nitro.hooks.callHook("dev:reload"); + return; // Encountered an error while bundling - case 'ERROR': - nitro.logger.error(formatRollupError(event.error)) + case "ERROR": + nitro.logger.error(formatRollupError(event.error)); } - }) - return watcher + }); + return watcher; } -async function _watch (nitro: Nitro, rollupConfig: RollupConfig) { - let rollupWatcher: rollup.RollupWatcher +async function _watch(nitro: Nitro, rollupConfig: RollupConfig) { + let rollupWatcher: rollup.RollupWatcher; const reload = debounce(async () => { - if (rollupWatcher) { await rollupWatcher.close() } - await scanHandlers(nitro) - rollupWatcher = startRollupWatcher(nitro, rollupConfig) - await writeTypes(nitro) - }) - - const watchPatterns = nitro.options.scanDirs.flatMap(dir => [ - join(dir, 'api'), - join(dir, 'routes'), - join(dir, 'middleware', GLOB_SCAN_PATTERN) - ]) - - const watchReloadEvents = new Set(['add', 'addDir', 'unlink', 'unlinkDir']) - const reloadWacher = watch(watchPatterns, { ignoreInitial: true }).on('all', (event) => { - if (watchReloadEvents.has(event)) { - reload() + if (rollupWatcher) { + await rollupWatcher.close(); + } + await scanHandlers(nitro); + rollupWatcher = startRollupWatcher(nitro, rollupConfig); + await writeTypes(nitro); + }); + + const watchPatterns = nitro.options.scanDirs.flatMap((dir) => [ + join(dir, "api"), + join(dir, "routes"), + join(dir, "middleware", GLOB_SCAN_PATTERN), + ]); + + const watchReloadEvents = new Set(["add", "addDir", "unlink", "unlinkDir"]); + const reloadWacher = watch(watchPatterns, { ignoreInitial: true }).on( + "all", + (event) => { + if (watchReloadEvents.has(event)) { + reload(); + } } - }) + ); - nitro.hooks.hook('close', () => { - rollupWatcher.close() - reloadWacher.close() - }) + nitro.hooks.hook("close", () => { + rollupWatcher.close(); + reloadWacher.close(); + }); - await reload() + await reload(); } -function formatRollupError (_error: RollupError | OnResolveResult) { +function formatRollupError(_error: RollupError | OnResolveResult) { try { - const logs: string[] = [] - for (const error of ('errors' in _error ? _error.errors : [_error as RollupError])) { - const id = (error as any).path || error.id || (_error as RollupError).id - let path = isAbsolute(id) ? relative(process.cwd(), id) : id - const location = (error as RollupError).loc || (error as PartialMessage).location + const logs: string[] = []; + for (const error of "errors" in _error + ? _error.errors + : [_error as RollupError]) { + const id = (error as any).path || error.id || (_error as RollupError).id; + let path = isAbsolute(id) ? relative(process.cwd(), id) : id; + const location = + (error as RollupError).loc || (error as PartialMessage).location; if (location) { - path += `:${location.line}:${location.column}` + path += `:${location.line}:${location.column}`; } - const text = (error as PartialMessage).text || (error as RollupError).frame - logs.push(`Rollup error while processing \`${path}\`` + text ? '\n\n' + text : '') + const text = + (error as PartialMessage).text || (error as RollupError).frame; + logs.push( + `Rollup error while processing \`${path}\`` + text ? "\n\n" + text : "" + ); } - return logs.join('\n\n') + return logs.join("\n\n"); } catch { - return _error?.toString() + return _error?.toString(); } } diff --git a/src/cli.ts b/src/cli.ts index 8dad53c5a8..eafebec635 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,48 +1,49 @@ #!/usr/bin/env node -import mri from 'mri' -import { resolve } from 'pathe' -import { createNitro } from './nitro' -import { build, prepare, copyPublicAssets } from './build' -import { prerender } from './prerender' -import { createDevServer } from './dev/server' +import mri from "mri"; +import { resolve } from "pathe"; +import { createNitro } from "./nitro"; +import { build, prepare, copyPublicAssets } from "./build"; +import { prerender } from "./prerender"; +import { createDevServer } from "./dev/server"; -async function main () { - const args = mri(process.argv.slice(2)) - const command = args._[0] - const rootDir = resolve(args._[1] || '.') +async function main() { + const args = mri(process.argv.slice(2)); + const command = args._[0]; + const rootDir = resolve(args._[1] || "."); - if (command === 'dev') { + if (command === "dev") { const nitro = await createNitro({ rootDir, dev: true, - preset: 'nitro-dev' - }) - const server = createDevServer(nitro) - await server.listen({}) - await prepare(nitro) - await build(nitro) - return + preset: "nitro-dev", + }); + const server = createDevServer(nitro); + await server.listen({}); + await prepare(nitro); + await build(nitro); + return; } - if (command === 'build') { + if (command === "build") { const nitro = await createNitro({ rootDir, - dev: false - }) - await prepare(nitro) - await copyPublicAssets(nitro) - await prerender(nitro) - await build(nitro) - await nitro.close() - process.exit(0) + dev: false, + }); + await prepare(nitro); + await copyPublicAssets(nitro); + await prerender(nitro); + await build(nitro); + await nitro.close(); + process.exit(0); } - console.error(`Unknown command ${command}! Usage: nitro dev|build [rootDir]`) - process.exit(1) + console.error(`Unknown command ${command}! Usage: nitro dev|build [rootDir]`); + process.exit(1); } +// eslint-disable-next-line unicorn/prefer-top-level-await main().catch((err) => { - console.error(err) - process.exit(1) -}) + console.error(err); + process.exit(1); +}); diff --git a/src/compress.ts b/src/compress.ts index 229f211add..6f74109f59 100644 --- a/src/compress.ts +++ b/src/compress.ts @@ -1,65 +1,76 @@ -import zlib from 'node:zlib' -import fsp from 'node:fs/promises' -import { existsSync } from 'node:fs' -import { globby } from 'globby' -import { resolve } from 'pathe' -import mime from 'mime' -import type { Nitro } from './types' +import zlib from "node:zlib"; +import fsp from "node:fs/promises"; +import { existsSync } from "node:fs"; +import { globby } from "globby"; +import { resolve } from "pathe"; +import mime from "mime"; +import type { Nitro } from "./types"; -export async function compressPublicAssets (nitro: Nitro) { - const publicFiles = await globby('**', { +export async function compressPublicAssets(nitro: Nitro) { + const publicFiles = await globby("**", { cwd: nitro.options.output.publicDir, absolute: false, dot: true, - ignore: ['**/*.gz', '**/*.br'] - }) + ignore: ["**/*.gz", "**/*.br"], + }); for (const fileName of publicFiles) { - const filePath = resolve(nitro.options.output.publicDir, fileName) - const fileContents = await fsp.readFile(filePath) - if (existsSync(filePath + '.gz') || existsSync(filePath + '.br')) { - continue + const filePath = resolve(nitro.options.output.publicDir, fileName); + const fileContents = await fsp.readFile(filePath); + if (existsSync(filePath + ".gz") || existsSync(filePath + ".br")) { + continue; } - const mimeType = mime.getType(fileName) || 'text/plain' + const mimeType = mime.getType(fileName) || "text/plain"; - if (fileContents.length < 1024 || fileName.endsWith('.map') || !isCompressableMime(mimeType)) { - continue + if ( + fileContents.length < 1024 || + fileName.endsWith(".map") || + !isCompressableMime(mimeType) + ) { + continue; } - const { gzip, brotli } = nitro.options.compressPublicAssets || {} as any + const { gzip, brotli } = nitro.options.compressPublicAssets || ({} as any); - const encodings = [gzip !== false && 'gzip', brotli !== false && 'br'].filter(Boolean) + const encodings = [ + gzip !== false && "gzip", + brotli !== false && "br", + ].filter(Boolean); for (const encoding of encodings) { - const suffix = '.' + (encoding === 'gzip' ? 'gz' : 'br') - const compressedPath = filePath + suffix - if (existsSync(compressedPath)) { continue } - const gzipOptions = { level: zlib.constants.Z_BEST_COMPRESSION } + const suffix = "." + (encoding === "gzip" ? "gz" : "br"); + const compressedPath = filePath + suffix; + if (existsSync(compressedPath)) { + continue; + } + const gzipOptions = { level: zlib.constants.Z_BEST_COMPRESSION }; const brotliOptions = { [zlib.constants.BROTLI_PARAM_MODE]: isTextMime(mimeType) ? zlib.constants.BROTLI_MODE_TEXT : zlib.constants.BROTLI_MODE_GENERIC, - [zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY, - [zlib.constants.BROTLI_PARAM_SIZE_HINT]: fileContents.length - } + [zlib.constants.BROTLI_PARAM_QUALITY]: + zlib.constants.BROTLI_MAX_QUALITY, + [zlib.constants.BROTLI_PARAM_SIZE_HINT]: fileContents.length, + }; const compressedBuff: Buffer = await new Promise((resolve, reject) => { - const cb = (error, result: Buffer) => error ? reject(error) : resolve(result) - if (encoding === 'gzip') { - zlib.gzip(fileContents, gzipOptions, cb) + const cb = (error, result: Buffer) => + error ? reject(error) : resolve(result); + if (encoding === "gzip") { + zlib.gzip(fileContents, gzipOptions, cb); } else { - zlib.brotliCompress(fileContents, brotliOptions, cb) + zlib.brotliCompress(fileContents, brotliOptions, cb); } - }) - await fsp.writeFile(compressedPath, compressedBuff) + }); + await fsp.writeFile(compressedPath, compressedBuff); } } } -function isTextMime (mimeType: string) { - return /text|javascript|json|xml/.test(mimeType) +function isTextMime(mimeType: string) { + return /text|javascript|json|xml/.test(mimeType); } -function isCompressableMime (mimeType: string) { - return /image|text|font|json|xml|javascript/.test(mimeType) +function isCompressableMime(mimeType: string) { + return /image|text|font|json|xml|javascript/.test(mimeType); } diff --git a/src/dev/error.ts b/src/dev/error.ts index 9e540b14bc..12818f0101 100644 --- a/src/dev/error.ts +++ b/src/dev/error.ts @@ -1,18 +1,19 @@ -import { NitroErrorHandler } from '../types' +import { NitroErrorHandler } from "../types"; -function errorHandler (error, event) { - event.res.setHeader('Content-Type', 'text/html; charset=UTF-8') - event.res.statusCode = 503 - event.res.statusMessage = 'Server Unavailable' +function errorHandler(error, event) { + event.res.setHeader("Content-Type", "text/html; charset=UTF-8"); + event.res.statusCode = 503; + event.res.statusMessage = "Server Unavailable"; - let body - let title + let body; + let title; if (error) { - title = `${event.res.statusCode} ${event.res.statusMessage}` - body = `
    ${error.stack}
    ` + title = `${event.res.statusCode} ${event.res.statusMessage}`; + body = `
    ${error.stack}
    `; } else { - title = 'Reloading server...' - body = '' + title = "Reloading server..."; + body = + ""; } event.res.end(` @@ -20,7 +21,7 @@ function errorHandler (error, event) { - ${error ? '' : ''} + ${error ? "" : ''} ${title} @@ -38,7 +39,7 @@ function errorHandler (error, event) { -`) +`); } -export default errorHandler as NitroErrorHandler +export default errorHandler as NitroErrorHandler; diff --git a/src/dev/server.ts b/src/dev/server.ts index a211b01eb8..44a5d8e9cb 100644 --- a/src/dev/server.ts +++ b/src/dev/server.ts @@ -1,208 +1,242 @@ -import { Worker } from 'worker_threads' -import { existsSync, promises as fsp } from 'fs' -import { debounce } from 'perfect-debounce' -import { App, createApp, eventHandler, fromNodeMiddleware, H3Error, H3Event, toNodeListener } from 'h3' -import httpProxy, { ServerOptions as HTTPProxyOptions } from 'http-proxy' -import { listen, Listener, ListenOptions } from 'listhen' -import { servePlaceholder } from 'serve-placeholder' -import serveStatic from 'serve-static' -import { resolve } from 'pathe' -import { joinURL } from 'ufo' -import { FSWatcher, watch } from 'chokidar' -import type { Nitro } from '../types' -import { createVFSHandler } from './vfs' -import defaultErrorHandler from './error' +import { Worker } from "node:worker_threads"; +import { existsSync, promises as fsp } from "node:fs"; +import { debounce } from "perfect-debounce"; +import { + App, + createApp, + eventHandler, + fromNodeMiddleware, + H3Error, + H3Event, + toNodeListener, +} from "h3"; +import httpProxy, { ServerOptions as HTTPProxyOptions } from "http-proxy"; +import { listen, Listener, ListenOptions } from "listhen"; +import { servePlaceholder } from "serve-placeholder"; +import serveStatic from "serve-static"; +import { resolve } from "pathe"; +import { joinURL } from "ufo"; +import { FSWatcher, watch } from "chokidar"; +import type { Nitro } from "../types"; +import { createVFSHandler } from "./vfs"; +import defaultErrorHandler from "./error"; export interface NitroWorker { - worker: Worker, - address: { host: string, port: number, socketPath?: string } + worker: Worker; + address: { host: string; port: number; socketPath?: string }; } export interface NitroDevServer { - reload: () => void, - listen: (port: ListenOptions['port'], opts?: Partial) => Promise, - app: App, - close: () => Promise, - watcher?: FSWatcher + reload: () => void; + listen: ( + port: ListenOptions["port"], + opts?: Partial + ) => Promise; + app: App; + close: () => Promise; + watcher?: FSWatcher; } -function initWorker (filename: string): Promise | null { +function initWorker(filename: string): Promise | null { if (!existsSync(filename)) { - return null + return null; } return new Promise((resolve, reject) => { - const worker = new Worker(filename) - worker.once('exit', (code) => { - reject(new Error(code ? '[worker] exited with code: ' + code : '[worker] exited')) - }) - worker.once('error', (err) => { - err.message = '[worker init] ' + err.message - reject(err) - }) + const worker = new Worker(filename); + worker.once("exit", (code) => { + reject( + new Error( + code ? "[worker] exited with code: " + code : "[worker] exited" + ) + ); + }); + worker.once("error", (err) => { + err.message = "[worker init] " + err.message; + reject(err); + }); const addressListener = (event) => { if (!event || !event.address) { - return + return; } - worker.off('message', addressListener) + worker.off("message", addressListener); resolve({ worker, - address: event.address - } as NitroWorker) - } - worker.on('message', addressListener) - }) + address: event.address, + } as NitroWorker); + }; + worker.on("message", addressListener); + }); } -async function killWorker (worker?: NitroWorker) { +async function killWorker(worker?: NitroWorker) { if (!worker) { - return + return; } if (worker.worker) { - worker.worker.removeAllListeners() - await worker.worker.terminate() - worker.worker = null + worker.worker.removeAllListeners(); + await worker.worker.terminate(); + worker.worker = null; } if (worker.address.socketPath && existsSync(worker.address.socketPath)) { - await fsp.rm(worker.address.socketPath) + await fsp.rm(worker.address.socketPath); } } -export function createDevServer (nitro: Nitro): NitroDevServer { +export function createDevServer(nitro: Nitro): NitroDevServer { // Worker - const workerEntry = resolve(nitro.options.output.dir, nitro.options.output.serverDir, 'index.mjs') + const workerEntry = resolve( + nitro.options.output.dir, + nitro.options.output.serverDir, + "index.mjs" + ); // Error handler - const errorHandler = nitro.options.devErrorHandler || defaultErrorHandler + const errorHandler = nitro.options.devErrorHandler || defaultErrorHandler; - let lastError: H3Error = null - let reloadPromise: Promise = null + let lastError: H3Error = null; + let reloadPromise: Promise = null; - let currentWorker: NitroWorker = null - async function _reload () { + let currentWorker: NitroWorker = null; + async function _reload() { // Kill old worker - const oldWorker = currentWorker - currentWorker = null - await killWorker(oldWorker) + const oldWorker = currentWorker; + currentWorker = null; + await killWorker(oldWorker); // Create a new worker - currentWorker = await initWorker(workerEntry) + currentWorker = await initWorker(workerEntry); } const reload = debounce(() => { - reloadPromise = _reload().then(() => { - lastError = null - }).catch((error) => { - console.error('[worker reload]', error) - lastError = error - }).finally(() => { - reloadPromise = null - }) - return reloadPromise - }) - nitro.hooks.hook('dev:reload', reload) + reloadPromise = _reload() + .then(() => { + lastError = null; + }) + .catch((error) => { + console.error("[worker reload]", error); + lastError = error; + }) + .finally(() => { + reloadPromise = null; + }); + return reloadPromise; + }); + nitro.hooks.hook("dev:reload", reload); // App - const app = createApp() + const app = createApp(); // Dev-only handlers for (const handler of nitro.options.devHandlers) { - app.use(handler.route || '/', handler.handler) + app.use(handler.route || "/", handler.handler); } // Debugging endpoint to view vfs - app.use('/_vfs', createVFSHandler(nitro)) + app.use("/_vfs", createVFSHandler(nitro)); // Serve asset dirs for (const asset of nitro.options.publicAssets) { - const url = joinURL(nitro.options.runtimeConfig.app.baseURL, asset.baseURL) - app.use(url, fromNodeMiddleware(serveStatic(asset.dir))) + const url = joinURL(nitro.options.runtimeConfig.app.baseURL, asset.baseURL); + app.use(url, fromNodeMiddleware(serveStatic(asset.dir))); if (!asset.fallthrough) { - app.use(url, fromNodeMiddleware(servePlaceholder())) + app.use(url, fromNodeMiddleware(servePlaceholder())); } } // User defined dev proxy for (const route of Object.keys(nitro.options.devProxy).sort().reverse()) { - let opts = nitro.options.devProxy[route] - if (typeof opts === 'string') { opts = { target: opts } } - const proxy = createProxy(opts) - app.use(route, eventHandler(async (event) => { - await proxy.handle(event) - })) + let opts = nitro.options.devProxy[route]; + if (typeof opts === "string") { + opts = { target: opts }; + } + const proxy = createProxy(opts); + app.use( + route, + eventHandler(async (event) => { + await proxy.handle(event); + }) + ); } // Main worker proxy - const proxy = createProxy() - proxy.proxy.on('proxyReq', (proxyReq, req) => { + const proxy = createProxy(); + proxy.proxy.on("proxyReq", (proxyReq, req) => { // TODO: Avoid overwriting headers if already set if (req.socket.remoteAddress) { - proxyReq.setHeader('X-Forwarded-For', req.socket.remoteAddress) + proxyReq.setHeader("X-Forwarded-For", req.socket.remoteAddress); } if (req.socket.remotePort) { - proxyReq.setHeader('X-Forwarded-Port', req.socket.remotePort) + proxyReq.setHeader("X-Forwarded-Port", req.socket.remotePort); } if (req.socket.remoteFamily) { - proxyReq.setHeader('X-Forwarded-Proto', req.socket.remoteFamily) - } - }) - app.use(eventHandler(async (event) => { - await reloadPromise - const address = currentWorker?.address - if (!address || (address.socketPath && !existsSync(address.socketPath))) { - return errorHandler(lastError, event) + proxyReq.setHeader("X-Forwarded-Proto", req.socket.remoteFamily); } - await proxy.handle(event, { target: address }).catch((err) => { - lastError = err - throw err + }); + app.use( + eventHandler(async (event) => { + await reloadPromise; + const address = currentWorker?.address; + if (!address || (address.socketPath && !existsSync(address.socketPath))) { + return errorHandler(lastError, event); + } + await proxy.handle(event, { target: address }).catch((err) => { + lastError = err; + throw err; + }); }) - })) + ); // Listen - let listeners: Listener[] = [] - const _listen: NitroDevServer['listen'] = async (port, opts?) => { - const listener = await listen(toNodeListener(app), { port, ...opts }) - listeners.push(listener) - return listener - } + let listeners: Listener[] = []; + const _listen: NitroDevServer["listen"] = async (port, opts?) => { + const listener = await listen(toNodeListener(app), { port, ...opts }); + listeners.push(listener); + return listener; + }; // Optional watcher - let watcher: FSWatcher = null - if (nitro.options.devServer.watch.length) { - watcher = watch(nitro.options.devServer.watch, nitro.options.watchOptions) - watcher - .on('add', reload) - .on('change', reload) + let watcher: FSWatcher = null; + if (nitro.options.devServer.watch.length > 0) { + watcher = watch(nitro.options.devServer.watch, nitro.options.watchOptions); + watcher.on("add", reload).on("change", reload); } // Close handler - async function close () { - if (watcher) { await watcher.close() } - await killWorker(currentWorker) - await Promise.all(listeners.map(l => l.close())) - listeners = [] + async function close() { + if (watcher) { + await watcher.close(); + } + await killWorker(currentWorker); + await Promise.all(listeners.map((l) => l.close())); + listeners = []; } - nitro.hooks.hook('close', close) + nitro.hooks.hook("close", close); return { reload, listen: _listen, app, close, - watcher - } + watcher, + }; } -function createProxy (defaults: HTTPProxyOptions = {}) { - const proxy = httpProxy.createProxy() +function createProxy(defaults: HTTPProxyOptions = {}) { + const proxy = httpProxy.createProxy(); const handle = (event: H3Event, opts: HTTPProxyOptions = {}) => { return new Promise((resolve, reject) => { - proxy.web(event.req, event.res, { ...defaults, ...opts }, (error: any) => { - if (error.code !== 'ECONNRESET') { - reject(error) + proxy.web( + event.req, + event.res, + { ...defaults, ...opts }, + (error: any) => { + if (error.code !== "ECONNRESET") { + reject(error); + } + resolve(); } - resolve() - }) - }) - } + ); + }); + }; return { proxy, - handle - } + handle, + }; } diff --git a/src/dev/vfs.ts b/src/dev/vfs.ts index 26188bac47..db57384963 100644 --- a/src/dev/vfs.ts +++ b/src/dev/vfs.ts @@ -1,50 +1,58 @@ -import { createError, eventHandler } from 'h3' -import type { Nitro } from '../types' +import { createError, eventHandler } from "h3"; +import type { Nitro } from "../types"; -export function createVFSHandler (nitro: Nitro) { +export function createVFSHandler(nitro: Nitro) { return eventHandler(async (event) => { const vfsEntries = { ...nitro.vfs, - ...nitro.options.virtual - } + ...nitro.options.virtual, + }; const items = Object.keys(vfsEntries) .map((key) => { - const linkClass = event.req.url === `/${encodeURIComponent(key)}` ? 'bg-gray-700 text-white' : 'hover:bg-gray-800 text-gray-200' - return `
  • ${key.replace(nitro.options.rootDir, '')}
  • ` + const linkClass = + event.req.url === `/${encodeURIComponent(key)}` + ? "bg-gray-700 text-white" + : "hover:bg-gray-800 text-gray-200"; + return `
  • ${key.replace( + nitro.options.rootDir, + "" + )}
  • `; }) - .join('\n') + .join("\n"); const files = `

    virtual files

      ${items}
    - ` + `; - const id = decodeURIComponent(event.req.url?.slice(1) || '') + const id = decodeURIComponent(event.req.url?.slice(1) || ""); - let file = '' + let file = ""; if (id in vfsEntries) { - let contents = vfsEntries[id] - if (typeof contents === 'function') { - contents = await contents() + let contents = vfsEntries[id]; + if (typeof contents === "function") { + contents = await contents(); } file = editorTemplate({ readOnly: true, - language: id.endsWith('html') ? 'html' : 'javascript', - theme: 'vs-dark', + language: id.endsWith("html") ? "html" : "javascript", + theme: "vs-dark", value: contents, - wordWrap: 'wordWrapColumn', - wordWrapColumn: 80 - }) + wordWrap: "wordWrapColumn", + wordWrapColumn: 80, + }); } else if (id) { - throw createError({ message: 'File not found', statusCode: 404 }) + throw createError({ message: "File not found", statusCode: 404 }); } else { file = `

    Select a virtual file to inspect

    - ` + `; } return ` @@ -60,13 +68,13 @@ export function createVFSHandler (nitro: Nitro) { ${file} -` - }) +`; + }); } -const monacoVersion = '0.30.0' -const monacoUrl = `https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/${monacoVersion}/min` -const vsUrl = `${monacoUrl}/vs` +const monacoVersion = "0.30.0"; +const monacoUrl = `https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/${monacoVersion}/min`; +const vsUrl = `${monacoUrl}/vs`; const editorTemplate = (options: Record) => `
    @@ -81,7 +89,9 @@ const editorTemplate = (options: Record) => ` window.MonacoEnvironment = { getWorkerUrl: () => proxy } require(['vs/editor/editor.main'], function () { - monaco.editor.create(document.getElementById('editor'), ${JSON.stringify(options)}) + monaco.editor.create(document.getElementById('editor'), ${JSON.stringify( + options + )}) }) -` +`; diff --git a/src/dirs.ts b/src/dirs.ts index b567e84654..d3d27af869 100644 --- a/src/dirs.ts +++ b/src/dirs.ts @@ -1,9 +1,9 @@ -import { fileURLToPath } from 'url' -import { dirname, resolve } from 'pathe' +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "pathe"; -let distDir = dirname(fileURLToPath(import.meta.url)) +let distDir = dirname(fileURLToPath(import.meta.url)); if (/(chunks|shared)$/.test(distDir)) { - distDir = dirname(distDir) + distDir = dirname(distDir); } -export const pkgDir = resolve(distDir, '..') -export const runtimeDir = resolve(distDir, 'runtime') +export const pkgDir = resolve(distDir, ".."); +export const runtimeDir = resolve(distDir, "runtime"); diff --git a/src/imports.ts b/src/imports.ts index 805eb2d5e4..e153232c36 100644 --- a/src/imports.ts +++ b/src/imports.ts @@ -1,20 +1,20 @@ -import type { Preset } from 'unimport' +import type { Preset } from "unimport"; export const nitroImports: Preset[] = [ { - from: '#internal/nitro', + from: "#internal/nitro", imports: [ - 'defineCachedFunction', - 'defineCachedEventHandler', - 'cachedFunction', - 'cachedEventHandler', - 'useRuntimeConfig', - 'useStorage', - 'useNitroApp', - 'defineNitroPlugin', - 'nitroPlugin', - 'defineRenderHandler', - 'getRouteRules' - ] - } -] + "defineCachedFunction", + "defineCachedEventHandler", + "cachedFunction", + "cachedEventHandler", + "useRuntimeConfig", + "useStorage", + "useNitroApp", + "defineNitroPlugin", + "nitroPlugin", + "defineRenderHandler", + "getRouteRules", + ], + }, +]; diff --git a/src/index.ts b/src/index.ts index 3f2730857b..84ecc1e4d8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ -export * from './build' -export * from './nitro' -export * from './scan' -export * from './dev/server' -export * from './options' -export * from './types' -export * from './prerender' -export * from './preset' +export * from "./build"; +export * from "./nitro"; +export * from "./scan"; +export * from "./dev/server"; +export * from "./options"; +export * from "./types"; +export * from "./prerender"; +export * from "./preset"; diff --git a/src/nitro.ts b/src/nitro.ts index 705bb0d7fd..773a390b0b 100644 --- a/src/nitro.ts +++ b/src/nitro.ts @@ -1,82 +1,86 @@ -import { existsSync } from 'fs' -import { resolve } from 'pathe' -import { createHooks, createDebugger } from 'hookable' -import { createUnimport } from 'unimport' -import consola from 'consola' -import type { NitroConfig, Nitro } from './types' -import { loadOptions } from './options' -import { scanPlugins } from './scan' -import { createStorage } from './storage' +import { existsSync } from "node:fs"; +import { resolve } from "pathe"; +import { createHooks, createDebugger } from "hookable"; +import { createUnimport } from "unimport"; +import consola from "consola"; +import type { NitroConfig, Nitro } from "./types"; +import { loadOptions } from "./options"; +import { scanPlugins } from "./scan"; +import { createStorage } from "./storage"; -export async function createNitro (config: NitroConfig = {}): Promise { +export async function createNitro(config: NitroConfig = {}): Promise { // Resolve options - const options = await loadOptions(config) + const options = await loadOptions(config); // Create context const nitro: Nitro = { options, hooks: createHooks(), vfs: {}, - logger: consola.withTag('nitro'), + logger: consola.withTag("nitro"), scannedHandlers: [], - close: () => nitro.hooks.callHook('close'), - storage: undefined - } + close: () => nitro.hooks.callHook("close"), + storage: undefined, + }; // Storage - nitro.storage = await createStorage(nitro) - nitro.hooks.hook('close', async () => { await nitro.storage.dispose() }) + nitro.storage = await createStorage(nitro); + nitro.hooks.hook("close", async () => { + await nitro.storage.dispose(); + }); if (nitro.options.debug) { - createDebugger(nitro.hooks, { tag: 'nitro' }) - nitro.options.plugins.push('#internal/nitro/debug') + createDebugger(nitro.hooks, { tag: "nitro" }); + nitro.options.plugins.push("#internal/nitro/debug"); } // Logger config if (nitro.options.logLevel !== undefined) { - nitro.logger.level = nitro.options.logLevel + nitro.logger.level = nitro.options.logLevel; } // Init hooks - nitro.hooks.addHooks(nitro.options.hooks) + nitro.hooks.addHooks(nitro.options.hooks); // Public assets for (const dir of options.scanDirs) { - const publicDir = resolve(dir, 'public') - if (!existsSync(publicDir)) { continue } - if (options.publicAssets.find(asset => asset.dir === publicDir)) { - continue + const publicDir = resolve(dir, "public"); + if (!existsSync(publicDir)) { + continue; + } + if (options.publicAssets.some((asset) => asset.dir === publicDir)) { + continue; } - options.publicAssets.push({ dir: publicDir } as any) + options.publicAssets.push({ dir: publicDir } as any); } for (const asset of options.publicAssets) { - asset.baseURL = asset.baseURL || '/' - const isTopLevel = asset.baseURL === '/' - asset.fallthrough = asset.fallthrough ?? isTopLevel - asset.maxAge = asset.maxAge ?? (isTopLevel ? 0 : 60) + asset.baseURL = asset.baseURL || "/"; + const isTopLevel = asset.baseURL === "/"; + asset.fallthrough = asset.fallthrough ?? isTopLevel; + asset.maxAge = asset.maxAge ?? (isTopLevel ? 0 : 60); } // Server assets nitro.options.serverAssets.push({ - baseName: 'server', - dir: resolve(nitro.options.srcDir, 'assets') - }) + baseName: "server", + dir: resolve(nitro.options.srcDir, "assets"), + }); // Plugins - const scannedPlugins = await scanPlugins(nitro) + const scannedPlugins = await scanPlugins(nitro); for (const plugin of scannedPlugins) { - if (!nitro.options.plugins.find(p => p === plugin)) { - nitro.options.plugins.push(plugin) + if (!nitro.options.plugins.includes(plugin)) { + nitro.options.plugins.push(plugin); } } if (nitro.options.imports) { - nitro.unimport = createUnimport(nitro.options.imports) + nitro.unimport = createUnimport(nitro.options.imports); // Support for importing from '#imports' - nitro.options.virtual['#imports'] = () => nitro.unimport.toExports() + nitro.options.virtual["#imports"] = () => nitro.unimport.toExports(); // Backward compatibility - nitro.options.virtual['#nitro'] = 'export * from "#imports"' + nitro.options.virtual["#nitro"] = 'export * from "#imports"'; } - return nitro + return nitro; } diff --git a/src/options.ts b/src/options.ts index 69a19b234e..605614d60a 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,19 +1,24 @@ -import { pathToFileURL } from 'node:url' -import { resolve, join, isAbsolute } from 'pathe' -import { loadConfig } from 'c12' -import { klona } from 'klona/full' -import { camelCase } from 'scule' -import { defu } from 'defu' -import { resolveModuleExportNames, resolvePath as resolveModule } from 'mlly' +import { pathToFileURL } from "node:url"; +import { resolve, join, isAbsolute } from "pathe"; +import { loadConfig } from "c12"; +import { klona } from "klona/full"; +import { camelCase } from "scule"; +import { defu } from "defu"; +import { resolveModuleExportNames, resolvePath as resolveModule } from "mlly"; // import escapeRE from 'escape-string-regexp' -import { withLeadingSlash, withoutTrailingSlash, withTrailingSlash } from 'ufo' -import { isTest, isDebug } from 'std-env' -import { findWorkspaceDir } from 'pkg-types' -import { resolvePath, detectTarget } from './utils' -import type { NitroConfig, NitroOptions, NitroRouteConfig, NitroRouteRules } from './types' -import { runtimeDir, pkgDir } from './dirs' -import * as _PRESETS from './presets' -import { nitroImports } from './imports' +import { withLeadingSlash, withoutTrailingSlash, withTrailingSlash } from "ufo"; +import { isTest, isDebug } from "std-env"; +import { findWorkspaceDir } from "pkg-types"; +import { resolvePath, detectTarget } from "./utils"; +import type { + NitroConfig, + NitroOptions, + NitroRouteConfig, + NitroRouteRules, +} from "./types"; +import { runtimeDir, pkgDir } from "./dirs"; +import * as _PRESETS from "./presets"; +import { nitroImports } from "./imports"; const NitroDefaults: NitroConfig = { // General @@ -23,11 +28,11 @@ const NitroDefaults: NitroConfig = { // Dirs scanDirs: [], - buildDir: '.nitro', + buildDir: ".nitro", output: { - dir: '{{ rootDir }}/.output', - serverDir: '{{ output.dir }}/server', - publicDir: '{{ output.dir }}/public' + dir: "{{ rootDir }}/.output", + serverDir: "{{ output.dir }}/server", + publicDir: "{{ output.dir }}/public", }, // Featueres @@ -39,8 +44,8 @@ const NitroDefaults: NitroConfig = { serverAssets: [], plugins: [], imports: { - exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/], - presets: nitroImports + exclude: [/[/\\]node_modules[/\\]/, /[/\\]\.git[/\\]/], + presets: nitroImports, }, virtual: {}, compressPublicAssets: false, @@ -52,27 +57,27 @@ const NitroDefaults: NitroConfig = { devProxy: {}, // Routing - baseURL: process.env.NITRO_APP_BASE_URL || '/', + baseURL: process.env.NITRO_APP_BASE_URL || "/", handlers: [], devHandlers: [], - errorHandler: '#internal/nitro/error', + errorHandler: "#internal/nitro/error", routeRules: {}, prerender: { crawlLinks: false, ignore: [], - routes: [] + routes: [], }, // Rollup alias: { - '#internal/nitro': runtimeDir + "#internal/nitro": runtimeDir, }, unenv: {}, analyze: false, moduleSideEffects: [ - 'unenv/runtime/polyfill/', - 'node-fetch-native/polyfill', - 'node-fetch-native/dist/polyfill' + "unenv/runtime/polyfill/", + "node-fetch-native/polyfill", + "node-fetch-native/dist/polyfill", ], replace: {}, node: true, @@ -81,180 +86,208 @@ const NitroDefaults: NitroConfig = { // Advanced typescript: { generateTsConfig: true, - internalPaths: false + internalPaths: false, }, nodeModulesDirs: [], hooks: {}, - commands: {} -} + commands: {}, +}; -export async function loadOptions (configOverrides: NitroConfig = {}): Promise { +export async function loadOptions( + configOverrides: NitroConfig = {} +): Promise { // Preset - let presetOverride = configOverrides.preset || process.env.NITRO_PRESET - const defaultPreset = detectTarget() || 'node-server' + let presetOverride = configOverrides.preset || process.env.NITRO_PRESET; + const defaultPreset = detectTarget() || "node-server"; if (configOverrides.dev) { - presetOverride = 'nitro-dev' + presetOverride = "nitro-dev"; } // Load configuration and preset - configOverrides = klona(configOverrides) + configOverrides = klona(configOverrides); const { config, layers } = await loadConfig({ - name: 'nitro', + name: "nitro", cwd: configOverrides.rootDir, dotenv: configOverrides.dev, - extend: { extendKey: ['extends', 'preset'] }, + extend: { extendKey: ["extends", "preset"] }, overrides: { ...configOverrides, - preset: presetOverride + preset: presetOverride, }, defaultConfig: { - preset: defaultPreset + preset: defaultPreset, }, defaults: NitroDefaults, - resolve (id: string) { - const presets = _PRESETS as any as Map - let matchedPreset = presets[camelCase(id)] || presets[id] + resolve(id: string) { + const presets = _PRESETS as any as Map; + let matchedPreset = presets[camelCase(id)] || presets[id]; if (!matchedPreset) { - return null + return null; } - if (typeof matchedPreset === 'function') { - matchedPreset = matchedPreset() + if (typeof matchedPreset === "function") { + matchedPreset = matchedPreset(); } return { - config: matchedPreset - } - } - }) - const options = klona(config) as NitroOptions - options._config = configOverrides + config: matchedPreset, + }; + }, + }); + const options = klona(config) as NitroOptions; + options._config = configOverrides; - options.preset = presetOverride || layers.find(l => l.config.preset)?.config.preset || defaultPreset + options.preset = + presetOverride || + layers.find((l) => l.config.preset)?.config.preset || + defaultPreset; - options.rootDir = resolve(options.rootDir || '.') - options.workspaceDir = await findWorkspaceDir(options.rootDir).catch(() => options.rootDir) - options.srcDir = resolve(options.srcDir || options.rootDir) - for (const key of ['srcDir', 'publicDir', 'buildDir']) { - options[key] = resolve(options.rootDir, options[key]) + options.rootDir = resolve(options.rootDir || "."); + options.workspaceDir = await findWorkspaceDir(options.rootDir).catch( + () => options.rootDir + ); + options.srcDir = resolve(options.srcDir || options.rootDir); + for (const key of ["srcDir", "publicDir", "buildDir"]) { + options[key] = resolve(options.rootDir, options[key]); } // Add aliases options.alias = { ...options.alias, - '~/': join(options.srcDir, '/'), - '@/': join(options.srcDir, '/'), - '~~/': join(options.rootDir, '/'), - '@@/': join(options.rootDir, '/') - } + "~/": join(options.srcDir, "/"), + "@/": join(options.srcDir, "/"), + "~~/": join(options.rootDir, "/"), + "@@/": join(options.rootDir, "/"), + }; // Resolve possibly template paths if (!options.entry) { - throw new Error(`Nitro entry is missing! Is "${options.preset}" preset correct?`) + throw new Error( + `Nitro entry is missing! Is "${options.preset}" preset correct?` + ); } - options.entry = resolvePath(options.entry, options) - options.output.dir = resolvePath(options.output.dir || NitroDefaults.output.dir, options) - options.output.publicDir = resolvePath(options.output.publicDir || NitroDefaults.output.publicDir, options) - options.output.serverDir = resolvePath(options.output.serverDir || NitroDefaults.output.serverDir, options) + options.entry = resolvePath(options.entry, options); + options.output.dir = resolvePath( + options.output.dir || NitroDefaults.output.dir, + options + ); + options.output.publicDir = resolvePath( + options.output.publicDir || NitroDefaults.output.publicDir, + options + ); + options.output.serverDir = resolvePath( + options.output.serverDir || NitroDefaults.output.serverDir, + options + ); - options.nodeModulesDirs.push(resolve(options.workspaceDir, 'node_modules')) - options.nodeModulesDirs.push(resolve(options.rootDir, 'node_modules')) - options.nodeModulesDirs.push(resolve(pkgDir, 'node_modules')) - options.nodeModulesDirs = Array.from(new Set(options.nodeModulesDirs.map(dir => resolve(options.rootDir, dir)))) + options.nodeModulesDirs.push(resolve(options.workspaceDir, "node_modules")); + options.nodeModulesDirs.push(resolve(options.rootDir, "node_modules")); + options.nodeModulesDirs.push(resolve(pkgDir, "node_modules")); + options.nodeModulesDirs = [ + ...new Set( + options.nodeModulesDirs.map((dir) => resolve(options.rootDir, dir)) + ), + ]; - if (!options.scanDirs.length) { - options.scanDirs = [options.srcDir] + if (options.scanDirs.length === 0) { + options.scanDirs = [options.srcDir]; } if (options.imports && Array.isArray(options.imports.exclude)) { - options.imports.exclude.push(options.buildDir) + options.imports.exclude.push(options.buildDir); } // Normalise absolute auto-import paths for windows machines if (options.imports && options.dev) { - options.imports.imports = options.imports.imports || [] + options.imports.imports = options.imports.imports || []; for (const entry of options.imports.imports) { if (isAbsolute(entry.from)) { - entry.from = pathToFileURL(entry.from).href + entry.from = pathToFileURL(entry.from).href; } } } // Add h3 auto imports preset if (options.imports) { - const h3Exports = await resolveModuleExportNames('h3', { url: import.meta.url }) + const h3Exports = await resolveModuleExportNames("h3", { + url: import.meta.url, + }); options.imports.presets.push({ - from: 'h3', - imports: h3Exports.filter(n => !n.match(/^[A-Z]/) && n !== 'use') - }) + from: "h3", + imports: h3Exports.filter((n) => !/^[A-Z]/.test(n) && n !== "use"), + }); } // Backward compatibility for options.routes - options.routeRules = defu(options.routeRules, (options as any).routes || {}) + options.routeRules = defu(options.routeRules, (options as any).routes || {}); // Normalize route rules (NitroRouteConfig => NitroRouteRules) - const normalizedRules: { [p: string]: NitroRouteRules } = {} + const normalizedRules: { [p: string]: NitroRouteRules } = {}; for (const path in options.routeRules) { - const routeConfig = options.routeRules[path] as NitroRouteConfig + const routeConfig = options.routeRules[path] as NitroRouteConfig; const routeRules: NitroRouteRules = { ...routeConfig, - redirect: undefined - } + redirect: undefined, + }; // Redirect if (routeConfig.redirect) { routeRules.redirect = { - to: '/', + to: "/", statusCode: 307, - ...(typeof routeConfig.redirect === 'string' ? { to: routeConfig.redirect } : routeConfig.redirect) - } + ...(typeof routeConfig.redirect === "string" + ? { to: routeConfig.redirect } + : routeConfig.redirect), + }; } // CORS if (routeConfig.cors) { routeRules.headers = { - 'access-control-allow-origin': '*', - 'access-control-allowed-methods': '*', - 'access-control-allow-headers': '*', - 'access-control-max-age': '0', - ...routeRules.headers - } + "access-control-allow-origin": "*", + "access-control-allowed-methods": "*", + "access-control-allow-headers": "*", + "access-control-max-age": "0", + ...routeRules.headers, + }; } // Cache: swr if (routeConfig.swr) { - routeRules.cache = routeRules.cache || {} - routeRules.cache.swr = true - if (typeof routeConfig.swr === 'number') { - routeRules.cache.maxAge = routeConfig.swr + routeRules.cache = routeRules.cache || {}; + routeRules.cache.swr = true; + if (typeof routeConfig.swr === "number") { + routeRules.cache.maxAge = routeConfig.swr; } } // Cache: static if (routeConfig.static) { - routeRules.cache = routeRules.cache || {} - routeRules.cache.static = true + routeRules.cache = routeRules.cache || {}; + routeRules.cache.static = true; } // Cache: false if (routeConfig.cache === false) { - routeRules.cache = false + routeRules.cache = false; } - normalizedRules[path] = routeRules + normalizedRules[path] = routeRules; } - options.routeRules = normalizedRules + options.routeRules = normalizedRules; - options.baseURL = withLeadingSlash(withTrailingSlash(options.baseURL)) + options.baseURL = withLeadingSlash(withTrailingSlash(options.baseURL)); options.runtimeConfig = defu(options.runtimeConfig, { app: { - baseURL: options.baseURL + baseURL: options.baseURL, }, nitro: { - routeRules: options.routeRules - } - }) + routeRules: options.routeRules, + }, + }); for (const asset of options.publicAssets) { - asset.dir = resolve(options.srcDir, asset.dir) - asset.baseURL = withLeadingSlash(withoutTrailingSlash(asset.baseURL || '/')) + asset.dir = resolve(options.srcDir, asset.dir); + asset.baseURL = withLeadingSlash( + withoutTrailingSlash(asset.baseURL || "/") + ); } - for (const pkg of ['defu', 'h3', 'radix3']) { + for (const pkg of ["defu", "h3", "radix3"]) { if (!options.alias[pkg]) { - options.alias[pkg] = await resolveModule(pkg, { url: import.meta.url }) + options.alias[pkg] = await resolveModule(pkg, { url: import.meta.url }); } } @@ -263,24 +296,27 @@ export async function loadOptions (configOverrides: NitroConfig = {}): Promise { - const path = resolvePath(p, options) + const path = resolvePath(p, options); if (options.dev && isAbsolute(path)) { - return pathToFileURL(path).href + return pathToFileURL(path).href; } - return path - }) + return path; + }); - return options + return options; } -export function defineNitroConfig (config: NitroConfig): NitroConfig { - return config +export function defineNitroConfig(config: NitroConfig): NitroConfig { + return config; } diff --git a/src/prerender.ts b/src/prerender.ts index 1d70d958a4..8f9713dd14 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -1,194 +1,250 @@ -import { pathToFileURL } from 'url' -import { resolve, join } from 'pathe' -import { joinURL, parseURL, withBase, withoutBase } from 'ufo' -import chalk from 'chalk' -import { createRouter as createRadixRouter, toRouteMatcher } from 'radix3' -import { defu } from 'defu' -import { createNitro } from './nitro' -import { build } from './build' -import type { Nitro, NitroRouteRules, PrerenderGenerateRoute, PrerenderRoute } from './types' -import { writeFile } from './utils' -import { compressPublicAssets } from './compress' - -const allowedExtensions = new Set(['', '.json']) - -export async function prerender (nitro: Nitro) { +import { pathToFileURL } from "node:url"; +import { resolve, join } from "pathe"; +import { joinURL, parseURL, withBase, withoutBase } from "ufo"; +import chalk from "chalk"; +import { createRouter as createRadixRouter, toRouteMatcher } from "radix3"; +import { defu } from "defu"; +import { createNitro } from "./nitro"; +import { build } from "./build"; +import type { + Nitro, + NitroRouteRules, + PrerenderGenerateRoute, + PrerenderRoute, +} from "./types"; +import { writeFile } from "./utils"; +import { compressPublicAssets } from "./compress"; + +const allowedExtensions = new Set(["", ".json"]); + +export async function prerender(nitro: Nitro) { if (nitro.options.noPublicDir) { - console.warn('[nitro] Skipping prerender since `noPublicDir` option is enabled.') - return + console.warn( + "[nitro] Skipping prerender since `noPublicDir` option is enabled." + ); + return; } // Initial list of routes to prerender - const routes = new Set(nitro.options.prerender.routes) + const routes = new Set(nitro.options.prerender.routes); // Extend with static prerender route rules const prerenderRulePaths = Object.entries(nitro.options.routeRules) - .filter(([path, options]) => options.prerender && !path.includes('*')) - .map(e => e[0]) + .filter(([path, options]) => options.prerender && !path.includes("*")) + .map((e) => e[0]); for (const route of prerenderRulePaths) { - routes.add(route) + routes.add(route); } // Crawl / at least if no routes are defined - if (nitro.options.prerender.crawlLinks && !routes.size) { - routes.add('/') + if (nitro.options.prerender.crawlLinks && routes.size === 0) { + routes.add("/"); } // Allow extending prereneder routes - await nitro.hooks.callHook('prerender:routes', routes) + await nitro.hooks.callHook("prerender:routes", routes); // Skip if no prerender routes specified - if (!routes.size) { - return + if (routes.size === 0) { + return; } // Build with prerender preset - nitro.logger.info('Initializing prerenderer') - nitro._prerenderedRoutes = [] + nitro.logger.info("Initializing prerenderer"); + nitro._prerenderedRoutes = []; const nitroRenderer = await createNitro({ ...nitro.options._config, rootDir: nitro.options.rootDir, logLevel: 0, - preset: 'nitro-prerender' - }) - await build(nitroRenderer) + preset: "nitro-prerender", + }); + await build(nitroRenderer); // Import renderer entry - const serverEntrypoint = resolve(nitroRenderer.options.output.serverDir, 'index.mjs') - const { localFetch } = await import(pathToFileURL(serverEntrypoint).href) + const serverEntrypoint = resolve( + nitroRenderer.options.output.serverDir, + "index.mjs" + ); + const { localFetch } = await import(pathToFileURL(serverEntrypoint).href); // Create route rule matcher - const _routeRulesMatcher = toRouteMatcher(createRadixRouter({ routes: nitro.options.routeRules })) - const _getRouteRules = (path: string) => defu({}, ..._routeRulesMatcher.matchAll(path).reverse()) as NitroRouteRules + const _routeRulesMatcher = toRouteMatcher( + createRadixRouter({ routes: nitro.options.routeRules }) + ); + const _getRouteRules = (path: string) => + defu({}, ..._routeRulesMatcher.matchAll(path).reverse()) as NitroRouteRules; // Start prerendering - const generatedRoutes = new Set() - const canPrerender = (route: string = '/') => { - if (generatedRoutes.has(route)) { return false } - if (route.length > 250) { return false } + const generatedRoutes = new Set(); + const canPrerender = (route: string = "/") => { + if (generatedRoutes.has(route)) { + return false; + } + if (route.length > 250) { + return false; + } for (const ignore of nitro.options.prerender.ignore) { - if (route.startsWith(ignore)) { return false } + if (route.startsWith(ignore)) { + return false; + } } - if (_getRouteRules(route).prerender === false) { return false } - return true - } + if (_getRouteRules(route).prerender === false) { + return false; + } + return true; + }; const generateRoute = async (route: string) => { - const start = Date.now() + const start = Date.now(); // Check if we should render route - if (!canPrerender(route)) { return } - generatedRoutes.add(route) - routes.delete(route) + if (!canPrerender(route)) { + return; + } + generatedRoutes.add(route); + routes.delete(route); // Create result object - const _route: PrerenderGenerateRoute = { route } + const _route: PrerenderGenerateRoute = { route }; // Fetch the route - const res = await (localFetch(withBase(route, nitro.options.baseURL), { headers: { 'x-nitro-prerender': route } }) as ReturnType) - _route.data = await res.arrayBuffer() - Object.defineProperty(_route, 'contents', { + const res = await (localFetch(withBase(route, nitro.options.baseURL), { + headers: { "x-nitro-prerender": route }, + }) as ReturnType); + _route.data = await res.arrayBuffer(); + Object.defineProperty(_route, "contents", { get: () => { if (!(_route as any)._contents) { - (_route as any)._contents = new TextDecoder('utf-8').decode(new Uint8Array(_route.data)) + (_route as any)._contents = new TextDecoder("utf8").decode( + new Uint8Array(_route.data) + ); } - return (_route as any)._contents + return (_route as any)._contents; }, - set (value: string) { - (_route as any)._contents = value - _route.data = new TextEncoder().encode(value) - } - }) + set(value: string) { + (_route as any)._contents = value; + _route.data = new TextEncoder().encode(value); + }, + }); if (res.status !== 200) { - _route.error = new Error(`[${res.status}] ${res.statusText}`) as any - _route.error.statusCode = res.status - _route.error.statusMessage = res.statusText + _route.error = new Error(`[${res.status}] ${res.statusText}`) as any; + _route.error.statusCode = res.status; + _route.error.statusMessage = res.statusText; } // Write to the file - const isImplicitHTML = !route.endsWith('.html') && (res.headers.get('content-type') || '').includes('html') - const routeWithIndex = route.endsWith('/') ? route + 'index' : route - _route.fileName = isImplicitHTML ? joinURL(route, 'index.html') : routeWithIndex - _route.fileName = withoutBase(_route.fileName, nitro.options.baseURL) + const isImplicitHTML = + !route.endsWith(".html") && + (res.headers.get("content-type") || "").includes("html"); + const routeWithIndex = route.endsWith("/") ? route + "index" : route; + _route.fileName = isImplicitHTML + ? joinURL(route, "index.html") + : routeWithIndex; + _route.fileName = withoutBase(_route.fileName, nitro.options.baseURL); - await nitro.hooks.callHook('prerender:generate', _route, nitro) + await nitro.hooks.callHook("prerender:generate", _route, nitro); // Check if route skipped or has errors - if (_route.skip || _route.error) { return } + if (_route.skip || _route.error) { + return; + } - const filePath = join(nitro.options.output.publicDir, _route.fileName) - await writeFile(filePath, Buffer.from(_route.data)) - nitro._prerenderedRoutes.push(_route) + const filePath = join(nitro.options.output.publicDir, _route.fileName); + await writeFile(filePath, Buffer.from(_route.data)); + nitro._prerenderedRoutes.push(_route); // Crawl route links if (!_route.error && isImplicitHTML) { - const extractedLinks = extractLinks(_route.contents, route, res, nitro.options.prerender.crawlLinks) + const extractedLinks = extractLinks( + _route.contents, + route, + res, + nitro.options.prerender.crawlLinks + ); for (const _link of extractedLinks) { if (canPrerender(_link)) { - routes.add(_link) + routes.add(_link); } } } - _route.generateTimeMS = Date.now() - start - return _route - } - - nitro.logger.info(nitro.options.prerender.crawlLinks - ? `Prerendering ${routes.size} initial routes with crawler` - : `Prerendering ${routes.size} routes` - ) - for (let i = 0; i < 100 && routes.size; i++) { - for (const route of Array.from(routes)) { - const _route = await generateRoute(route).catch(error => ({ route, error } as PrerenderRoute)) + _route.generateTimeMS = Date.now() - start; + return _route; + }; + + nitro.logger.info( + nitro.options.prerender.crawlLinks + ? `Prerendering ${routes.size} initial routes with crawler` + : `Prerendering ${routes.size} routes` + ); + for (let i = 0; i < 100 && routes.size > 0; i++) { + for (const route of routes) { + const _route = await generateRoute(route).catch( + (error) => ({ route, error } as PrerenderRoute) + ); // Skipped (not allowed or duplicate) - if (!_route) { continue } + if (!_route) { + continue; + } - await nitro.hooks.callHook('prerender:route', _route) - nitro.logger.log(chalk[_route.error ? 'yellow' : 'gray'](` ├─ ${_route.route} (${_route.generateTimeMS}ms) ${_route.error ? `(${_route.error})` : ''}`)) + await nitro.hooks.callHook("prerender:route", _route); + nitro.logger.log( + chalk[_route.error ? "yellow" : "gray"]( + ` ├─ ${_route.route} (${_route.generateTimeMS}ms) ${ + _route.error ? `(${_route.error})` : "" + }` + ) + ); } } if (nitro.options.compressPublicAssets) { - await compressPublicAssets(nitro) + await compressPublicAssets(nitro); } } -const LINK_REGEX = /href=['"]?([^'" >]+)/g +const LINK_REGEX = /href=["']?([^ "'>]+)/g; -function extractLinks (html: string, from: string, res: Response, crawlLinks: boolean) { - const links: string[] = [] - const _links: string[] = [] +function extractLinks( + html: string, + from: string, + res: Response, + crawlLinks: boolean +) { + const links: string[] = []; + const _links: string[] = []; // Extract from any to crawl if (crawlLinks) { _links.push( - ...Array.from(html.matchAll(LINK_REGEX)) - .map(m => m[1]) - .filter(link => allowedExtensions.has(getExtension(link))) - ) + ...[...html.matchAll(LINK_REGEX)] + .map((m) => m[1]) + .filter((link) => allowedExtensions.has(getExtension(link))) + ); } // Extract from x-nitro-prerender headers - const header = res.headers.get('x-nitro-prerender') || '' - _links.push(...header.split(',').map(i => i.trim())) + const header = res.headers.get("x-nitro-prerender") || ""; + _links.push(...header.split(",").map((i) => i.trim())); for (const link of _links.filter(Boolean)) { - const parsed = parseURL(link) - if (parsed.protocol) { continue } - let { pathname } = parsed - if (!pathname.startsWith('/')) { - const fromURL = new URL(from, 'http://localhost') - pathname = new URL(pathname, fromURL).pathname + const parsed = parseURL(link); + if (parsed.protocol) { + continue; + } + let { pathname } = parsed; + if (!pathname.startsWith("/")) { + const fromURL = new URL(from, "http://localhost"); + pathname = new URL(pathname, fromURL).pathname; } - links.push(pathname) + links.push(pathname); } - return links + return links; } -const EXT_REGEX = /\.[a-z0-9]+$/ +const EXT_REGEX = /\.[\da-z]+$/; -function getExtension (path: string): string { - return (path.match(EXT_REGEX) || [])[0] || '' +function getExtension(path: string): string { + return (path.match(EXT_REGEX) || [])[0] || ""; } diff --git a/src/preset.ts b/src/preset.ts index d0d3faffb9..4d9ade068a 100644 --- a/src/preset.ts +++ b/src/preset.ts @@ -1,6 +1,5 @@ +import type { NitroPreset } from "./types"; -import type { NitroPreset } from './types' - -export function defineNitroPreset (preset: NitroPreset) { - return preset +export function defineNitroPreset(preset: NitroPreset) { + return preset; } diff --git a/src/presets/aws-lambda.ts b/src/presets/aws-lambda.ts index d17049c87f..0ced0f8b10 100644 --- a/src/presets/aws-lambda.ts +++ b/src/presets/aws-lambda.ts @@ -1,5 +1,5 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const awsLambda = defineNitroPreset({ - entry: '#internal/nitro/entries/aws-lambda' -}) + entry: "#internal/nitro/entries/aws-lambda", +}); diff --git a/src/presets/azure-functions.ts b/src/presets/azure-functions.ts index ea514e3019..1898b2dbb6 100644 --- a/src/presets/azure-functions.ts +++ b/src/presets/azure-functions.ts @@ -1,73 +1,74 @@ -import { createWriteStream } from 'fs' -import archiver from 'archiver' -import { join, resolve } from 'pathe' -import { writeFile } from '../utils' -import { defineNitroPreset } from '../preset' -import type { Nitro } from '../types' +import { createWriteStream } from "node:fs"; +import archiver from "archiver"; +import { join, resolve } from "pathe"; +import { writeFile } from "../utils"; +import { defineNitroPreset } from "../preset"; +import type { Nitro } from "../types"; -// eslint-disable-next-line export const azureFunctions = defineNitroPreset({ serveStatic: true, - entry: '#internal/nitro/entries/azure-functions', + entry: "#internal/nitro/entries/azure-functions", commands: { - deploy: 'az functionapp deployment source config-zip -g -n --src {{ output.dir }}/deploy.zip' + deploy: + "az functionapp deployment source config-zip -g -n --src {{ output.dir }}/deploy.zip", }, hooks: { - async 'compiled' (ctx: Nitro) { - await writeRoutes(ctx) - } - } -}) + async compiled(ctx: Nitro) { + await writeRoutes(ctx); + }, + }, +}); -function zipDirectory (dir: string, outfile: string): Promise { - const archive = archiver('zip', { zlib: { level: 9 } }) - const stream = createWriteStream(outfile) +function zipDirectory(dir: string, outfile: string): Promise { + const archive = archiver("zip", { zlib: { level: 9 } }); + const stream = createWriteStream(outfile); return new Promise((resolve, reject) => { archive .directory(dir, false) - .on('error', (err: Error) => reject(err)) - .pipe(stream) + .on("error", (err: Error) => reject(err)) + .pipe(stream); - stream.on('close', () => resolve(undefined)) - archive.finalize() - }) + stream.on("close", () => resolve(undefined)); + archive.finalize(); + }); } -async function writeRoutes (nitro: Nitro) { +async function writeRoutes(nitro: Nitro) { const host = { - version: '2.0', - extensions: { http: { routePrefix: '' } } - } + version: "2.0", + extensions: { http: { routePrefix: "" } }, + }; const functionDefinition = { - entryPoint: 'handle', + entryPoint: "handle", bindings: [ { - authLevel: 'anonymous', - type: 'httpTrigger', - direction: 'in', - name: 'req', - route: '{*url}', - methods: [ - 'delete', - 'get', - 'head', - 'options', - 'patch', - 'post', - 'put' - ] + authLevel: "anonymous", + type: "httpTrigger", + direction: "in", + name: "req", + route: "{*url}", + methods: ["delete", "get", "head", "options", "patch", "post", "put"], }, { - type: 'http', - direction: 'out', - name: 'res' - } - ] - } + type: "http", + direction: "out", + name: "res", + }, + ], + }; - await writeFile(resolve(nitro.options.output.serverDir, 'function.json'), JSON.stringify(functionDefinition)) - await writeFile(resolve(nitro.options.output.dir, 'host.json'), JSON.stringify(host)) - await zipDirectory(nitro.options.output.dir, join(nitro.options.output.dir, 'deploy.zip')) + await writeFile( + resolve(nitro.options.output.serverDir, "function.json"), + JSON.stringify(functionDefinition) + ); + await writeFile( + resolve(nitro.options.output.dir, "host.json"), + JSON.stringify(host) + ); + await zipDirectory( + nitro.options.output.dir, + join(nitro.options.output.dir, "deploy.zip") + ); } diff --git a/src/presets/azure.ts b/src/presets/azure.ts index bde4ba833c..1aacb560cc 100644 --- a/src/presets/azure.ts +++ b/src/presets/azure.ts @@ -1,117 +1,140 @@ -import { readFile } from 'node:fs/promises' -import { join, resolve } from 'pathe' -import { writeFile } from '../utils' -import { defineNitroPreset } from '../preset' -import type { Nitro } from '../types' +import { readFile } from "node:fs/promises"; +import { join, resolve } from "pathe"; +import { writeFile } from "../utils"; +import { defineNitroPreset } from "../preset"; +import type { Nitro } from "../types"; export const azure = defineNitroPreset({ - entry: '#internal/nitro/entries/azure', + entry: "#internal/nitro/entries/azure", output: { - serverDir: '{{ output.dir }}/server/functions' + serverDir: "{{ output.dir }}/server/functions", }, commands: { - preview: 'npx @azure/static-web-apps-cli start ./public --api-location ./server' + preview: + "npx @azure/static-web-apps-cli start ./public --api-location ./server", }, hooks: { - async 'compiled' (ctx: Nitro) { - await writeRoutes(ctx) - } - } -}) + async compiled(ctx: Nitro) { + await writeRoutes(ctx); + }, + }, +}); -async function writeRoutes (nitro: Nitro) { +async function writeRoutes(nitro: Nitro) { const host = { - version: '2.0' - } + version: "2.0", + }; - let nodeVersion = '16' + let nodeVersion = "16"; try { - const currentNodeVersion = JSON.parse(await readFile(join(nitro.options.rootDir, 'package.json'), 'utf8')).engines.node - if (['16', '14'].includes(currentNodeVersion)) { - nodeVersion = currentNodeVersion + const currentNodeVersion = JSON.parse( + await readFile(join(nitro.options.rootDir, "package.json"), "utf8") + ).engines.node; + if (["16", "14"].includes(currentNodeVersion)) { + nodeVersion = currentNodeVersion; } } catch { - const currentNodeVersion = process.versions.node.slice(0, 2) - if (['16', '14'].includes(currentNodeVersion)) { - nodeVersion = currentNodeVersion + const currentNodeVersion = process.versions.node.slice(0, 2); + if (["16", "14"].includes(currentNodeVersion)) { + nodeVersion = currentNodeVersion; } } const config = { platform: { - apiRuntime: `node:${nodeVersion}` + apiRuntime: `node:${nodeVersion}`, }, routes: [], navigationFallback: { - rewrite: '/api/server' - } - } + rewrite: "/api/server", + }, + }; - const routeFiles = nitro._prerenderedRoutes || [] + const routeFiles = nitro._prerenderedRoutes || []; - const indexFileExists = routeFiles.some(route => route.fileName === '/index.html') + const indexFileExists = routeFiles.some( + (route) => route.fileName === "/index.html" + ); if (!indexFileExists) { config.routes.unshift( { - route: '/index.html', - redirect: '/' + route: "/index.html", + redirect: "/", }, { - route: '/', - rewrite: '/api/server' + route: "/", + rewrite: "/api/server", } - ) + ); } - const suffix = '/index.html'.length + const suffix = "/index.html".length; for (const { fileName } of routeFiles) { - if (!fileName.endsWith('/index.html')) { continue } + if (!fileName.endsWith("/index.html")) { + continue; + } config.routes.unshift({ - route: fileName.slice(0, -suffix) || '/', - rewrite: fileName - }) + route: fileName.slice(0, -suffix) || "/", + rewrite: fileName, + }); } for (const { fileName } of routeFiles) { - if (!fileName.endsWith('.html') || fileName.endsWith('index.html')) { continue } + if (!fileName.endsWith(".html") || fileName.endsWith("index.html")) { + continue; + } - const route = fileName.slice(0, -'.html'.length) - const existingRouteIndex = config.routes.findIndex(_route => _route.route === route) + const route = fileName.slice(0, -".html".length); + const existingRouteIndex = config.routes.findIndex( + (_route) => _route.route === route + ); if (existingRouteIndex > -1) { - config.routes.splice(existingRouteIndex, 1) + config.routes.splice(existingRouteIndex, 1); } config.routes.unshift({ route, - rewrite: fileName - }) + rewrite: fileName, + }); } const functionDefinition = { - entryPoint: 'handle', + entryPoint: "handle", bindings: [ { - authLevel: 'anonymous', - type: 'httpTrigger', - direction: 'in', - name: 'req', - route: '{*url}', - methods: ['delete', 'get', 'head', 'options', 'patch', 'post', 'put'] + authLevel: "anonymous", + type: "httpTrigger", + direction: "in", + name: "req", + route: "{*url}", + methods: ["delete", "get", "head", "options", "patch", "post", "put"], }, { - type: 'http', - direction: 'out', - name: 'res' - } - ] - } + type: "http", + direction: "out", + name: "res", + }, + ], + }; - await writeFile(resolve(nitro.options.output.serverDir, 'function.json'), JSON.stringify(functionDefinition, null, 2)) - await writeFile(resolve(nitro.options.output.serverDir, '../host.json'), JSON.stringify(host, null, 2)) - const stubPackageJson = resolve(nitro.options.output.serverDir, '../package.json') - await writeFile(stubPackageJson, JSON.stringify({ private: true })) - await writeFile(resolve(nitro.options.rootDir, 'staticwebapp.config.json'), JSON.stringify(config, null, 2)) + await writeFile( + resolve(nitro.options.output.serverDir, "function.json"), + JSON.stringify(functionDefinition, null, 2) + ); + await writeFile( + resolve(nitro.options.output.serverDir, "../host.json"), + JSON.stringify(host, null, 2) + ); + const stubPackageJson = resolve( + nitro.options.output.serverDir, + "../package.json" + ); + await writeFile(stubPackageJson, JSON.stringify({ private: true })); + await writeFile( + resolve(nitro.options.rootDir, "staticwebapp.config.json"), + JSON.stringify(config, null, 2) + ); if (!indexFileExists) { - await writeFile(resolve(nitro.options.output.publicDir, 'index.html'), '') + await writeFile(resolve(nitro.options.output.publicDir, "index.html"), ""); } } diff --git a/src/presets/base-worker.ts b/src/presets/base-worker.ts index 45d3328815..77e52716da 100644 --- a/src/presets/base-worker.ts +++ b/src/presets/base-worker.ts @@ -1,4 +1,4 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const baseWorker = defineNitroPreset({ entry: null, // Abstract @@ -7,11 +7,11 @@ export const baseWorker = defineNitroPreset({ noExternals: true, rollupConfig: { output: { - format: 'iife', + format: "iife", generatedCode: { - symbols: true - } - } + symbols: true, + }, + }, }, - inlineDynamicImports: true // iffe does not support code-splitting -}) + inlineDynamicImports: true, // iffe does not support code-splitting +}); diff --git a/src/presets/cleavr.ts b/src/presets/cleavr.ts index feabb0b130..ca0f17d883 100644 --- a/src/presets/cleavr.ts +++ b/src/presets/cleavr.ts @@ -1,5 +1,5 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const cleavr = defineNitroPreset({ - extends: 'node-server' -}) + extends: "node-server", +}); diff --git a/src/presets/cloudflare.ts b/src/presets/cloudflare.ts index 6059d95e80..12ea6bdcfc 100644 --- a/src/presets/cloudflare.ts +++ b/src/presets/cloudflare.ts @@ -1,44 +1,56 @@ -import { resolve } from 'pathe' -import fse from 'fs-extra' -import { writeFile } from '../utils' -import { defineNitroPreset } from '../preset' -import type { Nitro } from '../types' +import { resolve } from "pathe"; +import fse from "fs-extra"; +import { writeFile } from "../utils"; +import { defineNitroPreset } from "../preset"; +import type { Nitro } from "../types"; export const cloudflare = defineNitroPreset({ - extends: 'base-worker', - entry: '#internal/nitro/entries/cloudflare', + extends: "base-worker", + entry: "#internal/nitro/entries/cloudflare", commands: { - preview: 'npx wrangler dev ./server/index.mjs --site ./public --local', - deploy: 'npx wrangler publish' + preview: "npx wrangler dev ./server/index.mjs --site ./public --local", + deploy: "npx wrangler publish", }, hooks: { - async 'compiled' (nitro: Nitro) { - await writeFile(resolve(nitro.options.output.dir, 'package.json'), JSON.stringify({ private: true, main: './server/index.mjs' }, null, 2)) - await writeFile(resolve(nitro.options.output.dir, 'package-lock.json'), JSON.stringify({ lockfileVersion: 1 }, null, 2)) - } - } -}) + async compiled(nitro: Nitro) { + await writeFile( + resolve(nitro.options.output.dir, "package.json"), + JSON.stringify({ private: true, main: "./server/index.mjs" }, null, 2) + ); + await writeFile( + resolve(nitro.options.output.dir, "package-lock.json"), + JSON.stringify({ lockfileVersion: 1 }, null, 2) + ); + }, + }, +}); export const cloudflarePages = defineNitroPreset({ - extends: 'cloudflare', - entry: '#internal/nitro/entries/cloudflare-pages', + extends: "cloudflare", + entry: "#internal/nitro/entries/cloudflare-pages", commands: { - preview: 'npx wrangler pages dev .output/public', - deploy: 'npx wrangler pages publish .output/public' + preview: "npx wrangler pages dev .output/public", + deploy: "npx wrangler pages publish .output/public", }, output: { - serverDir: '{{ rootDir }}/functions' + serverDir: "{{ rootDir }}/functions", }, rollupConfig: { output: { - entryFileNames: 'path.js', - format: 'esm' - } + entryFileNames: "path.js", + format: "esm", + }, }, hooks: { - async 'compiled' (nitro: Nitro) { - await fse.move(resolve(nitro.options.output.serverDir, 'path.js'), resolve(nitro.options.output.serverDir, '[[path]].js')) - await fse.move(resolve(nitro.options.output.serverDir, 'path.js.map'), resolve(nitro.options.output.serverDir, '[[path]].js.map')) - } - } -}) + async compiled(nitro: Nitro) { + await fse.move( + resolve(nitro.options.output.serverDir, "path.js"), + resolve(nitro.options.output.serverDir, "[[path]].js") + ); + await fse.move( + resolve(nitro.options.output.serverDir, "path.js.map"), + resolve(nitro.options.output.serverDir, "[[path]].js.map") + ); + }, + }, +}); diff --git a/src/presets/deno.ts b/src/presets/deno.ts index 9e3487077a..13d69ebcf2 100644 --- a/src/presets/deno.ts +++ b/src/presets/deno.ts @@ -1,23 +1,22 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const deno = defineNitroPreset({ - entry: '#internal/nitro/entries/deno', + entry: "#internal/nitro/entries/deno", node: false, noExternals: true, - serveStatic: 'deno', + serveStatic: "deno", commands: { - preview: '', - deploy: 'cd ./ && deployctl deploy --project= server/index.ts' + preview: "", + deploy: + "cd ./ && deployctl deploy --project= server/index.ts", }, rollupConfig: { preserveEntrySignatures: false, - external: [ - 'https://deno.land/std/http/server.ts' - ], + external: ["https://deno.land/std/http/server.ts"], output: { - entryFileNames: 'index.ts', - manualChunks: () => 'index', - format: 'esm' - } - } -}) + entryFileNames: "index.ts", + manualChunks: () => "index", + format: "esm", + }, + }, +}); diff --git a/src/presets/digital-ocean.ts b/src/presets/digital-ocean.ts index 7ab9e60f36..391683db4e 100644 --- a/src/presets/digital-ocean.ts +++ b/src/presets/digital-ocean.ts @@ -1,5 +1,5 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const digitalOcean = defineNitroPreset({ - extends: 'node-server' -}) + extends: "node-server", +}); diff --git a/src/presets/firebase.ts b/src/presets/firebase.ts index 293c4e7fca..360acadaf0 100644 --- a/src/presets/firebase.ts +++ b/src/presets/firebase.ts @@ -1,96 +1,109 @@ -import { createRequire } from 'module' -import { existsSync } from 'node:fs' -import { readFile } from 'node:fs/promises' -import { join, relative, resolve } from 'pathe' -import { globby } from 'globby' -import { readPackageJSON } from 'pkg-types' -import { writeFile } from '../utils' -import { defineNitroPreset } from '../preset' -import type { Nitro } from '../types' +import { createRequire } from "node:module"; +import { existsSync } from "node:fs"; +import { readFile } from "node:fs/promises"; +import { join, relative, resolve } from "pathe"; +import { globby } from "globby"; +import { readPackageJSON } from "pkg-types"; +import { writeFile } from "../utils"; +import { defineNitroPreset } from "../preset"; +import type { Nitro } from "../types"; export const firebase = defineNitroPreset({ - entry: '#internal/nitro/entries/firebase', + entry: "#internal/nitro/entries/firebase", commands: { - deploy: 'npx firebase deploy' + deploy: "npx firebase deploy", }, hooks: { - async 'compiled' (ctx) { - await writeRoutes(ctx) - } - } -}) + async compiled(ctx) { + await writeRoutes(ctx); + }, + }, +}); -async function writeRoutes (nitro: Nitro) { - if (!existsSync(join(nitro.options.rootDir, 'firebase.json'))) { +async function writeRoutes(nitro: Nitro) { + if (!existsSync(join(nitro.options.rootDir, "firebase.json"))) { const firebase = { functions: { - source: relative(nitro.options.rootDir, nitro.options.output.serverDir) + source: relative(nitro.options.rootDir, nitro.options.output.serverDir), }, hosting: [ { - site: '', - public: relative(nitro.options.rootDir, nitro.options.output.publicDir), + site: "", + public: relative( + nitro.options.rootDir, + nitro.options.output.publicDir + ), cleanUrls: true, rewrites: [ { - source: '**', - function: 'server' - } - ] - } - ] - } - await writeFile(resolve(nitro.options.rootDir, 'firebase.json'), JSON.stringify(firebase)) + source: "**", + function: "server", + }, + ], + }, + ], + }; + await writeFile( + resolve(nitro.options.rootDir, "firebase.json"), + JSON.stringify(firebase) + ); } - const _require = createRequire(import.meta.url) + const _require = createRequire(import.meta.url); - const jsons = await globby(join(nitro.options.output.serverDir, 'node_modules/**/package.json')) - const prefixLength = `${nitro.options.output.serverDir}/node_modules/`.length - const suffixLength = '/package.json'.length + const jsons = await globby( + join(nitro.options.output.serverDir, "node_modules/**/package.json") + ); + const prefixLength = `${nitro.options.output.serverDir}/node_modules/`.length; + const suffixLength = "/package.json".length; + // eslint-disable-next-line unicorn/no-array-reduce const dependencies = jsons.reduce((obj, packageJson) => { - const dirname = packageJson.slice(prefixLength, -suffixLength) - if (!dirname.includes('node_modules')) { - obj[dirname] = _require(packageJson).version + const dirname = packageJson.slice(prefixLength, -suffixLength); + if (!dirname.includes("node_modules")) { + obj[dirname] = _require(packageJson).version; } - return obj - }, {} as Record) + return obj; + }, {} as Record); - let nodeVersion = '14' + let nodeVersion = "14"; try { - const currentNodeVersion = JSON.parse(await readFile(join(nitro.options.rootDir, 'package.json'), 'utf8')).engines.node - if (['16', '14'].includes(currentNodeVersion)) { - nodeVersion = currentNodeVersion + const currentNodeVersion = JSON.parse( + await readFile(join(nitro.options.rootDir, "package.json"), "utf8") + ).engines.node; + if (["16", "14"].includes(currentNodeVersion)) { + nodeVersion = currentNodeVersion; } } catch { - const currentNodeVersion = process.versions.node.slice(0, 2) - if (['16', '14'].includes(currentNodeVersion)) { - nodeVersion = currentNodeVersion + const currentNodeVersion = process.versions.node.slice(0, 2); + if (["16", "14"].includes(currentNodeVersion)) { + nodeVersion = currentNodeVersion; } } const getPackageVersion = async (id) => { - const pkg = await readPackageJSON(id, { url: nitro.options.nodeModulesDirs }) - return pkg.version - } + const pkg = await readPackageJSON(id, { + url: nitro.options.nodeModulesDirs, + }); + return pkg.version; + }; await writeFile( - resolve(nitro.options.output.serverDir, 'package.json'), + resolve(nitro.options.output.serverDir, "package.json"), JSON.stringify( { private: true, - type: 'module', - main: './index.mjs', + type: "module", + main: "./index.mjs", dependencies: { - 'firebase-functions-test': 'latest', - 'firebase-admin': await getPackageVersion('firebase-admin'), - 'firebase-functions': await getPackageVersion('firebase-functions'), - ...dependencies + "firebase-functions-test": "latest", + "firebase-admin": await getPackageVersion("firebase-admin"), + "firebase-functions": await getPackageVersion("firebase-functions"), + ...dependencies, }, - engines: { node: nodeVersion } + engines: { node: nodeVersion }, }, null, 2 ) - ) + ); } diff --git a/src/presets/heroku.ts b/src/presets/heroku.ts index a5bf6ce85c..307ed9a14c 100644 --- a/src/presets/heroku.ts +++ b/src/presets/heroku.ts @@ -1,5 +1,5 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const heroku = defineNitroPreset({ - extends: 'node-server' -}) + extends: "node-server", +}); diff --git a/src/presets/index.ts b/src/presets/index.ts index b331e4804e..0404fb8486 100644 --- a/src/presets/index.ts +++ b/src/presets/index.ts @@ -1,21 +1,21 @@ -export * from './aws-lambda' -export * from './azure-functions' -export * from './azure' -export * from './base-worker' -export * from './cloudflare' -export * from './deno' -export * from './digital-ocean' -export * from './firebase' -export * from './heroku' -export * from './layer0' -export * from './netlify' -export * from './nitro-dev' -export * from './nitro-prerender' -export * from './node-cli' -export * from './node-server' -export * from './node' -export * from './render-com' -export * from './service-worker' -export * from './stormkit' -export * from './vercel' -export * from './cleavr' +export * from "./aws-lambda"; +export * from "./azure-functions"; +export * from "./azure"; +export * from "./base-worker"; +export * from "./cloudflare"; +export * from "./deno"; +export * from "./digital-ocean"; +export * from "./firebase"; +export * from "./heroku"; +export * from "./layer0"; +export * from "./netlify"; +export * from "./nitro-dev"; +export * from "./nitro-prerender"; +export * from "./node-cli"; +export * from "./node-server"; +export * from "./node"; +export * from "./render-com"; +export * from "./service-worker"; +export * from "./stormkit"; +export * from "./vercel"; +export * from "./cleavr"; diff --git a/src/presets/layer0.ts b/src/presets/layer0.ts index 047543d6be..bb2de2c4c6 100644 --- a/src/presets/layer0.ts +++ b/src/presets/layer0.ts @@ -1,59 +1,65 @@ -import { promises as fsp } from 'fs' -import { resolve, dirname } from 'pathe' -import type { PackageJson } from 'pkg-types' -import { defineNitroPreset } from '../preset' +import { promises as fsp } from "node:fs"; +import { resolve, dirname } from "pathe"; +import type { PackageJson } from "pkg-types"; +import { defineNitroPreset } from "../preset"; export const layer0 = defineNitroPreset({ - extends: 'node', + extends: "node", commands: { - deploy: 'cd ./ && npm run deploy', - preview: 'cd ./ && npm run preview' + deploy: "cd ./ && npm run deploy", + preview: "cd ./ && npm run preview", }, hooks: { - async 'compiled' (nitro) { + async compiled(nitro) { // Write Layer0 config, router, and connector files const layer0Config = { - connector: './layer0', - name: 'nitro-app', - routes: 'routes.js', + connector: "./layer0", + name: "nitro-app", + routes: "routes.js", backends: {}, includeFiles: { - 'public/**/*': true, - 'server/**/*': true - } - } - const configPath = resolve(nitro.options.output.dir, 'layer0.config.js') - await writeFile(configPath, `module.exports = ${JSON.stringify(layer0Config, null, 2)}`) + "public/**/*": true, + "server/**/*": true, + }, + }; + const configPath = resolve(nitro.options.output.dir, "layer0.config.js"); + await writeFile( + configPath, + `module.exports = ${JSON.stringify(layer0Config, null, 2)}` + ); - const routerPath = resolve(nitro.options.output.dir, 'routes.js') - await writeFile(routerPath, routesTemplate()) + const routerPath = resolve(nitro.options.output.dir, "routes.js"); + await writeFile(routerPath, routesTemplate()); - const connectorPath = resolve(nitro.options.output.dir, 'layer0/prod.js') - await writeFile(connectorPath, entryTemplate()) + const connectorPath = resolve(nitro.options.output.dir, "layer0/prod.js"); + await writeFile(connectorPath, entryTemplate()); const pkgJSON: PackageJson & { scripts: Record } = { private: true, scripts: { - deploy: 'npm install && 0 deploy', - preview: 'npm install && 0 build && 0 run -p' + deploy: "npm install && 0 deploy", + preview: "npm install && 0 build && 0 run -p", }, devDependencies: { - '@layer0/cli': '^4.13.2', - '@layer0/core': '^4.13.2' - } - } - await writeFile(resolve(nitro.options.output.dir, 'package.json'), JSON.stringify(pkgJSON, null, 2)) - } - } -}) + "@layer0/cli": "^4.13.2", + "@layer0/core": "^4.13.2", + }, + }; + await writeFile( + resolve(nitro.options.output.dir, "package.json"), + JSON.stringify(pkgJSON, null, 2) + ); + }, + }, +}); -async function writeFile (path: string, contents: string) { - await fsp.mkdir(dirname(path), { recursive: true }) - await fsp.writeFile(path, contents, 'utf-8') +async function writeFile(path: string, contents: string) { + await fsp.mkdir(dirname(path), { recursive: true }); + await fsp.writeFile(path, contents, "utf8"); } // Layer0 entrypoint (.output/layer0/prod.js) -function entryTemplate () { +function entryTemplate() { return ` const http = require('http') @@ -62,11 +68,11 @@ module.exports = async function prod(port) { const server = http.createServer(handler) server.listen(port) } - `.trim() + `.trim(); } // Layer0 router (.output/routes.js) -function routesTemplate () { +function routesTemplate() { return ` import { Router } from '@layer0/core' @@ -76,5 +82,5 @@ export default router router.fallback(({ renderWithApp }) => { renderWithApp() }) -`.trim() +`.trim(); } diff --git a/src/presets/netlify.ts b/src/presets/netlify.ts index c4d6433e64..e4fd4049a5 100644 --- a/src/presets/netlify.ts +++ b/src/presets/netlify.ts @@ -1,27 +1,27 @@ -import { existsSync, promises as fsp } from 'fs' -import { join, dirname } from 'pathe' -import { defineNitroPreset } from '../preset' -import type { Nitro } from '../types' +import { existsSync, promises as fsp } from "node:fs"; +import { join, dirname } from "pathe"; +import { defineNitroPreset } from "../preset"; +import type { Nitro } from "../types"; // Netlify functions export const netlify = defineNitroPreset({ - extends: 'aws-lambda', - entry: '#internal/nitro/entries/netlify', + extends: "aws-lambda", + entry: "#internal/nitro/entries/netlify", output: { - dir: '{{ rootDir }}/.netlify/functions-internal', - publicDir: '{{ rootDir }}/dist' + dir: "{{ rootDir }}/.netlify/functions-internal", + publicDir: "{{ rootDir }}/dist", }, rollupConfig: { output: { - entryFileNames: 'server.mjs' - } + entryFileNames: "server.mjs", + }, }, hooks: { - async 'compiled' (nitro: Nitro) { - await writeHeaders(nitro) - await writeRedirects(nitro) + async compiled(nitro: Nitro) { + await writeHeaders(nitro); + await writeRedirects(nitro); - const serverCJSPath = join(nitro.options.output.serverDir, 'server.js') + const serverCJSPath = join(nitro.options.output.serverDir, "server.js"); const serverJSCode = ` let _handler exports.handler = function handler (event, context) { @@ -33,104 +33,124 @@ exports.handler = function handler (event, context) { return _handler(event, context) }) } -`.trim() - await fsp.writeFile(serverCJSPath, serverJSCode) - } - } -}) +`.trim(); + await fsp.writeFile(serverCJSPath, serverJSCode); + }, + }, +}); // Netlify builder export const netlifyBuilder = defineNitroPreset({ - extends: 'netlify', - entry: '#internal/nitro/entries/netlify-builder' -}) + extends: "netlify", + entry: "#internal/nitro/entries/netlify-builder", +}); // Netlify edge export const netlifyEdge = defineNitroPreset({ - extends: 'base-worker', - entry: '#internal/nitro/entries/netlify-edge', + extends: "base-worker", + entry: "#internal/nitro/entries/netlify-edge", output: { - serverDir: '{{ rootDir }}/.netlify/edge-functions', - publicDir: '{{ rootDir }}/dist' + serverDir: "{{ rootDir }}/.netlify/edge-functions", + publicDir: "{{ rootDir }}/dist", }, rollupConfig: { output: { - entryFileNames: 'server.js', - format: 'esm' - } + entryFileNames: "server.js", + format: "esm", + }, }, hooks: { - async 'compiled' (nitro: Nitro) { + async compiled(nitro: Nitro) { const manifest = { version: 1, functions: [ { - function: 'server', - pattern: '^.*$' - } - ] - } - const manifestPath = join(nitro.options.rootDir, '.netlify/edge-functions/manifest.json') - await fsp.mkdir(dirname(manifestPath), { recursive: true }) - await fsp.writeFile(manifestPath, JSON.stringify(manifest, null, 2)) - } - } -}) + function: "server", + pattern: "^.*$", + }, + ], + }; + const manifestPath = join( + nitro.options.rootDir, + ".netlify/edge-functions/manifest.json" + ); + await fsp.mkdir(dirname(manifestPath), { recursive: true }); + await fsp.writeFile(manifestPath, JSON.stringify(manifest, null, 2)); + }, + }, +}); -async function writeRedirects (nitro: Nitro) { - const redirectsPath = join(nitro.options.output.publicDir, '_redirects') - let contents = '/* /.netlify/functions/server 200' +async function writeRedirects(nitro: Nitro) { + const redirectsPath = join(nitro.options.output.publicDir, "_redirects"); + let contents = "/* /.netlify/functions/server 200"; // Rewrite static cached paths to builder functions - for (const [key] of Object.entries(nitro.options.routeRules) - .filter(([_, routeRules]) => routeRules.cache && (routeRules.cache?.static || routeRules.cache?.swr)) - ) { - contents = `${key.replace('/**', '/*')}\t/.netlify/builders/server 200\n` + contents + for (const [key] of Object.entries(nitro.options.routeRules).filter( + ([_, routeRules]) => + routeRules.cache && (routeRules.cache?.static || routeRules.cache?.swr) + )) { + contents = + `${key.replace("/**", "/*")}\t/.netlify/builders/server 200\n` + contents; } - for (const [key, routeRules] of Object.entries(nitro.options.routeRules).filter(([_, routeRules]) => routeRules.redirect)) { + for (const [key, routeRules] of Object.entries( + nitro.options.routeRules + ).filter(([_, routeRules]) => routeRules.redirect)) { // TODO: Remove map when netlify support 307/308 - let code = routeRules.redirect.statusCode - code = ({ 307: 302, 308: 301 })[code] || code - contents = `${key.replace('/**', '/*')}\t${routeRules.redirect.to}\t${code}\n` + contents + let code = routeRules.redirect.statusCode; + code = { 307: 302, 308: 301 }[code] || code; + contents = + `${key.replace("/**", "/*")}\t${routeRules.redirect.to}\t${code}\n` + + contents; } if (existsSync(redirectsPath)) { - const currentRedirects = await fsp.readFile(redirectsPath, 'utf-8') - if (currentRedirects.match(/^\/\* /m)) { - nitro.logger.info('Not adding Nitro fallback to `_redirects` (as an existing fallback was found).') - return + const currentRedirects = await fsp.readFile(redirectsPath, "utf8"); + if (/^\/\* /m.test(currentRedirects)) { + nitro.logger.info( + "Not adding Nitro fallback to `_redirects` (as an existing fallback was found)." + ); + return; } - nitro.logger.info('Adding Nitro fallback to `_redirects` to handle all unmatched routes.') - contents = currentRedirects + '\n' + contents + nitro.logger.info( + "Adding Nitro fallback to `_redirects` to handle all unmatched routes." + ); + contents = currentRedirects + "\n" + contents; } - await fsp.writeFile(redirectsPath, contents) + await fsp.writeFile(redirectsPath, contents); } -async function writeHeaders (nitro: Nitro) { - const headersPath = join(nitro.options.output.publicDir, '_headers') - let contents = '' +async function writeHeaders(nitro: Nitro) { + const headersPath = join(nitro.options.output.publicDir, "_headers"); + let contents = ""; - for (const [path, routeRules] of Object.entries(nitro.options.routeRules) - .filter(([_, routeRules]) => routeRules.headers)) { + for (const [path, routeRules] of Object.entries( + nitro.options.routeRules + ).filter(([_, routeRules]) => routeRules.headers)) { const headers = [ - path.replace('/**', '/*'), - ...Object.entries({ ...routeRules.headers }).map(([header, value]) => ` ${header}: ${value}`) - ].join('\n') + path.replace("/**", "/*"), + ...Object.entries({ ...routeRules.headers }).map( + ([header, value]) => ` ${header}: ${value}` + ), + ].join("\n"); - contents += headers + '\n' + contents += headers + "\n"; } if (existsSync(headersPath)) { - const currentHeaders = await fsp.readFile(headersPath, 'utf-8') - if (currentHeaders.match(/^\/\* /m)) { - nitro.logger.info('Not adding Nitro fallback to `_headers` (as an existing fallback was found).') - return + const currentHeaders = await fsp.readFile(headersPath, "utf8"); + if (/^\/\* /m.test(currentHeaders)) { + nitro.logger.info( + "Not adding Nitro fallback to `_headers` (as an existing fallback was found)." + ); + return; } - nitro.logger.info('Adding Nitro fallback to `_headers` to handle all unmatched routes.') - contents = currentHeaders + '\n' + contents + nitro.logger.info( + "Adding Nitro fallback to `_headers` to handle all unmatched routes." + ); + contents = currentHeaders + "\n" + contents; } - await fsp.writeFile(headersPath, contents) + await fsp.writeFile(headersPath, contents); } diff --git a/src/presets/nitro-dev.ts b/src/presets/nitro-dev.ts index 9b996b5b91..5ac39fd8a3 100644 --- a/src/presets/nitro-dev.ts +++ b/src/presets/nitro-dev.ts @@ -1,12 +1,12 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const nitroDev = defineNitroPreset({ - extends: 'node', - entry: '#internal/nitro/entries/nitro-dev', + extends: "node", + entry: "#internal/nitro/entries/nitro-dev", output: { - serverDir: '{{ buildDir }}/dev' + serverDir: "{{ buildDir }}/dev", }, externals: { trace: false }, inlineDynamicImports: true, // externals plugin limitation - sourceMap: true -}) + sourceMap: true, +}); diff --git a/src/presets/nitro-prerender.ts b/src/presets/nitro-prerender.ts index bb3d744228..7452bc0ec6 100644 --- a/src/presets/nitro-prerender.ts +++ b/src/presets/nitro-prerender.ts @@ -1,13 +1,13 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const nitroPrerender = defineNitroPreset({ - extends: 'node', - entry: '#internal/nitro/entries/nitro-prerenderer', + extends: "node", + entry: "#internal/nitro/entries/nitro-prerenderer", output: { - serverDir: '{{ buildDir }}/prerender' + serverDir: "{{ buildDir }}/prerender", }, commands: { - preview: 'npx serve -s ./public' + preview: "npx serve -s ./public", }, - externals: { trace: false } -}) + externals: { trace: false }, +}); diff --git a/src/presets/node-cli.ts b/src/presets/node-cli.ts index 0ace277903..33b1f49255 100644 --- a/src/presets/node-cli.ts +++ b/src/presets/node-cli.ts @@ -1,9 +1,9 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const cli = defineNitroPreset({ - extends: 'node', - entry: '#internal/nitro/entries/cli', + extends: "node", + entry: "#internal/nitro/entries/cli", commands: { - preview: 'Run with node ./server/index.mjs [route]' - } -}) + preview: "Run with node ./server/index.mjs [route]", + }, +}); diff --git a/src/presets/node-server.ts b/src/presets/node-server.ts index d88b83e9b8..d6d2dd4ef8 100644 --- a/src/presets/node-server.ts +++ b/src/presets/node-server.ts @@ -1,15 +1,15 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const nodeServer = defineNitroPreset({ - extends: 'node', - entry: '#internal/nitro/entries/node-server', + extends: "node", + entry: "#internal/nitro/entries/node-server", serveStatic: true, commands: { - preview: 'node ./server/index.mjs' - } -}) + preview: "node ./server/index.mjs", + }, +}); export const nodeCluster = defineNitroPreset({ - extends: 'node-server', - entry: '#internal/nitro/entries/node-cluster' -}) + extends: "node-server", + entry: "#internal/nitro/entries/node-cluster", +}); diff --git a/src/presets/node.ts b/src/presets/node.ts index 2ab4e772d4..59bc2577f8 100644 --- a/src/presets/node.ts +++ b/src/presets/node.ts @@ -1,5 +1,5 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const node = defineNitroPreset({ - entry: '#internal/nitro/entries/node' -}) + entry: "#internal/nitro/entries/node", +}); diff --git a/src/presets/render-com.ts b/src/presets/render-com.ts index 60cad4cd33..1f3357525f 100644 --- a/src/presets/render-com.ts +++ b/src/presets/render-com.ts @@ -1,5 +1,5 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const renderCom = defineNitroPreset({ - extends: 'node-server' -}) + extends: "node-server", +}); diff --git a/src/presets/service-worker.ts b/src/presets/service-worker.ts index f1f01ca42d..b908255a98 100644 --- a/src/presets/service-worker.ts +++ b/src/presets/service-worker.ts @@ -1,13 +1,16 @@ -import { existsSync, promises as fsp } from 'fs' -import { resolve } from 'pathe' -import { joinURL } from 'ufo' -import { defineNitroPreset } from '../preset' -import type { Nitro } from '../types' +import { existsSync, promises as fsp } from "node:fs"; +import { resolve } from "pathe"; +import { joinURL } from "ufo"; +import { defineNitroPreset } from "../preset"; +import type { Nitro } from "../types"; -const scriptTemplate = (baseURL = '/') => ` +const scriptTemplate = (baseURL = "/") => ` -` +`; -const htmlTemplate = (baseURL = '/') => ` +const htmlTemplate = (baseURL = "/") => ` - - + + ${scriptTemplate(baseURL)} Initializing nitro service worker... -` +`; export const serviceWorker = defineNitroPreset(() => { return { - extends: 'base-worker', - entry: '#internal/nitro/entries/service-worker', + extends: "base-worker", + entry: "#internal/nitro/entries/service-worker", output: { - serverDir: '{{ output.dir }}/public/server' + serverDir: "{{ output.dir }}/public/server", }, commands: { - preview: 'npx serve ./public' + preview: "npx serve ./public", }, hooks: { - 'prerender:generate' (route, nitro) { - const script = scriptTemplate(nitro.options.baseURL) - route.contents = route.contents.replace('', `${script}\n`) + "prerender:generate"(route, nitro) { + const script = scriptTemplate(nitro.options.baseURL); + route.contents = route.contents.replace( + "", + `${script}\n` + ); }, - async 'compiled' (nitro: Nitro) { + async compiled(nitro: Nitro) { // Write sw.js file - await fsp.writeFile(resolve(nitro.options.output.publicDir, 'sw.js'), `self.importScripts('${joinURL(nitro.options.baseURL, 'server/index.mjs')}');`, 'utf8') + await fsp.writeFile( + resolve(nitro.options.output.publicDir, "sw.js"), + `self.importScripts('${joinURL( + nitro.options.baseURL, + "server/index.mjs" + )}');`, + "utf8" + ); // Write fallback initializer files - const html = htmlTemplate(nitro.options.baseURL) - if (!existsSync(resolve(nitro.options.output.publicDir, 'index.html'))) { - await fsp.writeFile(resolve(nitro.options.output.publicDir, 'index.html'), html, 'utf8') + const html = htmlTemplate(nitro.options.baseURL); + if ( + !existsSync(resolve(nitro.options.output.publicDir, "index.html")) + ) { + await fsp.writeFile( + resolve(nitro.options.output.publicDir, "index.html"), + html, + "utf8" + ); } - if (!existsSync(resolve(nitro.options.output.publicDir, '200.html'))) { - await fsp.writeFile(resolve(nitro.options.output.publicDir, '200.html'), html, 'utf8') + if (!existsSync(resolve(nitro.options.output.publicDir, "200.html"))) { + await fsp.writeFile( + resolve(nitro.options.output.publicDir, "200.html"), + html, + "utf8" + ); } - if (!existsSync(resolve(nitro.options.output.publicDir, '404.html'))) { - await fsp.writeFile(resolve(nitro.options.output.publicDir, '404.html'), html, 'utf8') + if (!existsSync(resolve(nitro.options.output.publicDir, "404.html"))) { + await fsp.writeFile( + resolve(nitro.options.output.publicDir, "404.html"), + html, + "utf8" + ); } - } - } - } -}) + }, + }, + }; +}); diff --git a/src/presets/stormkit.ts b/src/presets/stormkit.ts index 7922b71e21..f90d23ffd5 100644 --- a/src/presets/stormkit.ts +++ b/src/presets/stormkit.ts @@ -1,8 +1,8 @@ -import { defineNitroPreset } from '../preset' +import { defineNitroPreset } from "../preset"; export const stormkit = defineNitroPreset({ - entry: '#internal/nitro/entries/stormkit', + entry: "#internal/nitro/entries/stormkit", output: { - dir: '{{ rootDir }}/.stormkit' - } -}) + dir: "{{ rootDir }}/.stormkit", + }, +}); diff --git a/src/presets/vercel.ts b/src/presets/vercel.ts index 467aeb7460..86f2d89043 100644 --- a/src/presets/vercel.ts +++ b/src/presets/vercel.ts @@ -1,143 +1,189 @@ -import fsp from 'fs/promises' -import { dirname, relative, resolve } from 'pathe' -import { defu } from 'defu' -import { withoutLeadingSlash } from 'ufo' -import { writeFile } from '../utils' -import { defineNitroPreset } from '../preset' -import type { Nitro } from '../types' -import type { VercelBuildConfigV3 } from '../types/presets' +import fsp from "node:fs/promises"; +import { dirname, relative, resolve } from "pathe"; +import { defu } from "defu"; +import { withoutLeadingSlash } from "ufo"; +import { writeFile } from "../utils"; +import { defineNitroPreset } from "../preset"; +import type { Nitro } from "../types"; +import type { VercelBuildConfigV3 } from "../types/presets"; // https://vercel.com/docs/build-output-api/v3 export const vercel = defineNitroPreset({ - extends: 'node', - entry: '#internal/nitro/entries/vercel', + extends: "node", + entry: "#internal/nitro/entries/vercel", output: { - dir: '{{ rootDir }}/.vercel/output', - serverDir: '{{ output.dir }}/functions/__nitro.func', - publicDir: '{{ output.dir }}/static' + dir: "{{ rootDir }}/.vercel/output", + serverDir: "{{ output.dir }}/functions/__nitro.func", + publicDir: "{{ output.dir }}/static", }, commands: { - deploy: '', - preview: '' + deploy: "", + preview: "", }, hooks: { - async 'compiled' (nitro: Nitro) { - const buildConfigPath = resolve(nitro.options.output.dir, 'config.json') - const buildConfig = generateBuildConfig(nitro) - await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)) + async compiled(nitro: Nitro) { + const buildConfigPath = resolve(nitro.options.output.dir, "config.json"); + const buildConfig = generateBuildConfig(nitro); + await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)); - const functionConfigPath = resolve(nitro.options.output.serverDir, '.vc-config.json') + const functionConfigPath = resolve( + nitro.options.output.serverDir, + ".vc-config.json" + ); const functionConfig = { - runtime: 'nodejs16.x', - handler: 'index.mjs', - launcherType: 'Nodejs', - shouldAddHelpers: false - } - await writeFile(functionConfigPath, JSON.stringify(functionConfig, null, 2)) + runtime: "nodejs16.x", + handler: "index.mjs", + launcherType: "Nodejs", + shouldAddHelpers: false, + }; + await writeFile( + functionConfigPath, + JSON.stringify(functionConfig, null, 2) + ); // Write prerender functions - for (const [key, value] of Object.entries(nitro.options.routeRules).filter(([_, value]) => value.cache && (value.cache.swr || value.cache.static))) { - if (!value.cache) { continue } // for type support - const funcPrefix = resolve(nitro.options.output.serverDir, '..' + generateEndpoint(key)) - await fsp.mkdir(dirname(funcPrefix), { recursive: true }) - await fsp.symlink('./' + relative(dirname(funcPrefix), nitro.options.output.serverDir), funcPrefix + '.func', 'junction') - await writeFile(funcPrefix + '.prerender-config.json', JSON.stringify({ - expiration: value.cache.static ? false : typeof value.cache.swr === 'number' ? value.cache.swr : 60, - allowQuery: key.includes('/**') ? ['url'] : undefined - })) + for (const [key, value] of Object.entries( + nitro.options.routeRules + ).filter( + ([_, value]) => value.cache && (value.cache.swr || value.cache.static) + )) { + if (!value.cache) { + continue; + } // for type support + const funcPrefix = resolve( + nitro.options.output.serverDir, + ".." + generateEndpoint(key) + ); + await fsp.mkdir(dirname(funcPrefix), { recursive: true }); + await fsp.symlink( + "./" + relative(dirname(funcPrefix), nitro.options.output.serverDir), + funcPrefix + ".func", + "junction" + ); + + let expiration: boolean | number = 60; + if (value.cache.static) { + expiration = false; + } else if (typeof value.cache.swr === "number") { + expiration = value.cache.swr; + } + + await writeFile( + funcPrefix + ".prerender-config.json", + JSON.stringify({ + expiration, + allowQuery: key.includes("/**") ? ["url"] : undefined, + }) + ); } - } - } -}) + }, + }, +}); export const vercelEdge = defineNitroPreset({ - extends: 'base-worker', - entry: '#internal/nitro/entries/vercel-edge', + extends: "base-worker", + entry: "#internal/nitro/entries/vercel-edge", output: { - dir: '{{ rootDir }}/.vercel/output', - serverDir: '{{ output.dir }}/functions/__nitro.func', - publicDir: '{{ output.dir }}/static' + dir: "{{ rootDir }}/.vercel/output", + serverDir: "{{ output.dir }}/functions/__nitro.func", + publicDir: "{{ output.dir }}/static", }, commands: { - deploy: '', - preview: '' + deploy: "", + preview: "", }, rollupConfig: { output: { - format: 'module' - } + format: "module", + }, }, hooks: { - async 'compiled' (nitro: Nitro) { - const buildConfigPath = resolve(nitro.options.output.dir, 'config.json') - const buildConfig = generateBuildConfig(nitro) - await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)) + async compiled(nitro: Nitro) { + const buildConfigPath = resolve(nitro.options.output.dir, "config.json"); + const buildConfig = generateBuildConfig(nitro); + await writeFile(buildConfigPath, JSON.stringify(buildConfig, null, 2)); - const functionConfigPath = resolve(nitro.options.output.serverDir, '.vc-config.json') + const functionConfigPath = resolve( + nitro.options.output.serverDir, + ".vc-config.json" + ); const functionConfig = { - runtime: 'edge', - entrypoint: 'index.mjs' - } - await writeFile(functionConfigPath, JSON.stringify(functionConfig, null, 2)) - } - } -}) + runtime: "edge", + entrypoint: "index.mjs", + }; + await writeFile( + functionConfigPath, + JSON.stringify(functionConfig, null, 2) + ); + }, + }, +}); -function generateBuildConfig (nitro: Nitro) { - return defu(nitro.options.vercel?.config, { +function generateBuildConfig(nitro: Nitro) { + return defu(nitro.options.vercel?.config, { version: 3, overrides: Object.fromEntries( - (nitro._prerenderedRoutes?.filter(r => r.fileName !== r.route) || []) - .map(({ route, fileName }) => - [withoutLeadingSlash(fileName), { path: route.replace(/^\//, '') }] - ) + ( + nitro._prerenderedRoutes?.filter((r) => r.fileName !== r.route) || [] + ).map(({ route, fileName }) => [ + withoutLeadingSlash(fileName), + { path: route.replace(/^\//, "") }, + ]) ), routes: [ ...Object.entries(nitro.options.routeRules) .filter(([_, routeRules]) => routeRules.redirect || routeRules.headers) .map(([path, routeRules]) => { let route = { - src: path.replace('/**', '/.*') - } + src: path.replace("/**", "/.*"), + }; if (routeRules.redirect) { route = defu(route, { status: routeRules.redirect.statusCode, - headers: { Location: routeRules.redirect.to } - }) + headers: { Location: routeRules.redirect.to }, + }); } if (routeRules.headers) { - route = defu(route, { headers: routeRules.headers }) + route = defu(route, { headers: routeRules.headers }); } - return route + return route; }), ...nitro.options.publicAssets - .filter(asset => !asset.fallthrough) - .map(asset => asset.baseURL) - .map(baseURL => ({ - src: baseURL + '(.*)', + .filter((asset) => !asset.fallthrough) + .map((asset) => asset.baseURL) + .map((baseURL) => ({ + src: baseURL + "(.*)", headers: { - 'cache-control': 'public,max-age=31536000,immutable' + "cache-control": "public,max-age=31536000,immutable", }, - continue: true + continue: true, })), { - handle: 'filesystem' + handle: "filesystem", }, ...Object.entries(nitro.options.routeRules) - .filter(([key, value]) => value.cache && (value.cache.swr || value.cache.static) && key.includes('/**')) + .filter( + ([key, value]) => + value.cache && + (value.cache.swr || value.cache.static) && + key.includes("/**") + ) .map(([key]) => ({ - src: key.replace(/^(.*)\/\*\*/, '(?$1/.*)'), - dest: generateEndpoint(key) + '?url=$url' + src: key.replace(/^(.*)\/\*\*/, "(?$1/.*)"), + dest: generateEndpoint(key) + "?url=$url", })), { - src: '/(.*)', - dest: '/__nitro' - } - ] - }) + src: "/(.*)", + dest: "/__nitro", + }, + ], + }); } -function generateEndpoint (url: string) { - return url.includes('/**') ? '/__nitro-' + withoutLeadingSlash(url.replace(/\/\*\*.*/, '').replace(/[^a-z]/g, '-')) : url +function generateEndpoint(url: string) { + return url.includes("/**") + ? "/__nitro-" + + withoutLeadingSlash(url.replace(/\/\*\*.*/, "").replace(/[^a-z]/g, "-")) + : url; } diff --git a/src/rollup/config.ts b/src/rollup/config.ts index c1d4f4bb63..104291bd7d 100644 --- a/src/rollup/config.ts +++ b/src/rollup/config.ts @@ -1,336 +1,418 @@ -import { pathToFileURL } from 'url' -import { dirname, join, normalize, relative, resolve } from 'pathe' -import type { InputOptions, OutputOptions } from 'rollup' -import { defu } from 'defu' -import { terser } from 'rollup-plugin-terser' -import type { RollupWasmOptions } from '@rollup/plugin-wasm' -import commonjs from '@rollup/plugin-commonjs' -import { nodeResolve } from '@rollup/plugin-node-resolve' -import alias from '@rollup/plugin-alias' -import json from '@rollup/plugin-json' -import wasmPlugin from '@rollup/plugin-wasm' -import inject from '@rollup/plugin-inject' -import { isWindows } from 'std-env' -import { visualizer } from 'rollup-plugin-visualizer' -import * as unenv from 'unenv' -import type { Preset } from 'unenv' -import { sanitizeFilePath } from 'mlly' -import unimportPlugin from 'unimport/unplugin' -import { hash } from 'ohash' -import type { Nitro } from '../types' -import { resolveAliases } from '../utils' -import { runtimeDir } from '../dirs' -import { replace } from './plugins/replace' -import { virtual } from './plugins/virtual' -import { dynamicRequire } from './plugins/dynamic-require' -import { externals } from './plugins/externals' -import { timing } from './plugins/timing' -import { publicAssets } from './plugins/public-assets' -import { serverAssets } from './plugins/server-assets' -import { handlers } from './plugins/handlers' -import { esbuild } from './plugins/esbuild' -import { raw } from './plugins/raw' -import { storage } from './plugins/storage' -import { importMeta } from './plugins/import-meta' - -export type RollupConfig = InputOptions & { output: OutputOptions } +import { pathToFileURL } from "node:url"; +import { dirname, join, normalize, relative, resolve } from "pathe"; +import type { InputOptions, OutputOptions } from "rollup"; +import { defu } from "defu"; +import { terser } from "rollup-plugin-terser"; +import type { RollupWasmOptions } from "@rollup/plugin-wasm"; +import commonjs from "@rollup/plugin-commonjs"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import alias from "@rollup/plugin-alias"; +import json from "@rollup/plugin-json"; +import wasmPlugin from "@rollup/plugin-wasm"; +import inject from "@rollup/plugin-inject"; +import { isWindows } from "std-env"; +import { visualizer } from "rollup-plugin-visualizer"; +import * as unenv from "unenv"; +import type { Preset } from "unenv"; +import { sanitizeFilePath } from "mlly"; +import unimportPlugin from "unimport/unplugin"; +import { hash } from "ohash"; +import type { Nitro } from "../types"; +import { resolveAliases } from "../utils"; +import { runtimeDir } from "../dirs"; +import { replace } from "./plugins/replace"; +import { virtual } from "./plugins/virtual"; +import { dynamicRequire } from "./plugins/dynamic-require"; +import { externals } from "./plugins/externals"; +import { timing } from "./plugins/timing"; +import { publicAssets } from "./plugins/public-assets"; +import { serverAssets } from "./plugins/server-assets"; +import { handlers } from "./plugins/handlers"; +import { esbuild } from "./plugins/esbuild"; +import { raw } from "./plugins/raw"; +import { storage } from "./plugins/storage"; +import { importMeta } from "./plugins/import-meta"; + +export type RollupConfig = InputOptions & { output: OutputOptions }; export const getRollupConfig = (nitro: Nitro) => { - const extensions: string[] = ['.ts', '.mjs', '.js', '.json', '.node'] + const extensions: string[] = [".ts", ".mjs", ".js", ".json", ".node"]; - const nodePreset = nitro.options.node === false ? unenv.nodeless : unenv.node + const nodePreset = nitro.options.node === false ? unenv.nodeless : unenv.node; const builtinPreset: Preset = { alias: { // General - debug: 'unenv/runtime/npm/debug', - consola: 'unenv/runtime/npm/consola', - ...nitro.options.alias - } - } + debug: "unenv/runtime/npm/debug", + consola: "unenv/runtime/npm/consola", + ...nitro.options.alias, + }, + }; - const env = unenv.env(nodePreset, builtinPreset, nitro.options.unenv) + const env = unenv.env(nodePreset, builtinPreset, nitro.options.unenv); if (nitro.options.sourceMap) { - env.polyfill.push('source-map-support/register.js') + env.polyfill.push("source-map-support/register.js"); } - const buildServerDir = join(nitro.options.buildDir, 'dist/server') - const runtimeAppDir = join(runtimeDir, 'app') + const buildServerDir = join(nitro.options.buildDir, "dist/server"); + const runtimeAppDir = join(runtimeDir, "app"); - const rollupConfig = defu(nitro.options.rollupConfig, { + const rollupConfig = defu(nitro.options.rollupConfig, { input: nitro.options.entry, output: { dir: nitro.options.output.serverDir, - entryFileNames: 'index.mjs', - chunkFileNames (chunkInfo) { - let prefix = '' - const modules = Object.keys(chunkInfo.modules) - const lastModule = modules[modules.length - 1] + entryFileNames: "index.mjs", + chunkFileNames(chunkInfo) { + let prefix = ""; + const modules = Object.keys(chunkInfo.modules); + const lastModule = modules[modules.length - 1]; if (lastModule.startsWith(buildServerDir)) { - prefix = join('app', relative(buildServerDir, dirname(lastModule))) + prefix = join("app", relative(buildServerDir, dirname(lastModule))); } else if (lastModule.startsWith(runtimeAppDir)) { - prefix = 'app' + prefix = "app"; } else if (lastModule.startsWith(nitro.options.buildDir)) { - prefix = 'build' + prefix = "build"; } else if (lastModule.startsWith(runtimeDir)) { - prefix = 'nitro' - } else if (nitro.options.handlers.find(m => lastModule.startsWith(m.handler as string))) { - prefix = 'handlers' - } else if (lastModule.includes('assets') || lastModule.startsWith('\0raw:')) { - prefix = 'raw' - } else if (lastModule.startsWith('\0')) { - prefix = 'rollup' + prefix = "nitro"; + } else if ( + nitro.options.handlers.some((m) => + lastModule.startsWith(m.handler as string) + ) + ) { + prefix = "handlers"; + } else if ( + lastModule.includes("assets") || + lastModule.startsWith("\0raw:") + ) { + prefix = "raw"; + } else if (lastModule.startsWith("\0")) { + prefix = "rollup"; } - return join('chunks', prefix, '[name].mjs') + return join("chunks", prefix, "[name].mjs"); }, inlineDynamicImports: nitro.options.inlineDynamicImports, - format: 'esm', - exports: 'auto', - intro: '', - outro: '', + format: "esm", + exports: "auto", + intro: "", + outro: "", preferConst: true, sanitizeFileName: sanitizeFilePath, sourcemap: nitro.options.sourceMap, sourcemapExcludeSources: true, - sourcemapPathTransform (relativePath, sourcemapPath) { - return resolve(dirname(sourcemapPath), relativePath) - } + sourcemapPathTransform(relativePath, sourcemapPath) { + return resolve(dirname(sourcemapPath), relativePath); + }, }, external: env.external, // https://github.com/rollup/rollup/pull/4021#issuecomment-809985618 makeAbsoluteExternalsRelative: false, plugins: [], - onwarn (warning, rollupWarn) { + onwarn(warning, rollupWarn) { if ( - !['CIRCULAR_DEPENDENCY', 'EVAL'].includes(warning.code) && - !warning.message.includes('Unsupported source map comment') + !["CIRCULAR_DEPENDENCY", "EVAL"].includes(warning.code) && + !warning.message.includes("Unsupported source map comment") ) { - rollupWarn(warning) + rollupWarn(warning); } }, treeshake: { - moduleSideEffects (id) { - const normalizedId = normalize(id) - const idWithoutNodeModules = normalizedId.split('node_modules/').pop() - return nitro.options.moduleSideEffects.some(m => normalizedId.startsWith(m) || idWithoutNodeModules.startsWith(m)) - } - } - }) + moduleSideEffects(id) { + const normalizedId = normalize(id); + const idWithoutNodeModules = normalizedId.split("node_modules/").pop(); + return nitro.options.moduleSideEffects.some( + (m) => + normalizedId.startsWith(m) || idWithoutNodeModules.startsWith(m) + ); + }, + }, + }); if (nitro.options.timing) { - rollupConfig.plugins.push(timing()) + rollupConfig.plugins.push(timing()); } if (nitro.options.imports) { - rollupConfig.plugins.push(unimportPlugin.rollup(nitro.options.imports)) + rollupConfig.plugins.push(unimportPlugin.rollup(nitro.options.imports)); } // Raw asset loader - rollupConfig.plugins.push(raw()) + rollupConfig.plugins.push(raw()); // WASM import support if (nitro.options.experimental.wasm) { - const options = { ...nitro.options.experimental.wasm as RollupWasmOptions } - rollupConfig.plugins.push(wasmPlugin(options)) + const options = { + ...(nitro.options.experimental.wasm as RollupWasmOptions), + }; + rollupConfig.plugins.push(wasmPlugin(options)); } // Build-time environment variables + let NODE_ENV = nitro.options.dev ? "development" : "production"; + if (nitro.options.preset === "nitro-prerender") { + NODE_ENV = "prerender"; + } const buildEnvVars = { - NODE_ENV: nitro.options.dev ? 'development' : (nitro.options.preset === 'nitro-prerender' ? 'prerender' : 'production'), - prerender: nitro.options.preset === 'nitro-prerender', + NODE_ENV, + prerender: nitro.options.preset === "nitro-prerender", server: true, client: false, dev: String(nitro.options.dev), RUNTIME_CONFIG: nitro.options.runtimeConfig, - DEBUG: nitro.options.dev - } + DEBUG: nitro.options.dev, + }; // Universal import.meta - rollupConfig.plugins.push(importMeta(nitro)) + rollupConfig.plugins.push(importMeta(nitro)); // https://github.com/rollup/plugins/tree/master/packages/replace - rollupConfig.plugins.push(replace({ - preventAssignment: true, - values: { - 'typeof window': '"undefined"', - _import_meta_url_: 'import.meta.url', - ...Object.fromEntries(['.', ';', ')', '[', ']', '}', ' '].map(d => [`import.meta${d}`, `globalThis._importMeta_${d}`])), - ...Object.fromEntries([';', '(', '{', '}', ' ', '\t', '\n'].map(d => [`${d}global.`, `${d}globalThis.`])), - ...Object.fromEntries(Object.entries(buildEnvVars).map(([key, val]) => ([`process.env.${key}`, JSON.stringify(val)]))), - ...Object.fromEntries(Object.entries(buildEnvVars).map(([key, val]) => ([`import.meta.env.${key}`, JSON.stringify(val)]))), - ...nitro.options.replace - } - })) + rollupConfig.plugins.push( + replace({ + preventAssignment: true, + values: { + "typeof window": '"undefined"', + _import_meta_url_: "import.meta.url", + ...Object.fromEntries( + [".", ";", ")", "[", "]", "}", " "].map((d) => [ + `import.meta${d}`, + `globalThis._importMeta_${d}`, + ]) + ), + ...Object.fromEntries( + [";", "(", "{", "}", " ", "\t", "\n"].map((d) => [ + `${d}global.`, + `${d}globalThis.`, + ]) + ), + ...Object.fromEntries( + Object.entries(buildEnvVars).map(([key, val]) => [ + `process.env.${key}`, + JSON.stringify(val), + ]) + ), + ...Object.fromEntries( + Object.entries(buildEnvVars).map(([key, val]) => [ + `import.meta.env.${key}`, + JSON.stringify(val), + ]) + ), + ...nitro.options.replace, + }, + }) + ); // esbuild - rollupConfig.plugins.push(esbuild({ - target: 'es2019', - sourceMap: nitro.options.sourceMap, - ...nitro.options.esbuild?.options - })) + rollupConfig.plugins.push( + esbuild({ + target: "es2019", + sourceMap: nitro.options.sourceMap, + ...nitro.options.esbuild?.options, + }) + ); // Dynamic Require Support - rollupConfig.plugins.push(dynamicRequire({ - dir: resolve(nitro.options.buildDir, 'dist/server'), - inline: nitro.options.node === false || nitro.options.inlineDynamicImports, - ignore: [ - 'client.manifest.mjs', - 'server.js', - 'server.cjs', - 'server.mjs', - 'server.manifest.mjs' - ] - })) + rollupConfig.plugins.push( + dynamicRequire({ + dir: resolve(nitro.options.buildDir, "dist/server"), + inline: + nitro.options.node === false || nitro.options.inlineDynamicImports, + ignore: [ + "client.manifest.mjs", + "server.js", + "server.cjs", + "server.mjs", + "server.manifest.mjs", + ], + }) + ); // Server assets - rollupConfig.plugins.push(serverAssets(nitro)) + rollupConfig.plugins.push(serverAssets(nitro)); // Public assets - rollupConfig.plugins.push(publicAssets(nitro)) + rollupConfig.plugins.push(publicAssets(nitro)); // Storage - rollupConfig.plugins.push(storage(nitro)) + rollupConfig.plugins.push(storage(nitro)); // Handlers - rollupConfig.plugins.push(handlers(nitro)) + rollupConfig.plugins.push(handlers(nitro)); // Polyfill - rollupConfig.plugins.push(virtual({ - '#internal/nitro/virtual/polyfill': env.polyfill.map(p => `import '${p}';`).join('\n') - }, nitro.vfs)) + rollupConfig.plugins.push( + virtual( + { + "#internal/nitro/virtual/polyfill": env.polyfill + .map((p) => `import '${p}';`) + .join("\n"), + }, + nitro.vfs + ) + ); // User virtuals - rollupConfig.plugins.push(virtual(nitro.options.virtual, nitro.vfs)) + rollupConfig.plugins.push(virtual(nitro.options.virtual, nitro.vfs)); // Plugins - rollupConfig.plugins.push(virtual({ - '#internal/nitro/virtual/plugins': ` -${nitro.options.plugins.map(plugin => `import _${hash(plugin)} from '${plugin}';`).join('\n')} + rollupConfig.plugins.push( + virtual( + { + "#internal/nitro/virtual/plugins": ` +${nitro.options.plugins + .map((plugin) => `import _${hash(plugin)} from '${plugin}';`) + .join("\n")} export const plugins = [ - ${nitro.options.plugins.map(plugin => `_${hash(plugin)}`).join(',\n')} + ${nitro.options.plugins.map((plugin) => `_${hash(plugin)}`).join(",\n")} ] - ` - }, nitro.vfs)) + `, + }, + nitro.vfs + ) + ); // https://github.com/rollup/plugins/tree/master/packages/alias - let buildDir = nitro.options.buildDir + let buildDir = nitro.options.buildDir; // Windows (native) dynamic imports should be file:// urls - if (isWindows && (nitro.options.externals?.trace === false) && nitro.options.dev) { - buildDir = pathToFileURL(buildDir).href + if ( + isWindows && + nitro.options.externals?.trace === false && + nitro.options.dev + ) { + buildDir = pathToFileURL(buildDir).href; } - rollupConfig.plugins.push(alias({ - entries: resolveAliases({ - '#build': buildDir, - '#internal/nitro/virtual/error-handler': nitro.options.errorHandler, - '~': nitro.options.srcDir, - '@/': nitro.options.srcDir, - '~~': nitro.options.rootDir, - '@@/': nitro.options.rootDir, - ...env.alias + rollupConfig.plugins.push( + alias({ + entries: resolveAliases({ + "#build": buildDir, + "#internal/nitro/virtual/error-handler": nitro.options.errorHandler, + "~": nitro.options.srcDir, + "@/": nitro.options.srcDir, + "~~": nitro.options.rootDir, + "@@/": nitro.options.rootDir, + ...env.alias, + }), }) - })) + ); // Externals Plugin if (!nitro.options.noExternals) { - rollupConfig.plugins.push(externals(defu(nitro.options.externals, { - outDir: nitro.options.output.serverDir, - moduleDirectories: nitro.options.nodeModulesDirs, - external: [ - ...(nitro.options.dev ? [nitro.options.buildDir] : []) - ], - inline: [ - '#', - '~', - '@/', - '~~', - '@@/', - 'virtual:', - runtimeDir, - nitro.options.srcDir, - ...nitro.options.handlers.map(m => m.handler).filter(i => typeof i === 'string') - ], - traceOptions: { - base: '/', - processCwd: nitro.options.rootDir, - exportsOnly: true - }, - exportConditions: [ - 'default', - nitro.options.dev ? 'development' : 'production', - 'module', - 'node', - 'import' - ] - }))) + rollupConfig.plugins.push( + externals( + defu(nitro.options.externals, { + outDir: nitro.options.output.serverDir, + moduleDirectories: nitro.options.nodeModulesDirs, + external: [...(nitro.options.dev ? [nitro.options.buildDir] : [])], + inline: [ + "#", + "~", + "@/", + "~~", + "@@/", + "virtual:", + runtimeDir, + nitro.options.srcDir, + ...nitro.options.handlers + .map((m) => m.handler) + .filter((i) => typeof i === "string"), + ], + traceOptions: { + base: "/", + processCwd: nitro.options.rootDir, + exportsOnly: true, + }, + exportConditions: [ + "default", + nitro.options.dev ? "development" : "production", + "module", + "node", + "import", + ], + }) + ) + ); } else { rollupConfig.plugins.push({ - name: 'no-externals', - async resolveId (id, from, options) { - const resolved = await this.resolve(id, from, { ...options, skipSelf: true }) + name: "no-externals", + async resolveId(id, from, options) { + const resolved = await this.resolve(id, from, { + ...options, + skipSelf: true, + }); if (!resolved || resolved.external) { - throw new Error(`Cannot resolve ${JSON.stringify(id)} from ${JSON.stringify(from)} and externals are not allowed!`) + throw new Error( + `Cannot resolve ${JSON.stringify(id)} from ${JSON.stringify( + from + )} and externals are not allowed!` + ); } - } - }) + }, + }); } // https://github.com/rollup/plugins/tree/master/packages/node-resolve - rollupConfig.plugins.push(nodeResolve({ - extensions, - preferBuiltins: !!nitro.options.node, - rootDir: nitro.options.rootDir, - modulePaths: nitro.options.nodeModulesDirs, - // 'module' is intentionally not supported because of externals - mainFields: ['main'], - exportConditions: [ - 'default', - nitro.options.dev ? 'development' : 'production', - 'module', - 'node', - 'import' - ] - })) + rollupConfig.plugins.push( + nodeResolve({ + extensions, + preferBuiltins: !!nitro.options.node, + rootDir: nitro.options.rootDir, + modulePaths: nitro.options.nodeModulesDirs, + // 'module' is intentionally not supported because of externals + mainFields: ["main"], + exportConditions: [ + "default", + nitro.options.dev ? "development" : "production", + "module", + "node", + "import", + ], + }) + ); // Automatically mock unresolved externals // rollupConfig.plugins.push(autoMock()) // https://github.com/rollup/plugins/tree/master/packages/commonjs - rollupConfig.plugins.push(commonjs({ - esmExternals: id => !id.startsWith('unenv/'), - requireReturnsDefault: 'auto', - ...nitro.options.commonJS - })) + rollupConfig.plugins.push( + commonjs({ + esmExternals: (id) => !id.startsWith("unenv/"), + requireReturnsDefault: "auto", + ...nitro.options.commonJS, + }) + ); // https://github.com/rollup/plugins/tree/master/packages/json - rollupConfig.plugins.push(json()) + rollupConfig.plugins.push(json()); // https://github.com/rollup/plugins/tree/master/packages/inject - rollupConfig.plugins.push(inject(env.inject)) + rollupConfig.plugins.push(inject(env.inject)); // https://github.com/TrySound/rollup-plugin-terser // https://github.com/terser/terser#minify-Nitro if (nitro.options.minify) { - rollupConfig.plugins.push(terser({ - mangle: { - keep_fnames: true, - keep_classnames: true - }, - format: { - comments: false - } - })) + rollupConfig.plugins.push( + terser({ + mangle: { + keep_fnames: true, + keep_classnames: true, + }, + format: { + comments: false, + }, + }) + ); } if (nitro.options.analyze) { // https://github.com/btd/rollup-plugin-visualizer - rollupConfig.plugins.push(visualizer({ - ...nitro.options.analyze, - filename: nitro.options.analyze.filename.replace('{name}', 'nitro'), - title: 'Nitro Server bundle stats' - })) + rollupConfig.plugins.push( + visualizer({ + ...nitro.options.analyze, + filename: nitro.options.analyze.filename.replace("{name}", "nitro"), + title: "Nitro Server bundle stats", + }) + ); } - return rollupConfig -} + return rollupConfig; +}; diff --git a/src/rollup/plugins/automock.ts b/src/rollup/plugins/automock.ts index 6867d94e8d..519f172972 100644 --- a/src/rollup/plugins/automock.ts +++ b/src/rollup/plugins/automock.ts @@ -1,18 +1,18 @@ -import consola from 'consola' +import consola from "consola"; -const internalRegex = /^\.|\?|\.[mc]?js$|.ts$|.json$/ +const internalRegex = /^\.|\?|\.[cm]?js$|.ts$|.json$/; -export function autoMock () { +export function autoMock() { return { - name: 'auto-mock', - resolveId (src: string) { + name: "auto-mock", + resolveId(src: string) { if (src && !internalRegex.test(src)) { - consola.warn('Auto mock external ', src) + consola.warn("Auto mock external ", src); return { - id: 'unenv/runtime/mock/proxy-cjs' - } + id: "unenv/runtime/mock/proxy-cjs", + }; } - return null - } - } + return null; + }, + }; } diff --git a/src/rollup/plugins/dynamic-require.ts b/src/rollup/plugins/dynamic-require.ts index da392f939a..377abe73b6 100644 --- a/src/rollup/plugins/dynamic-require.ts +++ b/src/rollup/plugins/dynamic-require.ts @@ -1,47 +1,50 @@ -import { pathToFileURL } from 'url' -import { resolve } from 'pathe' -import { globby } from 'globby' -import type { Plugin } from 'rollup' -import { genSafeVariableName } from 'knitwork' +import { pathToFileURL } from "node:url"; +import { resolve } from "pathe"; +import { globby } from "globby"; +import type { Plugin } from "rollup"; +import { genSafeVariableName } from "knitwork"; -const PLUGIN_NAME = 'dynamic-require' -const HELPER_DYNAMIC = `\0${PLUGIN_NAME}.mjs` -const DYNAMIC_REQUIRE_RE = /import\("\.\/" ?\+(.*)\).then/g +const PLUGIN_NAME = "dynamic-require"; +const HELPER_DYNAMIC = `\0${PLUGIN_NAME}.mjs`; +const DYNAMIC_REQUIRE_RE = /import\("\.\/" ?\+(.*)\).then/g; interface Options { - dir: string - inline: boolean - ignore: string[] - outDir?: string - prefix?: string + dir: string; + inline: boolean; + ignore: string[]; + outDir?: string; + prefix?: string; } interface Chunk { - id: string - src: string - name: string + id: string; + src: string; + name: string; meta?: { - id?: string - ids?: string[] - moduleIds?: string[] - } + id?: string; + ids?: string[]; + moduleIds?: string[]; + }; } interface TemplateContext { - chunks: Chunk[] + chunks: Chunk[]; } -export function dynamicRequire ({ dir, ignore, inline }: Options): Plugin { +export function dynamicRequire({ dir, ignore, inline }: Options): Plugin { return { name: PLUGIN_NAME, - transform (code: string, _id: string) { + transform(code: string, _id: string) { return { - code: code.replace(DYNAMIC_REQUIRE_RE, `import('${HELPER_DYNAMIC}').then(r => r.default || r).then(dynamicRequire => dynamicRequire($1)).then`), - map: null - } + code: code.replace( + DYNAMIC_REQUIRE_RE, + `import('${HELPER_DYNAMIC}').then(r => r.default || r).then(dynamicRequire => dynamicRequire($1)).then` + ), + map: null, + }; }, - resolveId (id: string) { - return id === HELPER_DYNAMIC ? id : null + resolveId(id: string) { + return id === HELPER_DYNAMIC ? id : null; }, // TODO: Async chunk loading over network! // renderDynamicImport () { @@ -49,63 +52,77 @@ export function dynamicRequire ({ dir, ignore, inline }: Options): Plugin { // left: 'fetch(', right: ')' // } // }, - async load (_id: string) { + async load(_id: string) { if (_id !== HELPER_DYNAMIC) { - return null + return null; } // Scan chunks - let files = [] + let files = []; try { - const wpManifest = resolve(dir, './server.manifest.json') - files = await import(pathToFileURL(wpManifest).href).then(r => Object.keys(r.files).filter(file => !ignore.includes(file))) + const wpManifest = resolve(dir, "./server.manifest.json"); + files = await import(pathToFileURL(wpManifest).href).then((r) => + Object.keys(r.files).filter((file) => !ignore.includes(file)) + ); } catch { - files = await globby('**/*.{cjs,mjs,js}', { cwd: dir, absolute: false, ignore }) + files = await globby("**/*.{cjs,mjs,js}", { + cwd: dir, + absolute: false, + ignore, + }); } - const chunks = (await Promise.all(files.map(async id => ({ - id, - src: resolve(dir, id).replace(/\\/g, '/'), - name: genSafeVariableName(id), - meta: await getWebpackChunkMeta(resolve(dir, id)) - })))).filter(chunk => chunk.meta) + const chunks = ( + await Promise.all( + files.map(async (id) => ({ + id, + src: resolve(dir, id).replace(/\\/g, "/"), + name: genSafeVariableName(id), + meta: await getWebpackChunkMeta(resolve(dir, id)), + })) + ) + ).filter((chunk) => chunk.meta); - return inline ? TMPL_INLINE({ chunks }) : TMPL_LAZY({ chunks }) - } - } + return inline ? TMPL_INLINE({ chunks }) : TMPL_LAZY({ chunks }); + }, + }; } -async function getWebpackChunkMeta (src: string) { - const chunk = await import(pathToFileURL(src).href).then(r => r.default || r || {}) - const { id, ids, modules } = chunk +async function getWebpackChunkMeta(src: string) { + const chunk = await import(pathToFileURL(src).href).then( + (r) => r.default || r || {} + ); + const { id, ids, modules } = chunk; if (!id && !ids) { - return null // Not a webpack chunk + return null; // Not a webpack chunk } return { id, ids, - moduleIds: Object.keys(modules || {}) - } + moduleIds: Object.keys(modules || {}), + }; } -function TMPL_INLINE ({ chunks }: TemplateContext) { - return `${chunks.map(i => `import * as ${i.name} from '${i.src}'`).join('\n')} +function TMPL_INLINE({ chunks }: TemplateContext) { + return `${chunks + .map((i) => `import * as ${i.name} from '${i.src}'`) + .join("\n")} const dynamicChunks = { - ${chunks.map(i => ` ['${i.id}']: ${i.name}`).join(',\n')} + ${chunks.map((i) => ` ['${i.id}']: ${i.name}`).join(",\n")} }; export default function dynamicRequire(id) { return Promise.resolve(dynamicChunks[id]); -};` +};`; } -function TMPL_LAZY ({ chunks }: TemplateContext) { +function TMPL_LAZY({ chunks }: TemplateContext) { return ` const dynamicChunks = { -${chunks.map(i => ` ['${i.id}']: () => import('${i.src}')`).join(',\n')} +${chunks.map((i) => ` ['${i.id}']: () => import('${i.src}')`).join(",\n")} }; export default function dynamicRequire(id) { return dynamicChunks[id](); -};` +};`; } diff --git a/src/rollup/plugins/esbuild.ts b/src/rollup/plugins/esbuild.ts index 1b5debb02c..21109563e6 100644 --- a/src/rollup/plugins/esbuild.ts +++ b/src/rollup/plugins/esbuild.ts @@ -1,81 +1,81 @@ // Based on https://github.com/egoist/rollup-plugin-esbuild (MIT) -import { extname, relative } from 'pathe' -import type { Plugin, PluginContext } from 'rollup' -import { Loader, TransformResult, transform } from 'esbuild' -import { createFilter } from '@rollup/pluginutils' -import type { FilterPattern } from '@rollup/pluginutils' +import { extname, relative } from "pathe"; +import type { Plugin, PluginContext } from "rollup"; +import { Loader, TransformResult, transform } from "esbuild"; +import { createFilter } from "@rollup/pluginutils"; +import type { FilterPattern } from "@rollup/pluginutils"; const defaultLoaders: { [ext: string]: Loader } = { - '.ts': 'ts', - '.js': 'js' -} + ".ts": "ts", + ".js": "js", +}; export type Options = { - include?: FilterPattern - exclude?: FilterPattern - sourceMap?: boolean - minify?: boolean - target: string | string[] - jsxFactory?: string - jsxFragment?: string + include?: FilterPattern; + exclude?: FilterPattern; + sourceMap?: boolean; + minify?: boolean; + target: string | string[]; + jsxFactory?: string; + jsxFragment?: string; define?: { - [k: string]: string - } + [k: string]: string; + }; /** * Use this tsconfig file instead * Disable it by setting to `false` */ - tsconfig?: string | false + tsconfig?: string | false; /** * Map extension to esbuild loader * Note that each entry (the extension) needs to start with a dot */ loaders?: { - [ext: string]: Loader | false - } -} + [ext: string]: Loader | false; + }; +}; -export function esbuild (options: Options): Plugin { +export function esbuild(options: Options): Plugin { const loaders = { - ...defaultLoaders - } + ...defaultLoaders, + }; if (options.loaders) { for (const key of Object.keys(options.loaders)) { - const value = options.loaders[key] - if (typeof value === 'string') { - loaders[key] = value + const value = options.loaders[key]; + if (typeof value === "string") { + loaders[key] = value; } else if (value === false) { - delete loaders[key] + delete loaders[key]; } } } - const extensions: string[] = Object.keys(loaders) + const extensions: string[] = Object.keys(loaders); const INCLUDE_REGEXP = new RegExp( - `\\.(${extensions.map(ext => ext.slice(1)).join('|')})$` - ) - const EXCLUDE_REGEXP = /node_modules/ + `\\.(${extensions.map((ext) => ext.slice(1)).join("|")})$` + ); + const EXCLUDE_REGEXP = /node_modules/; const filter = createFilter( options.include || INCLUDE_REGEXP, options.exclude || EXCLUDE_REGEXP - ) + ); return { - name: 'esbuild', + name: "esbuild", - async transform (code, id) { + async transform(code, id) { if (!filter(id)) { - return null + return null; } - const ext = extname(id) - const loader = loaders[ext] + const ext = extname(id); + const loader = loaders[ext]; if (!loader) { - return null + return null; } const result = await transform(code, { @@ -83,52 +83,53 @@ export function esbuild (options: Options): Plugin { target: options.target, define: options.define, sourcemap: options.sourceMap, - sourcefile: id - }) + sourcefile: id, + }); - printWarnings(id, result, this) + printWarnings(id, result, this); return ( result.code && { code: result.code, - map: result.map || null + map: result.map || null, } - ) + ); }, - async renderChunk (code) { + async renderChunk(code) { if (options.minify) { const result = await transform(code, { - loader: 'js', + loader: "js", minify: true, - target: options.target - }) + target: options.target, + }); if (result.code) { return { code: result.code, - map: result.map || null - } + map: result.map || null, + }; } } - return null - } - } + return null; + }, + }; } -function printWarnings ( +function printWarnings( id: string, result: TransformResult, plugin: PluginContext ) { if (result.warnings) { for (const warning of result.warnings) { - let message = '[esbuild]' + let message = "[esbuild]"; if (warning.location) { - message += ` (${relative(process.cwd(), id)}:${warning.location.line}:${warning.location.column - })` + message += ` (${relative(process.cwd(), id)}:${warning.location.line}:${ + warning.location.column + })`; } - message += ` ${warning.text}` - plugin.warn(message) + message += ` ${warning.text}`; + plugin.warn(message); } } } diff --git a/src/rollup/plugins/externals.ts b/src/rollup/plugins/externals.ts index 01cd39143a..1b018a1411 100644 --- a/src/rollup/plugins/externals.ts +++ b/src/rollup/plugins/externals.ts @@ -1,78 +1,100 @@ -import { existsSync, promises as fsp } from 'fs' -import { resolve, dirname, normalize, join, isAbsolute } from 'pathe' -import consola from 'consola' -import { nodeFileTrace, NodeFileTraceOptions } from '@vercel/nft' -import type { Plugin } from 'rollup' -import { resolvePath, isValidNodeImport, normalizeid } from 'mlly' -import semver from 'semver' -import { isDirectory, retry } from '../../utils' +import { existsSync, promises as fsp } from "node:fs"; +import { resolve, dirname, normalize, join, isAbsolute } from "pathe"; +import consola from "consola"; +import { nodeFileTrace, NodeFileTraceOptions } from "@vercel/nft"; +import type { Plugin } from "rollup"; +import { resolvePath, isValidNodeImport, normalizeid } from "mlly"; +import semver from "semver"; +import { isDirectory, retry } from "../../utils"; export interface NodeExternalsOptions { - inline?: string[] - external?: string[] - outDir?: string - trace?: boolean - traceOptions?: NodeFileTraceOptions - moduleDirectories?: string[] - exportConditions?: string[] - traceInclude?: string[] + inline?: string[]; + external?: string[]; + outDir?: string; + trace?: boolean; + traceOptions?: NodeFileTraceOptions; + moduleDirectories?: string[]; + exportConditions?: string[]; + traceInclude?: string[]; } -export function externals (opts: NodeExternalsOptions): Plugin { - const trackedExternals = new Set() +export function externals(opts: NodeExternalsOptions): Plugin { + const trackedExternals = new Set(); - const _resolveCache = new Map() + const _resolveCache = new Map(); const _resolve = async (id: string) => { - let resolved = _resolveCache.get(id) - if (resolved) { return resolved } + let resolved = _resolveCache.get(id); + if (resolved) { + return resolved; + } resolved = await resolvePath(id, { conditions: opts.exportConditions, - url: opts.moduleDirectories - }) - _resolveCache.set(id, resolved) - return resolved - } + url: opts.moduleDirectories, + }); + _resolveCache.set(id, resolved); + return resolved; + }; return { - name: 'node-externals', - async resolveId (originalId, importer, options) { + name: "node-externals", + async resolveId(originalId, importer, options) { // Skip internals - if (!originalId || originalId.startsWith('\x00') || originalId.includes('?') || originalId.startsWith('#')) { - return null + if ( + !originalId || + originalId.startsWith("\u0000") || + originalId.includes("?") || + originalId.startsWith("#") + ) { + return null; } // Skip relative paths - if (originalId.startsWith('.')) { - return null + if (originalId.startsWith(".")) { + return null; } // Normalize path (windows) - const id = normalize(originalId) + const id = normalize(originalId); // Id without .../node_modules/ - const idWithoutNodeModules = id.split('node_modules/').pop() + const idWithoutNodeModules = id.split("node_modules/").pop(); // Check for explicit inlines - if (opts.inline.find(i => (id.startsWith(i) || idWithoutNodeModules.startsWith(i)))) { - return null + if ( + opts.inline.some( + (i) => id.startsWith(i) || idWithoutNodeModules.startsWith(i) + ) + ) { + return null; } // Check for explicit externals - if (opts.external.find(i => (id.startsWith(i) || idWithoutNodeModules.startsWith(i)))) { - return { id, external: true } + if ( + opts.external.some( + (i) => id.startsWith(i) || idWithoutNodeModules.startsWith(i) + ) + ) { + return { id, external: true }; } // Resolve id using rollup resolver - const resolved = await this.resolve(originalId, importer, { ...options, skipSelf: true }) || { id } + const resolved = (await this.resolve(originalId, importer, { + ...options, + skipSelf: true, + })) || { id }; // Try resolving with mlly as fallback - if (!isAbsolute(resolved.id) || !existsSync(resolved.id) || await isDirectory(resolved.id)) { - resolved.id = await _resolve(resolved.id).catch(() => resolved.id) + if ( + !isAbsolute(resolved.id) || + !existsSync(resolved.id) || + (await isDirectory(resolved.id)) + ) { + resolved.id = await _resolve(resolved.id).catch(() => resolved.id); } // Inline invalid node imports - if (!await isValidNodeImport(resolved.id).catch(() => false)) { - return null + if (!(await isValidNodeImport(resolved.id).catch(() => false))) { + return null; } // Externalize with full path if trace is disabled @@ -80,18 +102,18 @@ export function externals (opts: NodeExternalsOptions): Plugin { return { ...resolved, id: isAbsolute(resolved.id) ? normalizeid(resolved.id) : resolved.id, - external: true - } + external: true, + }; } // -- Trace externals -- // Try to extract package name from path - const { pkgName, subpath } = parseNodeModulePath(resolved.id) + const { pkgName, subpath } = parseNodeModulePath(resolved.id); // Inline if cannot detect package name if (!pkgName) { - return null + return null; } // Normally package name should be same as originalId @@ -99,173 +121,214 @@ export function externals (opts: NodeExternalsOptions): Plugin { if (pkgName !== originalId) { // Subpath export if (!isAbsolute(originalId)) { - const fullPath = await _resolve(originalId) - trackedExternals.add(fullPath) + const fullPath = await _resolve(originalId); + trackedExternals.add(fullPath); return { id: originalId, - external: true - } + external: true, + }; } // Absolute path, we are not sure about subpath to generate import statement // Guess as main subpath export - const packageEntry = await _resolve(pkgName).catch(() => null) + const packageEntry = await _resolve(pkgName).catch(() => null); if (packageEntry !== originalId) { // Guess subpathexport - const guessedSubpath = pkgName + subpath.replace(/\.[a-z]+$/, '') - const resolvedGuess = await _resolve(guessedSubpath).catch(() => null) + const guessedSubpath = pkgName + subpath.replace(/\.[a-z]+$/, ""); + const resolvedGuess = await _resolve(guessedSubpath).catch( + () => null + ); if (resolvedGuess === originalId) { - trackedExternals.add(resolvedGuess) + trackedExternals.add(resolvedGuess); return { id: guessedSubpath, - external: true - } + external: true, + }; } // Inline since we cannot guess subpath - return null + return null; } } - trackedExternals.add(resolved.id) + trackedExternals.add(resolved.id); return { id: pkgName, - external: true - } + external: true, + }; }, - async buildEnd () { + async buildEnd() { if (opts.trace === false) { - return + return; } // Force trace paths for (const pkgName of opts.traceInclude || []) { - const path = await this.resolve(pkgName) + const path = await this.resolve(pkgName); if (path?.id) { - trackedExternals.add(path.id.replace(/\?.+/, '')) + trackedExternals.add(path.id.replace(/\?.+/, "")); } } // Trace files - let tracedFiles = await nodeFileTrace(Array.from(trackedExternals), opts.traceOptions) - .then(r => Array.from(r.fileList).map(f => resolve(opts.traceOptions.base, f))) - .then(r => r.filter(file => file.includes('node_modules'))) + let tracedFiles = await nodeFileTrace( + [...trackedExternals], + opts.traceOptions + ) + .then((r) => + [...r.fileList].map((f) => resolve(opts.traceOptions.base, f)) + ) + .then((r) => r.filter((file) => file.includes("node_modules"))); // Resolve symlinks - tracedFiles = await Promise.all(tracedFiles.map(file => fsp.realpath(file))) + tracedFiles = await Promise.all( + tracedFiles.map((file) => fsp.realpath(file)) + ); // Read package.json with cache - const packageJSONCache = new Map() // pkgDir => contents + const packageJSONCache = new Map(); // pkgDir => contents const getPackageJson = async (pkgDir: string) => { if (packageJSONCache.has(pkgDir)) { - return packageJSONCache.get(pkgDir) + return packageJSONCache.get(pkgDir); } - const pkgJSON = JSON.parse(await fsp.readFile(resolve(pkgDir, 'package.json'), 'utf8')) - packageJSONCache.set(pkgDir, pkgJSON) - return pkgJSON - } + const pkgJSON = JSON.parse( + await fsp.readFile(resolve(pkgDir, "package.json"), "utf8") + ); + packageJSONCache.set(pkgDir, pkgJSON); + return pkgJSON; + }; // Keep track of npm packages - const tracedPackages = new Map() // name => pkgDir - const ignoreDirs = [] - const ignoreWarns = new Set() + const tracedPackages = new Map(); // name => pkgDir + const ignoreDirs = []; + const ignoreWarns = new Set(); for (const file of tracedFiles) { - const { baseDir, pkgName } = parseNodeModulePath(file) + const { baseDir, pkgName } = parseNodeModulePath(file); if (!pkgName) { - continue + continue; } - let pkgDir = resolve(baseDir, pkgName) + let pkgDir = resolve(baseDir, pkgName); // Check for duplicate versions - const existingPkgDir = tracedPackages.get(pkgName) + const existingPkgDir = tracedPackages.get(pkgName); if (existingPkgDir && existingPkgDir !== pkgDir) { - const v1 = await getPackageJson(existingPkgDir).then(r => r.version) - const v2 = await getPackageJson(pkgDir).then(r => r.version) - const isNewer = semver.gt(v2, v1) + const v1 = await getPackageJson(existingPkgDir).then( + (r) => r.version + ); + const v2 = await getPackageJson(pkgDir).then((r) => r.version); + const isNewer = semver.gt(v2, v1); // Warn about major version differences - const getMajor = (v: string) => v.split('.').filter(s => s !== '0')[0] + const getMajor = (v: string) => v.split(".").find((s) => s !== "0"); if (getMajor(v1) !== getMajor(v2)) { - const warn = `Multiple major versions of package \`${pkgName}\` are being externalized. Picking latest version:\n\n` + [ - ` ${isNewer ? '-' : '+'} ` + existingPkgDir + '@' + v1, - ` ${isNewer ? '+' : '-'} ` + pkgDir + '@' + v2 - ].join('\n') + const warn = + `Multiple major versions of package \`${pkgName}\` are being externalized. Picking latest version:\n\n` + + [ + ` ${isNewer ? "-" : "+"} ` + existingPkgDir + "@" + v1, + ` ${isNewer ? "+" : "-"} ` + pkgDir + "@" + v2, + ].join("\n"); if (!ignoreWarns.has(warn)) { - consola.warn(warn) - ignoreWarns.add(warn) + consola.warn(warn); + ignoreWarns.add(warn); } } - const [newerDir, olderDir] = isNewer ? [pkgDir, existingPkgDir] : [existingPkgDir, pkgDir] + const [newerDir, olderDir] = isNewer + ? [pkgDir, existingPkgDir] + : [existingPkgDir, pkgDir]; // Try to map traced files from one package to another for minor/patch versions if (getMajor(v1) === getMajor(v2)) { - tracedFiles = tracedFiles.map(f => f.startsWith(olderDir + '/') ? f.replace(olderDir, newerDir) : f) + tracedFiles = tracedFiles.map((f) => + f.startsWith(olderDir + "/") ? f.replace(olderDir, newerDir) : f + ); } // Exclude older version files - ignoreDirs.push(olderDir + '/') - pkgDir = newerDir // Update for tracedPackages + ignoreDirs.push(olderDir + "/"); + pkgDir = newerDir; // Update for tracedPackages } // Add to traced packages - tracedPackages.set(pkgName, pkgDir) + tracedPackages.set(pkgName, pkgDir); } // Filter out files from ignored packages and dedup - tracedFiles = tracedFiles.filter(f => !ignoreDirs.some(d => f.startsWith(d))) - tracedFiles = Array.from(new Set(tracedFiles)) + tracedFiles = tracedFiles.filter( + (f) => !ignoreDirs.some((d) => f.startsWith(d)) + ); + tracedFiles = [...new Set(tracedFiles)]; // Ensure all package.json files are traced for (const pkgDir of tracedPackages.values()) { - const pkgJSON = join(pkgDir, 'package.json') + const pkgJSON = join(pkgDir, "package.json"); if (!tracedFiles.includes(pkgJSON)) { - tracedFiles.push(pkgJSON) + tracedFiles.push(pkgJSON); } } const writeFile = async (file: string) => { - if (!await isFile(file)) { return } - const src = resolve(opts.traceOptions.base, file) - const { pkgName, subpath } = parseNodeModulePath(file) - const dst = resolve(opts.outDir, `node_modules/${pkgName + subpath}`) - await fsp.mkdir(dirname(dst), { recursive: true }) + if (!(await isFile(file))) { + return; + } + const src = resolve(opts.traceOptions.base, file); + const { pkgName, subpath } = parseNodeModulePath(file); + const dst = resolve(opts.outDir, `node_modules/${pkgName + subpath}`); + await fsp.mkdir(dirname(dst), { recursive: true }); try { - await fsp.copyFile(src, dst) - } catch (err) { - consola.warn(`Could not resolve \`${src}\`. Skipping.`) + await fsp.copyFile(src, dst); + } catch { + consola.warn(`Could not resolve \`${src}\`. Skipping.`); } - } + }; // Write traced files - await Promise.all(tracedFiles.map(file => retry(() => writeFile(file), 3))) + await Promise.all( + tracedFiles.map((file) => retry(() => writeFile(file), 3)) + ); // Write an informative package.json - await fsp.writeFile(resolve(opts.outDir, 'package.json'), JSON.stringify({ - name: 'nitro-output', - version: '0.0.0', - private: true, - bundledDependencies: Array.from(tracedPackages.keys()) - }, null, 2), 'utf8') - } - } + await fsp.writeFile( + resolve(opts.outDir, "package.json"), + JSON.stringify( + { + name: "nitro-output", + version: "0.0.0", + private: true, + bundledDependencies: [...tracedPackages.keys()], + }, + null, + 2 + ), + "utf8" + ); + }, + }; } -function parseNodeModulePath (path: string) { - if (!path) { return {} } - const match = /^(.+\/node_modules\/)([^@/]+|@[^/]+\/[^/]+)(\/?.*?)?$/.exec(normalize(path)) - if (!match) { return {} } - const [, baseDir, pkgName, subpath] = match +function parseNodeModulePath(path: string) { + if (!path) { + return {}; + } + const match = /^(.+\/node_modules\/)([^/@]+|@[^/]+\/[^/]+)(\/?.*?)?$/.exec( + normalize(path) + ); + if (!match) { + return {}; + } + const [, baseDir, pkgName, subpath] = match; return { baseDir, pkgName, - subpath - } + subpath, + }; } -async function isFile (file: string) { +async function isFile(file: string) { try { - const stat = await fsp.stat(file) - return stat.isFile() + const stat = await fsp.stat(file); + return stat.isFile(); } catch (err) { - if (err.code === 'ENOENT') { return false } - throw err + if (err.code === "ENOENT") { + return false; + } + throw err; } } diff --git a/src/rollup/plugins/handlers.ts b/src/rollup/plugins/handlers.ts index e8ac38f6b2..e3c6ad20ee 100644 --- a/src/rollup/plugins/handlers.ts +++ b/src/rollup/plugins/handlers.ts @@ -1,42 +1,70 @@ -import { hash } from 'ohash' -import type { Nitro } from '../../types' -import { virtual } from './virtual' +import { hash } from "ohash"; +import type { Nitro } from "../../types"; +import { virtual } from "./virtual"; -const unique = (arr: any[]) => Array.from(new Set(arr)) +const unique = (arr: any[]) => [...new Set(arr)]; +const getImportId = (p: string, lazy?: boolean) => + (lazy ? "_lazy_" : "_") + hash(p).slice(0, 6); -export function handlers (nitro: Nitro) { - const getImportId = (p: string, lazy?: boolean) => (lazy ? '_lazy_' : '_') + hash(p).slice(0, 6) +export function handlers(nitro: Nitro) { + return virtual( + { + "#internal/nitro/virtual/server-handlers": () => { + const handlers = [...nitro.scannedHandlers, ...nitro.options.handlers]; + if (nitro.options.serveStatic) { + handlers.unshift({ + middleware: true, + handler: "#internal/nitro/static", + }); + } + if (nitro.options.renderer) { + handlers.push({ + route: "/**", + lazy: true, + handler: nitro.options.renderer, + }); + } - return virtual({ - '#internal/nitro/virtual/server-handlers': () => { - const handlers = [ - ...nitro.scannedHandlers, - ...nitro.options.handlers - ] - if (nitro.options.serveStatic) { - handlers.unshift({ middleware: true, handler: '#internal/nitro/static' }) - } - if (nitro.options.renderer) { - handlers.push({ route: '/**', lazy: true, handler: nitro.options.renderer }) - } + // Imports take priority + const imports = unique( + handlers.filter((h) => !h.lazy).map((h) => h.handler) + ); - // Imports take priority - const imports = unique(handlers.filter(h => !h.lazy).map(h => h.handler)) + // Lazy imports should fill in the gaps + // TODO: At least warn if a handler is imported both lazy and non lazy + const lazyImports = unique( + handlers.filter((h) => h.lazy).map((h) => h.handler) + ); - // Lazy imports should fill in the gaps - // TODO: At least warn if a handler is imported both lazy and non lazy - const lazyImports = unique(handlers.filter(h => h.lazy).map(h => h.handler)) + const code = ` +${imports + .map((handler) => `import ${getImportId(handler)} from '${handler}';`) + .join("\n")} - const code = ` -${imports.map(handler => `import ${getImportId(handler)} from '${handler}';`).join('\n')} - -${lazyImports.map(handler => `const ${getImportId(handler, true)} = () => import('${handler}');`).join('\n')} +${lazyImports + .map( + (handler) => + `const ${getImportId(handler, true)} = () => import('${handler}');` + ) + .join("\n")} export const handlers = [ -${handlers.map(h => ` { route: '${h.route || ''}', handler: ${getImportId(h.handler, h.lazy)}, lazy: ${!!h.lazy}, middleware: ${!!h.middleware}, method: ${JSON.stringify(h.method)} }`).join(',\n')} +${handlers + .map( + (h) => + ` { route: '${h.route || ""}', handler: ${getImportId( + h.handler, + h.lazy + )}, lazy: ${!!h.lazy}, middleware: ${!!h.middleware}, method: ${JSON.stringify( + h.method + )} }` + ) + .join(",\n")} ]; - `.trim() - return code - } - }, nitro.vfs) + `.trim(); + return code; + }, + }, + nitro.vfs + ); } diff --git a/src/rollup/plugins/import-meta.ts b/src/rollup/plugins/import-meta.ts index cb19c42789..0ac836f5ee 100644 --- a/src/rollup/plugins/import-meta.ts +++ b/src/rollup/plugins/import-meta.ts @@ -1,28 +1,32 @@ -import type { Plugin } from 'rollup' -import { Nitro } from '../../types' +import type { Plugin } from "rollup"; +import { Nitro } from "../../types"; -export function importMeta (nitro: Nitro): Plugin { - const ImportMetaRe = /import\.meta|globalThis._importMeta_/ +export function importMeta(nitro: Nitro): Plugin { + const ImportMetaRe = /import\.meta|globalThis._importMeta_/; return { - name: 'import-meta', - renderChunk (code, chunk) { - const isEntry = chunk.isEntry - if (!isEntry && (!ImportMetaRe.test(code) || code.includes('ROLLUP_NO_REPLACE'))) { - return + name: "import-meta", + renderChunk(code, chunk) { + const isEntry = chunk.isEntry; + if ( + !isEntry && + (!ImportMetaRe.test(code) || code.includes("ROLLUP_NO_REPLACE")) + ) { + return; } - const url = (nitro.options.node && isEntry) ? '_import_meta_url_' : '"file:///_entry.js"' - const env = nitro.options.node ? 'process.env' : '{}' - const ref = 'globalThis._importMeta_' - const stub = `{url:${url},env:${env}}` - const stubInit = isEntry - ? `${ref}=${stub};` - : `${ref}=${ref}||${stub};` + const url = + nitro.options.node && isEntry + ? "_import_meta_url_" + : '"file:///_entry.js"'; + const env = nitro.options.node ? "process.env" : "{}"; + const ref = "globalThis._importMeta_"; + const stub = `{url:${url},env:${env}}`; + const stubInit = isEntry ? `${ref}=${stub};` : `${ref}=${ref}||${stub};`; return { code: stubInit + code, - map: null - } - } - } + map: null, + }; + }, + }; } diff --git a/src/rollup/plugins/public-assets.ts b/src/rollup/plugins/public-assets.ts index 2ee471e83e..8a7d2b5565 100644 --- a/src/rollup/plugins/public-assets.ts +++ b/src/rollup/plugins/public-assets.ts @@ -1,53 +1,68 @@ -import { promises as fsp } from 'fs' -import { relative, resolve } from 'pathe' -import createEtag from 'etag' -import mime from 'mime' -import { globby } from 'globby' -import type { Plugin } from 'rollup' -import type { Nitro } from '../../types' -import { virtual } from './virtual' +import { promises as fsp } from "node:fs"; +import { relative, resolve } from "pathe"; +import createEtag from "etag"; +import mime from "mime"; +import { globby } from "globby"; +import type { Plugin } from "rollup"; +import type { Nitro } from "../../types"; +import { virtual } from "./virtual"; -export function publicAssets (nitro: Nitro): Plugin { - return virtual({ - // #internal/nitro/virtual/public-assets-data - '#internal/nitro/virtual/public-assets-data': async () => { - const assets: Record = {} - const files = await globby('**', { - cwd: nitro.options.output.publicDir, - absolute: false, - dot: true - }) - for (const id of files) { - let mimeType = mime.getType(id.replace(/\.(gz|br)$/, '')) || 'text/plain' - if (mimeType.startsWith('text')) { mimeType += '; charset=utf-8' } - const fullPath = resolve(nitro.options.output.publicDir, id) - const assetData = await fsp.readFile(fullPath) - const etag = createEtag(assetData) - const stat = await fsp.stat(fullPath) +export function publicAssets(nitro: Nitro): Plugin { + return virtual( + { + // #internal/nitro/virtual/public-assets-data + "#internal/nitro/virtual/public-assets-data": async () => { + const assets: Record< + string, + { + type: string; + etag: string; + mtime: string; + path: string; + size: number; + encoding?: string; + } + > = {}; + const files = await globby("**", { + cwd: nitro.options.output.publicDir, + absolute: false, + dot: true, + }); + for (const id of files) { + let mimeType = + mime.getType(id.replace(/\.(gz|br)$/, "")) || "text/plain"; + if (mimeType.startsWith("text")) { + mimeType += "; charset=utf-8"; + } + const fullPath = resolve(nitro.options.output.publicDir, id); + const assetData = await fsp.readFile(fullPath); + const etag = createEtag(assetData); + const stat = await fsp.stat(fullPath); - const assetId = '/' + decodeURIComponent(id) - assets[assetId] = { - type: mimeType, - encoding: id.endsWith('.gz') ? 'gzip' : id.endsWith('.br') ? 'br' : undefined, - etag, - mtime: stat.mtime.toJSON(), - size: stat.size, - path: relative(nitro.options.output.serverDir, fullPath) + const assetId = "/" + decodeURIComponent(id); + + let encoding; + if (id.endsWith(".gz")) { + encoding = "gzip"; + } else if (id.endsWith(".br")) { + encoding = "br"; + } + + assets[assetId] = { + type: mimeType, + encoding, + etag, + mtime: stat.mtime.toJSON(), + size: stat.size, + path: relative(nitro.options.output.serverDir, fullPath), + }; } - } - return `export default ${JSON.stringify(assets, null, 2)};` - }, - // #internal/nitro/virtual/public-assets-node - '#internal/nitro/virtual/public-assets-node': () => { - return ` + return `export default ${JSON.stringify(assets, null, 2)};`; + }, + // #internal/nitro/virtual/public-assets-node + "#internal/nitro/virtual/public-assets-node": () => { + return ` import { promises as fsp } from 'node:fs' import { fileURLToPath } from 'node:url' import { resolve, dirname } from 'pathe' @@ -55,29 +70,33 @@ import assets from '#internal/nitro/virtual/public-assets-data' export function readAsset (id) { const serverDir = dirname(fileURLToPath(import.meta.url)) return fsp.readFile(resolve(serverDir, assets[id].path)) -}` - }, - // #internal/nitro/virtual/public-assets-deno - '#internal/nitro/virtual/public-assets-deno': () => { - return ` +}`; + }, + // #internal/nitro/virtual/public-assets-deno + "#internal/nitro/virtual/public-assets-deno": () => { + return ` import assets from '#internal/nitro/virtual/public-assets-data' export function readAsset (id) { // https://deno.com/deploy/docs/serve-static-assets const path = '.' + new URL(\`../public\${id}\`, 'file://').pathname return Deno.readFile(path); -}` - }, - // #internal/nitro/virtual/public-assets - '#internal/nitro/virtual/public-assets': () => { - const publicAssetBases = nitro.options.publicAssets - .filter(dir => !dir.fallthrough && dir.baseURL !== '/') - .map(dir => dir.baseURL) +}`; + }, + // #internal/nitro/virtual/public-assets + "#internal/nitro/virtual/public-assets": () => { + const publicAssetBases = nitro.options.publicAssets + .filter((dir) => !dir.fallthrough && dir.baseURL !== "/") + .map((dir) => dir.baseURL); - return ` + return ` import assets from '#internal/nitro/virtual/public-assets-data' -${nitro.options.serveStatic - ? `export * from "#internal/nitro/virtual/public-assets-${nitro.options.serveStatic === 'deno' ? 'deno' : 'node'}"` - : 'export const readAsset = () => Promise(null)'} +${ + nitro.options.serveStatic + ? `export * from "#internal/nitro/virtual/public-assets-${ + nitro.options.serveStatic === "deno" ? "deno" : "node" + }"` + : "export const readAsset = () => Promise(null)" +} export const publicAssetBases = ${JSON.stringify(publicAssetBases)} @@ -94,7 +113,9 @@ export function isPublicAssetURL(id = '') { export function getAsset (id) { return assets[id] } -` - } - }, nitro.vfs) +`; + }, + }, + nitro.vfs + ); } diff --git a/src/rollup/plugins/raw.ts b/src/rollup/plugins/raw.ts index 1c389bc774..df80938a86 100644 --- a/src/rollup/plugins/raw.ts +++ b/src/rollup/plugins/raw.ts @@ -1,47 +1,57 @@ -import { promises as fsp } from 'fs' -import { extname } from 'pathe' -import type { Plugin } from 'rollup' +import { promises as fsp } from "node:fs"; +import { extname } from "pathe"; +import type { Plugin } from "rollup"; export interface RawOptions { - extensions?: string[] + extensions?: string[]; } -export function raw (opts: RawOptions = {}): Plugin { - const extensions = new Set(['.md', '.mdx', '.yml', '.txt', '.css', '.htm', '.html'] - .concat(opts.extensions || [])) +export function raw(opts: RawOptions = {}): Plugin { + const extensions = new Set([ + ".md", + ".mdx", + ".yml", + ".txt", + ".css", + ".htm", + ".html", + ...(opts.extensions || []), + ]); return { - name: 'raw', - resolveId (id) { - if (id[0] === '\0') { - return + name: "raw", + resolveId(id) { + if (id[0] === "\0") { + return; } - let isRawId = id.startsWith('raw:') + let isRawId = id.startsWith("raw:"); if (isRawId) { - id = id.substring(4) + id = id.slice(4); } else if (extensions.has(extname(id))) { - isRawId = true + isRawId = true; } // TODO: Support reasolving. Blocker is CommonJS custom resolver! if (isRawId) { - return { id: '\0raw:' + id } + return { id: "\0raw:" + id }; } }, - load (id) { - if (id.startsWith('\0raw:')) { + load(id) { + if (id.startsWith("\0raw:")) { // this.addWatchFile(id.substring(5)) - return fsp.readFile(id.substring(5), 'utf8') + return fsp.readFile(id.slice(5), "utf8"); } }, - transform (code, id) { - if (id.startsWith('\0raw:')) { + transform(code, id) { + if (id.startsWith("\0raw:")) { return { - code: `// ROLLUP_NO_REPLACE \n export default ${JSON.stringify(code)}`, - map: null - } + code: `// ROLLUP_NO_REPLACE \n export default ${JSON.stringify( + code + )}`, + map: null, + }; } - } - } + }, + }; } diff --git a/src/rollup/plugins/replace.ts b/src/rollup/plugins/replace.ts index e1fd65d559..5bb8c3768f 100644 --- a/src/rollup/plugins/replace.ts +++ b/src/rollup/plugins/replace.ts @@ -1,17 +1,23 @@ -import _replace, { RollupReplaceOptions } from '@rollup/plugin-replace' -import type { Plugin } from 'rollup' +import _replace from "@rollup/plugin-replace"; +import type { RollupReplaceOptions } from "@rollup/plugin-replace"; +import type { Plugin } from "rollup"; -const NO_REPLACE_RE = /ROLLUP_NO_REPLACE/ +const NO_REPLACE_RE = /ROLLUP_NO_REPLACE/; -export function replace (options: RollupReplaceOptions): Plugin { - const _plugin = _replace(options) +export function replace(options: RollupReplaceOptions): Plugin { + const _plugin = _replace(options); return { ..._plugin, // https://github.com/rollup/plugins/blob/master/packages/replace/src/index.js#L94 - renderChunk (code, chunk, options) { + renderChunk(code, chunk, options) { if (!NO_REPLACE_RE.test(code)) { - return (_plugin.renderChunk as Function).call(this, code, chunk, options) + return (_plugin.renderChunk as Function).call( + this, + code, + chunk, + options + ); } - } - } + }, + }; } diff --git a/src/rollup/plugins/server-assets.ts b/src/rollup/plugins/server-assets.ts index 3f60754727..7cdf319896 100644 --- a/src/rollup/plugins/server-assets.ts +++ b/src/rollup/plugins/server-assets.ts @@ -1,63 +1,74 @@ -import { promises as fsp } from 'fs' -import type { Plugin } from 'rollup' -import createEtag from 'etag' -import mime from 'mime' -import { resolve } from 'pathe' -import { normalizeKey } from 'unstorage' -import { globby } from 'globby' -import type { Nitro } from '../../types' -import { virtual } from './virtual' +import { promises as fsp } from "node:fs"; +import type { Plugin } from "rollup"; +import createEtag from "etag"; +import mime from "mime"; +import { resolve } from "pathe"; +import { normalizeKey } from "unstorage"; +import { globby } from "globby"; +import type { Nitro } from "../../types"; +import { virtual } from "./virtual"; export interface ServerAssetOptions { - inline: Boolean + inline: Boolean; dirs: { [assetdir: string]: { - dir: string - meta?: boolean - } - } + dir: string; + meta?: boolean; + }; + }; } interface ResolvedAsset { - fsPath: string, + fsPath: string; meta: { - type?: string, - etag?: string, - mtime?: string - } + type?: string; + etag?: string; + mtime?: string; + }; } -export function serverAssets (nitro: Nitro): Plugin { +export function serverAssets(nitro: Nitro): Plugin { // Development: Use filesystem - if (nitro.options.dev || nitro.options.preset === 'nitro-prerender') { - return virtual({ '#internal/nitro/virtual/server-assets': getAssetsDev(nitro) }, nitro.vfs) + if (nitro.options.dev || nitro.options.preset === "nitro-prerender") { + return virtual( + { "#internal/nitro/virtual/server-assets": getAssetsDev(nitro) }, + nitro.vfs + ); } // Production: Bundle assets - return virtual({ - '#internal/nitro/virtual/server-assets': async () => { - // Scan all assets - const assets: Record = {} - for (const asset of nitro.options.serverAssets) { - const files = await globby('**/*.*', { cwd: asset.dir, absolute: false }) - for (const _id of files) { - const fsPath = resolve(asset.dir, _id) - const id = asset.baseName + '/' + _id - assets[id] = { fsPath, meta: {} } - // @ts-ignore TODO: Use mime@2 types - let type = mime.getType(id) || 'text/plain' - if (type.startsWith('text')) { type += '; charset=utf-8' } - const etag = createEtag(await fsp.readFile(fsPath)) - const mtime = await fsp.stat(fsPath).then(s => s.mtime.toJSON()) - assets[id].meta = { type, etag, mtime } + return virtual( + { + "#internal/nitro/virtual/server-assets": async () => { + // Scan all assets + const assets: Record = {}; + for (const asset of nitro.options.serverAssets) { + const files = await globby("**/*.*", { + cwd: asset.dir, + absolute: false, + }); + for (const _id of files) { + const fsPath = resolve(asset.dir, _id); + const id = asset.baseName + "/" + _id; + assets[id] = { fsPath, meta: {} }; + // @ts-ignore TODO: Use mime@2 types + let type = mime.getType(id) || "text/plain"; + if (type.startsWith("text")) { + type += "; charset=utf-8"; + } + const etag = createEtag(await fsp.readFile(fsPath)); + const mtime = await fsp.stat(fsPath).then((s) => s.mtime.toJSON()); + assets[id].meta = { type, etag, mtime }; + } } - } - return getAssetProd(assets) - } - }, nitro.vfs) + return getAssetProd(assets); + }, + }, + nitro.vfs + ); } -function getAssetsDev (nitro: Nitro) { +function getAssetsDev(nitro: Nitro) { return ` import { createStorage } from 'unstorage' import fsDriver from 'unstorage/drivers/fs' @@ -68,14 +79,23 @@ export const assets = createStorage() for (const asset of serverAssets) { assets.mount(asset.baseName, fsDriver({ base: asset.dir })) -}` +}`; } -function getAssetProd (assets: Record) { +function getAssetProd(assets: Record) { return ` -const _assets = {\n${Object.entries(assets).map(([id, asset]) => - ` [${JSON.stringify(normalizeKey(id))}]: {\n import: () => import(${JSON.stringify('raw:' + asset.fsPath)}).then(r => r.default || r),\n meta: ${JSON.stringify(asset.meta)}\n }` -).join(',\n')}\n} +const _assets = {\n${Object.entries(assets) + .map( + ([id, asset]) => + ` [${JSON.stringify( + normalizeKey(id) + )}]: {\n import: () => import(${JSON.stringify( + "raw:" + asset.fsPath + )}).then(r => r.default || r),\n meta: ${JSON.stringify( + asset.meta + )}\n }` + ) + .join(",\n")}\n} ${normalizeKey.toString()} @@ -96,5 +116,5 @@ export const assets = { return Promise.resolve(_assets[id] ? _assets[id].meta : {}) } } -` +`; } diff --git a/src/rollup/plugins/storage.ts b/src/rollup/plugins/storage.ts index 530eb7c878..f686590bc2 100644 --- a/src/rollup/plugins/storage.ts +++ b/src/rollup/plugins/storage.ts @@ -1,26 +1,27 @@ -import { builtinDrivers } from 'unstorage' -import { genImport, genSafeVariableName } from 'knitwork' -import type { Nitro } from '../../types' -import { virtual } from './virtual' +import { builtinDrivers } from "unstorage"; +import { genImport, genSafeVariableName } from "knitwork"; +import type { Nitro } from "../../types"; +import { virtual } from "./virtual"; -export function storage (nitro: Nitro) { - const mounts: { path: string, driver: string, opts: object }[] = [] +export function storage(nitro: Nitro) { + const mounts: { path: string; driver: string; opts: object }[] = []; - const isDevOrPrerender = nitro.options.dev || nitro.options.preset === 'nitro-prerender' + const isDevOrPrerender = + nitro.options.dev || nitro.options.preset === "nitro-prerender"; const storageMounts = isDevOrPrerender ? { ...nitro.options.storage, ...nitro.options.devStorage } - : nitro.options.storage + : nitro.options.storage; for (const path in storageMounts) { - const mount = storageMounts[path] + const mount = storageMounts[path]; mounts.push({ path, driver: builtinDrivers[mount.driver] || mount.driver, - opts: mount - }) + opts: mount, + }); } - const driverImports = Array.from(new Set(mounts.map(m => m.driver))) + const driverImports = [...new Set(mounts.map((m) => m.driver))]; const bundledStorageCode = ` import { prefixStorage } from 'unstorage' @@ -37,14 +38,15 @@ for (const base of bundledStorage) { prefixStorage(storage, 'assets:nitro:bundled:' + base) ] })) -}` +}`; - return virtual({ - '#internal/nitro/virtual/storage': ` + return virtual( + { + "#internal/nitro/virtual/storage": ` import { createStorage } from 'unstorage' import { assets } from '#internal/nitro/virtual/server-assets' -${driverImports.map(i => genImport(i, genSafeVariableName(i))).join('\n')} +${driverImports.map((i) => genImport(i, genSafeVariableName(i))).join("\n")} const storage = createStorage({}) @@ -52,9 +54,22 @@ export const useStorage = () => storage storage.mount('/assets', assets) -${mounts.map(m => `storage.mount('${m.path}', ${genSafeVariableName(m.driver)}(${JSON.stringify(m.opts)}))`).join('\n')} +${mounts + .map( + (m) => + `storage.mount('${m.path}', ${genSafeVariableName( + m.driver + )}(${JSON.stringify(m.opts)}))` + ) + .join("\n")} -${(!isDevOrPrerender && nitro.options.bundledStorage.length) ? bundledStorageCode : ''} -` - }, nitro.vfs) +${ + !isDevOrPrerender && nitro.options.bundledStorage.length > 0 + ? bundledStorageCode + : "" +} +`, + }, + nitro.vfs + ); } diff --git a/src/rollup/plugins/timing.ts b/src/rollup/plugins/timing.ts index 92f7f05609..8a764cbd1c 100644 --- a/src/rollup/plugins/timing.ts +++ b/src/rollup/plugins/timing.ts @@ -1,11 +1,11 @@ -import { extname } from 'pathe' -import type { Plugin, RenderedChunk } from 'rollup' +import { extname } from "pathe"; +import type { Plugin, RenderedChunk } from "rollup"; -export interface Options { } +export interface Options {} -const TIMING = 'globalThis.__timing__' +const TIMING = "globalThis.__timing__"; -const iife = code => `(function() { ${code.trim()} })();`.replace(/\n/g, '') +const iife = (code) => `(function() { ${code.trim()} })();`.replace(/\n/g, ""); const HELPER = iife(` const start = () => Date.now(); @@ -15,28 +15,32 @@ const metrics = []; const logStart = id => { _s[id] = Date.now(); }; const logEnd = id => { const t = end(_s[id]); delete _s[id]; metrics.push([id, t]); console.debug('>', id + ' (' + t + 'ms)'); }; ${TIMING} = { start, end, metrics, logStart, logEnd }; -`) +`); -const HELPERIMPORT = "import './timing.js';" +const HELPERIMPORT = "import './timing.js';"; -export function timing (_opts: Options = {}): Plugin { +export function timing(_opts: Options = {}): Plugin { return { - name: 'timing', - generateBundle () { + name: "timing", + generateBundle() { this.emitFile({ - type: 'asset', - fileName: 'timing.js', - source: HELPER - }) + type: "asset", + fileName: "timing.js", + source: HELPER, + }); }, - renderChunk (code, chunk: RenderedChunk) { - let name = chunk.fileName || '' - name = name.replace(extname(name), '') - const logName = name === 'index' ? 'Nitro Start' : ('Load ' + name) + renderChunk(code, chunk: RenderedChunk) { + let name = chunk.fileName || ""; + name = name.replace(extname(name), ""); + const logName = name === "index" ? "Nitro Start" : "Load " + name; return { - code: (chunk.isEntry ? HELPERIMPORT : '') + `${TIMING}.logStart('${logName}');` + code + `;${TIMING}.logEnd('${logName}');`, - map: null - } - } - } + code: + (chunk.isEntry ? HELPERIMPORT : "") + + `${TIMING}.logStart('${logName}');` + + code + + `;${TIMING}.logEnd('${logName}');`, + map: null, + }; + }, + }; } diff --git a/src/rollup/plugins/virtual.ts b/src/rollup/plugins/virtual.ts index 1ba4b5241a..08224f5259 100644 --- a/src/rollup/plugins/virtual.ts +++ b/src/rollup/plugins/virtual.ts @@ -1,59 +1,70 @@ -import { resolve, dirname } from 'pathe' -import type { Plugin } from 'rollup' +import { resolve, dirname } from "pathe"; +import type { Plugin } from "rollup"; // Based on https://github.com/rollup/plugins/blob/master/packages/virtual/src/index.ts -export type VirtualModule = string | (() => string | Promise) +export type VirtualModule = string | (() => string | Promise); export interface RollupVirtualOptions { - [id: string]: VirtualModule + [id: string]: VirtualModule; } -const PREFIX = '\0virtual:' +const PREFIX = "\0virtual:"; -export function virtual (modules: RollupVirtualOptions, cache: Record = {}): Plugin { - const _modules = new Map() +export function virtual( + modules: RollupVirtualOptions, + cache: Record = {} +): Plugin { + const _modules = new Map(); for (const [id, mod] of Object.entries(modules)) { - cache[id] = mod - _modules.set(id, mod) - _modules.set(resolve(id), mod) + cache[id] = mod; + _modules.set(id, mod); + _modules.set(resolve(id), mod); } return { - name: 'virtual', + name: "virtual", - resolveId (id, importer) { - if (id in modules) { return PREFIX + id } + resolveId(id, importer) { + if (id in modules) { + return PREFIX + id; + } if (importer) { const importerNoPrefix = importer.startsWith(PREFIX) ? importer.slice(PREFIX.length) - : importer - const resolved = resolve(dirname(importerNoPrefix), id) - if (_modules.has(resolved)) { return PREFIX + resolved } + : importer; + const resolved = resolve(dirname(importerNoPrefix), id); + if (_modules.has(resolved)) { + return PREFIX + resolved; + } } - return null + return null; }, - async load (id) { - if (!id.startsWith(PREFIX)) { return null } + async load(id) { + if (!id.startsWith(PREFIX)) { + return null; + } - const idNoPrefix = id.slice(PREFIX.length) - if (!_modules.has(idNoPrefix)) { return null } + const idNoPrefix = id.slice(PREFIX.length); + if (!_modules.has(idNoPrefix)) { + return null; + } - let m = _modules.get(idNoPrefix) - if (typeof m === 'function') { - m = await m() + let m = _modules.get(idNoPrefix); + if (typeof m === "function") { + m = await m(); } - cache[id.replace(PREFIX, '')] = m + cache[id.replace(PREFIX, "")] = m; return { code: m as string, - map: null - } - } - } + map: null, + }; + }, + }; } diff --git a/src/runtime/app.ts b/src/runtime/app.ts index afd665d55e..cfda794306 100644 --- a/src/runtime/app.ts +++ b/src/runtime/app.ts @@ -1,82 +1,101 @@ -import { App as H3App, createApp, createRouter, lazyEventHandler, Router, toNodeListener } from 'h3' -import { createFetch, Headers } from 'ofetch' -import destr from 'destr' -import { createCall, createFetch as createLocalFetch } from 'unenv/runtime/fetch/index' -import { createHooks, Hookable } from 'hookable' -import { useRuntimeConfig } from './config' -import { timingMiddleware } from './timing' -import { cachedEventHandler } from './cache' -import { createRouteRulesHandler, getRouteRulesForPath } from './route-rules' -import { plugins } from '#internal/nitro/virtual/plugins' -import errorHandler from '#internal/nitro/virtual/error-handler' -import { handlers } from '#internal/nitro/virtual/server-handlers' +import { + App as H3App, + createApp, + createRouter, + lazyEventHandler, + Router, + toNodeListener, +} from "h3"; +import { createFetch, Headers } from "ofetch"; +import destr from "destr"; +import { + createCall, + createFetch as createLocalFetch, +} from "unenv/runtime/fetch/index"; +import { createHooks, Hookable } from "hookable"; +import { useRuntimeConfig } from "./config"; +import { timingMiddleware } from "./timing"; +import { cachedEventHandler } from "./cache"; +import { createRouteRulesHandler, getRouteRulesForPath } from "./route-rules"; +import { plugins } from "#internal/nitro/virtual/plugins"; +import errorHandler from "#internal/nitro/virtual/error-handler"; +import { handlers } from "#internal/nitro/virtual/server-handlers"; export interface NitroApp { - h3App: H3App - router: Router + h3App: H3App; + router: Router; // TODO: Type hooks and allow extending - hooks: Hookable - localCall: ReturnType - localFetch: ReturnType + hooks: Hookable; + localCall: ReturnType; + localFetch: ReturnType; } -function createNitroApp (): NitroApp { - const config = useRuntimeConfig() +function createNitroApp(): NitroApp { + const config = useRuntimeConfig(); - const hooks = createHooks() + const hooks = createHooks(); const h3App = createApp({ debug: destr(process.env.DEBUG), - onError: errorHandler - }) + onError: errorHandler, + }); - h3App.use(config.app.baseURL, timingMiddleware) + h3App.use(config.app.baseURL, timingMiddleware); - const router = createRouter() + const router = createRouter(); - h3App.use(createRouteRulesHandler()) + h3App.use(createRouteRulesHandler()); for (const h of handlers) { - let handler = h.lazy ? lazyEventHandler(h.handler) : h.handler + let handler = h.lazy ? lazyEventHandler(h.handler) : h.handler; if (h.middleware || !h.route) { - const middlewareBase = (config.app.baseURL + (h.route || '/')).replace(/\/+/g, '/') - h3App.use(middlewareBase, handler) + const middlewareBase = (config.app.baseURL + (h.route || "/")).replace( + /\/+/g, + "/" + ); + h3App.use(middlewareBase, handler); } else { - const routeRules = getRouteRulesForPath(h.route.replace(/:\w+|\*\*/g, '_')) + const routeRules = getRouteRulesForPath( + h.route.replace(/:\w+|\*\*/g, "_") + ); if (routeRules.cache) { handler = cachedEventHandler(handler, { - group: 'nitro/routes', - ...routeRules.cache - }) + group: "nitro/routes", + ...routeRules.cache, + }); } - router.use(h.route, handler, h.method) + router.use(h.route, handler, h.method); } } - h3App.use(config.app.baseURL, router) + h3App.use(config.app.baseURL, router); - const localCall = createCall(toNodeListener(h3App) as any) - const localFetch = createLocalFetch(localCall, globalThis.fetch) + const localCall = createCall(toNodeListener(h3App) as any); + const localFetch = createLocalFetch(localCall, globalThis.fetch); - const $fetch = createFetch({ fetch: localFetch, Headers, defaults: { baseURL: config.app.baseURL } }) + const $fetch = createFetch({ + fetch: localFetch, + Headers, + defaults: { baseURL: config.app.baseURL }, + }); // @ts-ignore - globalThis.$fetch = $fetch + globalThis.$fetch = $fetch; const app: NitroApp = { hooks, h3App, router, localCall, - localFetch - } + localFetch, + }; for (const plugin of plugins) { - plugin(app) + plugin(app); } - return app + return app; } -export const nitroApp: NitroApp = createNitroApp() +export const nitroApp: NitroApp = createNitroApp(); -export const useNitroApp = () => nitroApp +export const useNitroApp = () => nitroApp; diff --git a/src/runtime/cache.ts b/src/runtime/cache.ts index 4945d0db35..878ffd48cb 100644 --- a/src/runtime/cache.ts +++ b/src/runtime/cache.ts @@ -1,21 +1,26 @@ -import { hash } from 'ohash' -import { handleCacheHeaders, defineEventHandler, createEvent, EventHandler } from 'h3' -import type { H3Event } from 'h3' -import { parseURL } from 'ufo' -import { useStorage } from '#internal/nitro' - -export interface CacheEntry { - value?: T - expires?: number - mtime?: number - integrity?: string +import { hash } from "ohash"; +import { + handleCacheHeaders, + defineEventHandler, + createEvent, + EventHandler, +} from "h3"; +import type { H3Event } from "h3"; +import { parseURL } from "ufo"; +import { useStorage } from "#internal/nitro"; + +export interface CacheEntry { + value?: T; + expires?: number; + mtime?: number; + integrity?: string; } export interface CacheOptions { name?: string; getKey?: (...args: any[]) => string; - transform?: (entry: CacheEntry, ...args: any[]) => any - validate?: (entry: CacheEntry) => boolean + transform?: (entry: CacheEntry, ...args: any[]) => any; + validate?: (entry: CacheEntry) => boolean; group?: string; integrity?: any; maxAge?: number; @@ -26,245 +31,300 @@ export interface CacheOptions { } const defaultCacheOptions = { - name: '_', - base: '/cache', + name: "_", + base: "/cache", swr: true, - maxAge: 1 -} + maxAge: 1, +}; -export function defineCachedFunction (fn: ((...args) => T | Promise), opts: CacheOptions) { - opts = { ...defaultCacheOptions, ...opts } +export function defineCachedFunction( + fn: (...args) => T | Promise, + opts: CacheOptions +) { + opts = { ...defaultCacheOptions, ...opts }; - const pending: { [key: string]: Promise } = {} + const pending: { [key: string]: Promise } = {}; // Normalize cache params - const group = opts.group || 'nitro' - const name = opts.name || fn.name || '_' - const integrity = hash([opts.integrity, fn, opts]) - const validate = opts.validate || (() => true) - - async function get (key: string, resolver: () => T | Promise): Promise> { + const group = opts.group || "nitro"; + const name = opts.name || fn.name || "_"; + const integrity = hash([opts.integrity, fn, opts]); + const validate = opts.validate || (() => true); + + async function get( + key: string, + resolver: () => T | Promise + ): Promise> { // Use extension for key to avoid conflicting with parent namespace (foo/bar and foo/bar/baz) - const cacheKey = [opts.base, group, name, key + '.json'].filter(Boolean).join(':').replace(/:\/$/, ':index') - const entry: CacheEntry = await useStorage().getItem(cacheKey) as any || {} - - const ttl = (opts.maxAge ?? opts.maxAge ?? 0) * 1000 + const cacheKey = [opts.base, group, name, key + ".json"] + .filter(Boolean) + .join(":") + .replace(/:\/$/, ":index"); + const entry: CacheEntry = + ((await useStorage().getItem(cacheKey)) as any) || {}; + + const ttl = (opts.maxAge ?? opts.maxAge ?? 0) * 1000; if (ttl) { - entry.expires = Date.now() + ttl + entry.expires = Date.now() + ttl; } - const expired = (entry.integrity !== integrity) || (ttl && (Date.now() - (entry.mtime || 0)) > ttl) || !validate(entry) + const expired = + entry.integrity !== integrity || + (ttl && Date.now() - (entry.mtime || 0) > ttl) || + !validate(entry); const _resolve = async () => { if (!pending[key]) { // Remove cached entry to prevent using expired cache on concurrent requests - entry.value = undefined - entry.integrity = undefined - entry.mtime = undefined - entry.expires = undefined - pending[key] = Promise.resolve(resolver()) + entry.value = undefined; + entry.integrity = undefined; + entry.mtime = undefined; + entry.expires = undefined; + pending[key] = Promise.resolve(resolver()); } - entry.value = await pending[key] - entry.mtime = Date.now() - entry.integrity = integrity - delete pending[key] + entry.value = await pending[key]; + entry.mtime = Date.now(); + entry.integrity = integrity; + delete pending[key]; if (validate(entry)) { - useStorage().setItem(cacheKey, entry).catch(error => console.error('[nitro] [cache]', error)) + useStorage() + .setItem(cacheKey, entry) + .catch((error) => console.error("[nitro] [cache]", error)); } - } + }; - const _resolvePromise = expired ? _resolve() : Promise.resolve() + const _resolvePromise = expired ? _resolve() : Promise.resolve(); if (opts.swr && entry.value) { // eslint-disable-next-line no-console - _resolvePromise.catch(console.error) - return Promise.resolve(entry) + _resolvePromise.catch(console.error); + // eslint-disable-next-line unicorn/no-useless-promise-resolve-reject + return entry; } - return _resolvePromise.then(() => entry) + return _resolvePromise.then(() => entry); } return async (...args) => { - const key = (opts.getKey || getKey)(...args) - const entry = await get(key, () => fn(...args)) - let value = entry.value + const key = (opts.getKey || getKey)(...args); + const entry = await get(key, () => fn(...args)); + let value = entry.value; if (opts.transform) { - value = await opts.transform(entry, ...args) || value + value = (await opts.transform(entry, ...args)) || value; } - return value - } + return value; + }; } -export const cachedFunction = defineCachedFunction +export const cachedFunction = defineCachedFunction; -function getKey (...args: string[]) { - return args.length ? hash(args, {}) : '' +function getKey(...args: string[]) { + return args.length > 0 ? hash(args, {}) : ""; } -export interface ResponseCacheEntry { - body: T - code: number - headers: Record +export interface ResponseCacheEntry { + body: T; + code: number; + headers: Record; } -export interface CachedEventHandlerOptions extends Omit>, 'getKey' | 'transform' | 'validate'> { - headersOnly?: boolean +export interface CachedEventHandlerOptions + extends Omit< + CacheOptions>, + "getKey" | "transform" | "validate" + > { + headersOnly?: boolean; } -export function defineCachedEventHandler ( +export function defineCachedEventHandler( handler: EventHandler, opts: CachedEventHandlerOptions = defaultCacheOptions ): EventHandler { const _opts: CacheOptions> = { ...opts, getKey: (event) => { - const url = event.req.originalUrl || event.req.url - const friendlyName = decodeURI(parseURL(url).pathname).replace(/[^a-zA-Z0-9]/g, '').substring(0, 16) - const urlHash = hash(url) - return `${friendlyName}.${urlHash}` + const url = event.req.originalUrl || event.req.url; + const friendlyName = decodeURI(parseURL(url).pathname) + .replace(/[^\dA-Za-z]/g, "") + .slice(0, 16); + const urlHash = hash(url); + return `${friendlyName}.${urlHash}`; }, validate: (entry) => { - if (entry.value.code >= 400) { return false } - if (entry.value.body === undefined) { return false } - return true + if (entry.value.code >= 400) { + return false; + } + if (entry.value.body === undefined) { + return false; + } + return true; }, - group: opts.group || 'nitro/handlers', - integrity: [ - opts.integrity, - handler - ] - } - - const _cachedHandler = cachedFunction>(async (incomingEvent: H3Event) => { - // Create proxies to avoid sharing state with user request - const reqProxy = cloneWithProxy(incomingEvent.req, { headers: {} }) - const resHeaders: Record = {} - let _resSendBody - const resProxy = cloneWithProxy(incomingEvent.res, { - statusCode: 200, - getHeader (name) { return resHeaders[name] }, - setHeader (name, value) { resHeaders[name] = value as any; return this }, - getHeaderNames () { return Object.keys(resHeaders) }, - hasHeader (name) { return name in resHeaders }, - removeHeader (name) { delete resHeaders[name] }, - getHeaders () { return resHeaders }, - end (chunk, arg2?, arg3?) { - if (typeof chunk === 'string') { - _resSendBody = chunk - } - if (typeof arg2 === 'function') { arg2() } - if (typeof arg3 === 'function') { arg3() } - return this - }, - write (chunk, arg2?, arg3?) { - if (typeof chunk === 'string') { - _resSendBody = chunk - } - if (typeof arg2 === 'function') { arg2() } - if (typeof arg3 === 'function') { arg3() } - return this - }, - writeHead (statusCode, headers) { - this.statusCode = statusCode - if (headers) { - for (const header in headers) { - this.setHeader(header, headers[header]) + group: opts.group || "nitro/handlers", + integrity: [opts.integrity, handler], + }; + + const _cachedHandler = cachedFunction>( + async (incomingEvent: H3Event) => { + // Create proxies to avoid sharing state with user request + const reqProxy = cloneWithProxy(incomingEvent.req, { headers: {} }); + const resHeaders: Record = {}; + let _resSendBody; + const resProxy = cloneWithProxy(incomingEvent.res, { + statusCode: 200, + getHeader(name) { + return resHeaders[name]; + }, + setHeader(name, value) { + resHeaders[name] = value as any; + return this; + }, + getHeaderNames() { + return Object.keys(resHeaders); + }, + hasHeader(name) { + return name in resHeaders; + }, + removeHeader(name) { + delete resHeaders[name]; + }, + getHeaders() { + return resHeaders; + }, + end(chunk, arg2?, arg3?) { + if (typeof chunk === "string") { + _resSendBody = chunk; + } + if (typeof arg2 === "function") { + arg2(); + } + if (typeof arg3 === "function") { + arg3(); } + return this; + }, + write(chunk, arg2?, arg3?) { + if (typeof chunk === "string") { + _resSendBody = chunk; + } + if (typeof arg2 === "function") { + arg2(); + } + if (typeof arg3 === "function") { + arg3(); + } + return this; + }, + writeHead(statusCode, headers) { + this.statusCode = statusCode; + if (headers) { + for (const header in headers) { + this.setHeader(header, headers[header]); + } + } + return this; + }, + }); + + // Call handler + const event = createEvent(reqProxy, resProxy); + event.context = incomingEvent.context; + const body = (await handler(event)) || _resSendBody; + + // Collect cachable headers + const headers = event.res.getHeaders(); + headers.etag = headers.Etag || headers.etag || `W/"${hash(body)}"`; + headers["last-modified"] = + headers["Last-Modified"] || + headers["last-modified"] || + new Date().toUTCString(); + const cacheControl = []; + if (opts.swr) { + if (opts.maxAge) { + cacheControl.push(`s-maxage=${opts.maxAge}`); } - return this - } - }) - - // Call handler - const event = createEvent(reqProxy, resProxy) - event.context = incomingEvent.context - const body = await handler(event) || _resSendBody - - // Collect cachable headers - const headers = event.res.getHeaders() - headers.etag = headers.Etag || headers.etag || `W/"${hash(body)}"` - headers['last-modified'] = headers['Last-Modified'] || headers['last-modified'] || new Date().toUTCString() - const cacheControl = [] - if (opts.swr) { - if (opts.maxAge) { - cacheControl.push(`s-maxage=${opts.maxAge}`) + if (opts.staleMaxAge) { + cacheControl.push(`stale-while-revalidate=${opts.staleMaxAge}`); + } else { + cacheControl.push("stale-while-revalidate"); + } + } else if (opts.maxAge) { + cacheControl.push(`max-age=${opts.maxAge}`); } - if (opts.staleMaxAge) { - cacheControl.push(`stale-while-revalidate=${opts.staleMaxAge}`) - } else { - cacheControl.push('stale-while-revalidate') + if (cacheControl.length > 0) { + headers["cache-control"] = cacheControl.join(", "); } - } else if (opts.maxAge) { - cacheControl.push(`max-age=${opts.maxAge}`) - } - if (cacheControl.length) { - headers['cache-control'] = cacheControl.join(', ') - } - // Create cache entry for response - const cacheEntry: ResponseCacheEntry = { - code: event.res.statusCode, - headers, - body - } + // Create cache entry for response + const cacheEntry: ResponseCacheEntry = { + code: event.res.statusCode, + headers, + body, + }; - return cacheEntry - }, _opts) + return cacheEntry; + }, + _opts + ); return defineEventHandler(async (event) => { // Headers-only mode if (opts.headersOnly) { // TODO: Send SWR too if (handleCacheHeaders(event, { maxAge: opts.maxAge })) { - return + return; } - return handler(event) + return handler(event); } // Call with cache - const response = await _cachedHandler(event) + const response = await _cachedHandler(event); // Don't continue if response is already handled by user if (event.res.headersSent || event.res.writableEnded) { - return response.body + return response.body; } // Check for cache headers - if (handleCacheHeaders(event, { - modifiedTime: new Date(response.headers['last-modified'] as string), - etag: response.headers.etag as string, - maxAge: opts.maxAge - })) { - return + if ( + handleCacheHeaders(event, { + modifiedTime: new Date(response.headers["last-modified"] as string), + etag: response.headers.etag as string, + maxAge: opts.maxAge, + }) + ) { + return; } // Send status and headers - event.res.statusCode = response.code + event.res.statusCode = response.code; for (const name in response.headers) { - event.res.setHeader(name, response.headers[name]) + event.res.setHeader(name, response.headers[name]); } // Send body - return response.body - }) + return response.body; + }); } -function cloneWithProxy (obj: T, overrides: Partial): T { +function cloneWithProxy( + obj: T, + overrides: Partial +): T { return new Proxy(obj, { - get (target, property, receiver) { + get(target, property, receiver) { if (property in overrides) { - return overrides[property] + return overrides[property]; } - return Reflect.get(target, property, receiver) + return Reflect.get(target, property, receiver); }, - set (target, property, value, receiver) { + set(target, property, value, receiver) { if (property in overrides) { - overrides[property] = value - return true + overrides[property] = value; + return true; } - return Reflect.set(target, property, value, receiver) - } - }) + return Reflect.set(target, property, value, receiver); + }, + }); } -export const cachedEventHandler = defineCachedEventHandler +export const cachedEventHandler = defineCachedEventHandler; diff --git a/src/runtime/client.ts b/src/runtime/client.ts index c1d9ffff72..849dc854e0 100644 --- a/src/runtime/client.ts +++ b/src/runtime/client.ts @@ -1,7 +1,7 @@ // Client polyfill -import { $fetch } from 'ofetch' -import { $Fetch, NitroFetchRequest } from '../types' +import { $fetch } from "ofetch"; +import { $Fetch, NitroFetchRequest } from "../types"; if (!globalThis.$fetch) { - globalThis.$fetch = $fetch as $Fetch + globalThis.$fetch = $fetch as $Fetch; } diff --git a/src/runtime/config.ts b/src/runtime/config.ts index eb9ccd79c1..03bee111a9 100644 --- a/src/runtime/config.ts +++ b/src/runtime/config.ts @@ -1,49 +1,52 @@ -import destr from 'destr' -import { snakeCase } from 'scule' +import destr from "destr"; +import { snakeCase } from "scule"; // Bundled runtime config (injected by nitro) -const _runtimeConfig = process.env.RUNTIME_CONFIG as any +const _runtimeConfig = process.env.RUNTIME_CONFIG as any; -const ENV_PREFIX = 'NITRO_' -const ENV_PREFIX_ALT = _runtimeConfig.nitro.envPrefix ?? process.env.NITRO_ENV_PREFIX ?? '_' +const ENV_PREFIX = "NITRO_"; +const ENV_PREFIX_ALT = + _runtimeConfig.nitro.envPrefix ?? process.env.NITRO_ENV_PREFIX ?? "_"; // Allow override from process.env and deserialize const getEnv = (key: string) => { - const envKey = snakeCase(key).toUpperCase() - return destr(process.env[ENV_PREFIX + envKey] ?? process.env[ENV_PREFIX_ALT + envKey]) + const envKey = snakeCase(key).toUpperCase(); + return destr( + process.env[ENV_PREFIX + envKey] ?? process.env[ENV_PREFIX_ALT + envKey] + ); +}; +function isObject(input: unknown) { + return typeof input === "object" && !Array.isArray(input); } -function isObject (input: unknown) { - return typeof input === 'object' && !Array.isArray(input) -} -function overrideConfig (obj: object, parentKey: string = '') { +function overrideConfig(obj: object, parentKey: string = "") { for (const key in obj) { - const subKey = parentKey ? `${parentKey}_${key}` : key - const envValue = getEnv(subKey) + const subKey = parentKey ? `${parentKey}_${key}` : key; + const envValue = getEnv(subKey); if (isObject(obj[key])) { if (isObject(envValue)) { - obj[key] = { ...obj[key], ...envValue } + obj[key] = { ...obj[key], ...envValue }; } - overrideConfig(obj[key], subKey) + overrideConfig(obj[key], subKey); } else { - obj[key] = envValue ?? obj[key] + obj[key] = envValue ?? obj[key]; } } } -overrideConfig(_runtimeConfig) +overrideConfig(_runtimeConfig); // Named exports -const config = deepFreeze(_runtimeConfig) -export const useRuntimeConfig = () => config -export default config +const config = deepFreeze(_runtimeConfig); +export const useRuntimeConfig = () => config; +export default config; // Utils -function deepFreeze (object: Record) { - const propNames = Object.getOwnPropertyNames(object) +function deepFreeze(object: Record) { + const propNames = Object.getOwnPropertyNames(object); for (const name of propNames) { - const value = object[name] - if (value && typeof value === 'object') { - deepFreeze(value) + const value = object[name]; + if (value && typeof value === "object") { + deepFreeze(value); } } - return Object.freeze(object) + return Object.freeze(object); } diff --git a/src/runtime/debug.ts b/src/runtime/debug.ts index 74e4c4097c..e712d301ab 100644 --- a/src/runtime/debug.ts +++ b/src/runtime/debug.ts @@ -1,6 +1,6 @@ -import { createDebugger } from 'hookable' -import { defineNitroPlugin } from './plugin' +import { createDebugger } from "hookable"; +import { defineNitroPlugin } from "./plugin"; export default defineNitroPlugin((nitro) => { - createDebugger(nitro.hooks, { tag: 'nitro-runtime' }) -}) + createDebugger(nitro.hooks, { tag: "nitro-runtime" }); +}); diff --git a/src/runtime/entries/aws-lambda.ts b/src/runtime/entries/aws-lambda.ts index e59c9a848c..08b78c5fd7 100644 --- a/src/runtime/entries/aws-lambda.ts +++ b/src/runtime/entries/aws-lambda.ts @@ -1,19 +1,50 @@ -import type { APIGatewayProxyEvent, APIGatewayProxyEventHeaders, APIGatewayProxyEventV2, APIGatewayProxyResult, APIGatewayProxyResultV2, Context } from 'aws-lambda' -import '#internal/nitro/virtual/polyfill' -import { withQuery } from 'ufo' -import { nitroApp } from '../app' +import type { + APIGatewayProxyEvent, + APIGatewayProxyEventHeaders, + APIGatewayProxyEventV2, + APIGatewayProxyResult, + APIGatewayProxyResultV2, + Context, +} from "aws-lambda"; +import "#internal/nitro/virtual/polyfill"; +import { withQuery } from "ufo"; +import { nitroApp } from "../app"; // Compatibility types that work with AWS v1, AWS v2 & Netlify -type Event = Omit | Omit -type Result = Exclude & { statusCode: number } +type Event = + | Omit< + APIGatewayProxyEvent, + "pathParameters" | "stageVariables" | "requestContext" | "resource" + > + | Omit< + APIGatewayProxyEventV2, + "pathParameters" | "stageVariables" | "requestContext" | "resource" + >; +type Result = Exclude< + APIGatewayProxyResult | APIGatewayProxyResultV2, + string +> & { statusCode: number }; -export const handler = async function handler (event: Event, context: Context): Promise { - const query = { ...event.queryStringParameters, ...(event as APIGatewayProxyEvent).multiValueQueryStringParameters } - const url = withQuery((event as APIGatewayProxyEvent).path || (event as APIGatewayProxyEventV2).rawPath, query) - const method = (event as APIGatewayProxyEvent).httpMethod || (event as APIGatewayProxyEventV2).requestContext?.http?.method || 'get' +export const handler = async function handler( + event: Event, + context: Context +): Promise { + const query = { + ...event.queryStringParameters, + ...(event as APIGatewayProxyEvent).multiValueQueryStringParameters, + }; + const url = withQuery( + (event as APIGatewayProxyEvent).path || + (event as APIGatewayProxyEventV2).rawPath, + query + ); + const method = + (event as APIGatewayProxyEvent).httpMethod || + (event as APIGatewayProxyEventV2).requestContext?.http?.method || + "get"; - if ('cookies' in event && event.cookies) { - event.headers.cookie = event.cookies.join(';') + if ("cookies" in event && event.cookies) { + event.headers.cookie = event.cookies.join(";"); } const r = await nitroApp.localCall({ @@ -23,26 +54,37 @@ export const handler = async function handler (event: Event, context: Context): headers: normalizeIncomingHeaders(event.headers), method, query, - body: event.body // TODO: handle event.isBase64Encoded - }) + body: event.body, // TODO: handle event.isBase64Encoded + }); - const outgoingCookies = r.headers['set-cookie'] - const cookies = Array.isArray(outgoingCookies) ? outgoingCookies : outgoingCookies?.split(',') || [] + const outgoingCookies = r.headers["set-cookie"]; + const cookies = Array.isArray(outgoingCookies) + ? outgoingCookies + : outgoingCookies?.split(",") || []; return { cookies, statusCode: r.status, headers: normalizeOutgoingHeaders(r.headers), - body: r.body.toString() - } -} + body: r.body.toString(), + }; +}; -function normalizeIncomingHeaders (headers?: APIGatewayProxyEventHeaders) { - return Object.fromEntries(Object.entries(headers || {}).map(([key, value]) => [key.toLowerCase(), value!])) +function normalizeIncomingHeaders(headers?: APIGatewayProxyEventHeaders) { + return Object.fromEntries( + Object.entries(headers || {}).map(([key, value]) => [ + key.toLowerCase(), + value!, + ]) + ); } -function normalizeOutgoingHeaders (headers: Record) { - return Object.fromEntries(Object.entries(headers) - .filter(([key]) => !['set-cookie'].includes(key)) - .map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v!])) +function normalizeOutgoingHeaders( + headers: Record +) { + return Object.fromEntries( + Object.entries(headers) + .filter(([key]) => !["set-cookie"].includes(key)) + .map(([k, v]) => [k, Array.isArray(v) ? v.join(",") : v!]) + ); } diff --git a/src/runtime/entries/azure-functions.ts b/src/runtime/entries/azure-functions.ts index 5fd0a338c4..f5c4086087 100644 --- a/src/runtime/entries/azure-functions.ts +++ b/src/runtime/entries/azure-functions.ts @@ -1,20 +1,20 @@ -import '#internal/nitro/virtual/polyfill' -import { nitroApp } from '../app' +import "#internal/nitro/virtual/polyfill"; +import { nitroApp } from "../app"; -export async function handle (context, req) { - const url = '/' + (req.params.url || '') +export async function handle(context, req) { + const url = "/" + (req.params.url || ""); const { body, status, statusText, headers } = await nitroApp.localCall({ url, headers: req.headers, method: req.method, // https://github.com/Azure/azure-functions-host/issues/293 - body: req.rawBody - }) + body: req.rawBody, + }); context.res = { status, headers, - body: body ? body.toString() : statusText - } + body: body ? body.toString() : statusText, + }; } diff --git a/src/runtime/entries/azure.ts b/src/runtime/entries/azure.ts index 9171cd2954..e2aeffbfe6 100644 --- a/src/runtime/entries/azure.ts +++ b/src/runtime/entries/azure.ts @@ -1,17 +1,17 @@ -import '#internal/nitro/virtual/polyfill' -import { parseURL } from 'ufo' -import { nitroApp } from '../app' +import "#internal/nitro/virtual/polyfill"; +import { parseURL } from "ufo"; +import { nitroApp } from "../app"; -export async function handle (context, req) { - let url: string - if (req.headers['x-ms-original-url']) { +export async function handle(context, req) { + let url: string; + if (req.headers["x-ms-original-url"]) { // This URL has been proxied as there was no static file matching it. - const parsedURL = parseURL(req.headers['x-ms-original-url']) - url = parsedURL.pathname + parsedURL.search + const parsedURL = parseURL(req.headers["x-ms-original-url"]); + url = parsedURL.pathname + parsedURL.search; } else { // Because Azure SWA handles /api/* calls differently they // never hit the proxy and we have to reconstitute the URL. - url = '/api/' + (req.params.url || '') + url = "/api/" + (req.params.url || ""); } const { body, status, statusText, headers } = await nitroApp.localCall({ @@ -19,12 +19,12 @@ export async function handle (context, req) { headers: req.headers, method: req.method, // https://github.com/Azure/azure-functions-host/issues/293 - body: req.rawBody - }) + body: req.rawBody, + }); context.res = { status, headers, - body: body ? body.toString() : statusText - } + body: body ? body.toString() : statusText, + }; } diff --git a/src/runtime/entries/cli.ts b/src/runtime/entries/cli.ts index f08f867042..f5584a81bf 100644 --- a/src/runtime/entries/cli.ts +++ b/src/runtime/entries/cli.ts @@ -1,24 +1,27 @@ -import '#internal/nitro/virtual/polyfill' -import { nitroApp } from '../app' +import "#internal/nitro/virtual/polyfill"; +import { nitroApp } from "../app"; -async function cli () { - const url = process.argv[2] || '/' - const debug = (label, ...args) => console.debug(`> ${label}:`, ...args) - const r = await nitroApp.localCall({ url }) +async function cli() { + const url = process.argv[2] || "/"; + const debug = (label, ...args) => console.debug(`> ${label}:`, ...args); + const r = await nitroApp.localCall({ url }); - debug('URL', url) - debug('StatusCode', r.status) - debug('StatusMessage', r.statusText) + debug("URL", url); + debug("StatusCode", r.status); + debug("StatusMessage", r.statusText); // @ts-ignore for (const header of r.headers.entries()) { - debug(header[0], header[1]) + debug(header[0], header[1]); } - console.log('\n', r.body.toString()) + console.log("\n", r.body.toString()); } +// eslint-disable-next-line unicorn/prefer-module if (require.main === module) { + // eslint-disable-next-line unicorn/prefer-top-level-await cli().catch((err) => { - console.error(err) - process.exit(1) - }) + console.error(err); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); + }); } diff --git a/src/runtime/entries/cloudflare-pages.ts b/src/runtime/entries/cloudflare-pages.ts index a46219ca07..0751637984 100644 --- a/src/runtime/entries/cloudflare-pages.ts +++ b/src/runtime/entries/cloudflare-pages.ts @@ -1,38 +1,38 @@ -import '#internal/nitro/virtual/polyfill' -import { requestHasBody } from '../utils' -import { nitroApp } from '../app' +import "#internal/nitro/virtual/polyfill"; +import { requestHasBody } from "../utils"; +import { nitroApp } from "../app"; /** @see https://developers.cloudflare.com/pages/platform/functions/#writing-your-first-function */ interface CFRequestContext { /** same as existing Worker API */ - request: any + request: any; /** same as existing Worker API */ - env: any + env: any; /** if filename includes [id] or [[path]] **/ - params: any + params: any; /** Same as ctx.waitUntil in existing Worker API */ - waitUntil: any + waitUntil: any; /** Used for middleware or to fetch assets */ - next: any + next: any; /** Arbitrary space for passing data between middlewares */ - data: any + data: any; } -export async function onRequest (ctx: CFRequestContext) { +export async function onRequest(ctx: CFRequestContext) { try { // const asset = await env.ASSETS.fetch(request, { cacheControl: assetsCacheControl }) - const asset = await ctx.next() + const asset = await ctx.next(); if (asset.status !== 404) { - return asset + return asset; } - } catch (_err) { + } catch { // Ignore } - const url = new URL(ctx.request.url) - let body + const url = new URL(ctx.request.url); + let body; if (requestHasBody(ctx.request)) { - body = Buffer.from(await ctx.request.arrayBuffer()) + body = Buffer.from(await ctx.request.arrayBuffer()); } const r = await nitroApp.localCall({ @@ -41,21 +41,26 @@ export async function onRequest (ctx: CFRequestContext) { headers: ctx.request.headers, host: url.hostname, protocol: url.protocol, - body + body, // TODO: Allow passing custom context // cf: ctx, // TODO: Handle redirects? // redirect: ctx.request.redirect - }) + }); return new Response(r.body, { // @ts-ignore TODO: Should be HeadersInit instead of string[][] headers: normalizeOutgoingHeaders(r.headers), status: r.status, - statusText: r.statusText - }) + statusText: r.statusText, + }); } -function normalizeOutgoingHeaders (headers: Record) { - return Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v]) +function normalizeOutgoingHeaders( + headers: Record +) { + return Object.entries(headers).map(([k, v]) => [ + k, + Array.isArray(v) ? v.join(",") : v, + ]); } diff --git a/src/runtime/entries/cloudflare.ts b/src/runtime/entries/cloudflare.ts index 42c53b9965..09abd4a00c 100644 --- a/src/runtime/entries/cloudflare.ts +++ b/src/runtime/entries/cloudflare.ts @@ -1,25 +1,31 @@ -import '#internal/nitro/virtual/polyfill' -import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler' -import { withoutBase } from 'ufo' -import { requestHasBody } from '../utils' -import { nitroApp } from '../app' -import { useRuntimeConfig } from '#internal/nitro' - -addEventListener('fetch', (event: any) => { - event.respondWith(handleEvent(event)) -}) - -async function handleEvent (event: FetchEvent) { +import "#internal/nitro/virtual/polyfill"; +import { + getAssetFromKV, + mapRequestToAsset, +} from "@cloudflare/kv-asset-handler"; +import { withoutBase } from "ufo"; +import { requestHasBody } from "../utils"; +import { nitroApp } from "../app"; +import { useRuntimeConfig } from "#internal/nitro"; + +addEventListener("fetch", (event: any) => { + event.respondWith(handleEvent(event)); +}); + +async function handleEvent(event: FetchEvent) { try { - return await getAssetFromKV(event, { cacheControl: assetsCacheControl, mapRequestToAsset: baseURLModifier }) - } catch (_err) { + return await getAssetFromKV(event, { + cacheControl: assetsCacheControl, + mapRequestToAsset: baseURLModifier, + }); + } catch { // Ignore } - const url = new URL(event.request.url) - let body + const url = new URL(event.request.url); + let body; if (requestHasBody(event.request)) { - body = Buffer.from(await event.request.arrayBuffer()) + body = Buffer.from(await event.request.arrayBuffer()); } const r = await nitroApp.localCall({ @@ -30,18 +36,18 @@ async function handleEvent (event: FetchEvent) { headers: Object.fromEntries(event.request.headers.entries()), method: event.request.method, redirect: event.request.redirect, - body - }) + body, + }); return new Response(r.body, { // @ts-ignore TODO: Should be HeadersInit instead of string[][] headers: normalizeOutgoingHeaders(r.headers), status: r.status, - statusText: r.statusText - }) + statusText: r.statusText, + }); } -function assetsCacheControl (_request) { +function assetsCacheControl(_request) { // TODO: Detect public asset bases // if (request.url.startsWith(buildAssetsURL())) { // return { @@ -49,14 +55,19 @@ function assetsCacheControl (_request) { // edgeTTL: 31536000 // } // } - return {} + return {}; } const baseURLModifier = (request: Request) => { - const url = withoutBase(request.url, useRuntimeConfig().app.baseURL) - return mapRequestToAsset(new Request(url, request)) -} + const url = withoutBase(request.url, useRuntimeConfig().app.baseURL); + return mapRequestToAsset(new Request(url, request)); +}; -function normalizeOutgoingHeaders (headers: Record) { - return Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v]) +function normalizeOutgoingHeaders( + headers: Record +) { + return Object.entries(headers).map(([k, v]) => [ + k, + Array.isArray(v) ? v.join(",") : v, + ]); } diff --git a/src/runtime/entries/deno.ts b/src/runtime/entries/deno.ts index 62173922c2..c47706aaee 100644 --- a/src/runtime/entries/deno.ts +++ b/src/runtime/entries/deno.ts @@ -1,19 +1,19 @@ -import '#internal/nitro/virtual/polyfill' +import "#internal/nitro/virtual/polyfill"; // @ts-ignore -import { serve } from 'https://deno.land/std/http/server.ts' +import { serve } from "https://deno.land/std/http/server.ts"; -import { requestHasBody, useRequestBody } from '../utils' -import { nitroApp } from '../app' +import { requestHasBody, useRequestBody } from "../utils"; +import { nitroApp } from "../app"; serve((request: Request) => { - return handleRequest(request) -}) + return handleRequest(request); +}); -async function handleRequest (request: Request) { - const url = new URL(request.url) - let body +async function handleRequest(request: Request) { + const url = new URL(request.url); + let body; if (requestHasBody(request)) { - body = await useRequestBody(request) + body = await useRequestBody(request); } const r = await nitroApp.localCall({ @@ -23,17 +23,22 @@ async function handleRequest (request: Request) { headers: Object.fromEntries(request.headers.entries()), method: request.method, redirect: request.redirect, - body - }) + body, + }); return new Response(r.body || undefined, { // @ts-ignore TODO: Should be HeadersInit instead of string[][] headers: normalizeOutgoingHeaders(r.headers), status: r.status, - statusText: r.statusText - }) + statusText: r.statusText, + }); } -function normalizeOutgoingHeaders (headers: Record) { - return Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v]) +function normalizeOutgoingHeaders( + headers: Record +) { + return Object.entries(headers).map(([k, v]) => [ + k, + Array.isArray(v) ? v.join(",") : v, + ]); } diff --git a/src/runtime/entries/firebase.ts b/src/runtime/entries/firebase.ts index 1d0ecbcc69..443d30ccd3 100644 --- a/src/runtime/entries/firebase.ts +++ b/src/runtime/entries/firebase.ts @@ -1,7 +1,7 @@ -import '#internal/nitro/virtual/polyfill' +import "#internal/nitro/virtual/polyfill"; // @ts-ignore -import functions from 'firebase-functions' -import { toNodeListener } from 'h3' -import { nitroApp } from '../app' +import functions from "firebase-functions"; +import { toNodeListener } from "h3"; +import { nitroApp } from "../app"; -export const server = functions.https.onRequest(toNodeListener(nitroApp.h3App)) +export const server = functions.https.onRequest(toNodeListener(nitroApp.h3App)); diff --git a/src/runtime/entries/netlify-builder.ts b/src/runtime/entries/netlify-builder.ts index e9a5dbc2b7..45562aa043 100644 --- a/src/runtime/entries/netlify-builder.ts +++ b/src/runtime/entries/netlify-builder.ts @@ -1,4 +1,4 @@ -import { builder } from '@netlify/functions' -import { handler as _handler } from '#internal/nitro/entries/aws-lambda' +import { builder } from "@netlify/functions"; +import { handler as _handler } from "#internal/nitro/entries/aws-lambda"; -export const handler = builder(_handler) +export const handler = builder(_handler); diff --git a/src/runtime/entries/netlify-edge.ts b/src/runtime/entries/netlify-edge.ts index fdc8547138..77e33d0da1 100644 --- a/src/runtime/entries/netlify-edge.ts +++ b/src/runtime/entries/netlify-edge.ts @@ -1,12 +1,12 @@ -import '#internal/nitro/virtual/polyfill' -import { nitroApp } from '../app' -import { requestHasBody, useRequestBody } from '../utils' +import "#internal/nitro/virtual/polyfill"; +import { nitroApp } from "../app"; +import { requestHasBody, useRequestBody } from "../utils"; export default async function (request: Request, _context) { - const url = new URL(request.url) - let body + const url = new URL(request.url); + let body; if (requestHasBody(request)) { - body = await useRequestBody(request) + body = await useRequestBody(request); } const r = await nitroApp.localCall({ @@ -17,12 +17,12 @@ export default async function (request: Request, _context) { headers: request.headers, method: request.method, redirect: request.redirect, - body - }) + body, + }); return new Response(r.body, { headers: r.headers as HeadersInit, status: r.status, - statusText: r.statusText - }) + statusText: r.statusText, + }); } diff --git a/src/runtime/entries/netlify.ts b/src/runtime/entries/netlify.ts index 99f3bec646..e86cd3de58 100644 --- a/src/runtime/entries/netlify.ts +++ b/src/runtime/entries/netlify.ts @@ -1,31 +1,49 @@ -import '#internal/nitro/virtual/polyfill' -import type { Handler, HandlerResponse, HandlerContext, HandlerEvent } from '@netlify/functions/dist/main' -import type { APIGatewayProxyEventHeaders } from 'aws-lambda' -import { withQuery } from 'ufo' -import { nitroApp } from '../app' -import { getRouteRulesForPath } from '../route-rules' - -export const handler: Handler = async function handler (event, context) { - const query = { ...event.queryStringParameters, ...event.multiValueQueryStringParameters } - const url = withQuery(event.path, query) - const routeRules = getRouteRulesForPath(url) +import "#internal/nitro/virtual/polyfill"; +import type { + Handler, + HandlerResponse, + HandlerContext, + HandlerEvent, +} from "@netlify/functions/dist/main"; +import type { APIGatewayProxyEventHeaders } from "aws-lambda"; +import { withQuery } from "ufo"; +import { nitroApp } from "../app"; +import { getRouteRulesForPath } from "../route-rules"; + +export const handler: Handler = async function handler(event, context) { + const query = { + ...event.queryStringParameters, + ...event.multiValueQueryStringParameters, + }; + const url = withQuery(event.path, query); + const routeRules = getRouteRulesForPath(url); if (routeRules.cache && (routeRules.cache.swr || routeRules.cache.static)) { - const builder = await import('@netlify/functions').then(r => r.builder || r.default.builder) - const ttl = typeof routeRules.cache.swr === 'number' ? routeRules.cache.swr : 60 + const builder = await import("@netlify/functions").then( + (r) => r.builder || r.default.builder + ); + const ttl = + typeof routeRules.cache.swr === "number" ? routeRules.cache.swr : 60; const swrHandler = routeRules.cache.swr - ? ((event, context) => lambda(event, context).then(r => ({ ...r, ttl }))) as Handler - : lambda - return builder(swrHandler)(event, context) as any + ? (((event, context) => + lambda(event, context).then((r) => ({ ...r, ttl }))) as Handler) + : lambda; + return builder(swrHandler)(event, context) as any; } - return lambda(event, context) -} + return lambda(event, context); +}; -async function lambda (event: HandlerEvent, context: HandlerContext): Promise { - const query = { ...event.queryStringParameters, ...(event).multiValueQueryStringParameters } - const url = withQuery((event).path, query) - const method = (event).httpMethod || 'get' +async function lambda( + event: HandlerEvent, + context: HandlerContext +): Promise { + const query = { + ...event.queryStringParameters, + ...event.multiValueQueryStringParameters, + }; + const url = withQuery(event.path, query); + const method = event.httpMethod || "get"; const r = await nitroApp.localCall({ event, @@ -34,22 +52,31 @@ async function lambda (event: HandlerEvent, context: HandlerContext): Promise [key.toLowerCase(), value!])) +function normalizeIncomingHeaders(headers?: APIGatewayProxyEventHeaders) { + return Object.fromEntries( + Object.entries(headers || {}).map(([key, value]) => [ + key.toLowerCase(), + value!, + ]) + ); } -function normalizeOutgoingHeaders (headers: Record) { - return Object.fromEntries(Object.entries(headers) - .filter(([key]) => !['set-cookie'].includes(key)) - .map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v!])) +function normalizeOutgoingHeaders( + headers: Record +) { + return Object.fromEntries( + Object.entries(headers) + .filter(([key]) => !["set-cookie"].includes(key)) + .map(([k, v]) => [k, Array.isArray(v) ? v.join(",") : v!]) + ); } diff --git a/src/runtime/entries/nitro-dev.ts b/src/runtime/entries/nitro-dev.ts index 57421eb859..db66d4b085 100644 --- a/src/runtime/entries/nitro-dev.ts +++ b/src/runtime/entries/nitro-dev.ts @@ -1,44 +1,53 @@ -import '#internal/nitro/virtual/polyfill' -import { Server } from 'http' -import { tmpdir } from 'os' -import { join } from 'path' -import { mkdirSync } from 'fs' -import { threadId, parentPort } from 'worker_threads' -import { isWindows, provider } from 'std-env' -import { toNodeListener } from 'h3' -import { nitroApp } from '../app' +import "#internal/nitro/virtual/polyfill"; +import { Server } from "node:http"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { mkdirSync } from "node:fs"; +import { threadId, parentPort } from "node:worker_threads"; +import { isWindows, provider } from "std-env"; +import { toNodeListener } from "h3"; +import { nitroApp } from "../app"; -const server = new Server(toNodeListener(nitroApp.h3App)) +const server = new Server(toNodeListener(nitroApp.h3App)); -function getAddress () { - if (provider === 'stackblitz' || process.env.NITRO_NO_UNIX_SOCKET) { - return '0' +function getAddress() { + if (provider === "stackblitz" || process.env.NITRO_NO_UNIX_SOCKET) { + return "0"; } - const socketName = `worker-${process.pid}-${threadId}.sock` + const socketName = `worker-${process.pid}-${threadId}.sock`; if (isWindows) { - return join('\\\\.\\pipe\\nitro', socketName) + return join("\\\\.\\pipe\\nitro", socketName); } else { - const socketDir = join(tmpdir(), 'nitro') - mkdirSync(socketDir, { recursive: true }) - return join(socketDir, socketName) + const socketDir = join(tmpdir(), "nitro"); + mkdirSync(socketDir, { recursive: true }); + return join(socketDir, socketName); } } -const listenAddress = getAddress() +const listenAddress = getAddress(); server.listen(listenAddress, () => { - const _address = server.address() + const _address = server.address(); parentPort.postMessage({ - event: 'listen', - address: typeof _address === 'string' - ? { socketPath: _address } - : { host: 'localhost', port: _address.port } - }) -}) + event: "listen", + address: + typeof _address === "string" + ? { socketPath: _address } + : { host: "localhost", port: _address.port }, + }); +}); if (process.env.DEBUG) { - process.on('unhandledRejection', err => console.error('[nitro] [dev] [unhandledRejection]', err)) - process.on('uncaughtException', err => console.error('[nitro] [dev] [uncaughtException]', err)) + process.on("unhandledRejection", (err) => + console.error("[nitro] [dev] [unhandledRejection]", err) + ); + process.on("uncaughtException", (err) => + console.error("[nitro] [dev] [uncaughtException]", err) + ); } else { - process.on('unhandledRejection', err => console.error('[nitro] [dev] [unhandledRejection] ' + err)) - process.on('uncaughtException', err => console.error('[nitro] [dev] [uncaughtException] ' + err)) + process.on("unhandledRejection", (err) => + console.error("[nitro] [dev] [unhandledRejection] " + err) + ); + process.on("uncaughtException", (err) => + console.error("[nitro] [dev] [uncaughtException] " + err) + ); } diff --git a/src/runtime/entries/nitro-prerenderer.ts b/src/runtime/entries/nitro-prerenderer.ts index 96620a20cb..a17bdc4d3f 100644 --- a/src/runtime/entries/nitro-prerenderer.ts +++ b/src/runtime/entries/nitro-prerenderer.ts @@ -1,12 +1,20 @@ -import '#internal/nitro/virtual/polyfill' -import { nitroApp } from '../app' +import "#internal/nitro/virtual/polyfill"; +import { nitroApp } from "../app"; -export const localFetch = nitroApp.localFetch +export const localFetch = nitroApp.localFetch; if (process.env.DEBUG) { - process.on('unhandledRejection', err => console.error('[nitro] [dev] [unhandledRejection]', err)) - process.on('uncaughtException', err => console.error('[nitro] [dev] [uncaughtException]', err)) + process.on("unhandledRejection", (err) => + console.error("[nitro] [dev] [unhandledRejection]", err) + ); + process.on("uncaughtException", (err) => + console.error("[nitro] [dev] [uncaughtException]", err) + ); } else { - process.on('unhandledRejection', err => console.error('[nitro] [dev] [unhandledRejection] ' + err)) - process.on('uncaughtException', err => console.error('[nitro] [dev] [uncaughtException] ' + err)) + process.on("unhandledRejection", (err) => + console.error("[nitro] [dev] [unhandledRejection] " + err) + ); + process.on("uncaughtException", (err) => + console.error("[nitro] [dev] [uncaughtException] " + err) + ); } diff --git a/src/runtime/entries/node-cluster.ts b/src/runtime/entries/node-cluster.ts index b60b5eb94f..4e5f75a06e 100644 --- a/src/runtime/entries/node-cluster.ts +++ b/src/runtime/entries/node-cluster.ts @@ -1,17 +1,20 @@ -import os from 'node:os' -import cluster from 'node:cluster' +import os from "node:os"; +import cluster from "node:cluster"; if (cluster.isPrimary) { - const numberOfWorkers = parseInt(process.env.NITRO_CLUSTER_WORKERS) || os.cpus().length + const numberOfWorkers = + Number.parseInt(process.env.NITRO_CLUSTER_WORKERS) || os.cpus().length > 0; for (let i = 0; i < numberOfWorkers; i++) { - cluster.fork() + cluster.fork(); } - cluster.on('exit', () => { - cluster.fork() - }) + cluster.on("exit", () => { + cluster.fork(); + }); } else { - import('./node-server').catch((error) => { - console.error(error) - process.exit(1) - }) + // eslint-disable-next-line unicorn/prefer-top-level-await + import("./node-server").catch((error) => { + console.error(error); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); + }); } diff --git a/src/runtime/entries/node-server.ts b/src/runtime/entries/node-server.ts index 743443bbb7..06004b280c 100644 --- a/src/runtime/entries/node-server.ts +++ b/src/runtime/entries/node-server.ts @@ -1,39 +1,54 @@ -import '#internal/nitro/virtual/polyfill' -import { Server as HttpServer } from 'http' -import type { AddressInfo } from 'net' -import { Server as HttpsServer } from 'https' -import destr from 'destr' -import { toNodeListener } from 'h3' -import { nitroApp } from '../app' -import { useRuntimeConfig } from '#internal/nitro' +import "#internal/nitro/virtual/polyfill"; +import { Server as HttpServer } from "node:http"; +import type { AddressInfo } from "node:net"; +import { Server as HttpsServer } from "node:https"; +import destr from "destr"; +import { toNodeListener } from "h3"; +import { nitroApp } from "../app"; +import { useRuntimeConfig } from "#internal/nitro"; -const cert = process.env.NITRO_SSL_CERT -const key = process.env.NITRO_SSL_KEY +const cert = process.env.NITRO_SSL_CERT; +const key = process.env.NITRO_SSL_KEY; -const server = cert && key ? new HttpsServer({ key, cert }, toNodeListener(nitroApp.h3App)) : new HttpServer(toNodeListener(nitroApp.h3App)) +const server = + cert && key + ? new HttpsServer({ key, cert }, toNodeListener(nitroApp.h3App)) + : new HttpServer(toNodeListener(nitroApp.h3App)); -const port = (destr(process.env.NITRO_PORT || process.env.PORT) || 3000) as number -const host = process.env.NITRO_HOST || process.env.HOST +const port = (destr(process.env.NITRO_PORT || process.env.PORT) || + 3000) as number; +const host = process.env.NITRO_HOST || process.env.HOST; // @ts-ignore const s = server.listen(port, host, (err) => { if (err) { - console.error(err) - process.exit(1) + console.error(err); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); } - const protocol = cert && key ? 'https' : 'http' - const i = s.address() as AddressInfo - const baseURL = (useRuntimeConfig().app.baseURL || '').replace(/\/$/, '') - const url = `${protocol}://${i.family === 'IPv6' ? `[${i.address}]` : i.address}:${i.port}${baseURL}` - console.log(`Listening ${url}`) -}) + const protocol = cert && key ? "https" : "http"; + const i = s.address() as AddressInfo; + const baseURL = (useRuntimeConfig().app.baseURL || "").replace(/\/$/, ""); + const url = `${protocol}://${ + i.family === "IPv6" ? `[${i.address}]` : i.address + }:${i.port}${baseURL}`; + console.log(`Listening ${url}`); +}); if (process.env.DEBUG) { - process.on('unhandledRejection', err => console.error('[nitro] [dev] [unhandledRejection]', err)) - process.on('uncaughtException', err => console.error('[nitro] [dev] [uncaughtException]', err)) + process.on("unhandledRejection", (err) => + console.error("[nitro] [dev] [unhandledRejection]", err) + ); + process.on("uncaughtException", (err) => + console.error("[nitro] [dev] [uncaughtException]", err) + ); } else { - process.on('unhandledRejection', err => console.error('[nitro] [dev] [unhandledRejection] ' + err)) - process.on('uncaughtException', err => console.error('[nitro] [dev] [uncaughtException] ' + err)) + process.on("unhandledRejection", (err) => + console.error("[nitro] [dev] [unhandledRejection] " + err) + ); + process.on("uncaughtException", (err) => + console.error("[nitro] [dev] [uncaughtException] " + err) + ); } -export default {} +export default {}; diff --git a/src/runtime/entries/node.ts b/src/runtime/entries/node.ts index 82c456606a..83c3b62862 100644 --- a/src/runtime/entries/node.ts +++ b/src/runtime/entries/node.ts @@ -1,16 +1,24 @@ -import '#internal/nitro/virtual/polyfill' -import { toNodeListener } from 'h3' -import { nitroApp } from '../app' +import "#internal/nitro/virtual/polyfill"; +import { toNodeListener } from "h3"; +import { nitroApp } from "../app"; -export const listener = toNodeListener(nitroApp.h3App) +export const listener = toNodeListener(nitroApp.h3App); /** @deprecated use new `listener` export instead */ -export const handler = listener +export const handler = listener; if (process.env.DEBUG) { - process.on('unhandledRejection', err => console.error('[nitro] [dev] [unhandledRejection]', err)) - process.on('uncaughtException', err => console.error('[nitro] [dev] [uncaughtException]', err)) + process.on("unhandledRejection", (err) => + console.error("[nitro] [dev] [unhandledRejection]", err) + ); + process.on("uncaughtException", (err) => + console.error("[nitro] [dev] [uncaughtException]", err) + ); } else { - process.on('unhandledRejection', err => console.error('[nitro] [dev] [unhandledRejection] ' + err)) - process.on('uncaughtException', err => console.error('[nitro] [dev] [uncaughtException] ' + err)) + process.on("unhandledRejection", (err) => + console.error("[nitro] [dev] [unhandledRejection] " + err) + ); + process.on("uncaughtException", (err) => + console.error("[nitro] [dev] [uncaughtException] " + err) + ); } diff --git a/src/runtime/entries/service-worker.ts b/src/runtime/entries/service-worker.ts index 5ece718c4f..4536e64b49 100644 --- a/src/runtime/entries/service-worker.ts +++ b/src/runtime/entries/service-worker.ts @@ -1,21 +1,21 @@ -import '#internal/nitro/virtual/polyfill' -import { requestHasBody, useRequestBody } from '../utils' -import { nitroApp } from '../app' -import { isPublicAssetURL } from '#internal/nitro/virtual/public-assets' - -addEventListener('fetch', (event: any) => { - const url = new URL(event.request.url) - if (isPublicAssetURL(url.pathname) || url.pathname.includes('/_server/')) { - return +import "#internal/nitro/virtual/polyfill"; +import { requestHasBody, useRequestBody } from "../utils"; +import { nitroApp } from "../app"; +import { isPublicAssetURL } from "#internal/nitro/virtual/public-assets"; + +addEventListener("fetch", (event: any) => { + const url = new URL(event.request.url); + if (isPublicAssetURL(url.pathname) || url.pathname.includes("/_server/")) { + return; } - event.respondWith(handleEvent(url, event)) -}) + event.respondWith(handleEvent(url, event)); +}); -async function handleEvent (url, event) { - let body +async function handleEvent(url, event) { + let body; if (requestHasBody(event.request)) { - body = await useRequestBody(event.request) + body = await useRequestBody(event.request); } const r = await nitroApp.localCall({ @@ -26,22 +26,22 @@ async function handleEvent (url, event) { headers: event.request.headers, method: event.request.method, redirect: event.request.redirect, - body - }) + body, + }); return new Response(r.body, { headers: r.headers as HeadersInit, status: r.status, - statusText: r.statusText - }) + statusText: r.statusText, + }); } -declare const self: ServiceWorkerGlobalScope +declare const self: ServiceWorkerGlobalScope; -self.addEventListener('install', () => { - self.skipWaiting() -}) +self.addEventListener("install", () => { + self.skipWaiting(); +}); -self.addEventListener('activate', (event) => { - event.waitUntil(self.clients.claim()) -}) +self.addEventListener("activate", (event) => { + event.waitUntil(self.clients.claim()); +}); diff --git a/src/runtime/entries/stormkit.ts b/src/runtime/entries/stormkit.ts index c078fdc940..9979d4cd96 100644 --- a/src/runtime/entries/stormkit.ts +++ b/src/runtime/entries/stormkit.ts @@ -1,28 +1,28 @@ -import type { Handler } from 'aws-lambda' -import '#internal/nitro/virtual/polyfill' -import { nitroApp } from '../app' +import type { Handler } from "aws-lambda"; +import "#internal/nitro/virtual/polyfill"; +import { nitroApp } from "../app"; interface StormkitEvent { - url: string // e.g. /my/path, /my/path?with=query - path: string - method: string - body?: string - query?: Record> - headers?: Record - rawHeaders?: Array + url: string; // e.g. /my/path, /my/path?with=query + path: string; + method: string; + body?: string; + query?: Record>; + headers?: Record; + rawHeaders?: Array; } export interface StormkitResult { - statusCode: number - headers?: { [header: string]: boolean | number | string } | undefined - body?: string | undefined + statusCode: number; + headers?: { [header: string]: boolean | number | string } | undefined; + body?: string | undefined; } export const handler: Handler = async function ( event, context ) { - const method = event.method || 'get' + const method = event.method || "get"; const r = await nitroApp.localCall({ event, @@ -31,16 +31,23 @@ export const handler: Handler = async function ( headers: event.headers, method, query: event.query, - body: event.body - }) + body: event.body, + }); return { statusCode: r.status, headers: normalizeOutgoingHeaders(r.headers), - body: r.body.toString() - } -} + body: r.body.toString(), + }; +}; -function normalizeOutgoingHeaders (headers: Record) { - return Object.fromEntries(Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v!])) +function normalizeOutgoingHeaders( + headers: Record +) { + return Object.fromEntries( + Object.entries(headers).map(([k, v]) => [ + k, + Array.isArray(v) ? v.join(",") : v!, + ]) + ); } diff --git a/src/runtime/entries/vercel-edge.ts b/src/runtime/entries/vercel-edge.ts index cd8ef85b23..5e8914d232 100644 --- a/src/runtime/entries/vercel-edge.ts +++ b/src/runtime/entries/vercel-edge.ts @@ -1,12 +1,12 @@ -import '#internal/nitro/virtual/polyfill' -import { requestHasBody, useRequestBody } from '#internal/nitro/utils' -import { nitroApp } from '#internal/nitro/app' +import "#internal/nitro/virtual/polyfill"; +import { requestHasBody, useRequestBody } from "#internal/nitro/utils"; +import { nitroApp } from "#internal/nitro/app"; -export default async function handleEvent (request, event) { - const url = new URL(request.url) - let body +export default async function handleEvent(request, event) { + const url = new URL(request.url); + let body; if (requestHasBody(request)) { - body = await useRequestBody(request) + body = await useRequestBody(request); } const r = await nitroApp.localCall({ @@ -16,17 +16,22 @@ export default async function handleEvent (request, event) { protocol: url.protocol, headers: Object.fromEntries(request.headers.entries()), method: request.method, - body - }) + body, + }); return new Response(r.body, { // @ts-ignore TODO: Should be HeadersInit instead of string[][] headers: normalizeOutgoingHeaders(r.headers), status: r.status, - statusText: r.statusText - }) + statusText: r.statusText, + }); } -function normalizeOutgoingHeaders (headers: Record) { - return Object.entries(headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v]) +function normalizeOutgoingHeaders( + headers: Record +) { + return Object.entries(headers).map(([k, v]) => [ + k, + Array.isArray(v) ? v.join(",") : v, + ]); } diff --git a/src/runtime/entries/vercel.ts b/src/runtime/entries/vercel.ts index ecdfbda822..674d9ada1a 100644 --- a/src/runtime/entries/vercel.ts +++ b/src/runtime/entries/vercel.ts @@ -1,17 +1,17 @@ -import '#internal/nitro/virtual/polyfill' -import { toNodeListener, NodeListener } from 'h3' -import { parseQuery } from 'ufo' -import { nitroApp } from '../app' +import "#internal/nitro/virtual/polyfill"; +import { toNodeListener, NodeListener } from "h3"; +import { parseQuery } from "ufo"; +import { nitroApp } from "../app"; -const handler = toNodeListener(nitroApp.h3App) +const handler = toNodeListener(nitroApp.h3App); -export default function (req, res) { - const query = req.headers['x-now-route-matches'] as string +export default function (req, res) { + const query = req.headers["x-now-route-matches"] as string; if (query) { - const { url } = parseQuery(query) + const { url } = parseQuery(query); if (url) { - req.url = url as string + req.url = url as string; } } - return handler(req, res) -} + return handler(req, res); +}; diff --git a/src/runtime/error.ts b/src/runtime/error.ts index 3b8b8ecedb..63cf236f0e 100644 --- a/src/runtime/error.ts +++ b/src/runtime/error.ts @@ -1,58 +1,63 @@ // import ansiHTML from 'ansi-html' -import type { NitroErrorHandler } from '../types' -import { normalizeError, isJsonRequest } from './utils' +import type { NitroErrorHandler } from "../types"; +import { normalizeError, isJsonRequest } from "./utils"; -const isDev = process.env.NODE_ENV === 'development' +const isDev = process.env.NODE_ENV === "development"; interface ParsedError { - url: string - statusCode: number - statusMessage: number - message: string - stack?: string[] + url: string; + statusCode: number; + statusMessage: number; + message: string; + stack?: string[]; } -export default function (error, event) { - const { stack, statusCode, statusMessage, message } = normalizeError(error) +export default function (error, event) { + const { stack, statusCode, statusMessage, message } = normalizeError(error); - const showDetails = isDev && statusCode !== 404 + const showDetails = isDev && statusCode !== 404; const errorObject = { - url: event.req.url || '', + url: event.req.url || "", statusCode, statusMessage, message, - stack: showDetails ? stack.map(i => i.text) : undefined - } + stack: showDetails ? stack.map((i) => i.text) : undefined, + }; // Console output if (error.unhandled || error.fatal) { const tags = [ - '[nitro]', - '[request error]', - error.unhandled && '[unhandled]', - error.fatal && '[fatal]' - ].filter(Boolean).join(' ') - console.error(tags, error.message + '\n' + stack.map(l => ' ' + l.text).join(' \n')) + "[nitro]", + "[request error]", + error.unhandled && "[unhandled]", + error.fatal && "[fatal]", + ] + .filter(Boolean) + .join(" "); + console.error( + tags, + error.message + "\n" + stack.map((l) => " " + l.text).join(" \n") + ); } - event.res.statusCode = statusCode + event.res.statusCode = statusCode; if (statusMessage) { - event.res.statusMessage = statusMessage + event.res.statusMessage = statusMessage; } if (isJsonRequest(event)) { - event.res.setHeader('Content-Type', 'application/json') - event.res.end(JSON.stringify(errorObject)) + event.res.setHeader("Content-Type", "application/json"); + event.res.end(JSON.stringify(errorObject)); } else { - event.res.setHeader('Content-Type', 'text/html') - event.res.end(renderHTMLError(errorObject)) + event.res.setHeader("Content-Type", "text/html"); + event.res.end(renderHTMLError(errorObject)); } -} +}; -function renderHTMLError (error: ParsedError): string { - const statusCode = error.statusCode || 500 - const statusMessage = error.statusMessage || 'Request Error' +function renderHTMLError(error: ParsedError): string { + const statusCode = error.statusCode || 500; + const statusMessage = error.statusMessage || "Request Error"; return ` @@ -70,7 +75,10 @@ function renderHTMLError (error: ParsedError): string { ${error.message}

    - ${'\n' + (error.stack || []).map(i => `  ${i}`).join('
    ')} + ${ + "\n" + + (error.stack || []).map((i) => `  ${i}`).join("
    ") + }