mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
feat: experimental client-side Node.js compatibility (#25028)
This commit is contained in:
parent
807ead6f1a
commit
dab2188d58
@ -359,3 +359,18 @@ const { data } = await useAsyncData(async () => {
|
||||
const { data } = await useAsyncData(route.params.slug, async () => {
|
||||
return await $fetch(`/api/my-page/${route.params.slug}`)
|
||||
})
|
||||
```
|
||||
|
||||
## clientNodeCompat
|
||||
|
||||
With this feature, Nuxt will automatically polyfill Node.js imports in the client build using [`unenv`](https://github.com/unjs/unenv).
|
||||
|
||||
::alert{type=info}
|
||||
To make globals like `Buffer` work in the browser, you need to manually inject them.
|
||||
|
||||
```ts
|
||||
import { Buffer } from 'node:buffer'
|
||||
|
||||
globalThis.Buffer = globalThis.Buffer || Buffer
|
||||
```
|
||||
::
|
||||
|
@ -50,6 +50,7 @@ export default defineBuildConfig({
|
||||
'pug',
|
||||
'sass-loader',
|
||||
'c12',
|
||||
'unenv',
|
||||
// Implicit
|
||||
'@vue/compiler-core',
|
||||
'@vue/shared',
|
||||
|
@ -50,6 +50,7 @@
|
||||
"ofetch": "1.3.3",
|
||||
"unbuild": "latest",
|
||||
"unctx": "2.3.1",
|
||||
"unenv": "^1.9.0",
|
||||
"vite": "5.0.11",
|
||||
"vue": "3.4.14",
|
||||
"vue-bundle-renderer": "2.0.0",
|
||||
|
@ -307,6 +307,21 @@ export default defineUntypedSchema({
|
||||
},
|
||||
/** @type {Pick<typeof import('ofetch')['FetchOptions'], 'timeout' | 'retry' | 'retryDelay' | 'retryStatusCodes'>} */
|
||||
useFetch: {}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Automatically polyfill Node.js imports in the client build using `unenv`.
|
||||
* @see https://github.com/unjs/unenv
|
||||
*
|
||||
* **Note:** To make globals like `Buffer` work in the browser, you need to manually inject them.
|
||||
*
|
||||
* ```ts
|
||||
* import { Buffer } from 'node:buffer'
|
||||
*
|
||||
* globalThis.Buffer = globalThis.Buffer || Buffer
|
||||
* ```
|
||||
* @type {boolean}
|
||||
*/
|
||||
clientNodeCompat: false,
|
||||
}
|
||||
})
|
||||
|
@ -60,6 +60,7 @@
|
||||
"std-env": "^3.7.0",
|
||||
"strip-literal": "^2.0.0",
|
||||
"ufo": "^1.3.2",
|
||||
"unenv": "^1.8.0",
|
||||
"unplugin": "^1.6.0",
|
||||
"vite": "5.0.11",
|
||||
"vite-node": "^1.1.1",
|
||||
|
@ -8,6 +8,7 @@ import { logger } from '@nuxt/kit'
|
||||
import { getPort } from 'get-port-please'
|
||||
import { joinURL, withoutLeadingSlash } from 'ufo'
|
||||
import { defu } from 'defu'
|
||||
import { env, nodeless } from 'unenv'
|
||||
import { appendCorsHeaders, appendCorsPreflightHeaders, defineEventHandler } from 'h3'
|
||||
import type { ViteConfig } from '@nuxt/schema'
|
||||
import { chunkErrorPlugin } from './plugins/chunk-error'
|
||||
@ -19,6 +20,13 @@ import { viteNodePlugin } from './vite-node'
|
||||
import { createViteLogger } from './utils/logger'
|
||||
|
||||
export async function buildClient (ctx: ViteBuildContext) {
|
||||
const nodeCompat = ctx.nuxt.options.experimental.clientNodeCompat ? {
|
||||
alias: env(nodeless).alias,
|
||||
define: {
|
||||
global: 'globalThis',
|
||||
}
|
||||
} : { alias: {}, define: {} }
|
||||
|
||||
const clientConfig: ViteConfig = vite.mergeConfig(ctx.config, vite.mergeConfig({
|
||||
configFile: false,
|
||||
base: ctx.nuxt.options.dev
|
||||
@ -48,15 +56,18 @@ export async function buildClient (ctx: ViteBuildContext) {
|
||||
'import.meta.browser': true,
|
||||
'import.meta.nitro': false,
|
||||
'import.meta.prerender': false,
|
||||
'module.hot': false
|
||||
'module.hot': false,
|
||||
...nodeCompat.define
|
||||
},
|
||||
optimizeDeps: {
|
||||
entries: [ctx.entry]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
...nodeCompat.alias,
|
||||
...ctx.config.resolve?.alias,
|
||||
'#build/plugins': resolve(ctx.nuxt.options.buildDir, 'plugins/client'),
|
||||
'#internal/nitro': resolve(ctx.nuxt.options.buildDir, 'nitro.client.mjs')
|
||||
'#internal/nitro': resolve(ctx.nuxt.options.buildDir, 'nitro.client.mjs'),
|
||||
},
|
||||
dedupe: [
|
||||
'vue'
|
||||
|
@ -57,6 +57,7 @@
|
||||
"std-env": "^3.7.0",
|
||||
"time-fix-plugin": "^2.0.7",
|
||||
"ufo": "^1.3.2",
|
||||
"unenv": "^1.8.0",
|
||||
"unplugin": "^1.6.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"vue-bundle-renderer": "^2.0.0",
|
||||
|
@ -5,6 +5,7 @@ import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
|
||||
import { logger } from '@nuxt/kit'
|
||||
import { joinURL } from 'ufo'
|
||||
import ForkTSCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
|
||||
import { env, nodeless } from 'unenv'
|
||||
|
||||
import type { WebpackConfigContext } from '../utils/config'
|
||||
import { applyPresets } from '../utils/config'
|
||||
@ -20,7 +21,8 @@ export function client (ctx: WebpackConfigContext) {
|
||||
clientOptimization,
|
||||
clientDevtool,
|
||||
clientPerformance,
|
||||
clientHMR
|
||||
clientHMR,
|
||||
clientNodeCompat,
|
||||
])
|
||||
}
|
||||
|
||||
@ -48,6 +50,24 @@ function clientPerformance (ctx: WebpackConfigContext) {
|
||||
}
|
||||
}
|
||||
|
||||
function clientNodeCompat(ctx: WebpackConfigContext) {
|
||||
if (!ctx.nuxt.options.experimental.clientNodeCompat) {
|
||||
return
|
||||
}
|
||||
ctx.config.plugins!.push(new webpack.DefinePlugin({ global: 'globalThis', }))
|
||||
|
||||
ctx.config.resolve = ctx.config.resolve || {}
|
||||
ctx.config.resolve.fallback = {
|
||||
...env(nodeless).alias,
|
||||
...ctx.config.resolve.fallback,
|
||||
}
|
||||
|
||||
// https://github.com/webpack/webpack/issues/13290#issuecomment-1188760779
|
||||
ctx.config.plugins!.unshift(new webpack.NormalModuleReplacementPlugin(/node:/, (resource) => {
|
||||
resource.request = resource.request.replace(/^node:/, '');
|
||||
}))
|
||||
}
|
||||
|
||||
function clientHMR (ctx: WebpackConfigContext) {
|
||||
if (!ctx.isDev) {
|
||||
return
|
||||
|
@ -508,6 +508,9 @@ importers:
|
||||
unctx:
|
||||
specifier: 2.3.1
|
||||
version: 2.3.1
|
||||
unenv:
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0
|
||||
vite:
|
||||
specifier: 5.0.11
|
||||
version: 5.0.11(@types/node@20.11.5)
|
||||
@ -616,6 +619,9 @@ importers:
|
||||
ufo:
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2
|
||||
unenv:
|
||||
specifier: ^1.8.0
|
||||
version: 1.9.0
|
||||
unplugin:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
@ -749,6 +755,9 @@ importers:
|
||||
ufo:
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2
|
||||
unenv:
|
||||
specifier: ^1.8.0
|
||||
version: 1.9.0
|
||||
unplugin:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
|
@ -2285,6 +2285,16 @@ describe('keepalive', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Node.js compatibility for client-side', () => {
|
||||
it('should work', async () => {
|
||||
const { page } = await renderPage('/node-compat')
|
||||
const html = await page.innerHTML('body')
|
||||
expect(html).toContain('Nuxt is Awesome!')
|
||||
expect(html).toContain('CWD: [available]')
|
||||
await page.close()
|
||||
})
|
||||
})
|
||||
|
||||
function normaliseIslandResult (result: NuxtIslandResponse) {
|
||||
return {
|
||||
...result,
|
||||
|
1
test/fixtures/basic/nuxt.config.ts
vendored
1
test/fixtures/basic/nuxt.config.ts
vendored
@ -200,6 +200,7 @@ export default defineNuxtConfig({
|
||||
respectNoSSRHeader: true,
|
||||
clientFallback: true,
|
||||
restoreState: true,
|
||||
clientNodeCompat: true,
|
||||
componentIslands: {
|
||||
selectiveClient: true
|
||||
},
|
||||
|
20
test/fixtures/basic/pages/node-compat.vue
vendored
Normal file
20
test/fixtures/basic/pages/node-compat.vue
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { Buffer } from 'node:buffer'
|
||||
import process from 'node:process'
|
||||
|
||||
const base64 = atob(Buffer.from('Nuxt is Awesome!', 'utf8').toString('base64'))
|
||||
const cwd = typeof process.cwd == 'function' && "[available]"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ClientOnly>
|
||||
<div>
|
||||
{{ base64 }}
|
||||
</div>
|
||||
<div>
|
||||
CWD: {{ cwd }}
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue
Block a user