Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

Commit

Permalink
Merge branch 'main' into fix/#components-import
Browse files Browse the repository at this point in the history
  • Loading branch information
huang-julien committed Sep 12, 2022
2 parents 36df3b5 + 8bccdf4 commit b2e9bc2
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 46 deletions.
139 changes: 139 additions & 0 deletions docs/content/3.api/1.composables/use-runtime-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# `useRuntimeConfig`

The `useRuntimeConfig` composable is used to expose config variables within your app.

## Usage

```vue [app.vue]
<script setup lang="ts">
const config = useRuntimeConfig()
</script>
```

```ts [server/api/foo.ts]
export default defineEventHandler((event) => {
const config = useRuntimeConfig()
})
```

## Define Runtime Config

The example below shows how to set a public API base URL and a secret API token that is only accessible on the server.

We should always define `runtimeConfig` variables inside `nuxt.config`.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
runtimeConfig: {
// Private keys are only available on the server
apiSecret: '123',

// Public keys that are exposed to the client
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
}
}
})
```

::alert
Variables that need to be accessible on the server are added directly inside `runtimeConfig`. Variables that need to be accessible on both the client and the server are defined in `runtimeConfig.public`.
::

::ReadMore{link="/guide/features/runtime-config"}
::

## Acess Runtime Config

To access runtime config, we can use `useRuntimeConfig()` composable:

```ts [server/api/test.ts]
export default async () => {
const config = useRuntimeConfig()

// Access public variables
const result = await $fetch(`/test`, {
baseURL: config.public.apiBase,
headers: {
// Access a private variable (only available on the server)
Authorization: `Bearer ${config.apiSecret}`
}
})
return result
}
```

In this example, since `apiBase` is defined within the `public` namespace, it is universally accessible on both server and client-side, while `apiSecret` **is only accessible on the server-side**.

## Environment Variables

It is possible to update runtime config values using a matching environment variable name prefixed with `NUXT_`.

::ReadMore{link="/guide/features/runtime-config"}
::

### Using the `.env` File

We can set the environment variables inside the `.env` file to make them accessible during **development** and **build/generate**.

``` [.env]
NUXT_PUBLIC_API_BASE_URL = "https://api.localhost:5555"
NUXT_API_SECRET = "123"
```

::alert{type=info}
Any environment variables set within `.env` file are accessed using `process.env` in the Nuxt app during **development** and **build/generate**.
::

::alert{type=warning}
In **production runtime**, you should use platform environment variables and `.env` is not used.
::

::alert{type=warning}
When using git, make sure to add `.env` to the `.gitignore` file to avoid leaking secrets to the git history.
::

## `app` namespace

Nuxt uses `app` namespace in runtime-config with keys including `baseURL` and `cdnURL`. You can customize their values at runtime by setting environment variables.

::alert{type=info}
This is a reserved namespace. You should not introduce additional keys inside `app`.
::

### `app.baseURL`

By default, the `baseURL` is set to `'/'`.

However, the `baseURL` can be updated at runtime by setting the `NUXT_APP_BASE_URL` as an environment variable.

Then, you can access this new base URL using `config.app.baseURL`:

```ts [/plugins/my-plugin.ts]
export default defineNuxtPlugin((NuxtApp) => {
const config = useRuntimeConfig()

// Access baseURL universally
const baseURL = config.app.baseURL
})
```

### `app.cdnURL`

This example shows how to set a custom CDN url and access them using `useRuntimeConfig()`.

You can use a custom CDN for serving static assets inside `.output/public` using the `NUXT_APP_CDN_URL` environment variable.

And then access the new CDN url using `config.app.cdnURL`.

```ts [server/api/foo.ts]
export default defineEventHandler((event) => {
const config = useRuntimeConfig()

// Access cdnURL universally
const cdnURL = config.app.cdnURL
})
```

::ReadMore{link="/guide/features/runtime-config"}
::
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@
"@nuxtjs/eslint-config-typescript": "^11.0.0",
"@types/node": "^16.11.58",
"@types/rimraf": "^3",
"@unocss/reset": "^0.45.18",
"@unocss/reset": "^0.45.21",
"case-police": "^0.5.10",
"changelogen": "^0.3.0",
"eslint": "^8.23.0",
"eslint": "^8.23.1",
"eslint-plugin-jsdoc": "^39.3.6",
"execa": "^6.1.0",
"expect-type": "^0.14.2",
Expand Down
10 changes: 5 additions & 5 deletions packages/nuxt/src/app/composables/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FetchOptions } from 'ohmyfetch'
import type { FetchError, FetchOptions } from 'ohmyfetch'
import type { TypedInternalResponse, NitroFetchRequest } from 'nitropack'
import { computed, isRef, Ref } from 'vue'
import type { AsyncDataOptions, _Transform, KeyOfRes, AsyncData, PickFrom } from './asyncData'
Expand All @@ -16,7 +16,7 @@ export interface UseFetchOptions<

export function useFetch<
ResT = void,
ErrorT = Error,
ErrorT = FetchError,
ReqT extends NitroFetchRequest = NitroFetchRequest,
_ResT = ResT extends void ? FetchResult<ReqT> : ResT,
Transform extends (res: _ResT) => any = (res: _ResT) => _ResT,
Expand All @@ -27,7 +27,7 @@ export function useFetch<
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, ErrorT | null | true>
export function useFetch<
ResT = void,
ErrorT = Error,
ErrorT = FetchError,
ReqT extends NitroFetchRequest = NitroFetchRequest,
_ResT = ResT extends void ? FetchResult<ReqT> : ResT,
Transform extends (res: _ResT) => any = (res: _ResT) => _ResT,
Expand Down Expand Up @@ -93,7 +93,7 @@ export function useFetch<

export function useLazyFetch<
ResT = void,
ErrorT = Error,
ErrorT = FetchError,
ReqT extends NitroFetchRequest = NitroFetchRequest,
_ResT = ResT extends void ? FetchResult<ReqT> : ResT,
Transform extends (res: _ResT) => any = (res: _ResT) => _ResT,
Expand All @@ -104,7 +104,7 @@ export function useLazyFetch<
): AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, ErrorT | null | true>
export function useLazyFetch<
ResT = void,
ErrorT = Error,
ErrorT = FetchError,
ReqT extends NitroFetchRequest = NitroFetchRequest,
_ResT = ResT extends void ? FetchResult<ReqT> : ResT,
Transform extends (res: _ResT) => any = (res: _ResT) => _ResT,
Expand Down
4 changes: 2 additions & 2 deletions packages/nuxt/src/core/plugins/import-protection.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createRequire } from 'node:module'
import { createUnplugin } from 'unplugin'
import { logger } from '@nuxt/kit'
import { isAbsolute, join, relative, resolve } from 'pathe'
import { isAbsolute, join, relative } from 'pathe'
import type { Nuxt } from '@nuxt/schema'
import escapeRE from 'escape-string-regexp'

Expand All @@ -21,7 +21,7 @@ export const vueAppPatterns = (nuxt: Nuxt) => [
[new RegExp(`^${escapeRE(m as string)}$`), 'Importing directly from module entry points is not allowed.']),
...[/(^|node_modules\/)@nuxt\/kit/, /^nitropack/]
.map(i => [i, 'This module cannot be imported in the Vue part of your app.']),
[new RegExp(escapeRE(resolve(nuxt.options.srcDir, (nuxt.options.dir as any).server || 'server')) + '\\/(api|routes|middleware|plugins)\\/'), 'Importing from server is not allowed in the Vue part of your app.']
[new RegExp(escapeRE(join(nuxt.options.srcDir, (nuxt.options.dir as any).server || 'server')) + '\\/(api|routes|middleware|plugins)\\/'), 'Importing from server is not allowed in the Vue part of your app.']
] as ImportProtectionOptions['patterns']

export const ImportProtectionPlugin = createUnplugin(function (options: ImportProtectionOptions) {
Expand Down
48 changes: 48 additions & 0 deletions packages/nuxt/test/import-protection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { normalize } from 'pathe'
import { describe, expect, it } from 'vitest'
import { ImportProtectionPlugin, vueAppPatterns } from '../src/core/plugins/import-protection'

const testsToTriggerOn = [
['~/nuxt.config', 'app.vue', true],
['./nuxt.config', 'app.vue', true],
['./nuxt.config.ts', 'app.vue', true],
['nuxt.config.ts', 'app.vue', true],
['./.nuxt/nuxt.config', 'app.vue', false],
['.nuxt/nuxt.config', 'app.vue', false],
['nuxt', 'components/Component.vue', true],
['nuxt3', 'components/Component.vue', true],
['/root/node_modules/@vue/composition-api', 'components/Component.vue', true],
['@vue/composition-api', 'components/Component.vue', true],
['@nuxt/kit', 'components/Component.vue', true],
['/root/node_modules/@nuxt/kit', 'components/Component.vue', true],
['some-nuxt-module', 'components/Component.vue', true],
['/root/src/server/api/test.ts', 'components/Component.vue', true],
['src/server/api/test.ts', 'components/Component.vue', true]
] as const

describe('import protection', () => {
it.each(testsToTriggerOn)('should protect %s', async (id, importer, isProtected) => {
const result = await transformWithImportProtection(id, importer)
if (!isProtected) {
expect(result).toBeNull()
} else {
expect(result).toBeDefined()
expect(normalize(result)).contains('unenv/runtime/mock/proxy')
}
})
})

const transformWithImportProtection = (id: string, importer: string) => {
const plugin = ImportProtectionPlugin.rollup({
rootDir: '/root',
patterns: vueAppPatterns({
options: {
modules: ['some-nuxt-module'],
srcDir: 'src/',
dir: { server: 'server' }
}
} as any)
})

return (plugin as any).resolveId(id, importer)
}
2 changes: 1 addition & 1 deletion packages/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@rollup/plugin-replace": "^4.0.0",
"@vitejs/plugin-vue": "^3.1.0",
"@vitejs/plugin-vue-jsx": "^2.0.1",
"autoprefixer": "^10.4.8",
"autoprefixer": "^10.4.9",
"chokidar": "^3.5.3",
"cssnano": "^5.1.13",
"defu": "^6.1.0",
Expand Down
6 changes: 0 additions & 6 deletions packages/vite/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import { getPort } from 'get-port-please'
import { joinURL, withLeadingSlash, withoutLeadingSlash, withTrailingSlash } from 'ufo'
import escapeRE from 'escape-string-regexp'
import defu from 'defu'
import { sanitizeFilePath } from 'mlly'
import { filename } from 'pathe/utils'
import type { OutputOptions } from 'rollup'
import { cacheDirPlugin } from './plugins/cache-dir'
import { wpfs } from './utils/wpfs'
Expand Down Expand Up @@ -80,10 +78,6 @@ export async function buildClient (ctx: ViteBuildContext) {
// We want to respect users' own rollup output options
clientConfig.build!.rollupOptions = defu(clientConfig.build!.rollupOptions!, {
output: {
// https://github.com/vitejs/vite/tree/main/packages/vite/src/node/build.ts#L464-L478
assetFileNames: ctx.nuxt.options.dev
? undefined
: chunk => withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, `${sanitizeFilePath(filename(chunk.name!))}.[hash].[ext]`)),
chunkFileNames: ctx.nuxt.options.dev ? undefined : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[name].[hash].js')),
entryFileNames: ctx.nuxt.options.dev ? 'entry.js' : withoutLeadingSlash(join(ctx.nuxt.options.app.buildAssetsDir, '[name].[hash].js'))
} as OutputOptions
Expand Down
10 changes: 9 additions & 1 deletion packages/vite/src/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { logger, isIgnored, resolvePath } from '@nuxt/kit'
import type { Options } from '@vitejs/plugin-vue'
import replace from '@rollup/plugin-replace'
import { sanitizeFilePath } from 'mlly'
import { withoutLeadingSlash } from 'ufo'
import { filename } from 'pathe/utils'
import { buildClient } from './client'
import { buildServer } from './server'
import virtual from './plugins/virtual'
Expand Down Expand Up @@ -55,7 +57,13 @@ export async function bundle (nuxt: Nuxt) {
css: resolveCSSOptions(nuxt),
build: {
rollupOptions: {
output: { sanitizeFileName: sanitizeFilePath }
output: {
sanitizeFileName: sanitizeFilePath,
// https://github.com/vitejs/vite/tree/main/packages/vite/src/node/build.ts#L464-L478
assetFileNames: nuxt.options.dev
? undefined
: chunk => withoutLeadingSlash(join(nuxt.options.app.buildAssetsDir, `${sanitizeFilePath(filename(chunk.name!))}.[hash].[ext]`))
}
},
watch: {
exclude: nuxt.options.ignore
Expand Down
2 changes: 1 addition & 1 deletion packages/webpack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@babel/core": "^7.19.0",
"@nuxt/friendly-errors-webpack-plugin": "^2.5.2",
"@nuxt/kit": "3.0.0-rc.9",
"autoprefixer": "^10.4.8",
"autoprefixer": "^10.4.9",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.1.0",
"cssnano": "^5.1.13",
Expand Down
4 changes: 2 additions & 2 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { joinURL } from 'ufo'
// import { isWindows } from 'std-env'
import { isWindows } from 'std-env'
import { setup, fetch, $fetch, startServer, createPage, url } from '@nuxt/test-utils'
// eslint-disable-next-line import/order
import { expectNoClientErrors, renderPage } from './utils'
Expand Down Expand Up @@ -586,7 +586,7 @@ describe('app config', () => {
})
})

describe('payload rendering', () => {
describe.skipIf(process.env.NUXT_TEST_DEV || isWindows)('payload rendering', () => {
it('renders a payload', async () => {
const payload = await $fetch('/random/a/_payload.js', { responseType: 'text' })
expect(payload).toMatch(
Expand Down
5 changes: 3 additions & 2 deletions test/fixtures/basic/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { describe, it } from 'vitest'
import type { Ref } from 'vue'
import type { AppConfig } from '@nuxt/schema'

import type { FetchError } from 'ohmyfetch'
import { NavigationFailure, RouteLocationNormalizedLoaded, RouteLocationRaw, useRouter as vueUseRouter } from 'vue-router'
import { defineNuxtConfig } from '~~/../../../packages/nuxt/src'
import type { NavigateToOptions } from '~~/../../../packages/nuxt/dist/app/composables/router'
Expand Down Expand Up @@ -47,7 +48,7 @@ describe('API routes', () => {
expectTypeOf(useFetch('/api/other').data).toEqualTypeOf<Ref<unknown>>()
expectTypeOf(useFetch<TestResponse>('/test').data).toEqualTypeOf<Ref<TestResponse>>()

expectTypeOf(useFetch('/error').error).toEqualTypeOf<Ref<Error | null | true>>()
expectTypeOf(useFetch('/error').error).toEqualTypeOf<Ref<FetchError | null | true>>()
expectTypeOf(useFetch<any, string>('/error').error).toEqualTypeOf<Ref<string | null | true>>()

expectTypeOf(useLazyFetch('/api/hello').data).toEqualTypeOf<Ref<string>>()
Expand All @@ -57,7 +58,7 @@ describe('API routes', () => {
expectTypeOf(useLazyFetch('/api/other').data).toEqualTypeOf<Ref<unknown>>()
expectTypeOf(useLazyFetch<TestResponse>('/test').data).toEqualTypeOf<Ref<TestResponse>>()

expectTypeOf(useLazyFetch('/error').error).toEqualTypeOf<Ref<Error | null | true>>()
expectTypeOf(useLazyFetch('/error').error).toEqualTypeOf<Ref<FetchError | null | true>>()
expectTypeOf(useLazyFetch<any, string>('/error').error).toEqualTypeOf<Ref<string | null | true>>()
})
})
Expand Down

0 comments on commit b2e9bc2

Please sign in to comment.