Add support for legacy decorators through tsconfig/jsconfig (#31376)
This commit is contained in:
parent
ae1cee59d6
commit
e0531e30f4
9 changed files with 128 additions and 42 deletions
|
@ -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/**
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
: {
|
||||
|
|
|
@ -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 = {
|
||||
|
|
43
test/development/basic/legacy-decorators.test.ts
Normal file
43
test/development/basic/legacy-decorators.test.ts
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
5
test/development/basic/legacy-decorators/jsconfig.json
Normal file
5
test/development/basic/legacy-decorators/jsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true
|
||||
}
|
||||
}
|
35
test/development/basic/legacy-decorators/pages/index.js
Normal file
35
test/development/basic/legacy-decorators/pages/index.js
Normal file
|
@ -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 (
|
||||
<>
|
||||
<span id="count">Current number: {this.props.counter.count}</span>
|
||||
<button id="increase" onClick={() => this.props.counter.increase()}>
|
||||
Increase
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return <CounterView counter={myCounter} />
|
||||
}
|
Loading…
Reference in a new issue