Add support for legacy decorators through tsconfig/jsconfig (#31376)

This commit is contained in:
Tim Neutkens 2021-11-13 18:26:13 +01:00 committed by GitHub
parent ae1cee59d6
commit e0531e30f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 128 additions and 42 deletions

View file

@ -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/**

View file

@ -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,
},
],

View file

@ -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),

View file

@ -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)

View file

@ -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,
},
}
: {

View file

@ -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 = {

View 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()
}
}
})
})

View file

@ -0,0 +1,5 @@
{
"compilerOptions": {
"experimentalDecorators": true
}
}

View 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} />
}