2020-07-19 13:49:44 -04:00
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
// This module is the entry point for "compiler" isolate, ie. the one
// that is created when Deno needs to compile TS/WASM to JS.
//
2020-07-23 09:29:36 -04:00
// It provides two functions that should be called by Rust:
// - `bootstrapCompilerRuntime`
2020-07-19 13:49:44 -04:00
// This functions must be called when creating isolate
// to properly setup runtime.
2020-07-23 09:29:36 -04:00
// - `tsCompilerOnMessage`
// This function must be called when sending a request
// to the compiler.
2020-07-19 13:49:44 -04:00
// Removes the `__proto__` for security reasons. This intentionally makes
// Deno non compliant with ECMA-262 Annex B.2.2.1
//
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete Object . prototype . _ _proto _ _ ;
( ( window ) => {
const core = Deno . core ;
const { assert , log , notImplemented } = window . _ _bootstrap . util ;
2020-07-23 09:29:36 -04:00
const dispatchJson = window . _ _bootstrap . dispatchJson ;
const util = window . _ _bootstrap . util ;
const errorStack = window . _ _bootstrap . errorStack ;
function opNow ( ) {
const res = dispatchJson . sendSync ( "op_now" ) ;
return res . seconds * 1e3 + res . subsecNanos / 1e6 ;
}
const DiagnosticCategory = {
0 : "Log" ,
1 : "Debug" ,
2 : "Info" ,
3 : "Error" ,
4 : "Warning" ,
5 : "Suggestion" ,
Log : 0 ,
Debug : 1 ,
Info : 2 ,
Error : 3 ,
Warning : 4 ,
Suggestion : 5 ,
} ;
2020-07-19 13:49:44 -04:00
const unstableDenoGlobalProperties = [
"umask" ,
"linkSync" ,
"link" ,
"symlinkSync" ,
"symlink" ,
"loadavg" ,
"osRelease" ,
"openPlugin" ,
"DiagnosticCategory" ,
"DiagnosticMessageChain" ,
"DiagnosticItem" ,
"Diagnostic" ,
"formatDiagnostics" ,
"CompilerOptions" ,
"TranspileOnlyResult" ,
"transpileOnly" ,
"compile" ,
"bundle" ,
"Location" ,
"applySourceMap" ,
"LinuxSignal" ,
"MacOSSignal" ,
"Signal" ,
"SignalStream" ,
"signal" ,
"signals" ,
"setRaw" ,
"utimeSync" ,
"utime" ,
"ShutdownMode" ,
"shutdown" ,
"DatagramConn" ,
"UnixListenOptions" ,
"listen" ,
"listenDatagram" ,
"UnixConnectOptions" ,
"connect" ,
"StartTlsOptions" ,
"startTls" ,
"kill" ,
"PermissionName" ,
"PermissionState" ,
"RunPermissionDescriptor" ,
"ReadPermissionDescriptor" ,
"WritePermissionDescriptor" ,
"NetPermissionDescriptor" ,
"EnvPermissionDescriptor" ,
"PluginPermissionDescriptor" ,
"HrtimePermissionDescriptor" ,
"PermissionDescriptor" ,
"Permissions" ,
"PermissionStatus" ,
"hostname" ,
"ppid" ,
] ;
function transformMessageText ( messageText , code ) {
switch ( code ) {
case 2339 : {
const property = messageText
. replace ( /^Property '/ , "" )
. replace ( /' does not exist on type 'typeof Deno'\./ , "" ) ;
if (
messageText . endsWith ( "on type 'typeof Deno'." ) &&
unstableDenoGlobalProperties . includes ( property )
) {
return ` ${ messageText } 'Deno. ${ property } ' is an unstable API. Did you forget to run with the '--unstable' flag? ` ;
}
break ;
}
case 2551 : {
const suggestionMessagePattern = / Did you mean '(.+)'\?$/ ;
const property = messageText
. replace ( /^Property '/ , "" )
. replace ( /' does not exist on type 'typeof Deno'\./ , "" )
. replace ( suggestionMessagePattern , "" ) ;
const suggestion = messageText . match ( suggestionMessagePattern ) ;
const replacedMessageText = messageText . replace (
suggestionMessagePattern ,
"" ,
) ;
if ( suggestion && unstableDenoGlobalProperties . includes ( property ) ) {
const suggestedProperty = suggestion [ 1 ] ;
return ` ${ replacedMessageText } 'Deno. ${ property } ' is an unstable API. Did you forget to run with the '--unstable' flag, or did you mean ' ${ suggestedProperty } '? ` ;
}
break ;
}
}
return messageText ;
}
function fromDiagnosticCategory (
category ,
) {
switch ( category ) {
case ts . DiagnosticCategory . Error :
return DiagnosticCategory . Error ;
case ts . DiagnosticCategory . Message :
return DiagnosticCategory . Info ;
case ts . DiagnosticCategory . Suggestion :
return DiagnosticCategory . Suggestion ;
case ts . DiagnosticCategory . Warning :
return DiagnosticCategory . Warning ;
default :
throw new Error (
` Unexpected DiagnosticCategory: " ${ category } "/" ${
ts . DiagnosticCategory [ category ]
} " ` ,
) ;
}
}
function getSourceInformation (
sourceFile ,
start ,
length ,
) {
const scriptResourceName = sourceFile . fileName ;
const {
line : lineNumber ,
character : startColumn ,
} = sourceFile . getLineAndCharacterOfPosition ( start ) ;
const endPosition = sourceFile . getLineAndCharacterOfPosition (
start + length ,
) ;
const endColumn = lineNumber === endPosition . line
? endPosition . character
: startColumn ;
const lastLineInFile = sourceFile . getLineAndCharacterOfPosition (
sourceFile . text . length ,
) . line ;
const lineStart = sourceFile . getPositionOfLineAndCharacter ( lineNumber , 0 ) ;
const lineEnd = lineNumber < lastLineInFile
? sourceFile . getPositionOfLineAndCharacter ( lineNumber + 1 , 0 )
: sourceFile . text . length ;
const sourceLine = sourceFile . text
. slice ( lineStart , lineEnd )
. replace ( /\s+$/g , "" )
. replace ( "\t" , " " ) ;
return {
sourceLine ,
lineNumber ,
scriptResourceName ,
startColumn ,
endColumn ,
} ;
}
function fromDiagnosticMessageChain (
messageChain ,
) {
if ( ! messageChain ) {
return undefined ;
}
return messageChain . map ( ( { messageText , code , category , next } ) => {
const message = transformMessageText ( messageText , code ) ;
return {
message ,
code ,
category : fromDiagnosticCategory ( category ) ,
next : fromDiagnosticMessageChain ( next ) ,
} ;
} ) ;
}
function parseDiagnostic (
item ,
) {
const {
messageText ,
category : sourceCategory ,
code ,
file ,
start : startPosition ,
length ,
} = item ;
const sourceInfo = file && startPosition && length
? getSourceInformation ( file , startPosition , length )
: undefined ;
const endPosition = startPosition && length
? startPosition + length
: undefined ;
const category = fromDiagnosticCategory ( sourceCategory ) ;
let message ;
let messageChain ;
if ( typeof messageText === "string" ) {
message = transformMessageText ( messageText , code ) ;
} else {
message = transformMessageText ( messageText . messageText , messageText . code ) ;
messageChain = fromDiagnosticMessageChain ( [ messageText ] ) [ 0 ] ;
}
const base = {
message ,
messageChain ,
code ,
category ,
startPosition ,
endPosition ,
} ;
return sourceInfo ? { ... base , ... sourceInfo } : base ;
}
function parseRelatedInformation (
relatedInformation ,
) {
const result = [ ] ;
for ( const item of relatedInformation ) {
result . push ( parseDiagnostic ( item ) ) ;
}
return result ;
}
function fromTypeScriptDiagnostic (
diagnostics ,
) {
const items = [ ] ;
for ( const sourceDiagnostic of diagnostics ) {
const item = parseDiagnostic ( sourceDiagnostic ) ;
if ( sourceDiagnostic . relatedInformation ) {
item . relatedInformation = parseRelatedInformation (
sourceDiagnostic . relatedInformation ,
) ;
}
items . push ( item ) ;
}
return { items } ;
}
// We really don't want to depend on JSON dispatch during snapshotting, so
// this op exchanges strings with Rust as raw byte arrays.
function getAsset ( name ) {
const opId = core . ops ( ) [ "op_fetch_asset" ] ;
const sourceCodeBytes = core . dispatch ( opId , core . encode ( name ) ) ;
return core . decode ( sourceCodeBytes ) ;
}
// Constants used by `normalizeString` and `resolvePath`
const CHAR _DOT = 46 ; /* . */
const CHAR _FORWARD _SLASH = 47 ; /* / */
// Using incremental compile APIs requires that all
// paths must be either relative or absolute. Since
// analysis in Rust operates on fully resolved URLs,
// it makes sense to use the same scheme here.
const ASSETS = "asset://" ;
const OUT _DIR = "deno://" ;
// This constant is passed to compiler settings when
// doing incremental compiles. Contents of this
// file are passed back to Rust and saved to $DENO_DIR.
const TS _BUILD _INFO = "cache:///tsbuildinfo.json" ;
// TODO(Bartlomieju): this check should be done in Rust
const IGNORED _COMPILER _OPTIONS = [
"allowSyntheticDefaultImports" ,
"allowUmdGlobalAccess" ,
"assumeChangesOnlyAffectDirectDependencies" ,
"baseUrl" ,
"build" ,
"composite" ,
"declaration" ,
"declarationDir" ,
"declarationMap" ,
"diagnostics" ,
"downlevelIteration" ,
"emitBOM" ,
"emitDeclarationOnly" ,
"esModuleInterop" ,
"extendedDiagnostics" ,
"forceConsistentCasingInFileNames" ,
"generateCpuProfile" ,
"help" ,
"importHelpers" ,
"incremental" ,
"inlineSourceMap" ,
"inlineSources" ,
"init" ,
"listEmittedFiles" ,
"listFiles" ,
"mapRoot" ,
"maxNodeModuleJsDepth" ,
"module" ,
"moduleResolution" ,
"newLine" ,
"noEmit" ,
"noEmitHelpers" ,
"noEmitOnError" ,
"noLib" ,
"noResolve" ,
"out" ,
"outDir" ,
"outFile" ,
"paths" ,
"preserveSymlinks" ,
"preserveWatchOutput" ,
"pretty" ,
"rootDir" ,
"rootDirs" ,
"showConfig" ,
"skipDefaultLibCheck" ,
"skipLibCheck" ,
"sourceMap" ,
"sourceRoot" ,
"stripInternal" ,
"target" ,
"traceResolution" ,
"tsBuildInfoFile" ,
"types" ,
"typeRoots" ,
"version" ,
"watch" ,
] ;
const DEFAULT _BUNDLER _OPTIONS = {
allowJs : true ,
inlineSourceMap : false ,
module : ts . ModuleKind . System ,
outDir : undefined ,
outFile : ` ${ OUT _DIR } /bundle.js ` ,
// disabled until we have effective way to modify source maps
sourceMap : false ,
} ;
const DEFAULT _INCREMENTAL _COMPILE _OPTIONS = {
allowJs : false ,
allowNonTsExtensions : true ,
checkJs : false ,
esModuleInterop : true ,
incremental : true ,
inlineSourceMap : true ,
jsx : ts . JsxEmit . React ,
module : ts . ModuleKind . ESNext ,
outDir : OUT _DIR ,
resolveJsonModule : true ,
sourceMap : false ,
strict : true ,
stripComments : true ,
target : ts . ScriptTarget . ESNext ,
tsBuildInfoFile : TS _BUILD _INFO ,
} ;
const DEFAULT _COMPILE _OPTIONS = {
allowJs : false ,
allowNonTsExtensions : true ,
checkJs : false ,
esModuleInterop : true ,
jsx : ts . JsxEmit . React ,
module : ts . ModuleKind . ESNext ,
outDir : OUT _DIR ,
sourceMap : true ,
strict : true ,
removeComments : true ,
target : ts . ScriptTarget . ESNext ,
} ;
const DEFAULT _TRANSPILE _OPTIONS = {
esModuleInterop : true ,
inlineSourceMap : true ,
jsx : ts . JsxEmit . React ,
module : ts . ModuleKind . ESNext ,
removeComments : true ,
target : ts . ScriptTarget . ESNext ,
} ;
const DEFAULT _RUNTIME _COMPILE _OPTIONS = {
outDir : undefined ,
} ;
const DEFAULT _RUNTIME _TRANSPILE _OPTIONS = {
esModuleInterop : true ,
module : ts . ModuleKind . ESNext ,
sourceMap : true ,
scriptComments : true ,
target : ts . ScriptTarget . ESNext ,
} ;
const CompilerHostTarget = {
Main : "main" ,
Runtime : "runtime" ,
Worker : "worker" ,
} ;
// Warning! The values in this enum are duplicated in `cli/msg.rs`
// Update carefully!
const MediaType = {
0 : "JavaScript" ,
1 : "JSX" ,
2 : "TypeScript" ,
3 : "TSX" ,
4 : "Json" ,
5 : "Wasm" ,
6 : "Unknown" ,
JavaScript : 0 ,
JSX : 1 ,
TypeScript : 2 ,
TSX : 3 ,
Json : 4 ,
Wasm : 5 ,
Unknown : 6 ,
} ;
function getExtension ( fileName , mediaType ) {
switch ( mediaType ) {
case MediaType . JavaScript :
return ts . Extension . Js ;
case MediaType . JSX :
return ts . Extension . Jsx ;
case MediaType . TypeScript :
return fileName . endsWith ( ".d.ts" ) ? ts . Extension . Dts : ts . Extension . Ts ;
case MediaType . TSX :
return ts . Extension . Tsx ;
case MediaType . Wasm :
// Custom marker for Wasm type.
return ts . Extension . Js ;
case MediaType . Unknown :
default :
throw TypeError (
` Cannot resolve extension for " ${ fileName } " with mediaType " ${
MediaType [ mediaType ]
} " . ` ,
) ;
}
}
/ * * A g l o b a l c a c h e o f m o d u l e s o u r c e f i l e s t h a t h a v e b e e n l o a d e d .
* This cache will be rewritten to be populated on compiler startup
* with files provided from Rust in request message .
* /
const SOURCE _FILE _CACHE = new Map ( ) ;
/ * * A m a p o f m a p s w h i c h c a c h e r e s o l v e d s p e c i f i e r f o r e a c h i m p o r t i n a f i l e .
* This cache is used so ` resolveModuleNames ` ops is called as few times
* as possible .
*
* First map ' s key is "referrer" URL ( "file://a/b/c/mod.ts" )
* Second map ' s key is "raw" import specifier ( "./foo.ts" )
* Second map ' s value is resolved import URL ( "file:///a/b/c/foo.ts" )
* /
const RESOLVED _SPECIFIER _CACHE = new Map ( ) ;
function configure (
defaultOptions ,
source ,
path ,
cwd ,
) {
const { config , error } = ts . parseConfigFileTextToJson ( path , source ) ;
if ( error ) {
return { diagnostics : [ error ] , options : defaultOptions } ;
}
const { options , errors } = ts . convertCompilerOptionsFromJson (
config . compilerOptions ,
cwd ,
) ;
const ignoredOptions = [ ] ;
for ( const key of Object . keys ( options ) ) {
if (
IGNORED _COMPILER _OPTIONS . includes ( key ) &&
( ! ( key in defaultOptions ) || options [ key ] !== defaultOptions [ key ] )
) {
ignoredOptions . push ( key ) ;
delete options [ key ] ;
}
}
return {
options : Object . assign ( { } , defaultOptions , options ) ,
ignoredOptions : ignoredOptions . length ? ignoredOptions : undefined ,
diagnostics : errors . length ? errors : undefined ,
} ;
}
class SourceFile {
constructor ( json ) {
this . processed = false ;
Object . assign ( this , json ) ;
this . extension = getExtension ( this . url , this . mediaType ) ;
}
static addToCache ( json ) {
if ( SOURCE _FILE _CACHE . has ( json . url ) ) {
throw new TypeError ( "SourceFile already exists" ) ;
}
const sf = new SourceFile ( json ) ;
SOURCE _FILE _CACHE . set ( sf . url , sf ) ;
return sf ;
}
static getCached ( url ) {
return SOURCE _FILE _CACHE . get ( url ) ;
}
static cacheResolvedUrl (
resolvedUrl ,
rawModuleSpecifier ,
containingFile ,
) {
containingFile = containingFile || "" ;
let innerCache = RESOLVED _SPECIFIER _CACHE . get ( containingFile ) ;
if ( ! innerCache ) {
innerCache = new Map ( ) ;
RESOLVED _SPECIFIER _CACHE . set ( containingFile , innerCache ) ;
}
innerCache . set ( rawModuleSpecifier , resolvedUrl ) ;
}
static getResolvedUrl (
moduleSpecifier ,
containingFile ,
) {
const containingCache = RESOLVED _SPECIFIER _CACHE . get ( containingFile ) ;
if ( containingCache ) {
return containingCache . get ( moduleSpecifier ) ;
}
return undefined ;
}
}
function getAssetInternal ( filename ) {
const lastSegment = filename . split ( "/" ) . pop ( ) ;
const url = ts . libMap . has ( lastSegment )
? ts . libMap . get ( lastSegment )
: lastSegment ;
const sourceFile = SourceFile . getCached ( url ) ;
if ( sourceFile ) {
return sourceFile ;
}
const name = url . includes ( "." ) ? url : ` ${ url } .d.ts ` ;
const sourceCode = getAsset ( name ) ;
return SourceFile . addToCache ( {
url ,
filename : ` ${ ASSETS } / ${ name } ` ,
mediaType : MediaType . TypeScript ,
versionHash : "1" ,
sourceCode ,
} ) ;
}
class Host {
# options = DEFAULT _COMPILE _OPTIONS ;
# target = "" ;
# writeFile = null ;
/* Deno specific APIs */
constructor ( {
bundle = false ,
incremental = false ,
target ,
unstable ,
writeFile ,
} ) {
this . # target = target ;
this . # writeFile = writeFile ;
if ( bundle ) {
// options we need to change when we are generating a bundle
Object . assign ( this . # options , DEFAULT _BUNDLER _OPTIONS ) ;
} else if ( incremental ) {
Object . assign ( this . # options , DEFAULT _INCREMENTAL _COMPILE _OPTIONS ) ;
}
if ( unstable ) {
this . # options . lib = [
target === CompilerHostTarget . Worker
? "lib.deno.worker.d.ts"
: "lib.deno.window.d.ts" ,
"lib.deno.unstable.d.ts" ,
] ;
}
}
get options ( ) {
return this . # options ;
}
configure (
cwd ,
path ,
configurationText ,
) {
log ( "compiler::host.configure" , path ) ;
const { options , ... result } = configure (
this . # options ,
configurationText ,
path ,
cwd ,
) ;
this . # options = options ;
return result ;
}
mergeOptions ( ... options ) {
Object . assign ( this . # options , ... options ) ;
return Object . assign ( { } , this . # options ) ;
}
/* TypeScript CompilerHost APIs */
fileExists ( _fileName ) {
return notImplemented ( ) ;
}
getCanonicalFileName ( fileName ) {
return fileName ;
}
getCompilationSettings ( ) {
log ( "compiler::host.getCompilationSettings()" ) ;
return this . # options ;
}
getCurrentDirectory ( ) {
return "" ;
}
getDefaultLibFileName ( _options ) {
log ( "compiler::host.getDefaultLibFileName()" ) ;
switch ( this . # target ) {
case CompilerHostTarget . Main :
case CompilerHostTarget . Runtime :
return ` ${ ASSETS } /lib.deno.window.d.ts ` ;
case CompilerHostTarget . Worker :
return ` ${ ASSETS } /lib.deno.worker.d.ts ` ;
}
}
getNewLine ( ) {
return "\n" ;
}
getSourceFile (
fileName ,
languageVersion ,
onError ,
shouldCreateNewSourceFile ,
) {
log ( "compiler::host.getSourceFile" , fileName ) ;
try {
assert ( ! shouldCreateNewSourceFile ) ;
const sourceFile = fileName . startsWith ( ASSETS )
? getAssetInternal ( fileName )
: SourceFile . getCached ( fileName ) ;
assert ( sourceFile != null ) ;
if ( ! sourceFile . tsSourceFile ) {
assert ( sourceFile . sourceCode != null ) ;
const tsSourceFileName = fileName . startsWith ( ASSETS )
? sourceFile . filename
: fileName ;
sourceFile . tsSourceFile = ts . createSourceFile (
tsSourceFileName ,
sourceFile . sourceCode ,
languageVersion ,
) ;
sourceFile . tsSourceFile . version = sourceFile . versionHash ;
delete sourceFile . sourceCode ;
}
return sourceFile . tsSourceFile ;
} catch ( e ) {
if ( onError ) {
onError ( String ( e ) ) ;
} else {
throw e ;
}
return undefined ;
}
}
readFile ( _fileName ) {
return notImplemented ( ) ;
}
resolveModuleNames (
moduleNames ,
containingFile ,
) {
log ( "compiler::host.resolveModuleNames" , {
moduleNames ,
containingFile ,
} ) ;
const resolved = moduleNames . map ( ( specifier ) => {
const maybeUrl = SourceFile . getResolvedUrl ( specifier , containingFile ) ;
log ( "compiler::host.resolveModuleNames maybeUrl" , {
specifier ,
maybeUrl ,
} ) ;
let sourceFile = undefined ;
if ( specifier . startsWith ( ASSETS ) ) {
sourceFile = getAssetInternal ( specifier ) ;
} else if ( typeof maybeUrl !== "undefined" ) {
sourceFile = SourceFile . getCached ( maybeUrl ) ;
}
if ( ! sourceFile ) {
return undefined ;
}
return {
resolvedFileName : sourceFile . url ,
isExternalLibraryImport : specifier . startsWith ( ASSETS ) ,
extension : sourceFile . extension ,
} ;
} ) ;
log ( resolved ) ;
return resolved ;
}
useCaseSensitiveFileNames ( ) {
return true ;
}
writeFile (
fileName ,
data ,
_writeByteOrderMark ,
_onError ,
sourceFiles ,
) {
log ( "compiler::host.writeFile" , fileName ) ;
this . # writeFile ( fileName , data , sourceFiles ) ;
}
}
class IncrementalCompileHost extends Host {
# buildInfo = "" ;
constructor ( options ) {
super ( { ... options , incremental : true } ) ;
const { buildInfo } = options ;
if ( buildInfo ) {
this . # buildInfo = buildInfo ;
}
}
readFile ( fileName ) {
if ( fileName == TS _BUILD _INFO ) {
return this . # buildInfo ;
}
throw new Error ( "unreachable" ) ;
}
}
// NOTE: target doesn't really matter here,
// this is in fact a mock host created just to
// load all type definitions and snapshot them.
let SNAPSHOT _HOST = new Host ( {
target : CompilerHostTarget . Main ,
writeFile ( ) { } ,
} ) ;
const SNAPSHOT _COMPILER _OPTIONS = SNAPSHOT _HOST . getCompilationSettings ( ) ;
// This is a hacky way of adding our libs to the libs available in TypeScript()
// as these are internal APIs of TypeScript which maintain valid libs
ts . libs . push ( "deno.ns" , "deno.window" , "deno.worker" , "deno.shared_globals" ) ;
ts . libMap . set ( "deno.ns" , "lib.deno.ns.d.ts" ) ;
ts . libMap . set ( "deno.window" , "lib.deno.window.d.ts" ) ;
ts . libMap . set ( "deno.worker" , "lib.deno.worker.d.ts" ) ;
ts . libMap . set ( "deno.shared_globals" , "lib.deno.shared_globals.d.ts" ) ;
ts . libMap . set ( "deno.unstable" , "lib.deno.unstable.d.ts" ) ;
// this pre-populates the cache at snapshot time of our library files, so they
// are available in the future when needed.
SNAPSHOT _HOST . getSourceFile (
` ${ ASSETS } /lib.deno.ns.d.ts ` ,
ts . ScriptTarget . ESNext ,
) ;
SNAPSHOT _HOST . getSourceFile (
` ${ ASSETS } /lib.deno.window.d.ts ` ,
ts . ScriptTarget . ESNext ,
) ;
SNAPSHOT _HOST . getSourceFile (
` ${ ASSETS } /lib.deno.worker.d.ts ` ,
ts . ScriptTarget . ESNext ,
) ;
SNAPSHOT _HOST . getSourceFile (
` ${ ASSETS } /lib.deno.shared_globals.d.ts ` ,
ts . ScriptTarget . ESNext ,
) ;
SNAPSHOT _HOST . getSourceFile (
` ${ ASSETS } /lib.deno.unstable.d.ts ` ,
ts . ScriptTarget . ESNext ,
) ;
// We never use this program; it's only created
// during snapshotting to hydrate and populate
// source file cache with lib declaration files.
const _TS _SNAPSHOT _PROGRAM = ts . createProgram ( {
rootNames : [ ` ${ ASSETS } /bootstrap.ts ` ] ,
options : SNAPSHOT _COMPILER _OPTIONS ,
host : SNAPSHOT _HOST ,
} ) ;
// Derference the snapshot host so it can be GCed
SNAPSHOT _HOST = undefined ;
// This function is called only during snapshotting process
const SYSTEM _LOADER = getAsset ( "system_loader.js" ) ;
const SYSTEM _LOADER _ES5 = getAsset ( "system_loader_es5.js" ) ;
function buildLocalSourceFileCache (
sourceFileMap ,
) {
for ( const entry of Object . values ( sourceFileMap ) ) {
assert ( entry . sourceCode . length > 0 ) ;
SourceFile . addToCache ( {
url : entry . url ,
filename : entry . url ,
mediaType : entry . mediaType ,
sourceCode : entry . sourceCode ,
versionHash : entry . versionHash ,
} ) ;
for ( const importDesc of entry . imports ) {
let mappedUrl = importDesc . resolvedSpecifier ;
const importedFile = sourceFileMap [ importDesc . resolvedSpecifier ] ;
assert ( importedFile ) ;
const isJsOrJsx = importedFile . mediaType === MediaType . JavaScript ||
importedFile . mediaType === MediaType . JSX ;
// If JS or JSX perform substitution for types if available
if ( isJsOrJsx ) {
if ( importedFile . typeHeaders . length > 0 ) {
const typeHeaders = importedFile . typeHeaders [ 0 ] ;
mappedUrl = typeHeaders . resolvedSpecifier ;
} else if ( importDesc . resolvedTypeDirective ) {
mappedUrl = importDesc . resolvedTypeDirective ;
} else if ( importedFile . typesDirectives . length > 0 ) {
const typeDirective = importedFile . typesDirectives [ 0 ] ;
mappedUrl = typeDirective . resolvedSpecifier ;
}
}
mappedUrl = mappedUrl . replace ( "memory://" , "" ) ;
SourceFile . cacheResolvedUrl ( mappedUrl , importDesc . specifier , entry . url ) ;
}
for ( const fileRef of entry . referencedFiles ) {
SourceFile . cacheResolvedUrl (
fileRef . resolvedSpecifier . replace ( "memory://" , "" ) ,
fileRef . specifier ,
entry . url ,
) ;
}
for ( const fileRef of entry . libDirectives ) {
SourceFile . cacheResolvedUrl (
fileRef . resolvedSpecifier . replace ( "memory://" , "" ) ,
fileRef . specifier ,
entry . url ,
) ;
}
}
}
function buildSourceFileCache (
sourceFileMap ,
) {
for ( const entry of Object . values ( sourceFileMap ) ) {
SourceFile . addToCache ( {
url : entry . url ,
filename : entry . url ,
mediaType : entry . mediaType ,
sourceCode : entry . sourceCode ,
versionHash : entry . versionHash ,
} ) ;
for ( const importDesc of entry . imports ) {
let mappedUrl = importDesc . resolvedSpecifier ;
const importedFile = sourceFileMap [ importDesc . resolvedSpecifier ] ;
// IMPORTANT: due to HTTP redirects we might end up in situation
// where URL points to a file with completely different URL.
// In that case we take value of `redirect` field and cache
// resolved specifier pointing to the value of the redirect.
// It's not very elegant solution and should be rethinked.
assert ( importedFile ) ;
if ( importedFile . redirect ) {
mappedUrl = importedFile . redirect ;
}
const isJsOrJsx = importedFile . mediaType === MediaType . JavaScript ||
importedFile . mediaType === MediaType . JSX ;
// If JS or JSX perform substitution for types if available
if ( isJsOrJsx ) {
if ( importedFile . typeHeaders . length > 0 ) {
const typeHeaders = importedFile . typeHeaders [ 0 ] ;
mappedUrl = typeHeaders . resolvedSpecifier ;
} else if ( importDesc . resolvedTypeDirective ) {
mappedUrl = importDesc . resolvedTypeDirective ;
} else if ( importedFile . typesDirectives . length > 0 ) {
const typeDirective = importedFile . typesDirectives [ 0 ] ;
mappedUrl = typeDirective . resolvedSpecifier ;
}
}
SourceFile . cacheResolvedUrl ( mappedUrl , importDesc . specifier , entry . url ) ;
}
for ( const fileRef of entry . referencedFiles ) {
SourceFile . cacheResolvedUrl (
fileRef . resolvedSpecifier ,
fileRef . specifier ,
entry . url ,
) ;
}
for ( const fileRef of entry . libDirectives ) {
SourceFile . cacheResolvedUrl (
fileRef . resolvedSpecifier ,
fileRef . specifier ,
entry . url ,
) ;
}
}
}
// Warning! The values in this enum are duplicated in `cli/msg.rs`
// Update carefully!
const CompilerRequestType = {
Compile : 0 ,
Transpile : 1 ,
Bundle : 2 ,
RuntimeCompile : 3 ,
RuntimeBundle : 4 ,
RuntimeTranspile : 5 ,
} ;
function createBundleWriteFile ( state ) {
return function writeFile (
_fileName ,
data ,
sourceFiles ,
) {
assert ( sourceFiles != null ) ;
assert ( state . host ) ;
// we only support single root names for bundles
assert ( state . rootNames . length === 1 ) ;
state . bundleOutput = buildBundle (
state . rootNames [ 0 ] ,
data ,
sourceFiles ,
state . host . options . target ? ? ts . ScriptTarget . ESNext ,
) ;
} ;
}
function createCompileWriteFile (
state ,
) {
return function writeFile (
fileName ,
data ,
sourceFiles ,
) {
const isBuildInfo = fileName === TS _BUILD _INFO ;
if ( isBuildInfo ) {
assert ( isBuildInfo ) ;
state . buildInfo = data ;
return ;
}
assert ( sourceFiles ) ;
assert ( sourceFiles . length === 1 ) ;
state . emitMap [ fileName ] = {
filename : sourceFiles [ 0 ] . fileName ,
contents : data ,
} ;
} ;
}
function createRuntimeCompileWriteFile (
state ,
) {
return function writeFile (
fileName ,
data ,
sourceFiles ,
) {
assert ( sourceFiles ) ;
assert ( sourceFiles . length === 1 ) ;
state . emitMap [ fileName ] = {
filename : sourceFiles [ 0 ] . fileName ,
contents : data ,
} ;
} ;
}
function convertCompilerOptions ( str ) {
const options = JSON . parse ( str ) ;
const out = { } ;
const keys = Object . keys ( options ) ;
const files = [ ] ;
for ( const key of keys ) {
switch ( key ) {
case "jsx" :
const value = options [ key ] ;
if ( value === "preserve" ) {
out [ key ] = ts . JsxEmit . Preserve ;
} else if ( value === "react" ) {
out [ key ] = ts . JsxEmit . React ;
} else {
out [ key ] = ts . JsxEmit . ReactNative ;
}
break ;
case "module" :
switch ( options [ key ] ) {
case "amd" :
out [ key ] = ts . ModuleKind . AMD ;
break ;
case "commonjs" :
out [ key ] = ts . ModuleKind . CommonJS ;
break ;
case "es2015" :
case "es6" :
out [ key ] = ts . ModuleKind . ES2015 ;
break ;
case "esnext" :
out [ key ] = ts . ModuleKind . ESNext ;
break ;
case "none" :
out [ key ] = ts . ModuleKind . None ;
break ;
case "system" :
out [ key ] = ts . ModuleKind . System ;
break ;
case "umd" :
out [ key ] = ts . ModuleKind . UMD ;
break ;
default :
throw new TypeError ( "Unexpected module type" ) ;
}
break ;
case "target" :
switch ( options [ key ] ) {
case "es3" :
out [ key ] = ts . ScriptTarget . ES3 ;
break ;
case "es5" :
out [ key ] = ts . ScriptTarget . ES5 ;
break ;
case "es6" :
case "es2015" :
out [ key ] = ts . ScriptTarget . ES2015 ;
break ;
case "es2016" :
out [ key ] = ts . ScriptTarget . ES2016 ;
break ;
case "es2017" :
out [ key ] = ts . ScriptTarget . ES2017 ;
break ;
case "es2018" :
out [ key ] = ts . ScriptTarget . ES2018 ;
break ;
case "es2019" :
out [ key ] = ts . ScriptTarget . ES2019 ;
break ;
case "es2020" :
out [ key ] = ts . ScriptTarget . ES2020 ;
break ;
case "esnext" :
out [ key ] = ts . ScriptTarget . ESNext ;
break ;
default :
throw new TypeError ( "Unexpected emit target." ) ;
}
break ;
case "types" :
const types = options [ key ] ;
assert ( types ) ;
files . push ( ... types ) ;
break ;
default :
out [ key ] = options [ key ] ;
}
}
return {
options : out ,
files : files . length ? files : undefined ,
} ;
}
const ignoredDiagnostics = [
// TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is
// not a module.
2306 ,
// TS1375: 'await' expressions are only allowed at the top level of a file
// when that file is a module, but this file has no imports or exports.
// Consider adding an empty 'export {}' to make this file a module.
1375 ,
// TS1103: 'for-await-of' statement is only allowed within an async function
// or async generator.
1103 ,
// TS2691: An import path cannot end with a '.ts' extension. Consider
// importing 'bad-module' instead.
2691 ,
// TS5009: Cannot find the common subdirectory path for the input files.
5009 ,
// TS5055: Cannot write file
// 'http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js'
// because it would overwrite input file.
5055 ,
// TypeScript is overly opinionated that only CommonJS modules kinds can
// support JSON imports. Allegedly this was fixed in
// Microsoft/TypeScript#26825 but that doesn't seem to be working here,
// so we will ignore complaints about this compiler setting.
5070 ,
// TS7016: Could not find a declaration file for module '...'. '...'
// implicitly has an 'any' type. This is due to `allowJs` being off by
// default but importing of a JavaScript module.
7016 ,
] ;
const stats = [ ] ;
let statsStart = 0 ;
function performanceStart ( ) {
stats . length = 0 ;
// TODO(kitsonk) replace with performance.mark() when landed
2020-07-23 09:29:36 -04:00
statsStart = opNow ( ) ;
2020-07-19 13:49:44 -04:00
ts . performance . enable ( ) ;
}
function performanceProgram ( {
program ,
fileCount ,
} ) {
if ( program ) {
if ( "getProgram" in program ) {
program = program . getProgram ( ) ;
}
stats . push ( { key : "Files" , value : program . getSourceFiles ( ) . length } ) ;
stats . push ( { key : "Nodes" , value : program . getNodeCount ( ) } ) ;
stats . push ( { key : "Identifiers" , value : program . getIdentifierCount ( ) } ) ;
stats . push ( { key : "Symbols" , value : program . getSymbolCount ( ) } ) ;
stats . push ( { key : "Types" , value : program . getTypeCount ( ) } ) ;
stats . push ( {
key : "Instantiations" ,
value : program . getInstantiationCount ( ) ,
} ) ;
} else if ( fileCount != null ) {
stats . push ( { key : "Files" , value : fileCount } ) ;
}
const programTime = ts . performance . getDuration ( "Program" ) ;
const bindTime = ts . performance . getDuration ( "Bind" ) ;
const checkTime = ts . performance . getDuration ( "Check" ) ;
const emitTime = ts . performance . getDuration ( "Emit" ) ;
stats . push ( { key : "Parse time" , value : programTime } ) ;
stats . push ( { key : "Bind time" , value : bindTime } ) ;
stats . push ( { key : "Check time" , value : checkTime } ) ;
stats . push ( { key : "Emit time" , value : emitTime } ) ;
stats . push ( {
key : "Total TS time" ,
value : programTime + bindTime + checkTime + emitTime ,
} ) ;
}
function performanceEnd ( ) {
// TODO(kitsonk) replace with performance.measure() when landed
2020-07-23 09:29:36 -04:00
const duration = opNow ( ) - statsStart ;
2020-07-19 13:49:44 -04:00
stats . push ( { key : "Compile time" , value : duration } ) ;
return stats ;
}
// TODO(Bartlomieju): this check should be done in Rust; there should be no
function processConfigureResponse (
configResult ,
configPath ,
) {
const { ignoredOptions , diagnostics } = configResult ;
if ( ignoredOptions ) {
2020-07-23 09:29:36 -04:00
const msg =
` Unsupported compiler options in " ${ configPath } " \n The following options were ignored: \n ${
ignoredOptions . map ( ( value ) => value ) . join ( ", " )
} \ n ` ;
core . print ( msg , true ) ;
2020-07-19 13:49:44 -04:00
}
return diagnostics ;
}
function normalizeString ( path ) {
let res = "" ;
let lastSegmentLength = 0 ;
let lastSlash = - 1 ;
let dots = 0 ;
let code ;
for ( let i = 0 , len = path . length ; i <= len ; ++ i ) {
if ( i < len ) code = path . charCodeAt ( i ) ;
else if ( code === CHAR _FORWARD _SLASH ) break ;
else code = CHAR _FORWARD _SLASH ;
if ( code === CHAR _FORWARD _SLASH ) {
if ( lastSlash === i - 1 || dots === 1 ) {
// NOOP
} else if ( lastSlash !== i - 1 && dots === 2 ) {
if (
res . length < 2 ||
lastSegmentLength !== 2 ||
res . charCodeAt ( res . length - 1 ) !== CHAR _DOT ||
res . charCodeAt ( res . length - 2 ) !== CHAR _DOT
) {
if ( res . length > 2 ) {
const lastSlashIndex = res . lastIndexOf ( "/" ) ;
if ( lastSlashIndex === - 1 ) {
res = "" ;
lastSegmentLength = 0 ;
} else {
res = res . slice ( 0 , lastSlashIndex ) ;
lastSegmentLength = res . length - 1 - res . lastIndexOf ( "/" ) ;
}
lastSlash = i ;
dots = 0 ;
continue ;
} else if ( res . length === 2 || res . length === 1 ) {
res = "" ;
lastSegmentLength = 0 ;
lastSlash = i ;
dots = 0 ;
continue ;
}
}
} else {
if ( res . length > 0 ) res += "/" + path . slice ( lastSlash + 1 , i ) ;
else res = path . slice ( lastSlash + 1 , i ) ;
lastSegmentLength = i - lastSlash - 1 ;
}
lastSlash = i ;
dots = 0 ;
} else if ( code === CHAR _DOT && dots !== - 1 ) {
++ dots ;
} else {
dots = - 1 ;
}
}
return res ;
}
function commonPath ( paths , sep = "/" ) {
const [ first = "" , ... remaining ] = paths ;
if ( first === "" || remaining . length === 0 ) {
return first . substring ( 0 , first . lastIndexOf ( sep ) + 1 ) ;
}
const parts = first . split ( sep ) ;
let endOfPrefix = parts . length ;
for ( const path of remaining ) {
const compare = path . split ( sep ) ;
for ( let i = 0 ; i < endOfPrefix ; i ++ ) {
if ( compare [ i ] !== parts [ i ] ) {
endOfPrefix = i ;
}
}
if ( endOfPrefix === 0 ) {
return "" ;
}
}
const prefix = parts . slice ( 0 , endOfPrefix ) . join ( sep ) ;
return prefix . endsWith ( sep ) ? prefix : ` ${ prefix } ${ sep } ` ;
}
let rootExports ;
function normalizeUrl ( rootName ) {
const match = /^(\S+:\/{2,3})(.+)$/ . exec ( rootName ) ;
if ( match ) {
const [ , protocol , path ] = match ;
return ` ${ protocol } ${ normalizeString ( path ) } ` ;
} else {
return rootName ;
}
}
function buildBundle (
rootName ,
data ,
sourceFiles ,
target ,
) {
// when outputting to AMD and a single outfile, TypeScript makes up the module
// specifiers which are used to define the modules, and doesn't expose them
// publicly, so we have to try to replicate
const sources = sourceFiles . map ( ( sf ) => sf . fileName ) ;
const sharedPath = commonPath ( sources ) ;
rootName = normalizeUrl ( rootName )
. replace ( sharedPath , "" )
. replace ( /\.\w+$/i , "" ) ;
// If one of the modules requires support for top-level-await, TypeScript will
// emit the execute function as an async function. When this is the case we
// need to bubble up the TLA to the instantiation, otherwise we instantiate
// synchronously.
const hasTla = data . match ( /execute:\sasync\sfunction\s/ ) ;
let instantiate ;
if ( rootExports && rootExports . length ) {
instantiate = hasTla
? ` const __exp = await __instantiate(" ${ rootName } ", true); \n `
: ` const __exp = __instantiate(" ${ rootName } ", false); \n ` ;
for ( const rootExport of rootExports ) {
if ( rootExport === "default" ) {
instantiate += ` export default __exp[" ${ rootExport } "]; \n ` ;
} else {
instantiate +=
` export const ${ rootExport } = __exp[" ${ rootExport } "]; \n ` ;
}
}
} else {
instantiate = hasTla
? ` await __instantiate(" ${ rootName } ", true); \n `
: ` __instantiate(" ${ rootName } ", false); \n ` ;
}
const es5Bundle = target === ts . ScriptTarget . ES3 ||
target === ts . ScriptTarget . ES5 ||
target === ts . ScriptTarget . ES2015 ||
target === ts . ScriptTarget . ES2016 ;
return ` ${
es5Bundle ? SYSTEM _LOADER _ES5 : SYSTEM _LOADER
} \ n$ { data } \ n$ { instantiate } ` ;
}
function setRootExports ( program , rootModule ) {
// get a reference to the type checker, this will let us find symbols from
// the AST.
const checker = program . getTypeChecker ( ) ;
// get a reference to the main source file for the bundle
const mainSourceFile = program . getSourceFile ( rootModule ) ;
assert ( mainSourceFile ) ;
// retrieve the internal TypeScript symbol for this AST node
const mainSymbol = checker . getSymbolAtLocation ( mainSourceFile ) ;
if ( ! mainSymbol ) {
return ;
}
rootExports = checker
. getExportsOfModule ( mainSymbol )
// .getExportsOfModule includes type only symbols which are exported from
// the module, so we need to try to filter those out. While not critical
// someone looking at the bundle would think there is runtime code behind
// that when there isn't. There appears to be no clean way of figuring that
// out, so inspecting SymbolFlags that might be present that are type only
. filter (
( sym ) =>
sym . flags & ts . SymbolFlags . Class ||
! (
sym . flags & ts . SymbolFlags . Interface ||
sym . flags & ts . SymbolFlags . TypeLiteral ||
sym . flags & ts . SymbolFlags . Signature ||
sym . flags & ts . SymbolFlags . TypeParameter ||
sym . flags & ts . SymbolFlags . TypeAlias ||
sym . flags & ts . SymbolFlags . Type ||
sym . flags & ts . SymbolFlags . Namespace ||
sym . flags & ts . SymbolFlags . InterfaceExcludes ||
sym . flags & ts . SymbolFlags . TypeParameterExcludes ||
sym . flags & ts . SymbolFlags . TypeAliasExcludes
) ,
)
. map ( ( sym ) => sym . getName ( ) ) ;
}
function compile ( {
allowJs ,
buildInfo ,
config ,
configPath ,
rootNames ,
target ,
unstable ,
cwd ,
sourceFileMap ,
type ,
performance ,
} ) {
if ( performance ) {
performanceStart ( ) ;
}
log ( ">>> compile start" , { rootNames , type : CompilerRequestType [ type ] } ) ;
// When a programme is emitted, TypeScript will call `writeFile` with
// each file that needs to be emitted. The Deno compiler host delegates
// this, to make it easier to perform the right actions, which vary
// based a lot on the request.
const state = {
rootNames ,
emitMap : { } ,
} ;
const host = new IncrementalCompileHost ( {
bundle : false ,
target ,
unstable ,
writeFile : createCompileWriteFile ( state ) ,
rootNames ,
buildInfo ,
} ) ;
let diagnostics = [ ] ;
host . mergeOptions ( { allowJs } ) ;
// if there is a configuration supplied, we need to parse that
if ( config && config . length && configPath ) {
const configResult = host . configure ( cwd , configPath , config ) ;
diagnostics = processConfigureResponse ( configResult , configPath ) || [ ] ;
}
buildSourceFileCache ( sourceFileMap ) ;
// if there was a configuration and no diagnostics with it, we will continue
// to generate the program and possibly emit it.
if ( diagnostics . length === 0 ) {
const options = host . getCompilationSettings ( ) ;
const program = ts . createIncrementalProgram ( {
rootNames ,
options ,
host ,
} ) ;
// TODO(bartlomieju): check if this is ok
diagnostics = [
... program . getConfigFileParsingDiagnostics ( ) ,
... program . getSyntacticDiagnostics ( ) ,
... program . getOptionsDiagnostics ( ) ,
... program . getGlobalDiagnostics ( ) ,
... program . getSemanticDiagnostics ( ) ,
] ;
diagnostics = diagnostics . filter (
( { code } ) => ! ignoredDiagnostics . includes ( code ) ,
) ;
// We will only proceed with the emit if there are no diagnostics.
if ( diagnostics . length === 0 ) {
const emitResult = program . emit ( ) ;
// If `checkJs` is off we still might be compiling entry point JavaScript file
// (if it has `.ts` imports), but it won't be emitted. In that case we skip
// assertion.
if ( options . checkJs ) {
assert (
emitResult . emitSkipped === false ,
"Unexpected skip of the emit." ,
) ;
}
// emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned
// without casting.
diagnostics = emitResult . diagnostics ;
}
performanceProgram ( { program } ) ;
}
log ( "<<< compile end" , { rootNames , type : CompilerRequestType [ type ] } ) ;
const stats = performance ? performanceEnd ( ) : undefined ;
return {
emitMap : state . emitMap ,
buildInfo : state . buildInfo ,
diagnostics : fromTypeScriptDiagnostic ( diagnostics ) ,
stats ,
} ;
}
function transpile ( {
config : configText ,
configPath ,
cwd ,
performance ,
sourceFiles ,
} ) {
if ( performance ) {
performanceStart ( ) ;
}
log ( ">>> transpile start" ) ;
let compilerOptions ;
if ( configText && configPath && cwd ) {
const { options , ... response } = configure (
DEFAULT _TRANSPILE _OPTIONS ,
configText ,
configPath ,
cwd ,
) ;
const diagnostics = processConfigureResponse ( response , configPath ) ;
if ( diagnostics && diagnostics . length ) {
return {
diagnostics : fromTypeScriptDiagnostic ( diagnostics ) ,
emitMap : { } ,
} ;
}
compilerOptions = options ;
} else {
compilerOptions = Object . assign ( { } , DEFAULT _TRANSPILE _OPTIONS ) ;
}
const emitMap = { } ;
let diagnostics = [ ] ;
for ( const { sourceCode , fileName } of sourceFiles ) {
const {
outputText ,
sourceMapText ,
diagnostics : diags ,
} = ts . transpileModule ( sourceCode , {
fileName ,
compilerOptions ,
reportDiagnostics : true ,
} ) ;
if ( diags ) {
diagnostics = diagnostics . concat ( ... diags ) ;
}
emitMap [ ` ${ fileName } .js ` ] = { filename : fileName , contents : outputText } ;
// currently we inline source maps, but this is good logic to have if this
// ever changes
if ( sourceMapText ) {
emitMap [ ` ${ fileName } .map ` ] = {
filename : fileName ,
contents : sourceMapText ,
} ;
}
}
performanceProgram ( { fileCount : sourceFiles . length } ) ;
const stats = performance ? performanceEnd ( ) : undefined ;
log ( "<<< transpile end" ) ;
return {
diagnostics : fromTypeScriptDiagnostic ( diagnostics ) ,
emitMap ,
stats ,
} ;
}
function bundle ( {
config ,
configPath ,
rootNames ,
target ,
unstable ,
cwd ,
sourceFileMap ,
type ,
2020-07-23 09:29:36 -04:00
performance ,
2020-07-19 13:49:44 -04:00
} ) {
if ( performance ) {
performanceStart ( ) ;
}
log ( ">>> bundle start" , {
rootNames ,
type : CompilerRequestType [ type ] ,
} ) ;
// When a programme is emitted, TypeScript will call `writeFile` with
// each file that needs to be emitted. The Deno compiler host delegates
// this, to make it easier to perform the right actions, which vary
// based a lot on the request.
const state = {
rootNames ,
bundleOutput : undefined ,
} ;
const host = new Host ( {
bundle : true ,
target ,
unstable ,
writeFile : createBundleWriteFile ( state ) ,
} ) ;
state . host = host ;
let diagnostics = [ ] ;
// if there is a configuration supplied, we need to parse that
if ( config && config . length && configPath ) {
const configResult = host . configure ( cwd , configPath , config ) ;
diagnostics = processConfigureResponse ( configResult , configPath ) || [ ] ;
}
buildSourceFileCache ( sourceFileMap ) ;
// if there was a configuration and no diagnostics with it, we will continue
// to generate the program and possibly emit it.
if ( diagnostics . length === 0 ) {
const options = host . getCompilationSettings ( ) ;
const program = ts . createProgram ( {
rootNames ,
options ,
host ,
} ) ;
diagnostics = ts
. getPreEmitDiagnostics ( program )
. filter ( ( { code } ) => ! ignoredDiagnostics . includes ( code ) ) ;
// We will only proceed with the emit if there are no diagnostics.
if ( diagnostics . length === 0 ) {
// we only support a single root module when bundling
assert ( rootNames . length === 1 ) ;
setRootExports ( program , rootNames [ 0 ] ) ;
const emitResult = program . emit ( ) ;
assert (
emitResult . emitSkipped === false ,
"Unexpected skip of the emit." ,
) ;
// emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned
// without casting.
diagnostics = emitResult . diagnostics ;
}
if ( performance ) {
performanceProgram ( { program } ) ;
}
}
let bundleOutput ;
if ( diagnostics . length === 0 ) {
assert ( state . bundleOutput ) ;
bundleOutput = state . bundleOutput ;
}
const stats = performance ? performanceEnd ( ) : undefined ;
const result = {
bundleOutput ,
diagnostics : fromTypeScriptDiagnostic ( diagnostics ) ,
stats ,
} ;
log ( "<<< bundle end" , {
rootNames ,
type : CompilerRequestType [ type ] ,
} ) ;
return result ;
}
function runtimeCompile (
request ,
) {
const { options , rootNames , target , unstable , sourceFileMap } = request ;
log ( ">>> runtime compile start" , {
rootNames ,
} ) ;
// if there are options, convert them into TypeScript compiler options,
// and resolve any external file references
let convertedOptions ;
if ( options ) {
const result = convertCompilerOptions ( options ) ;
convertedOptions = result . options ;
}
buildLocalSourceFileCache ( sourceFileMap ) ;
const state = {
rootNames ,
emitMap : { } ,
} ;
const host = new Host ( {
bundle : false ,
target ,
writeFile : createRuntimeCompileWriteFile ( state ) ,
} ) ;
const compilerOptions = [ DEFAULT _RUNTIME _COMPILE _OPTIONS ] ;
if ( convertedOptions ) {
compilerOptions . push ( convertedOptions ) ;
}
if ( unstable ) {
compilerOptions . push ( {
lib : [
"deno.unstable" ,
... ( ( convertedOptions && convertedOptions . lib ) || [ "deno.window" ] ) ,
] ,
} ) ;
}
host . mergeOptions ( ... compilerOptions ) ;
const program = ts . createProgram ( {
rootNames ,
options : host . getCompilationSettings ( ) ,
host ,
} ) ;
const diagnostics = ts
. getPreEmitDiagnostics ( program )
. filter ( ( { code } ) => ! ignoredDiagnostics . includes ( code ) ) ;
const emitResult = program . emit ( ) ;
assert ( emitResult . emitSkipped === false , "Unexpected skip of the emit." ) ;
log ( "<<< runtime compile finish" , {
rootNames ,
emitMap : Object . keys ( state . emitMap ) ,
} ) ;
const maybeDiagnostics = diagnostics . length
? fromTypeScriptDiagnostic ( diagnostics ) . items
: [ ] ;
return {
diagnostics : maybeDiagnostics ,
emitMap : state . emitMap ,
} ;
}
function runtimeBundle ( request ) {
const { options , rootNames , target , unstable , sourceFileMap } = request ;
log ( ">>> runtime bundle start" , {
rootNames ,
} ) ;
// if there are options, convert them into TypeScript compiler options,
// and resolve any external file references
let convertedOptions ;
if ( options ) {
const result = convertCompilerOptions ( options ) ;
convertedOptions = result . options ;
}
buildLocalSourceFileCache ( sourceFileMap ) ;
const state = {
rootNames ,
bundleOutput : undefined ,
} ;
const host = new Host ( {
bundle : true ,
target ,
writeFile : createBundleWriteFile ( state ) ,
} ) ;
state . host = host ;
const compilerOptions = [ DEFAULT _RUNTIME _COMPILE _OPTIONS ] ;
if ( convertedOptions ) {
compilerOptions . push ( convertedOptions ) ;
}
if ( unstable ) {
compilerOptions . push ( {
lib : [
"deno.unstable" ,
... ( ( convertedOptions && convertedOptions . lib ) || [ "deno.window" ] ) ,
] ,
} ) ;
}
compilerOptions . push ( DEFAULT _BUNDLER _OPTIONS ) ;
host . mergeOptions ( ... compilerOptions ) ;
const program = ts . createProgram ( {
rootNames ,
options : host . getCompilationSettings ( ) ,
host ,
} ) ;
setRootExports ( program , rootNames [ 0 ] ) ;
const diagnostics = ts
. getPreEmitDiagnostics ( program )
. filter ( ( { code } ) => ! ignoredDiagnostics . includes ( code ) ) ;
const emitResult = program . emit ( ) ;
assert ( emitResult . emitSkipped === false , "Unexpected skip of the emit." ) ;
log ( "<<< runtime bundle finish" , {
rootNames ,
} ) ;
const maybeDiagnostics = diagnostics . length
? fromTypeScriptDiagnostic ( diagnostics ) . items
: [ ] ;
return {
diagnostics : maybeDiagnostics ,
output : state . bundleOutput ,
} ;
}
function runtimeTranspile (
request ,
) {
const result = { } ;
const { sources , options } = request ;
const compilerOptions = options
? Object . assign (
{ } ,
DEFAULT _RUNTIME _TRANSPILE _OPTIONS ,
convertCompilerOptions ( options ) . options ,
)
: DEFAULT _RUNTIME _TRANSPILE _OPTIONS ;
for ( const [ fileName , inputText ] of Object . entries ( sources ) ) {
const { outputText : source , sourceMapText : map } = ts . transpileModule (
inputText ,
{
fileName ,
compilerOptions ,
} ,
) ;
result [ fileName ] = { source , map } ;
}
return Promise . resolve ( result ) ;
}
2020-07-23 09:29:36 -04:00
function opCompilerRespond ( msg ) {
dispatchJson . sendSync ( "op_compiler_respond" , msg ) ;
}
async function tsCompilerOnMessage ( msg ) {
const request = msg . data ;
2020-07-19 13:49:44 -04:00
switch ( request . type ) {
case CompilerRequestType . Compile : {
const result = compile ( request ) ;
2020-07-23 09:29:36 -04:00
opCompilerRespond ( result ) ;
2020-07-19 13:49:44 -04:00
break ;
}
case CompilerRequestType . Transpile : {
const result = transpile ( request ) ;
2020-07-23 09:29:36 -04:00
opCompilerRespond ( result ) ;
2020-07-19 13:49:44 -04:00
break ;
}
case CompilerRequestType . Bundle : {
const result = bundle ( request ) ;
2020-07-23 09:29:36 -04:00
opCompilerRespond ( result ) ;
2020-07-19 13:49:44 -04:00
break ;
}
case CompilerRequestType . RuntimeCompile : {
const result = runtimeCompile ( request ) ;
2020-07-23 09:29:36 -04:00
opCompilerRespond ( result ) ;
2020-07-19 13:49:44 -04:00
break ;
}
case CompilerRequestType . RuntimeBundle : {
const result = runtimeBundle ( request ) ;
2020-07-23 09:29:36 -04:00
opCompilerRespond ( result ) ;
2020-07-19 13:49:44 -04:00
break ;
}
case CompilerRequestType . RuntimeTranspile : {
const result = await runtimeTranspile ( request ) ;
2020-07-23 09:29:36 -04:00
opCompilerRespond ( result ) ;
2020-07-19 13:49:44 -04:00
break ;
}
default :
2020-07-23 09:29:36 -04:00
throw new Error (
2020-07-19 13:49:44 -04:00
` !!! unhandled CompilerRequestType: ${ request . type } ( ${
CompilerRequestType [ request . type ]
} ) ` ,
) ;
}
}
2020-07-23 09:29:36 -04:00
// TODO(bartlomieju): temporary solution, must be fixed when moving
// dispatches to separate crates
function initOps ( ) {
const opsMap = core . ops ( ) ;
for ( const [ _name , opId ] of Object . entries ( opsMap ) ) {
core . setAsyncHandler ( opId , dispatchJson . asyncMsgFromRust ) ;
}
2020-07-19 13:49:44 -04:00
}
2020-07-23 09:29:36 -04:00
function runtimeStart ( source ) {
initOps ( ) ;
// First we send an empty `Start` message to let the privileged side know we
// are ready. The response should be a `StartRes` message containing the CLI
// args and other info.
const s = dispatchJson . sendSync ( "op_start" ) ;
util . setLogDebug ( s . debugFlag , source ) ;
errorStack . setPrepareStackTrace ( Error ) ;
return s ;
}
let hasBootstrapped = false ;
function bootstrapCompilerRuntime ( ) {
if ( hasBootstrapped ) {
throw new Error ( "Worker runtime already bootstrapped" ) ;
}
hasBootstrapped = true ;
globalThis . _ _bootstrap = undefined ;
runtimeStart ( "TS" ) ;
}
globalThis . bootstrapCompilerRuntime = bootstrapCompilerRuntime ;
globalThis . tsCompilerOnMessage = tsCompilerOnMessage ;
2020-07-19 13:49:44 -04:00
} ) ( this ) ;