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 +}