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

swc transpiler and new --transpiler option to use third-party transpilers #1160

Merged
merged 22 commits into from Feb 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
19990d4
WIP experimental swc compiler
cspotcode Nov 22, 2020
1551509
Fix optional peer dep
cspotcode Nov 23, 2020
fcd27cc
wip
cspotcode Nov 27, 2020
77436c3
properly merge all of ts onto exports object
cspotcode Dec 10, 2020
f77e1b1
fix clobbering of code because swc does not append a //# sourcemap co…
cspotcode Dec 10, 2020
c82c820
More changes:
cspotcode Jan 17, 2021
b13a631
Merge remote-tracking branch 'origin/master' into ab/swc-compiler
cspotcode Jan 17, 2021
78b49e3
Fix bug in swc loading to allow swc API instance to be passed to factory
cspotcode Jan 17, 2021
230f638
lint fixes
cspotcode Jan 17, 2021
f1b1be0
Fix typo in createTypescriptCompiler function name
cspotcode Jan 17, 2021
ab67165
Switch from hacky overloading the "compiler" config to implementing a…
cspotcode Feb 23, 2021
c1548cf
Merge remote-tracking branch 'origin/master' into ab/swc-compiler
cspotcode Feb 23, 2021
369d43a
fix package.json files array and add --transpiler CLI flag
cspotcode Feb 23, 2021
7103b68
make --transpiler imply --transpile-only and add tests
cspotcode Feb 23, 2021
026b4c1
fixes
cspotcode Feb 23, 2021
d72dbd0
add missing test files
cspotcode Feb 23, 2021
52082fd
add @swc/core dep to tests
cspotcode Feb 23, 2021
7a855ed
add some jsdoc to new transpiler api surface
cspotcode Feb 23, 2021
6fe8249
change transpiler options to be specified as "transpiler: [name, {/*o…
cspotcode Feb 27, 2021
14671a3
Merge remote-tracking branch 'origin/master' into ab/swc-compiler
cspotcode Feb 27, 2021
c3c5a3e
fix
cspotcode Feb 27, 2021
1a06089
cleanup comments
cspotcode Feb 27, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
111 changes: 111 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions package.json
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-experimental": "./transpilers/swc-experimental.js",
"./node10/tsconfig.json": "./node10/tsconfig.json",
"./node12/tsconfig.json": "./node12/tsconfig.json",
"./node14/tsconfig.json": "./node14/tsconfig.json"
Expand All @@ -36,6 +37,7 @@
"ts-node-transpile-only": "dist/bin-transpile.js"
},
"files": [
"transpilers/",
"dist/",
"dist-raw/",
"register/",
Expand Down Expand Up @@ -105,6 +107,8 @@
"timeout": "300s"
},
"devDependencies": {
"@swc/core": ">=1.2.45",
"@swc/wasm": ">=1.2.45",
"@types/chai": "^4.0.4",
"@types/diff": "^4.0.2",
"@types/lodash": "^4.14.151",
Expand Down Expand Up @@ -134,8 +138,18 @@
"util.promisify": "^1.0.1"
},
"peerDependencies": {
"@swc/core": ">=1.2.45",
"@swc/wasm": ">=1.2.45",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
},
"dependencies": {
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
Expand Down
8 changes: 6 additions & 2 deletions src/bin.ts
Expand Up @@ -41,6 +41,7 @@ export function main (argv: string[] = process.argv.slice(2), entrypointArgs: Re
'--ignore-diagnostics': [String],
'--ignore': [String],
'--transpile-only': Boolean,
'--transpiler': String,
'--type-check': Boolean,
'--compiler-host': Boolean,
'--pretty': Boolean,
Expand Down Expand Up @@ -95,6 +96,7 @@ export function main (argv: string[] = process.argv.slice(2), entrypointArgs: Re
'--ignore': ignore,
'--transpile-only': transpileOnly,
'--type-check': typeCheck,
'--transpiler': transpiler,
'--compiler-host': compilerHost,
'--pretty': pretty,
'--skip-project': skipProject,
Expand All @@ -120,11 +122,12 @@ export function main (argv: string[] = process.argv.slice(2), entrypointArgs: Re
--cwd-mode Use current directory instead of <script.ts> for config resolution
--show-config Print resolved configuration and exit

-T, --transpile-only Use TypeScript's faster \`transpileModule\`
-T, --transpile-only Use TypeScript's faster \`transpileModule\` or a third-party 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
-C, --compiler [name] Specify a custom TypeScript compiler
--transpiler [name] Specify a third-party, non-typechecking transpiler
-D, --ignore-diagnostics [code] Ignore TypeScript warnings by diagnostic code
-O, --compiler-options [opts] JSON object to merge with compiler options

Expand Down Expand Up @@ -159,8 +162,9 @@ export function main (argv: string[] = process.argv.slice(2), entrypointArgs: Re
emit,
files,
pretty,
transpileOnly,
transpileOnly: transpileOnly ?? transpiler != null ? true : undefined, // tslint:disable-line:strict-type-predicates
typeCheck,
transpiler,
compilerHost,
ignore,
preferTsExts,
Expand Down
14 changes: 14 additions & 0 deletions src/index.spec.ts
Expand Up @@ -99,6 +99,8 @@ 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-experimental')

testsDirRequire.resolve('ts-node/node10/tsconfig.json')
testsDirRequire.resolve('ts-node/node12/tsconfig.json')
testsDirRequire.resolve('ts-node/node14/tsconfig.json')
Expand Down Expand Up @@ -272,6 +274,18 @@ test.suite('ts-node', (test) => {
expect(err.message).to.contain('error TS1003: Identifier expected')
})

test('should support third-party transpilers via --transpiler', async () => {
const { err, stdout } = await exec(`${cmdNoProject} --transpiler ts-node/transpilers/swc-experimental transpile-only-swc`)
expect(err).to.equal(null)
expect(stdout).to.contain('hello world')
})

test('should support third-party transpilers via tsconfig', async () => {
const { err, stdout } = await exec(`${cmdNoProject} transpile-only-swc-via-tsconfig`)
expect(err).to.equal(null)
expect(stdout).to.contain('hello world')
})

test('should pipe into `ts-node` and evaluate', async () => {
const execPromise = exec(cmd)
execPromise.child.stdin!.end("console.log('hello')")
Expand Down
78 changes: 61 additions & 17 deletions src/index.ts
Expand Up @@ -7,6 +7,7 @@ import { fileURLToPath } from 'url'
import type * as _ts from 'typescript'
import { Module, createRequire as nodeCreateRequire, createRequireFromPath as nodeCreateRequireFromPath } from 'module'
import type _createRequire from 'create-require'
import { Transpiler, TranspilerFactory } from './transpilers/types'
import { getDefaultTsconfigJsonForNodeVersion } from './tsconfigs'

/** @internal */
Expand Down Expand Up @@ -132,6 +133,19 @@ export interface TSCommon {
parseJsonConfigFileContent: typeof _ts.parseJsonConfigFileContent
formatDiagnostics: typeof _ts.formatDiagnostics
formatDiagnosticsWithColorAndContext: typeof _ts.formatDiagnosticsWithColorAndContext

createDocumentRegistry: typeof _ts.createDocumentRegistry
JsxEmit: typeof _ts.JsxEmit
createModuleResolutionCache: typeof _ts.createModuleResolutionCache
resolveModuleName: typeof _ts.resolveModuleName
resolveModuleNameFromCache: typeof _ts.resolveModuleNameFromCache
resolveTypeReferenceDirective: typeof _ts.resolveTypeReferenceDirective
createIncrementalCompilerHost: typeof _ts.createIncrementalCompilerHost
createSourceFile: typeof _ts.createSourceFile
getDefaultLibFileName: typeof _ts.getDefaultLibFileName
createIncrementalProgram: typeof _ts.createIncrementalProgram
createEmitAndSemanticDiagnosticsBuilderProgram: typeof _ts.createEmitAndSemanticDiagnosticsBuilderProgram

libs?: string[]
}

Expand All @@ -156,6 +170,10 @@ export namespace TSInternal {
}
}

export interface TSCompilerFactory {
createTypescriptCompiler (options?: any): TSCommon
}

/**
* Export the current version.
*/
Expand Down Expand Up @@ -237,6 +255,10 @@ export interface CreateOptions {
* @default "typescript"
*/
compiler?: string
/**
* Specify a custom transpiler for use with transpileOnly
*/
transpiler?: string | [string, object]
/**
* Paths which should not be compiled.
*
Expand Down Expand Up @@ -568,6 +590,24 @@ export function create (rawOptions: CreateOptions = {}): Service {
getCanonicalFileName: ts.sys.useCaseSensitiveFileNames ? x => x : x => x.toLowerCase()
}

if (options.transpileOnly && typeof transformers === 'function') {
throw new TypeError('Transformers function is unavailable in "--transpile-only"')
}
let customTranspiler: Transpiler | undefined = undefined
if (options.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]
const transpilerOptions = typeof options.transpiler === 'string' ? {} : options.transpiler[1] ?? {}
// TODO mimic fixed resolution logic from loadCompiler master
// TODO refactor into a more generic "resolve dep relative to project" helper
const transpilerPath = require.resolve(transpilerName, { paths: [cwd, __dirname] })
const transpilerFactory: TranspilerFactory = require(transpilerPath).create
customTranspiler = transpilerFactory({
service: { options, config },
...transpilerOptions
})
}

// Install source map support and read from memory cache.
sourceMapSupport.install({
environment: 'node',
Expand Down Expand Up @@ -1022,17 +1062,20 @@ export function create (rawOptions: CreateOptions = {}): Service {
}
}
} else {
if (typeof transformers === 'function') {
throw new TypeError('Transformers function is unavailable in "--transpile-only"')
}

getOutput = (code: string, fileName: string): SourceOutput => {
const result = ts.transpileModule(code, {
fileName,
compilerOptions: config.options,
reportDiagnostics: true,
transformers: transformers
})
let result: _ts.TranspileOutput
if (customTranspiler) {
result = customTranspiler.transpile(code, {
fileName
})
} else {
result = ts.transpileModule(code, {
fileName,
compilerOptions: config.options,
reportDiagnostics: true,
transformers: transformers as _ts.CustomTransformers | undefined
})
}

const diagnosticList = filterDiagnostics(result.diagnostics || [], ignoreDiagnostics)
if (diagnosticList.length) reportTSError(diagnosticList)
Expand Down Expand Up @@ -1285,12 +1328,12 @@ function filterRecognizedTsConfigTsNodeOptions (jsonObject: any): TsConfigOption
const {
compiler, compilerHost, compilerOptions, emit, files, ignore,
ignoreDiagnostics, logError, preferTsExts, pretty, require, skipIgnore,
transpileOnly, typeCheck
transpileOnly, typeCheck, transpiler
} = jsonObject as TsConfigOptions
const filteredTsConfigOptions = {
compiler, compilerHost, compilerOptions, emit, files, ignore,
ignoreDiagnostics, logError, preferTsExts, pretty, require, skipIgnore,
transpileOnly, typeCheck
transpileOnly, typeCheck, transpiler
}
// 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 All @@ -1308,10 +1351,11 @@ type SourceOutput = [string, string]
*/
function updateOutput (outputText: string, fileName: string, sourceMap: string, getExtension: (fileName: string) => string) {
const base64Map = Buffer.from(updateSourceMap(sourceMap, fileName), 'utf8').toString('base64')
const sourceMapContent = `data:application/json;charset=utf-8;base64,${base64Map}`
const sourceMapLength = `${basename(fileName)}.map`.length + (getExtension(fileName).length - extname(fileName).length)

return outputText.slice(0, -sourceMapLength) + sourceMapContent
const sourceMapContent = `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${base64Map}`
// Expected form: `//# sourceMappingURL=foo.js.map` for input file foo.tsx
const sourceMapLength = /*//# sourceMappingURL=*/ 21 + /*foo.tsx*/ basename(fileName).length - /*.tsx*/ extname(fileName).length + /*.js*/ getExtension(fileName).length + /*.map*/ 4
// Only rewrite if existing directive exists, to support compilers that do not append a sourcemap directive
return (outputText.slice(-sourceMapLength, -sourceMapLength + 21) === '//# sourceMappingURL=' ? outputText.slice(0, -sourceMapLength) : outputText) + sourceMapContent
}

/**
Expand All @@ -1337,7 +1381,7 @@ function filterDiagnostics (diagnostics: readonly _ts.Diagnostic[], ignore: numb
*
* Reference: https://github.com/microsoft/TypeScript/blob/fcd9334f57d85b73dd66ad2d21c02e84822f4841/src/services/utilities.ts#L705-L731
*/
function getTokenAtPosition (ts: typeof _ts, sourceFile: _ts.SourceFile, position: number): _ts.Node {
function getTokenAtPosition (ts: TSCommon, sourceFile: _ts.SourceFile, position: number): _ts.Node {
let current: _ts.Node = sourceFile

outer: while (true) {
Expand Down