support "useDefineForClassFields" in "tsconfig.json"

This commit is contained in:
Evan Wallace
2020-06-21 18:00:58 -07:00
parent 055d772977
commit 005e9736ea
5 changed files with 99 additions and 7 deletions

View File

@@ -8,7 +8,7 @@
* Add the `--strict:class-fields` option
This affects the transform for instance and static class fields. In loose mode (the default), class field initialization is transformed to a normal assignment. This is what the TypeScript compiler does by default. However, it doesn't follow the JavaScript specification exactly (e.g. it may call setter methods). Enable `--strict:class-fields` if you need accurate class field initialization.
This affects the transform for instance and static class fields. In loose mode (the default), class field initialization is transformed to a normal assignment. This is what the TypeScript compiler does by default. However, it doesn't follow the JavaScript specification exactly (e.g. it may call setter methods). Either enable `--strict:class-fields` or add `useDefineForClassFields` to your `tsconfig.json` file if you need accurate class field initialization.
Note that you can also just use `--strict` to enable strictness for all transforms instead of using `--strict:...` for each transform.

View File

@@ -155,7 +155,7 @@ These syntax features are conditionally transformed for older browsers depending
}
```
This increases code size and decreases performance, but follows the JavaScript specification more accurately. If you need this accuracy, you should enable the `--strict:class-fields` option.
This increases code size and decreases performance, but follows the JavaScript specification more accurately. If you need this accuracy, you should either enable the `--strict:class-fields` option or add the `useDefineForClassFields` flag to your `tsconfig.json` file.
* **Private member performance**

View File

@@ -47,9 +47,10 @@ type Bundle struct {
}
type parseFlags struct {
isEntryPoint bool
isDisabled bool
ignoreIfUnused bool
isEntryPoint bool
isDisabled bool
ignoreIfUnused bool
strictClassFields bool
}
type parseArgs struct {
@@ -101,6 +102,11 @@ func parseFile(args parseArgs) {
}
}
// Allow the strict class field transform flag to be overridden
if args.flags.strictClassFields {
args.parseOptions.Strict.ClassFields = true
}
// Get the file extension
extension := args.fs.Ext(args.absPath)
@@ -360,8 +366,9 @@ func ScanBundle(
switch resolveResult.Status {
case resolver.ResolveEnabled, resolver.ResolveDisabled:
flags := parseFlags{
isDisabled: resolveResult.Status == resolver.ResolveDisabled,
ignoreIfUnused: resolveResult.IgnoreIfUnused,
isDisabled: resolveResult.Status == resolver.ResolveDisabled,
ignoreIfUnused: resolveResult.IgnoreIfUnused,
strictClassFields: resolveResult.StrictClassFields,
}
prettyPath := res.PrettyPath(resolveResult.AbsolutePath)
sourceIndex := maybeParseFile(resolveResult.AbsolutePath, prettyPath, &source, pathRange, flags)

View File

@@ -2117,3 +2117,67 @@ Foo.s_foo = 123;
},
})
}
func TestLowerClassFieldStrictTsconfigJson2020(t *testing.T) {
expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import loose from './loose'
import strict from './strict'
console.log(loose, strict)
`,
"/loose/index.js": `
export default class {
foo
}
`,
"/loose/tsconfig.json": `
{
"compilerOptions": {
"useDefineForClassFields": false
}
}
`,
"/strict/index.js": `
export default class {
foo
}
`,
"/strict/tsconfig.json": `
{
"compilerOptions": {
"useDefineForClassFields": true
}
}
`,
},
entryPaths: []string{"/entry.js"},
parseOptions: parser.ParseOptions{
IsBundling: true,
Target: parser.ES2020,
},
bundleOptions: BundleOptions{
IsBundling: true,
AbsOutputFile: "/out.js",
},
expected: map[string]string{
"/out.js": `// /loose/index.js
class loose_default {
constructor() {
this.foo = void 0;
}
}
// /strict/index.js
class strict_default {
constructor() {
__publicField(this, "foo", void 0);
}
}
// /entry.js
console.log(loose_default, strict_default);
`,
},
})
}

View File

@@ -28,6 +28,9 @@ type ResolveResult struct {
// If true, any ES6 imports to this file can be considered to have no side
// effects. This means they should be removed if unused.
IgnoreIfUnused bool
// If true, the class field transform should use Object.defineProperty().
StrictClassFields bool
}
type Resolver interface {
@@ -95,6 +98,15 @@ func (r *resolver) Resolve(sourcePath string, importPath string) (result Resolve
}
}
// Copy the "useDefineForClassFields" value from the nearest enclosing
// "tsconfig.json" file if present
for info := dirInfo; info != nil; info = info.parent {
if info.tsConfigJson != nil {
result.StrictClassFields = info.tsConfigJson.useDefineForClassFields
break
}
}
// Is this entry itself a symlink?
if entry := dirInfo.entries[base]; entry.Symlink != "" {
result.AbsolutePath = entry.Symlink
@@ -277,6 +289,8 @@ type tsConfigJson struct {
// module-style path names and the fallback paths are relative to the
// "baseUrl" value in the "tsconfig.json" file.
paths map[string][]string
useDefineForClassFields bool
}
type dirInfo struct {
@@ -349,6 +363,13 @@ func (r *resolver) parseJsTsConfig(file string, path string, info *dirInfo) {
}
}
// Parse the "useDefineForClassFields" field
if useDefineForClassFieldsJson, _, ok := getProperty(compilerOptionsJson, "useDefineForClassFields"); ok {
if useDefineForClassFields, ok := getBool(useDefineForClassFieldsJson); ok {
info.tsConfigJson.useDefineForClassFields = useDefineForClassFields
}
}
// Parse the "paths" field
if pathsJson, pathsKeyLoc, ok := getProperty(compilerOptionsJson, "paths"); ok {
if info.tsConfigJson.absPathBaseUrl == nil {