diff --git a/.eslintignore b/.eslintignore
index 6da29e5dd8..1f567252c7 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -24,6 +24,7 @@ packages/next-codemod/**/*.d.ts
packages/next-env/**/*.d.ts
packages/create-next-app/templates/**
test/integration/eslint/**
+test/development/basic/legacy-decorators/**/*
test-timings.json
packages/next/build/swc/tests/**
bench/nested-deps/pages/**
diff --git a/packages/next/build/jest/jest.ts b/packages/next/build/jest/jest.ts
index b4a098c6ec..644d5cd115 100644
--- a/packages/next/build/jest/jest.ts
+++ b/packages/next/build/jest/jest.ts
@@ -2,7 +2,7 @@ import { loadEnvConfig } from '@next/env'
import { resolve, join } from 'path'
import loadConfig from '../../server/config'
import { PHASE_TEST } from '../../shared/lib/constants'
-// import loadJsConfig from '../load-jsconfig'
+import loadJsConfig from '../load-jsconfig'
import * as Log from '../output/log'
async function getConfig(dir: string) {
@@ -51,7 +51,7 @@ export default function nextJest(options: { dir?: string } = {}) {
// Will be called and awaited by Jest
return async () => {
let nextConfig
- let paths
+ let jsConfig
let resolvedBaseUrl
let isEsmProject = false
if (options.dir) {
@@ -62,9 +62,9 @@ export default function nextJest(options: { dir?: string } = {}) {
nextConfig = await getConfig(resolvedDir)
loadEnvConfig(resolvedDir, false, Log)
// TODO: revisit when bug in SWC is fixed that strips `.css`
- // const result = await loadJsConfig(resolvedDir, nextConfig)
- // paths = result?.jsConfig?.compilerOptions?.paths
- // resolvedBaseUrl = result.resolvedBaseUrl
+ const result = await loadJsConfig(resolvedDir, nextConfig)
+ jsConfig = result.jsConfig
+ resolvedBaseUrl = result.resolvedBaseUrl
}
// Ensure provided async config is supported
const resolvedJestConfig =
@@ -107,10 +107,9 @@ export default function nextJest(options: { dir?: string } = {}) {
'^.+\\.(js|jsx|ts|tsx)$': [
require.resolve('../swc/jest-transformer'),
{
- styledComponents:
- nextConfig && nextConfig.experimental.styledComponents,
- paths,
- resolvedBaseUrl: resolvedBaseUrl,
+ nextConfig,
+ jsConfig,
+ resolvedBaseUrl,
isEsmProject,
},
],
diff --git a/packages/next/build/swc/jest-transformer.js b/packages/next/build/swc/jest-transformer.js
index 72dfc686f3..b58b9a102f 100644
--- a/packages/next/build/swc/jest-transformer.js
+++ b/packages/next/build/swc/jest-transformer.js
@@ -48,9 +48,9 @@ module.exports = {
isServer:
jestConfig.testEnvironment && jestConfig.testEnvironment === 'node',
filename,
- styledComponents: inputOptions.styledComponents,
- paths: inputOptions.paths,
- baseUrl: inputOptions.resolvedBaseUrl,
+ nextConfig: inputOptions.nextConfig,
+ jsConfig: inputOptions.jsConfig,
+ resolvedBaseUrl: inputOptions.resolvedBaseUrl,
esm:
isSupportEsm &&
isEsm(Boolean(inputOptions.isEsmProject), filename, jestConfig),
diff --git a/packages/next/build/swc/options.js b/packages/next/build/swc/options.js
index 36206ccb24..1475914d19 100644
--- a/packages/next/build/swc/options.js
+++ b/packages/next/build/swc/options.js
@@ -6,32 +6,36 @@ function getBaseSWCOptions({
development,
hasReactRefresh,
globalWindow,
- styledComponents,
- paths,
- baseUrl,
- importSource,
+ nextConfig,
+ resolvedBaseUrl,
+ jsConfig,
}) {
const isTSFile = filename.endsWith('.ts')
const isTypeScript = isTSFile || filename.endsWith('.tsx')
-
+ const paths = jsConfig?.compilerOptions?.paths
+ const enableDecorators = Boolean(
+ jsConfig?.compilerOptions?.experimentalDecorators
+ )
return {
jsc: {
- ...(baseUrl && paths
+ ...(resolvedBaseUrl && paths
? {
- baseUrl,
+ baseUrl: resolvedBaseUrl,
paths,
}
: {}),
parser: {
syntax: isTypeScript ? 'typescript' : 'ecmascript',
dynamicImport: true,
+ decorators: enableDecorators,
// Exclude regular TypeScript files from React transformation to prevent e.g. generic parameters and angle-bracket type assertion from being interpreted as JSX tags.
[isTypeScript ? 'tsx' : 'jsx']: isTSFile ? false : true,
},
transform: {
+ legacyDecorator: enableDecorators,
react: {
- importSource: importSource || 'react',
+ importSource: jsConfig?.compilerOptions?.jsxImportSource || 'react',
runtime: 'automatic',
pragma: 'React.createElement',
pragmaFrag: 'React.Fragment',
@@ -57,7 +61,7 @@ function getBaseSWCOptions({
},
},
},
- styledComponents: styledComponents
+ styledComponents: nextConfig?.experimental?.styledComponents
? {
displayName: Boolean(development),
}
@@ -69,18 +73,19 @@ export function getJestSWCOptions({
isServer,
filename,
esm,
- styledComponents,
- paths,
- baseUrl,
+ nextConfig,
+ jsConfig,
+ // This is not passed yet as "paths" resolving needs a test first
+ // resolvedBaseUrl,
}) {
let baseOptions = getBaseSWCOptions({
filename,
development: false,
hasReactRefresh: false,
globalWindow: !isServer,
- styledComponents,
- paths,
- baseUrl,
+ nextConfig,
+ jsConfig,
+ // resolvedBaseUrl,
})
const isNextDist = nextDistPath.test(filename)
@@ -108,16 +113,19 @@ export function getLoaderSWCOptions({
pagesDir,
isPageFile,
hasReactRefresh,
- styledComponents,
- importSource,
+ nextConfig,
+ jsConfig,
+ // This is not passed yet as "paths" resolving is handled by webpack currently.
+ // resolvedBaseUrl,
}) {
let baseOptions = getBaseSWCOptions({
filename,
development,
globalWindow: !isServer,
hasReactRefresh,
- styledComponents,
- importSource,
+ nextConfig,
+ jsConfig,
+ // resolvedBaseUrl,
})
const isNextDist = nextDistPath.test(filename)
diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts
index f07ae2c6b5..f25337ef17 100644
--- a/packages/next/build/webpack-config.ts
+++ b/packages/next/build/webpack-config.ts
@@ -441,8 +441,8 @@ export default async function getBaseWebpackConfig(
isServer: isMiddleware || isServer,
pagesDir,
hasReactRefresh: !isMiddleware && hasReactRefresh,
- styledComponents: config.experimental.styledComponents,
- importSource: jsConfig?.compilerOptions?.jsxImportSource,
+ nextConfig: config,
+ jsConfig,
},
}
: {
diff --git a/packages/next/build/webpack/loaders/next-swc-loader.js b/packages/next/build/webpack/loaders/next-swc-loader.js
index ef47963784..370994b8fc 100644
--- a/packages/next/build/webpack/loaders/next-swc-loader.js
+++ b/packages/next/build/webpack/loaders/next-swc-loader.js
@@ -35,13 +35,8 @@ async function loaderTransform(parentTrace, source, inputSourceMap) {
let loaderOptions = this.getOptions() || {}
- const {
- isServer,
- pagesDir,
- hasReactRefresh,
- styledComponents,
- importSource,
- } = loaderOptions
+ const { isServer, pagesDir, hasReactRefresh, nextConfig, jsConfig } =
+ loaderOptions
const isPageFile = filename.startsWith(pagesDir)
const swcOptions = getLoaderSWCOptions({
@@ -51,8 +46,8 @@ async function loaderTransform(parentTrace, source, inputSourceMap) {
isPageFile,
development: this.mode === 'development',
hasReactRefresh,
- styledComponents,
- importSource,
+ nextConfig,
+ jsConfig,
})
const programmaticOptions = {
diff --git a/test/development/basic/legacy-decorators.test.ts b/test/development/basic/legacy-decorators.test.ts
new file mode 100644
index 0000000000..77c6e9387c
--- /dev/null
+++ b/test/development/basic/legacy-decorators.test.ts
@@ -0,0 +1,43 @@
+import { join } from 'path'
+import webdriver from 'next-webdriver'
+import { createNext, FileRef } from 'e2e-utils'
+import { NextInstance } from 'test/lib/next-modes/base'
+import { check } from 'next-test-utils'
+
+describe('Legacy decorators SWC option', () => {
+ let next: NextInstance
+
+ beforeAll(async () => {
+ next = await createNext({
+ files: {
+ 'jsconfig.json': new FileRef(
+ join(__dirname, 'legacy-decorators/jsconfig.json')
+ ),
+ pages: new FileRef(join(__dirname, 'legacy-decorators/pages')),
+ },
+ dependencies: {
+ mobx: '6.3.7',
+ 'mobx-react': '7.2.1',
+ },
+ })
+ })
+ afterAll(() => next.destroy())
+
+ it('should compile with legacy decorators enabled', async () => {
+ let browser
+ try {
+ browser = await webdriver(next.appPort, '/')
+ const text = await browser.elementByCss('#count').text()
+ expect(text).toBe('Current number: 0')
+ await browser.elementByCss('#increase').click()
+ await check(
+ () => browser.elementByCss('#count').text(),
+ /Current number: 1/
+ )
+ } finally {
+ if (browser) {
+ await browser.close()
+ }
+ }
+ })
+})
diff --git a/test/development/basic/legacy-decorators/jsconfig.json b/test/development/basic/legacy-decorators/jsconfig.json
new file mode 100644
index 0000000000..504cd646e1
--- /dev/null
+++ b/test/development/basic/legacy-decorators/jsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "experimentalDecorators": true
+ }
+}
diff --git a/test/development/basic/legacy-decorators/pages/index.js b/test/development/basic/legacy-decorators/pages/index.js
new file mode 100644
index 0000000000..bffa26c674
--- /dev/null
+++ b/test/development/basic/legacy-decorators/pages/index.js
@@ -0,0 +1,35 @@
+import React from 'react'
+import { makeAutoObservable } from 'mobx'
+import { observer } from 'mobx-react'
+
+class Counter {
+ count = 0
+
+ constructor() {
+ makeAutoObservable(this)
+ }
+
+ increase() {
+ this.count += 1
+ }
+}
+
+const myCounter = new Counter()
+
+@observer
+class CounterView extends React.Component {
+ render() {
+ return (
+ <>
+ Current number: {this.props.counter.count}
+
+ >
+ )
+ }
+}
+
+export default function Home() {
+ return
+}