Skip to content

Commit 0022f38

Browse files
authored
Fix swc issues (#2062)
* Bump minimum version of swc to 1.3.84 * Fix type error caused by swc's mismatched types between @swc/wasm and @swc/core * Update swc options to match latest swc version, fix bug where swc was emitting import assertions as `with` which node does not understand * Pass correct useDefineForClassFields, derived from tsconfig * Fix bug where ts-node errored when swc or custom transpiler returned undefined for sourcemap * pass jsxImportSource to swc * Bump min swc version to 1.3.85 Fix issue introduced by swc 1.3.85 with module options that are now forbidden instead of ignored for certain module types remove failing useDefineForClassFields case because I forgot that implicit compiler options were in effect, so default target is actually much higher than I expected
1 parent 9a2a275 commit 0022f38

File tree

11 files changed

+325
-75
lines changed

11 files changed

+325
-75
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@
116116
"@cspotcode/ava-lib": "https://github.com/cspotcode/ava-lib#805aab17b2b89c388596b6dc2b4eece403c5fb87",
117117
"@cspotcode/expect-stream": "https://github.com/cspotcode/node-expect-stream#4e425ff1eef240003af8716291e80fbaf3e3ae8f",
118118
"@microsoft/api-extractor": "^7.19.4",
119-
"@swc/core": "1.3.32",
120-
"@swc/wasm": "1.3.32",
119+
"@swc/core": "1.3.85",
120+
"@swc/wasm": "1.3.85",
121121
"@types/diff": "^4.0.2",
122122
"@types/lodash": "^4.14.151",
123123
"@types/node": "13.13.5",
@@ -145,8 +145,8 @@
145145
"workaround-broken-npm-prepack-behavior": "https://github.com/cspotcode/workaround-broken-npm-prepack-behavior#1a7adbbb8a527784daf97edad6ba42d6e96611f6"
146146
},
147147
"peerDependencies": {
148-
"@swc/core": ">=1.2.50",
149-
"@swc/wasm": ">=1.2.50",
148+
"@swc/core": ">=1.3.85",
149+
"@swc/wasm": ">=1.3.85",
150150
"@types/node": "*",
151151
"typescript": ">=4.4"
152152
},

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1158,7 +1158,7 @@ export function createFromPreloadedConfig(foundConfigResult: ReturnType<typeof f
11581158
const diagnosticList = filterDiagnostics(result.diagnostics || [], diagnosticFilters);
11591159
if (diagnosticList.length) reportTSError(diagnosticList);
11601160

1161-
return [result.outputText, result.sourceMapText as string, false];
1161+
return [result.outputText, result.sourceMapText ?? '{}', false];
11621162
};
11631163
}
11641164

src/test/transpilers.spec.ts

Lines changed: 181 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@
33
// Should consolidate them here.
44

55
import { context } from './testlib';
6-
import { ctxTsNode, testsDirRequire, tsSupportsImportAssertions, tsSupportsReact17JsxFactories } from './helpers';
6+
import {
7+
CMD_TS_NODE_WITHOUT_PROJECT_FLAG,
8+
createExec,
9+
ctxTsNode,
10+
testsDirRequire,
11+
TEST_DIR,
12+
tsSupportsImportAssertions,
13+
tsSupportsReact17JsxFactories,
14+
} from './helpers';
715
import { createSwcOptions } from '../transpilers/swc';
816
import * as expect from 'expect';
917
import { outdent } from 'outdent';
18+
import { join } from 'path';
1019

1120
const test = context(ctxTsNode);
1221

@@ -78,10 +87,7 @@ test.suite('swc', (test) => {
7887
.create({
7988
swc: true,
8089
skipProject: true,
81-
compilerOptions: {
82-
module: 'esnext',
83-
...compilerOptions,
84-
},
90+
compilerOptions,
8591
})
8692
.compile(input, 'input.tsx');
8793
expect(code.replace(/\/\/# sourceMappingURL.*/, '').trim()).toBe(expectedOutput);
@@ -93,12 +99,17 @@ test.suite('swc', (test) => {
9399
const div = <div></div>;
94100
`;
95101

96-
test(compileMacro, { jsx: 'react' }, input, `const div = /*#__PURE__*/ React.createElement("div", null);`);
102+
test(
103+
compileMacro,
104+
{ module: 'esnext', jsx: 'react' },
105+
input,
106+
`const div = /*#__PURE__*/ React.createElement("div", null);`
107+
);
97108
test.suite('react 17 jsx factories', (test) => {
98109
test.if(tsSupportsReact17JsxFactories);
99110
test(
100111
compileMacro,
101-
{ jsx: 'react-jsx' },
112+
{ module: 'esnext', jsx: 'react-jsx' },
102113
input,
103114
outdent`
104115
import { jsx as _jsx } from "react/jsx-runtime";
@@ -107,7 +118,7 @@ test.suite('swc', (test) => {
107118
);
108119
test(
109120
compileMacro,
110-
{ jsx: 'react-jsxdev' },
121+
{ module: 'esnext', jsx: 'react-jsxdev' },
111122
input,
112123
outdent`
113124
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
@@ -139,4 +150,166 @@ test.suite('swc', (test) => {
139150
`
140151
);
141152
});
153+
154+
test.suite('useDefineForClassFields', (test) => {
155+
const input = outdent`
156+
class Foo {
157+
bar = 1;
158+
}
159+
`;
160+
const outputNative = outdent`
161+
let Foo = class Foo {
162+
bar = 1;
163+
};
164+
`;
165+
const outputCtorAssignment = outdent`
166+
let Foo = class Foo {
167+
constructor(){
168+
this.bar = 1;
169+
}
170+
};
171+
`;
172+
const outputDefine = outdent`
173+
function _define_property(obj, key, value) {
174+
if (key in obj) {
175+
Object.defineProperty(obj, key, {
176+
value: value,
177+
enumerable: true,
178+
configurable: true,
179+
writable: true
180+
});
181+
} else {
182+
obj[key] = value;
183+
}
184+
return obj;
185+
}
186+
let Foo = class Foo {
187+
constructor(){
188+
_define_property(this, "bar", 1);
189+
}
190+
};
191+
`;
192+
test(
193+
'useDefineForClassFields unset, should default to true and emit native property assignment b/c `next` target',
194+
compileMacro,
195+
{ module: 'esnext', target: 'ESNext' },
196+
input,
197+
outputNative
198+
);
199+
test(
200+
'useDefineForClassFields unset, should default to true and emit native property assignment b/c new target',
201+
compileMacro,
202+
{ module: 'esnext', target: 'ES2022' },
203+
input,
204+
outputNative
205+
);
206+
test(
207+
'useDefineForClassFields unset, should default to false b/c old target',
208+
compileMacro,
209+
{ module: 'esnext', target: 'ES2021' },
210+
input,
211+
outputCtorAssignment
212+
);
213+
test(
214+
'useDefineForClassFields=true, should emit native property assignment b/c new target',
215+
compileMacro,
216+
{
217+
module: 'esnext',
218+
useDefineForClassFields: true,
219+
target: 'ES2022',
220+
},
221+
input,
222+
outputNative
223+
);
224+
test(
225+
'useDefineForClassFields=true, should emit define b/c old target',
226+
compileMacro,
227+
{
228+
module: 'esnext',
229+
useDefineForClassFields: true,
230+
target: 'ES2021',
231+
},
232+
input,
233+
outputDefine
234+
);
235+
test(
236+
'useDefineForClassFields=false, new target, should still emit legacy property assignment in ctor',
237+
compileMacro,
238+
{
239+
module: 'esnext',
240+
useDefineForClassFields: false,
241+
target: 'ES2022',
242+
},
243+
input,
244+
outputCtorAssignment
245+
);
246+
test(
247+
'useDefineForClassFields=false, old target, should emit legacy property assignment in ctor',
248+
compileMacro,
249+
{
250+
module: 'esnext',
251+
useDefineForClassFields: false,
252+
},
253+
input,
254+
outputCtorAssignment
255+
);
256+
});
257+
258+
test.suite('jsx and jsxImportSource', (test) => {
259+
test(
260+
'jsx=react-jsx',
261+
compileMacro,
262+
{
263+
module: 'esnext',
264+
jsx: 'react-jsx',
265+
},
266+
outdent`
267+
<div></div>
268+
`,
269+
outdent`
270+
/*#__PURE__*/ import { jsx as _jsx } from "react/jsx-runtime";
271+
_jsx("div", {});
272+
`
273+
);
274+
test(
275+
'jsx=react-jsx w/custom jsxImportSource',
276+
compileMacro,
277+
{
278+
module: 'esnext',
279+
jsx: 'react-jsx',
280+
jsxImportSource: 'foo',
281+
},
282+
outdent`
283+
<div></div>
284+
`,
285+
outdent`
286+
/*#__PURE__*/ import { jsx as _jsx } from "foo/jsx-runtime";
287+
_jsx("div", {});
288+
`
289+
);
290+
});
291+
292+
test.suite(
293+
'#1996 regression: ts-node gracefully allows swc to not return a sourcemap for type-only files',
294+
(test) => {
295+
// https://github.com/TypeStrong/ts-node/issues/1996
296+
// @swc/core 1.3.51 returned `undefined` instead of sourcemap if the file was empty or only exported types.
297+
// Newer swc versions do not do this. But our typedefs technically allow it.
298+
const exec = createExec({
299+
cwd: join(TEST_DIR, '1996'),
300+
});
301+
test('import empty file w/swc', async (t) => {
302+
const r = await exec(`${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} ./index.ts`);
303+
expect(r.err).toBe(null);
304+
expect(r.stdout).toMatch(/#1996 regression test./);
305+
});
306+
test('use custom transpiler which never returns a sourcemap', async (t) => {
307+
const r = await exec(
308+
`${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} --project tsconfig.custom-transpiler.json ./empty.ts`
309+
);
310+
expect(r.err).toBe(null);
311+
expect(r.stdout).toMatch(/#1996 regression test with custom transpiler./);
312+
});
313+
}
314+
);
142315
});

src/transpilers/swc.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import type * as swcWasm from '@swc/wasm';
33
import type * as swcTypes from '@swc/core';
44
import type { CreateTranspilerOptions, Transpiler } from './types';
55
import type { NodeModuleEmitKind } from '..';
6+
import { getUseDefineForClassFields } from '../ts-internals';
67

7-
type SwcInstance = typeof swcWasm;
8+
type SwcInstance = typeof swcTypes;
89
export interface SwcTranspilerOptions extends CreateTranspilerOptions {
910
/**
1011
* swc compiler to use for compilation
@@ -28,7 +29,7 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {
2829
let swcDepName: string = 'swc';
2930
if (typeof swc === 'string') {
3031
swcDepName = swc;
31-
swcInstance = require(transpilerConfigLocalResolveHelper(swc, true)) as typeof swcWasm;
32+
swcInstance = require(transpilerConfigLocalResolveHelper(swc, true)) as SwcInstance;
3233
} else if (swc == null) {
3334
let swcResolved;
3435
try {
@@ -44,9 +45,9 @@ export function create(createOptions: SwcTranspilerOptions): Transpiler {
4445
);
4546
}
4647
}
47-
swcInstance = require(swcResolved) as typeof swcWasm;
48+
swcInstance = require(swcResolved) as SwcInstance;
4849
} else {
49-
swcInstance = swc;
50+
swcInstance = swc as any as SwcInstance;
5051
}
5152

5253
// Prepare SWC options derived from typescript compiler options
@@ -142,6 +143,7 @@ export function createSwcOptions(
142143
strict,
143144
alwaysStrict,
144145
noImplicitUseStrict,
146+
jsxImportSource,
145147
} = compilerOptions;
146148

147149
let swcTarget = targetMapping.get(target!) ?? 'es3';
@@ -194,6 +196,8 @@ export function createSwcOptions(
194196
jsx === JsxEmit.ReactJSX || jsx === JsxEmit.ReactJSXDev ? 'automatic' : undefined;
195197
const jsxDevelopment: swcTypes.ReactConfig['development'] = jsx === JsxEmit.ReactJSXDev ? true : undefined;
196198

199+
const useDefineForClassFields = getUseDefineForClassFields(compilerOptions);
200+
197201
const nonTsxOptions = createVariant(false);
198202
const tsxOptions = createVariant(true);
199203
return { nonTsxOptions, tsxOptions };
@@ -204,11 +208,15 @@ export function createSwcOptions(
204208
// isModule: true,
205209
module: moduleType
206210
? {
207-
noInterop: !esModuleInterop,
208211
type: moduleType,
209-
strictMode,
210-
// For NodeNext and Node12, emit as CJS but do not transform dynamic imports
211-
ignoreDynamic: nodeModuleEmitKind === 'nodecjs',
212+
...(moduleType === 'amd' || moduleType === 'commonjs' || moduleType === 'umd'
213+
? {
214+
noInterop: !esModuleInterop,
215+
strictMode,
216+
// For NodeNext and Node12, emit as CJS but do not transform dynamic imports
217+
ignoreDynamic: nodeModuleEmitKind === 'nodecjs',
218+
}
219+
: {}),
212220
}
213221
: undefined,
214222
swcrc: false,
@@ -232,12 +240,15 @@ export function createSwcOptions(
232240
pragma: jsxFactory!,
233241
pragmaFrag: jsxFragmentFactory!,
234242
runtime: jsxRuntime,
243+
importSource: jsxImportSource,
235244
},
245+
useDefineForClassFields,
236246
},
237247
keepClassNames,
238248
experimental: {
239-
keepImportAssertions: true,
240-
},
249+
keepImportAttributes: true,
250+
emitAssertForImportAttributes: true,
251+
} as swcTypes.JscConfig['experimental'],
241252
},
242253
};
243254

src/ts-internals.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,28 @@ function replaceWildcardCharacter(match: string, singleAsteriskRegexFragment: st
329329
function isImplicitGlob(lastPathComponent: string): boolean {
330330
return !/[.*?]/.test(lastPathComponent);
331331
}
332+
333+
const ts_ScriptTarget_ES5 = 1;
334+
const ts_ScriptTarget_ES2022 = 9;
335+
const ts_ScriptTarget_ESNext = 99;
336+
const ts_ModuleKind_Node16 = 100;
337+
const ts_ModuleKind_NodeNext = 199;
338+
// https://github.com/microsoft/TypeScript/blob/fc418a2e611c88cf9afa0115ff73490b2397d311/src/compiler/utilities.ts#L8761
339+
export function getUseDefineForClassFields(compilerOptions: _ts.CompilerOptions): boolean {
340+
return compilerOptions.useDefineForClassFields === undefined
341+
? getEmitScriptTarget(compilerOptions) >= ts_ScriptTarget_ES2022
342+
: compilerOptions.useDefineForClassFields;
343+
}
344+
345+
// https://github.com/microsoft/TypeScript/blob/fc418a2e611c88cf9afa0115ff73490b2397d311/src/compiler/utilities.ts#L8556
346+
export function getEmitScriptTarget(compilerOptions: {
347+
module?: _ts.CompilerOptions['module'];
348+
target?: _ts.CompilerOptions['target'];
349+
}): _ts.ScriptTarget {
350+
return (
351+
compilerOptions.target ??
352+
((compilerOptions.module === ts_ModuleKind_Node16 && ts_ScriptTarget_ES2022) ||
353+
(compilerOptions.module === ts_ModuleKind_NodeNext && ts_ScriptTarget_ESNext) ||
354+
ts_ScriptTarget_ES5)
355+
);
356+
}

tests/1996/empty.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};

tests/1996/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import * as empty from './empty';
2+
empty;
3+
console.log('#1996 regression test.');

tests/1996/transpiler.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// A custom transpiler that returns `undefined` instead of a sourcemap, which is
2+
// allowed according to our typedefs.
3+
4+
exports.create = function () {
5+
return {
6+
transpile(input, options) {
7+
return {
8+
outputText: 'console.log("#1996 regression test with custom transpiler.")',
9+
sourceMapText: undefined,
10+
};
11+
},
12+
};
13+
};

0 commit comments

Comments
 (0)