From 3af848f9a6914e71137ad9ba77ae008510584b09 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Mon, 10 Feb 2025 19:16:36 +0000 Subject: [PATCH] feat(kit,nuxt,schema): support experimental decorators syntax (#27672) --- .../1.experimental-features.md | 38 +++++ packages/kit/src/template.ts | 10 ++ .../nuxt/src/core/plugins/plugin-metadata.ts | 3 +- packages/nuxt/src/core/plugins/prehydrate.ts | 3 +- packages/nuxt/src/core/utils/parse.ts | 19 +-- packages/nuxt/src/pages/route-rules.ts | 3 +- packages/nuxt/src/pages/utils.ts | 3 +- packages/schema/build.config.ts | 1 + packages/schema/package.json | 1 + packages/schema/src/config/esbuild.ts | 32 ++++ packages/schema/src/config/experimental.ts | 6 + packages/schema/src/config/index.ts | 2 + packages/schema/src/config/vite.ts | 10 +- packages/schema/src/config/webpack.ts | 6 +- pnpm-lock.yaml | 143 ++++++++++++++++-- test/basic.test.ts | 10 +- test/fixtures/basic/nuxt.config.ts | 1 + .../basic/pages/experimental/decorators.vue | 26 ++++ .../server/api/experimental/decorators.ts | 14 ++ 19 files changed, 291 insertions(+), 40 deletions(-) create mode 100644 packages/schema/src/config/esbuild.ts create mode 100644 test/fixtures/basic/pages/experimental/decorators.vue create mode 100644 test/fixtures/basic/server/api/experimental/decorators.ts diff --git a/docs/2.guide/3.going-further/1.experimental-features.md b/docs/2.guide/3.going-further/1.experimental-features.md index 2d03733513..994a79b0fe 100644 --- a/docs/2.guide/3.going-further/1.experimental-features.md +++ b/docs/2.guide/3.going-further/1.experimental-features.md @@ -524,3 +524,41 @@ Alternatively, you can render the template alongside the Nuxt app root by settin ``` This avoids a white flash when hydrating a client-only page. + +## decorators + +This option enables enabling decorator syntax across your entire Nuxt/Nitro app, powered by [esbuild](https://github.com/evanw/esbuild/releases/tag/v0.21.3). + +For a long time, TypeScript has had support for decorators via `compilerOptions.experimentalDecorators`. This implementation predated the TC39 standardization process. Now, decorators are a [Stage 3 Proposal](https://github.com/tc39/proposal-decorators), and supported without special configuration in TS 5.0+ (see https://github.com/microsoft/TypeScript/pull/52582 and https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#decorators). + +Enabling `experimental.decorators` enables support for the TC39 proposal, **NOT** for TypeScript's previous `compilerOptions.experimentalDecorators` implementation. + +::warning +Note that there may be changes before this finally lands in the JS standard. +:: + +### Usage + +```ts twoslash [nuxt.config.ts] +export default defineNuxtConfig({ + experimental: { + decorators: true, + }, +}) +``` + +```ts [app.vue] +function something (_method: () => unknown) { + return () => 'decorated' +} + +class SomeClass { + @something + public someMethod () { + return 'initial' + } +} + +const value = new SomeClass().someMethod() +// this will return 'decorated' +``` diff --git a/packages/kit/src/template.ts b/packages/kit/src/template.ts index 1f56930922..e79d28bf60 100644 --- a/packages/kit/src/template.ts +++ b/packages/kit/src/template.ts @@ -180,6 +180,8 @@ export async function _generateTypes (nuxt: Nuxt) { .then(r => r?.version && gte(r.version, '5.4.0')) .catch(() => isV4) + const useDecorators = Boolean(nuxt.options.experimental?.decorators) + // https://www.totaltypescript.com/tsconfig-cheat-sheet const tsConfig: TSConfig = defu(nuxt.options.typescript?.tsConfig, { compilerOptions: { @@ -197,12 +199,20 @@ export async function _generateTypes (nuxt: Nuxt) { noUncheckedIndexedAccess: isV4, forceConsistentCasingInFileNames: true, noImplicitOverride: true, + /* Decorator support */ + ...useDecorators + ? { + useDefineForClassFields: false, + experimentalDecorators: false, + } + : {}, /* If NOT transpiling with TypeScript: */ module: hasTypescriptVersionWithModulePreserve ? 'preserve' : 'ESNext', noEmit: true, /* If your code runs in the DOM: */ lib: [ 'ESNext', + ...useDecorators ? ['esnext.decorators'] : [], 'dom', 'dom.iterable', 'webworker', diff --git a/packages/nuxt/src/core/plugins/plugin-metadata.ts b/packages/nuxt/src/core/plugins/plugin-metadata.ts index beba0a6901..ba7d9f44bf 100644 --- a/packages/nuxt/src/core/plugins/plugin-metadata.ts +++ b/packages/nuxt/src/core/plugins/plugin-metadata.ts @@ -1,5 +1,4 @@ import type { Literal, Property, SpreadElement } from 'estree' -import { transform } from 'esbuild' import { defu } from 'defu' import { findExports } from 'mlly' import type { Nuxt } from '@nuxt/schema' @@ -8,7 +7,7 @@ import MagicString from 'magic-string' import { normalize } from 'pathe' import type { ObjectPlugin, PluginMeta } from 'nuxt/app' -import { parseAndWalk, withLocations } from '../../core/utils/parse' +import { parseAndWalk, transform, withLocations } from '../../core/utils/parse' import { logger } from '../../utils' const internalOrderMap = { diff --git a/packages/nuxt/src/core/plugins/prehydrate.ts b/packages/nuxt/src/core/plugins/prehydrate.ts index ce3a8a6e2a..24bc2e2407 100644 --- a/packages/nuxt/src/core/plugins/prehydrate.ts +++ b/packages/nuxt/src/core/plugins/prehydrate.ts @@ -1,9 +1,8 @@ -import { transform } from 'esbuild' import { createUnplugin } from 'unplugin' import MagicString from 'magic-string' import { hash } from 'ohash' -import { parseAndWalk, withLocations } from '../../core/utils/parse' +import { parseAndWalk, transform, withLocations } from '../../core/utils/parse' import { isJS, isVue } from '../utils' export function PrehydrateTransformPlugin (options: { sourcemap?: boolean } = {}) { diff --git a/packages/nuxt/src/core/utils/parse.ts b/packages/nuxt/src/core/utils/parse.ts index fb8f82e14a..95910140e8 100644 --- a/packages/nuxt/src/core/utils/parse.ts +++ b/packages/nuxt/src/core/utils/parse.ts @@ -1,22 +1,17 @@ import { walk as _walk } from 'estree-walker' import type { Node, SyncHandler } from 'estree-walker' -import type { - ArrowFunctionExpression, - CatchClause, - Program as ESTreeProgram, - FunctionDeclaration, - FunctionExpression, - Identifier, - ImportDefaultSpecifier, - ImportNamespaceSpecifier, - ImportSpecifier, - VariableDeclaration, -} from 'estree' +import type { ArrowFunctionExpression, CatchClause, Program as ESTreeProgram, FunctionDeclaration, FunctionExpression, Identifier, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, VariableDeclaration } from 'estree' import { parse } from 'acorn' import type { Program } from 'acorn' +import { type SameShape, type TransformOptions, type TransformResult, transform as esbuildTransform } from 'esbuild' +import { tryUseNuxt } from '@nuxt/kit' export type { Node } +export async function transform (input: string | Uint8Array, options?: SameShape): Promise> { + return await esbuildTransform(input, { ...tryUseNuxt()?.options.esbuild.options, ...options }) +} + type WithLocations = T & { start: number, end: number } type WalkerCallback = (this: ThisParameterType, node: WithLocations, parent: WithLocations | null, ctx: { key: string | number | symbol | null | undefined, index: number | null | undefined, ast: Program | Node }) => void diff --git a/packages/nuxt/src/pages/route-rules.ts b/packages/nuxt/src/pages/route-rules.ts index 7b0a66945e..c1127c0ced 100644 --- a/packages/nuxt/src/pages/route-rules.ts +++ b/packages/nuxt/src/pages/route-rules.ts @@ -1,11 +1,10 @@ import { runInNewContext } from 'node:vm' -import { transform } from 'esbuild' import type { NuxtPage } from '@nuxt/schema' import type { NitroRouteConfig } from 'nitropack' import { normalize } from 'pathe' import { getLoader } from '../core/utils' -import { parseAndWalk } from '../core/utils/parse' +import { parseAndWalk, transform } from '../core/utils/parse' import { extractScriptContent, pathToNitroGlob } from './utils' const ROUTE_RULE_RE = /\bdefineRouteRules\(/ diff --git a/packages/nuxt/src/pages/utils.ts b/packages/nuxt/src/pages/utils.ts index 73455bba4c..6bb5039e03 100644 --- a/packages/nuxt/src/pages/utils.ts +++ b/packages/nuxt/src/pages/utils.ts @@ -7,12 +7,11 @@ import { genArrayFromRaw, genDynamicImport, genImport, genSafeVariableName } fro import escapeRE from 'escape-string-regexp' import { filename } from 'pathe/utils' import { hash } from 'ohash' -import { transform } from 'esbuild' import type { Property } from 'estree' import type { NuxtPage } from 'nuxt/schema' import { klona } from 'klona' -import { parseAndWalk, withLocations } from '../core/utils/parse' +import { parseAndWalk, transform, withLocations } from '../core/utils/parse' import { getLoader, uniqueBy } from '../core/utils' import { logger, toArray } from '../utils' diff --git a/packages/schema/build.config.ts b/packages/schema/build.config.ts index c78328916c..7e920ba419 100644 --- a/packages/schema/build.config.ts +++ b/packages/schema/build.config.ts @@ -39,6 +39,7 @@ export default defineBuildConfig({ 'consola', 'css-minimizer-webpack-plugin', 'cssnano', + 'esbuild', 'esbuild-loader', 'file-loader', 'h3', diff --git a/packages/schema/package.json b/packages/schema/package.json index 968105b12d..5ce31decd0 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -50,6 +50,7 @@ "chokidar": "4.0.3", "compatx": "0.1.8", "css-minimizer-webpack-plugin": "7.0.0", + "esbuild": "0.25.0", "esbuild-loader": "4.3.0", "file-loader": "6.2.0", "h3": "1.15.0", diff --git a/packages/schema/src/config/esbuild.ts b/packages/schema/src/config/esbuild.ts new file mode 100644 index 0000000000..e675dc74ba --- /dev/null +++ b/packages/schema/src/config/esbuild.ts @@ -0,0 +1,32 @@ +import { defu } from 'defu' +import type { TransformOptions } from 'esbuild' +import { defineResolvers } from '../utils/definition' + +export default defineResolvers({ + esbuild: { + /** + * Configure shared esbuild options used within Nuxt and passed to other builders, such as Vite or Webpack. + * @type {import('esbuild').TransformOptions} + */ + options: { + jsxFactory: 'h', + jsxFragment: 'Fragment', + tsconfigRaw: { + $resolve: async (_val, get) => { + const val: NonNullable> = typeof _val === 'string' ? JSON.parse(_val) : (_val && typeof _val === 'object' ? _val : {}) + + const useDecorators = await get('experimental').then(r => r?.decorators === true) + if (!useDecorators) { + return val + } + return defu(val, { + compilerOptions: { + useDefineForClassFields: false, + experimentalDecorators: false, + }, + } satisfies TransformOptions['tsconfigRaw']) + }, + }, + }, + }, +}) diff --git a/packages/schema/src/config/experimental.ts b/packages/schema/src/config/experimental.ts index 5a387dc9a5..c6b3b7dfd7 100644 --- a/packages/schema/src/config/experimental.ts +++ b/packages/schema/src/config/experimental.ts @@ -136,6 +136,12 @@ export default defineResolvers({ }, }, experimental: { + /** + * Enable to use experimental decorators in Nuxt and Nitro. + * + * @see https://github.com/tc39/proposal-decorators + */ + decorators: false, /** * Set to true to generate an async entry point for the Vue bundle (for module federation support). */ diff --git a/packages/schema/src/config/index.ts b/packages/schema/src/config/index.ts index c35253fe7c..225f0bcf97 100644 --- a/packages/schema/src/config/index.ts +++ b/packages/schema/src/config/index.ts @@ -5,6 +5,7 @@ import app from './app' import build from './build' import common from './common' import dev from './dev' +import esbuild from './esbuild' import experimental from './experimental' import generate from './generate' import internal from './internal' @@ -28,6 +29,7 @@ export default { ...postcss, ...router, ...typescript, + ...esbuild, ...vite, ...webpack, } satisfies ResolvableConfigSchema diff --git a/packages/schema/src/config/vite.ts b/packages/schema/src/config/vite.ts index 0f3d04dbba..0a19108fde 100644 --- a/packages/schema/src/config/vite.ts +++ b/packages/schema/src/config/vite.ts @@ -1,4 +1,5 @@ import { consola } from 'consola' +import defu from 'defu' import { resolve } from 'pathe' import { isTest } from 'std-env' import { defineResolvers } from '../utils/definition' @@ -86,6 +87,9 @@ export default defineResolvers({ }, }, optimizeDeps: { + esbuildOptions: { + $resolve: async (val, get) => defu(val && typeof val === 'object' ? val : {}, await get('esbuild.options')), + }, exclude: { $resolve: async (val, get) => [ ...Array.isArray(val) ? val : [], @@ -95,9 +99,9 @@ export default defineResolvers({ }, }, esbuild: { - jsxFactory: 'h', - jsxFragment: 'Fragment', - tsconfigRaw: '{}', + $resolve: async (val, get) => { + return defu(val && typeof val === 'object' ? val : {}, await get('esbuild.options')) + }, }, clearScreen: true, build: { diff --git a/packages/schema/src/config/webpack.ts b/packages/schema/src/config/webpack.ts index 86f78af662..810c0c1cae 100644 --- a/packages/schema/src/config/webpack.ts +++ b/packages/schema/src/config/webpack.ts @@ -160,9 +160,9 @@ export default defineResolvers({ * @type {Omit} */ esbuild: { - jsxFactory: 'h', - jsxFragment: 'Fragment', - tsconfigRaw: '{}', + $resolve: async (val, get) => { + return defu(val && typeof val === 'object' ? val : {}, await get('esbuild.options')) + }, }, /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce1e774a0f..8d5743e043 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -713,10 +713,10 @@ importers: version: 4.2.4 '@types/webpack-bundle-analyzer': specifier: 4.7.0 - version: 4.7.0 + version: 4.7.0(esbuild@0.25.0) '@types/webpack-hot-middleware': specifier: 2.25.9 - version: 2.25.9 + version: 2.25.9(esbuild@0.25.0) '@unhead/schema': specifier: 1.11.18 version: 1.11.18 @@ -746,13 +746,16 @@ importers: version: 0.1.8 css-minimizer-webpack-plugin: specifier: 7.0.0 - version: 7.0.0(webpack@5.96.1) + version: 7.0.0(esbuild@0.25.0)(webpack@5.96.1(esbuild@0.25.0)) + esbuild: + specifier: 0.25.0 + version: 0.25.0 esbuild-loader: specifier: 4.3.0 - version: 4.3.0(webpack@5.96.1) + version: 4.3.0(webpack@5.96.1(esbuild@0.25.0)) file-loader: specifier: 6.2.0 - version: 6.2.0(webpack@5.96.1) + version: 6.2.0(webpack@5.96.1(esbuild@0.25.0)) h3: specifier: 1.15.0 version: 1.15.0 @@ -764,7 +767,7 @@ importers: version: 7.0.3 mini-css-extract-plugin: specifier: 2.9.2 - version: 2.9.2(webpack@5.96.1) + version: 2.9.2(webpack@5.96.1(esbuild@0.25.0)) nitropack: specifier: 2.10.4 version: 2.10.4(typescript@5.7.3) @@ -779,7 +782,7 @@ importers: version: 8.5.2 sass-loader: specifier: 16.0.4 - version: 16.0.4(@rspack/core@1.2.3)(webpack@5.96.1) + version: 16.0.4(@rspack/core@1.2.3)(webpack@5.96.1(esbuild@0.25.0)) scule: specifier: 1.3.0 version: 1.3.0 @@ -806,16 +809,16 @@ importers: version: 2.1.1 vue-loader: specifier: 17.4.2 - version: 17.4.2(@vue/compiler-sfc@3.5.13)(vue@3.5.13(typescript@5.7.3))(webpack@5.96.1) + version: 17.4.2(@vue/compiler-sfc@3.5.13)(vue@3.5.13(typescript@5.7.3))(webpack@5.96.1(esbuild@0.25.0)) vue-router: specifier: 4.5.0 version: 4.5.0(vue@3.5.13(typescript@5.7.3)) webpack: specifier: 5.96.1 - version: 5.96.1 + version: 5.96.1(esbuild@0.25.0) webpack-dev-middleware: specifier: 7.4.2 - version: 7.4.2(webpack@5.96.1) + version: 7.4.2(webpack@5.96.1(esbuild@0.25.0)) packages/ui-templates: devDependencies: @@ -9742,6 +9745,17 @@ snapshots: - uglify-js - webpack-cli + '@types/webpack-bundle-analyzer@4.7.0(esbuild@0.25.0)': + dependencies: + '@types/node': 22.13.1 + tapable: 2.2.1 + webpack: 5.96.1(esbuild@0.25.0) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + '@types/webpack-hot-middleware@2.25.9': dependencies: '@types/connect': 3.4.38 @@ -9753,6 +9767,17 @@ snapshots: - uglify-js - webpack-cli + '@types/webpack-hot-middleware@2.25.9(esbuild@0.25.0)': + dependencies: + '@types/connect': 3.4.38 + tapable: 2.2.1 + webpack: 5.96.1(esbuild@0.25.0) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + - webpack-cli + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.33': @@ -11037,6 +11062,18 @@ snapshots: '@rspack/core': 1.2.3 webpack: 5.97.1 + css-minimizer-webpack-plugin@7.0.0(esbuild@0.25.0)(webpack@5.96.1(esbuild@0.25.0)): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + cssnano: 7.0.6(postcss@8.5.2) + jest-worker: 29.7.0 + postcss: 8.5.2 + schema-utils: 4.3.0 + serialize-javascript: 6.0.2 + webpack: 5.96.1(esbuild@0.25.0) + optionalDependencies: + esbuild: 0.25.0 + css-minimizer-webpack-plugin@7.0.0(webpack@5.96.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -11393,6 +11430,14 @@ snapshots: dependencies: es-errors: 1.3.0 + esbuild-loader@4.3.0(webpack@5.96.1(esbuild@0.25.0)): + dependencies: + esbuild: 0.25.0 + get-tsconfig: 4.8.0 + loader-utils: 2.0.4 + webpack: 5.96.1(esbuild@0.25.0) + webpack-sources: 1.4.3 + esbuild-loader@4.3.0(webpack@5.96.1): dependencies: esbuild: 0.25.0 @@ -11804,6 +11849,12 @@ snapshots: dependencies: flat-cache: 4.0.1 + file-loader@6.2.0(webpack@5.96.1(esbuild@0.25.0)): + dependencies: + loader-utils: 2.0.4 + schema-utils: 3.3.0 + webpack: 5.96.1(esbuild@0.25.0) + file-loader@6.2.0(webpack@5.96.1): dependencies: loader-utils: 2.0.4 @@ -13248,6 +13299,12 @@ snapshots: min-indent@1.0.1: {} + mini-css-extract-plugin@2.9.2(webpack@5.96.1(esbuild@0.25.0)): + dependencies: + schema-utils: 4.3.0 + tapable: 2.2.1 + webpack: 5.96.1(esbuild@0.25.0) + mini-css-extract-plugin@2.9.2(webpack@5.96.1): dependencies: schema-utils: 4.3.0 @@ -14538,12 +14595,12 @@ snapshots: safe-buffer@5.2.1: {} - sass-loader@16.0.4(@rspack/core@1.2.3)(webpack@5.96.1): + sass-loader@16.0.4(@rspack/core@1.2.3)(webpack@5.96.1(esbuild@0.25.0)): dependencies: neo-async: 2.6.2 optionalDependencies: '@rspack/core': 1.2.3 - webpack: 5.96.1 + webpack: 5.96.1(esbuild@0.25.0) sass@1.78.0: dependencies: @@ -14926,6 +14983,17 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 + terser-webpack-plugin@5.3.11(esbuild@0.25.0)(webpack@5.96.1(esbuild@0.25.0)): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 4.3.0 + serialize-javascript: 6.0.2 + terser: 5.37.0 + webpack: 5.96.1(esbuild@0.25.0) + optionalDependencies: + esbuild: 0.25.0 + terser-webpack-plugin@5.3.11(webpack@5.96.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -15631,6 +15699,16 @@ snapshots: dependencies: vue: 3.5.13(typescript@5.7.3) + vue-loader@17.4.2(@vue/compiler-sfc@3.5.13)(vue@3.5.13(typescript@5.7.3))(webpack@5.96.1(esbuild@0.25.0)): + dependencies: + chalk: 4.1.2 + hash-sum: 2.0.0 + watchpack: 2.4.2 + webpack: 5.96.1(esbuild@0.25.0) + optionalDependencies: + '@vue/compiler-sfc': 3.5.13 + vue: 3.5.13(typescript@5.7.3) + vue-loader@17.4.2(@vue/compiler-sfc@3.5.13)(vue@3.5.13(typescript@5.7.3))(webpack@5.96.1): dependencies: chalk: 4.1.2 @@ -15718,6 +15796,17 @@ snapshots: - bufferutil - utf-8-validate + webpack-dev-middleware@7.4.2(webpack@5.96.1(esbuild@0.25.0)): + dependencies: + colorette: 2.0.20 + memfs: 4.14.1 + mime-types: 2.1.35 + on-finished: 2.4.1 + range-parser: 1.2.1 + schema-utils: 4.3.0 + optionalDependencies: + webpack: 5.96.1(esbuild@0.25.0) + webpack-dev-middleware@7.4.2(webpack@5.96.1): dependencies: colorette: 2.0.20 @@ -15785,6 +15874,36 @@ snapshots: - esbuild - uglify-js + webpack@5.96.1(esbuild@0.25.0): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.14.0 + browserslist: 4.24.3 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.0 + es-module-lexer: 1.6.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.11(esbuild@0.25.0)(webpack@5.96.1(esbuild@0.25.0)) + watchpack: 2.4.2 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + webpack@5.97.1: dependencies: '@types/eslint-scope': 3.7.7 diff --git a/test/basic.test.ts b/test/basic.test.ts index 6a05e9aa9e..c120268363 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -2784,8 +2784,14 @@ describe('teleports', () => { }) }) -describe('Node.js compatibility for client-side', () => { - it('should work', async () => { +describe('experimental', () => { + it('decorators support works', async () => { + const html = await $fetch('/experimental/decorators') + expect(html).toContain('decorated-decorated') + expectNoClientErrors('/experimental/decorators') + }) + + it('Node.js compatibility for client-side', async () => { const { page } = await renderPage('/experimental/node-compat') await page.locator('body').getByText('Nuxt is Awesome!').waitFor() expect(await page.innerHTML('body')).toContain('CWD: [available]') diff --git a/test/fixtures/basic/nuxt.config.ts b/test/fixtures/basic/nuxt.config.ts index a0a5d1e966..23b9544378 100644 --- a/test/fixtures/basic/nuxt.config.ts +++ b/test/fixtures/basic/nuxt.config.ts @@ -161,6 +161,7 @@ export default defineNuxtConfig({ inlineStyles: id => !!id && !id.includes('assets.vue'), }, experimental: { + decorators: true, typedPages: true, polyfillVueUseHead: true, respectNoSSRHeader: true, diff --git a/test/fixtures/basic/pages/experimental/decorators.vue b/test/fixtures/basic/pages/experimental/decorators.vue new file mode 100644 index 0000000000..dfe7a49fb7 --- /dev/null +++ b/test/fixtures/basic/pages/experimental/decorators.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/test/fixtures/basic/server/api/experimental/decorators.ts b/test/fixtures/basic/server/api/experimental/decorators.ts new file mode 100644 index 0000000000..94de19fce7 --- /dev/null +++ b/test/fixtures/basic/server/api/experimental/decorators.ts @@ -0,0 +1,14 @@ +export default eventHandler((_event) => { + function something (_method: () => unknown) { + return () => 'decorated' + } + + class SomeClass { + @something + public someMethod () { + return 'initial' + } + } + + return new SomeClass().someMethod() +})