From 005e9736ea6c89df7185e30eeb07e1fca8f421a5 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Sun, 21 Jun 2020 18:00:58 -0700 Subject: [PATCH] support "useDefineForClassFields" in "tsconfig.json" --- CHANGELOG.md | 2 +- README.md | 2 +- internal/bundler/bundler.go | 17 +++++-- internal/bundler/bundler_lower_test.go | 64 ++++++++++++++++++++++++++ internal/resolver/resolver.go | 21 +++++++++ 5 files changed, 99 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 390233d7..c755db80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/README.md b/README.md index 282eb79a..b6d2bd9c 100644 --- a/README.md +++ b/README.md @@ -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** diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 90059b7e..db46823c 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -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) diff --git a/internal/bundler/bundler_lower_test.go b/internal/bundler/bundler_lower_test.go index 5a36bd31..87c65b90 100644 --- a/internal/bundler/bundler_lower_test.go +++ b/internal/bundler/bundler_lower_test.go @@ -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); +`, + }, + }) +} diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index c564ea69..b344ab1a 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -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 {