Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --swc shorthand for the built-in swc transpiler; graduate the swc transpiler out of experimental status #1536

Merged
merged 6 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"./esm.mjs": "./esm.mjs",
"./esm/transpile-only": "./esm/transpile-only.mjs",
"./esm/transpile-only.mjs": "./esm/transpile-only.mjs",
"./transpilers/swc": "./transpilers/swc.js",
"./transpilers/swc-experimental": "./transpilers/swc-experimental.js",
"./node10/tsconfig.json": "./node10/tsconfig.json",
"./node12/tsconfig.json": "./node12/tsconfig.json",
Expand Down
4 changes: 4 additions & 0 deletions src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function main(
'--ignore': [String],
'--transpile-only': Boolean,
'--transpiler': String,
'--swc': Boolean,
'--type-check': Boolean,
'--compiler-host': Boolean,
'--pretty': Boolean,
Expand Down Expand Up @@ -116,6 +117,7 @@ export function main(
'--transpile-only': transpileOnly,
'--type-check': typeCheck,
'--transpiler': transpiler,
'--swc': swc,
'--compiler-host': compilerHost,
'--pretty': pretty,
'--skip-project': skipProject,
Expand Down Expand Up @@ -145,6 +147,7 @@ export function main(
--show-config Print resolved configuration and exit

-T, --transpile-only Use TypeScript's faster \`transpileModule\` or a third-party transpiler
--swc Use the swc transpiler
-H, --compiler-host Use TypeScript's compiler host API
-I, --ignore [pattern] Override the path patterns to skip compilation
-P, --project [path] Path to TypeScript JSON project file
Expand Down Expand Up @@ -256,6 +259,7 @@ export function main(
experimentalReplAwait: noExperimentalReplAwait ? false : undefined,
typeCheck,
transpiler,
swc,
compilerHost,
ignore,
preferTsExts,
Expand Down
2 changes: 2 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ function filterRecognizedTsConfigTsNodeOptions(
scopeDir,
moduleTypes,
experimentalReplAwait,
swc,
...unrecognized
} = jsonObject as TsConfigOptions;
const filteredTsConfigOptions = {
Expand All @@ -298,6 +299,7 @@ function filterRecognizedTsConfigTsNodeOptions(
scope,
scopeDir,
moduleTypes,
swc,
};
// Use the typechecker to make sure this implementation has the correct set of properties
const catchExtraneousProps: keyof TsConfigOptions = (null as any) as keyof typeof filteredTsConfigOptions;
Expand Down
38 changes: 32 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ export interface CreateOptions {
* Specify a custom transpiler for use with transpileOnly
*/
transpiler?: string | [string, object];
/**
* Transpile with swc instead of the TypeScript compiler, and skip typechecking.
*
* Equivalent to setting both `transpileOnly: true` and `transpiler: 'ts-node/transpilers/swc'`
*/
swc?: boolean;
/**
* Paths which should not be compiled.
*
Expand Down Expand Up @@ -608,11 +614,33 @@ export function create(rawOptions: CreateOptions = {}): Service {
({ compiler, ts } = loadCompiler(options.compiler, configFilePath));
}

// swc implies two other options
// typeCheck option was implemented specifically to allow overriding tsconfig transpileOnly from the command-line
// So we should allow using typeCheck to override swc
if (options.swc && !options.typeCheck) {
if (options.transpileOnly === false) {
throw new Error(
"Cannot enable 'swc' option with 'transpileOnly: false'. 'swc' implies 'transpileOnly'."
);
}
if (options.transpiler) {
throw new Error(
"Cannot specify both 'swc' and 'transpiler' options. 'swc' uses the built-in swc transpiler."
);
}
}

const readFile = options.readFile || ts.sys.readFile;
const fileExists = options.fileExists || ts.sys.fileExists;
// typeCheck can override transpileOnly, useful for CLI flag to override config file
const transpileOnly =
options.transpileOnly === true && options.typeCheck !== true;
(options.transpileOnly === true || options.swc === true) &&
options.typeCheck !== true;
const transpiler = options.transpiler
? options.transpiler
: options.swc
? require.resolve('./transpilers/swc.js')
: undefined;
const transformers = options.transformers || undefined;
const diagnosticFilters: Array<DiagnosticFilter> = [
{
Expand Down Expand Up @@ -668,17 +696,15 @@ export function create(rawOptions: CreateOptions = {}): Service {
);
}
let customTranspiler: Transpiler | undefined = undefined;
if (options.transpiler) {
if (transpiler) {
if (!transpileOnly)
throw new Error(
'Custom transpiler can only be used when transpileOnly is enabled.'
);
const transpilerName =
typeof options.transpiler === 'string'
? options.transpiler
: options.transpiler[0];
typeof transpiler === 'string' ? transpiler : transpiler[0];
const transpilerOptions =
typeof options.transpiler === 'string' ? {} : options.transpiler[1] ?? {};
typeof transpiler === 'string' ? {} : transpiler[1] ?? {};
// TODO mimic fixed resolution logic from loadCompiler main
// TODO refactor into a more generic "resolve dep relative to project" helper
const transpilerPath = require.resolve(transpilerName, {
Expand Down
41 changes: 26 additions & 15 deletions src/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ test.suite('ts-node', (test) => {
testsDirRequire.resolve('ts-node/esm/transpile-only');
testsDirRequire.resolve('ts-node/esm/transpile-only.mjs');

testsDirRequire.resolve('ts-node/transpilers/swc');
testsDirRequire.resolve('ts-node/transpilers/swc-experimental');

testsDirRequire.resolve('ts-node/node10/tsconfig.json');
Expand Down Expand Up @@ -304,21 +305,31 @@ test.suite('ts-node', (test) => {
expect(err.message).toMatch('error TS1003: Identifier expected');
});

test('should support third-party transpilers via --transpiler', async () => {
const { err, stdout } = await exec(
`${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} --transpiler ts-node/transpilers/swc-experimental transpile-only-swc`
);
expect(err).toBe(null);
expect(stdout).toMatch('Hello World!');
});

test('should support third-party transpilers via tsconfig', async () => {
const { err, stdout } = await exec(
`${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} transpile-only-swc-via-tsconfig`
);
expect(err).toBe(null);
expect(stdout).toMatch('Hello World!');
});
for (const flavor of [
'--transpiler ts-node/transpilers/swc transpile-only-swc',
'--transpiler ts-node/transpilers/swc-experimental transpile-only-swc',
'--swc transpile-only-swc',
'transpile-only-swc-via-tsconfig',
'transpile-only-swc-shorthand-via-tsconfig',
]) {
test(`should support swc and third-party transpilers: ${flavor}`, async () => {
const { err, stdout } = await exec(
`${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} ${flavor}`,
{
env: {
...process.env,
NODE_OPTIONS: `${
process.env.NODE_OPTIONS || ''
} --require ${require.resolve('../../tests/spy-swc-transpiler')}`,
},
}
);
expect(err).toBe(null);
expect(stdout).toMatch(
'Hello World! swc transpiler invocation count: 1\n'
);
});
}

if (semver.gte(process.version, '12.16.0')) {
test('swc transpiler supports native ESM emit', async () => {
Expand Down
16 changes: 16 additions & 0 deletions tests/spy-swc-transpiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Spy on the swc transpiler so that tests can prove it was used rather than
// TypeScript's `transpileModule`.
const swcTranspiler = require('ts-node/transpilers/swc');

global.swcTranspilerCalls = 0;

const wrappedCreate = swcTranspiler.create;
swcTranspiler.create = function (...args) {
const transpiler = wrappedCreate(...args);
const wrappedTranspile = transpiler.transpile;
transpiler.transpile = function (...args) {
global.swcTranspilerCalls++;
return wrappedTranspile.call(this, ...args);
};
return transpiler;
};
13 changes: 13 additions & 0 deletions tests/transpile-only-swc-shorthand-via-tsconfig/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Test for #1343
const Decorator = function () {};
@Decorator
class World {}

// intentional type errors to check transpile-only ESM loader skips type checking
parseInt(1101, 2);
const x: number = `Hello ${World.name}! swc transpiler invocation count: ${global.swcTranspilerCalls}`;
console.log(x);

// test module type emit
import { readFileSync } from 'fs';
readFileSync;
12 changes: 12 additions & 0 deletions tests/transpile-only-swc-shorthand-via-tsconfig/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"ts-node": {
"swc": true
},
"compilerOptions": {
"target": "ES2018",
"module": "CommonJS",
"allowJs": true,
"jsx": "react",
"experimentalDecorators": true
}
}
2 changes: 1 addition & 1 deletion tests/transpile-only-swc-via-tsconfig/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class World {}

// intentional type errors to check transpile-only ESM loader skips type checking
parseInt(1101, 2);
const x: number = `Hello ${World.name}!`;
const x: number = `Hello ${World.name}! swc transpiler invocation count: ${global.swcTranspilerCalls}`;
console.log(x);

// test module type emit
Expand Down
2 changes: 1 addition & 1 deletion tests/transpile-only-swc-via-tsconfig/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"ts-node": {
"transpileOnly": true,
"transpiler": "ts-node/transpilers/swc-experimental"
"transpiler": "ts-node/transpilers/swc"
},
"compilerOptions": {
"target": "ES2018",
Expand Down
2 changes: 1 addition & 1 deletion tests/transpile-only-swc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class World {}

// intentional type errors to check transpile-only ESM loader skips type checking
parseInt(1101, 2);
const x: number = `Hello ${World.name}!`;
const x: number = `Hello ${World.name}! swc transpiler invocation count: ${global.swcTranspilerCalls}`;
console.log(x);

// test module type emit
Expand Down
1 change: 1 addition & 0 deletions transpilers/swc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('../dist/transpilers/swc')
1 change: 1 addition & 0 deletions website/docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ _Environment variables, where available, are in `ALL_CAPS`_
- `-I, --ignore [pattern]` Override the path patterns to skip compilation <br/>*Default:* `/node_modules/` <br/>*Environment:* `TS_NODE_IGNORE`
- `--skip-ignore` Skip ignore checks <br/>*Default:* `false` <br/>*Environment:* `TS_NODE_SKIP_IGNORE`
- `-C, --compiler [name]` Specify a custom TypeScript compiler <br/>*Default:* `typescript` <br/>*Environment:* `TS_NODE_COMPILER`
- `--swc` Transpile with [swc](./transpilers.md#swc). Implies `--transpile-only` <br/>*Default:* `false`
- `--transpiler [name]` Specify a third-party, non-typechecking transpiler
- `--prefer-ts-exts` Re-order file extensions so that TypeScript imports are preferred <br/>*Default:* `false` <br/>*Environment:* `TS_NODE_PREFER_TS_EXTS`

Expand Down
2 changes: 1 addition & 1 deletion website/docs/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ These tricks will make ts-node faster.
It is often better to use `tsc --noEmit` to typecheck once before your tests run or as a lint step. In these cases, ts-node can skip typechecking.

* Enable [`transpileOnly`](./options.md) to skip typechecking
* Use our [`swc` integration](./transpilers.md#bundled-swc-integration)
* Use our [`swc` integration](./transpilers.md#swc)
* This is by far the fastest option

## With typechecking
Expand Down
35 changes: 26 additions & 9 deletions website/docs/transpilers.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Third-party transpilers
title: Transpilers
---

In transpile-only mode, we skip typechecking to speed up execution time. You can go a step further and use a
Expand All @@ -13,12 +13,11 @@ boilerplate.
> For our purposes, a compiler implements TypeScript's API and can perform typechecking.
> A third-party transpiler does not. Both transform TypeScript into JavaScript.

## Bundled `swc` integration
## swc

We have bundled an experimental `swc` integration.
swc support is built-in via the `--swc` flag or `"swc": true` tsconfig option.

[`swc`](https://swc.rs) is a TypeScript-compatible transpiler implemented in Rust. This makes it an order of magnitude faster
than `transpileModule`.
[`swc`](https://swc.rs) is a TypeScript-compatible transpiler implemented in Rust. This makes it an order of magnitude faster than vanilla `transpileOnly`.

To use it, first install `@swc/core` or `@swc/wasm`. If using `importHelpers`, also install `@swc/helpers`. If `target` is less than "es2015" and using either `async`/`await` or generator functions, also install `regenerator-runtime`.

Expand All @@ -31,17 +30,35 @@ Then add the following to your `tsconfig.json`.
```json title="tsconfig.json"
{
"ts-node": {
"transpileOnly": true,
"transpiler": "ts-node/transpilers/swc-experimental"
"swc": true
}
}
```

> `swc` uses `@swc/helpers` instead of `tslib`. If you have enabled `importHelpers`, you must also install `@swc/helpers`.

## Third-party transpilers

The `transpiler` option allows using third-party transpiler integrations with ts-node. `transpiler` must be given the
name of a module which can be `require()`d. The built-in `swc` integration is exposed as `ts-node/transpilers/swc`.

For example, to use a hypothetical "speedy-ts-compiler", first install it into your project: `npm install speedy-ts-compiler`

Then add the following to your tsconfig:

```json title="tsconfig.json"
{
"ts-node": {
"transpileOnly": true,
"transpiler": "speedy-ts-compiler"
}
}
```

## Writing your own integration

To write your own transpiler integration, check our [API docs](https://typestrong.org/ts-node/api/interfaces/TranspilerModule.html).

Integrations are `require()`d, so they can be published to npm. The module must export a `create` function matching the
[`TranspilerModule`](https://typestrong.org/ts-node/api/interfaces/TranspilerModule.html) interface.
Integrations are `require()`d by ts-node, so they can be published to npm for convenience. The module must export a `create` function described by our
[`TranspilerModule`](https://typestrong.org/ts-node/api/interfaces/TranspilerModule.html) interface. `create` is invoked by ts-node
at startup to create the transpiler. The transpiler is used repeatedly to transform TypeScript into JavaScript.