Skip to content

Commit

Permalink
feat:Add support for useDefineForClassFields typescript option (#36335)
Browse files Browse the repository at this point in the history
* feat:Add support for useDefineForClassFields typescript option

* test:add test for useDefineForClassFields option

* test: fix lint error
  • Loading branch information
Chastrlove committed Apr 21, 2022
1 parent 084a0ca commit 9fe2f26
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/next/build/swc/options.js
Expand Up @@ -40,6 +40,9 @@ function getBaseSWCOptions({
const emitDecoratorMetadata = Boolean(
jsConfig?.compilerOptions?.emitDecoratorMetadata
)
const useDefineForClassFields = Boolean(
jsConfig?.compilerOptions?.useDefineForClassFields
)
return {
jsc: {
...(resolvedBaseUrl && paths
Expand All @@ -63,6 +66,7 @@ function getBaseSWCOptions({
: {}),
legacyDecorator: enableDecorators,
decoratorMetadata: emitDecoratorMetadata,
useDefineForClassFields: useDefineForClassFields,
react: {
importSource:
jsConfig?.compilerOptions?.jsxImportSource ??
Expand Down
90 changes: 90 additions & 0 deletions test/development/basic/define-class-fields.test.ts
@@ -0,0 +1,90 @@
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('useDefineForClassFields SWC option', () => {
let next: NextInstance

beforeAll(async () => {
next = await createNext({
files: {
'tsconfig.json': new FileRef(
join(__dirname, 'define-class-fields/tsconfig.json')
),
pages: new FileRef(join(__dirname, 'define-class-fields/pages')),
},
dependencies: {
mobx: '6.3.7',
typescript: '*',
'@types/react': '*',
'@types/node': '*',
'mobx-react': '7.2.1',
},
})
})

afterAll(() => next.destroy())

it('tsx should compile with useDefineForClassFields enabled', async () => {
let browser
try {
browser = await webdriver(next.appPort, '/')
await browser.elementByCss('#action').click()
await check(
() => browser.elementByCss('#name').text(),
/this is my name: next/
)
} finally {
if (browser) {
await browser.close()
}
}
})

it("Initializes resident to undefined after the call to 'super()' when with useDefineForClassFields enabled", async () => {
let browser
try {
browser = await webdriver(next.appPort, '/animal')
expect(await browser.elementByCss('#dog').text()).toBe('')
expect(await browser.elementByCss('#dogDecl').text()).toBe('dog')
} finally {
if (browser) {
await browser.close()
}
}
})

async function matchLogs$(browser) {
let data_foundLog = false
let name_foundLog = false

const browserLogs = await browser.log('browser')

browserLogs.forEach((log) => {
if (log.message.includes('data changed')) {
data_foundLog = true
}
if (log.message.includes('name changed')) {
name_foundLog = true
}
})
return [data_foundLog, name_foundLog]
}

it('set accessors from base classes won’t get triggered with useDefineForClassFields enabled', async () => {
let browser
try {
browser = await webdriver(next.appPort, '/derived')
await matchLogs$(browser).then(([data_foundLog, name_foundLog]) => {
expect(data_foundLog).toBe(true)
expect(name_foundLog).toBe(false)
})
} finally {
if (browser) {
await browser.close()
}
}
})
})
53 changes: 53 additions & 0 deletions test/development/basic/define-class-fields/pages/animal.tsx
@@ -0,0 +1,53 @@
import React from 'react'

interface Animal {
animalStuff: any
}
interface Dog extends Animal {
dogStuff: any
}

class AnimalHouse {
resident: Animal
constructor(animal: Animal) {
this.resident = animal
}
}
class DogHouse extends AnimalHouse {
// Initializes 'resident' to 'undefined'
// after the call to 'super()' when
// using 'useDefineForClassFields'!
// @ts-ignore
resident: Dog
// useless constructor only for type checker
/* eslint-disable @typescript-eslint/no-useless-constructor */
constructor(dog: Dog) {
super(dog)
}
}

class DogHouseWithDeclare extends AnimalHouse {
declare resident: Dog
// useless constructor only for type checker
/* eslint-disable @typescript-eslint/no-useless-constructor */
constructor(dog: Dog) {
super(dog)
}
}

export default function AnimalView() {
const dog = new DogHouse({
animalStuff: 'animal',
dogStuff: 'dog',
})
const dogDeclare = new DogHouseWithDeclare({
animalStuff: 'animal',
dogStuff: 'dog',
})
return (
<>
<div id={'dog'}>{dog.resident}</div>
<div id={'dogDecl'}>{dogDeclare.resident?.dogStuff}</div>
</>
)
}
33 changes: 33 additions & 0 deletions test/development/basic/define-class-fields/pages/derived.tsx
@@ -0,0 +1,33 @@
import React from 'react'

class Base {
set data(value: number) {
console.log('data changed to ' + value)
}

set name(value: number) {
console.log('name changed to ' + value)
}
}

class Derived extends Base {
// No longer triggers a 'console.log'
// when using 'useDefineForClassFields'.
// @ts-ignore
name = 10
constructor() {
super()
//triggers a 'console.log'
this.data = 10
}
}

export default function DerivedView() {
const obj = new Derived()
return (
<>
<div id={'data'}>{obj.data}</div>
<div id={'name'}>{obj.name}</div>
</>
)
}
39 changes: 39 additions & 0 deletions test/development/basic/define-class-fields/pages/index.tsx
@@ -0,0 +1,39 @@
// @ts-ignore
import { makeObservable, observable } from 'mobx'
// @ts-ignore
import { observer } from 'mobx-react'
import React from 'react'

class Person {
//Declarations are initialized with Object.defineProperty.

// @ts-ignore
name: string

constructor() {
//without useDefineForClassFields it will be error
makeObservable(this, {
name: observable,
})
}
}

const person = new Person()

const PersonView = observer(() => {
const changeName = () => {
person.name = 'next'
}
return (
<>
<div id="name">this is my name: {person.name}</div>
<button id="action" onClick={changeName}>
Change Name
</button>
</>
)
})

export default function Home() {
return <PersonView />
}
19 changes: 19 additions & 0 deletions test/development/basic/define-class-fields/tsconfig.json
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"useDefineForClassFields": true,
"esModuleInterop": true,
"module": "esnext",
"jsx": "preserve",
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": false,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true
}
}

0 comments on commit 9fe2f26

Please sign in to comment.