diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index dcef6ff4fb..33364963ee 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -15,6 +15,7 @@
true
true
snupkg
+ $(OtherFlags) --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen
diff --git a/src/Fable.Cli/Fable.Cli.fsproj b/src/Fable.Cli/Fable.Cli.fsproj
index 562e89f817..154d9aa91d 100644
--- a/src/Fable.Cli/Fable.Cli.fsproj
+++ b/src/Fable.Cli/Fable.Cli.fsproj
@@ -57,11 +57,13 @@
+
+
diff --git a/src/Fable.Cli/FileWatchers.fsi b/src/Fable.Cli/FileWatchers.fsi
new file mode 100644
index 0000000000..0ba984e013
--- /dev/null
+++ b/src/Fable.Cli/FileWatchers.fsi
@@ -0,0 +1,45 @@
+module Fable.Cli.FileWatcher
+
+open System
+open System.IO
+
+type IFileSystemWatcher =
+ inherit IDisposable
+
+ []
+ abstract OnFileChange: IEvent
+
+ []
+ abstract OnError: IEvent
+
+ /// Directory path
+ abstract BasePath: string with get, set
+ abstract EnableRaisingEvents: bool with get, set
+ /// File name filters
+ abstract GlobFilters: string list
+
+/// An alternative file watcher based on polling.
+/// ignoredDirectoryNameRegexes allows ignoring directories to improve performance.
+type PollingFileWatcher =
+ new:
+ watchedDirectoryPath: string * ignoredDirectoryNameRegexes: string seq ->
+ PollingFileWatcher
+
+ /// Defaults to false. Must be set to true to start raising events.
+ member EnableRaisingEvents: bool with get, set
+ interface IDisposable
+
+/// A wrapper around the immutable polling watcher,
+/// implementing IFileSystemWatcher with its mutable BasePath.
+type ResetablePollingFileWatcher =
+ new:
+ fileNameGlobFilters: string list *
+ ignoredDirectoryNameRegexes: string seq ->
+ ResetablePollingFileWatcher
+
+ interface IFileSystemWatcher
+
+/// A FileSystemWatcher wrapper that implements the IFileSystemWatcher interface.
+type DotnetFileWatcher =
+ new: globFilters: string list -> DotnetFileWatcher
+ interface IFileSystemWatcher
diff --git a/src/Fable.Cli/Util.fsi b/src/Fable.Cli/Util.fsi
new file mode 100644
index 0000000000..716540fd7d
--- /dev/null
+++ b/src/Fable.Cli/Util.fsi
@@ -0,0 +1,204 @@
+namespace Fable.Cli
+
+#nowarn "3391"
+
+open System
+
+type RunProcess =
+ new:
+ exeFile: string * args: string list * ?watch: bool * ?fast: bool ->
+ RunProcess
+
+ member ExeFile: string
+ member Args: string list
+ member IsWatch: bool
+ member IsFast: bool
+
+type CliArgs =
+ {
+ ProjectFile: string
+ RootDir: string
+ OutDir: string option
+ IsWatch: bool
+ Precompile: bool
+ PrecompiledLib: string option
+ PrintAst: bool
+ FableLibraryPath: string option
+ Configuration: string
+ NoRestore: bool
+ NoCache: bool
+ NoParallelTypeCheck: bool
+ SourceMaps: bool
+ SourceMapsRoot: string option
+ Exclude: string list
+ Replace: Map
+ RunProcess: RunProcess option
+ CompilerOptions: Fable.CompilerOptions
+ }
+
+ member ProjectFileAsRelativePath: string
+ member RunProcessEnv: (string * string) list
+
+[]
+module Log =
+ val newLine: string
+ /// To be called only at the beginning of the app
+ val makeVerbose: unit -> unit
+ val makeSilent: unit -> unit
+ val inSameLineIfNotCI: msg: string -> unit
+ val always: msg: string -> unit
+ val verbose: msg: Lazy -> unit
+ val warning: msg: string -> unit
+ val error: msg: string -> unit
+ val showFemtoMsg: show: (unit -> bool) -> unit
+
+module File =
+ val defaultFileExt: usesOutDir: bool -> language: Fable.Language -> string
+
+ val changeExtensionButUseDefaultExtensionInFableModules:
+ lang: Fable.Language ->
+ isInFableModules: bool ->
+ filePath: string ->
+ fileExt: string ->
+ string
+
+ val relPathToCurDir: path: string -> string
+ /// File.ReadAllText fails with locked files. See https://stackoverflow.com/a/1389172
+ val readAllTextNonBlocking: path: string -> string
+
+ val tryFindNonEmptyDirectoryUpwards:
+ opts:
+ {|
+ exclude: string list
+ matches: string list
+ |} ->
+ dir: string ->
+ string option
+
+ val tryFindUpwards: fileName: string -> dir: string -> string option
+
+ val tryNodeModulesBin:
+ workingDir: string -> exeFile: string -> string option
+
+ /// System.IO.GetFullPath doesn't change the case of the argument in case insensitive file systems
+ /// even if it doesn't match the actual path, causing unexpected issues when comparing files later.
+ val getExactFullPath: pathName: string -> string
+ /// FAKE and other tools clean dirs but don't remove them, so check whether it doesn't exist or it's empty
+ val isDirectoryEmpty: dir: string -> bool
+ val safeDelete: path: string -> unit
+ val withLock: dir: string -> action: (unit -> 'T) -> 'T
+
+[]
+module Process =
+ val startWithEnv:
+ envVars: (string * string) list ->
+ (string -> string -> string list -> unit)
+
+ val runSyncWithEnv:
+ envVars: (string * string) list ->
+ workingDir: string ->
+ exePath: string ->
+ args: string list ->
+ int
+
+ val runSync:
+ workingDir: string -> exePath: string -> args: string list -> int
+
+type PathResolver =
+ abstract TryPrecompiledOutPath:
+ sourceDir: string * relativePath: string -> string option
+
+ abstract GetOrAddDeduplicateTargetDir:
+ importDir: string * addTargetDir: (Set -> string) -> string
+
+module Imports =
+ val getRelativePath: path: string -> pathTo: string -> string
+
+ val getTargetAbsolutePath:
+ pathResolver: PathResolver ->
+ importPath: string ->
+ projDir: string ->
+ outDir: string ->
+ string
+
+ val getImportPath:
+ pathResolver: PathResolver ->
+ sourcePath: string ->
+ targetPath: string ->
+ projDir: string ->
+ outDir: string option ->
+ importPath: string ->
+ string
+
+module Observable =
+ type SingleObservable<'T> =
+ new: dispose: (unit -> unit) -> SingleObservable<'T>
+ member Trigger: v: 'T -> unit
+ interface IObservable<'T>
+
+ val throttle: ms: int -> obs: IObservable<'T> -> IObservable<'T array>
+
+[]
+module ResultCE =
+ type ResultBuilder =
+ new: unit -> ResultBuilder
+ member Zero: Result
+
+ member Bind:
+ v: Result<'d, 'e> * f: ('d -> Result<'f, 'e>) -> Result<'f, 'e>
+
+ member Return: v: 'b -> Result<'b, 'c>
+ member ReturnFrom: v: 'a -> 'a
+
+ val result: ResultBuilder
+
+module Json =
+ val read: path: string -> 'T
+ val write: path: string -> data: 'T -> unit
+
+module Performance =
+ val measure: f: (unit -> 'a) -> 'a * int64
+ val measureAsync: f: (unit -> Async<'a>) -> Async<'a * int64>
+
+type PrecompiledFileJson =
+ {
+ RootModule: string
+ OutPath: string
+ }
+
+type PrecompiledInfoJson =
+ {
+ CompilerVersion: string
+ CompilerOptions: Fable.CompilerOptions
+ FableLibDir: string
+ Files: Map
+ InlineExprHeaders: string[]
+ }
+
+type PrecompiledInfoImpl =
+ new:
+ fableModulesDir: string * info: PrecompiledInfoJson ->
+ PrecompiledInfoImpl
+
+ member CompilerVersion: string
+ member CompilerOptions: Fable.CompilerOptions
+ member Files: Map
+ member FableLibDir: string
+ member DllPath: string
+ member TryPrecompiledOutPath: normalizedFullPath: string -> string option
+ static member GetDllPath: fableModulesDir: string -> string
+ interface Fable.Transforms.State.PrecompiledInfo
+ static member GetPath: fableModulesDir: string -> string
+
+ static member GetInlineExprsPath:
+ fableModulesDir: string * index: int -> string
+
+ static member Load: fableModulesDir: string -> PrecompiledInfoImpl
+
+ static member Save:
+ files: Map *
+ inlineExprs: (string * 'a) array *
+ compilerOptions: Fable.CompilerOptions *
+ fableModulesDir: string *
+ fableLibDir: string ->
+ unit
diff --git a/src/Fable.Transforms/FSharp2Fable.fsi b/src/Fable.Transforms/FSharp2Fable.fsi
new file mode 100644
index 0000000000..f043a925d6
--- /dev/null
+++ b/src/Fable.Transforms/FSharp2Fable.fsi
@@ -0,0 +1,18 @@
+module rec Fable.Transforms.FSharp2Fable.Compiler
+
+open FSharp.Compiler.Symbols
+open Fable
+open Fable.AST
+
+val getRootFSharpEntities:
+ declarations: FSharpImplementationFileDeclaration list -> FSharpEntity seq
+
+val getRootModule:
+ declarations: FSharpImplementationFileDeclaration list -> string
+
+val getInlineExprs:
+ fileName: string ->
+ declarations: FSharpImplementationFileDeclaration list ->
+ (string * InlineExprLazy) list
+
+val transformFile: com: Compiler -> Fable.File
diff --git a/src/Fable.Transforms/Fable.Transforms.fsproj b/src/Fable.Transforms/Fable.Transforms.fsproj
index 173effc88e..1b24b1bc6e 100644
--- a/src/Fable.Transforms/Fable.Transforms.fsproj
+++ b/src/Fable.Transforms/Fable.Transforms.fsproj
@@ -24,7 +24,9 @@
+
+
diff --git a/src/Fable.Transforms/FableTransforms.fsi b/src/Fable.Transforms/FableTransforms.fsi
new file mode 100644
index 0000000000..87ce3df078
--- /dev/null
+++ b/src/Fable.Transforms/FableTransforms.fsi
@@ -0,0 +1,20 @@
+module Fable.Transforms.FableTransforms
+
+open Fable
+open Fable.AST.Fable
+
+val isIdentCaptured: identName: string -> expr: Expr -> bool
+val isTailRecursive: identName: string -> expr: Expr -> bool * bool
+val replaceValues: replacements: Map -> expr: Expr -> Expr
+val uncurryType: typ: Type -> Type
+
+val getTransformations: _com: Compiler -> (#Compiler -> Expr -> Expr) list
+
+val transformDeclaration:
+ transformations: (Compiler -> Expr -> Expr) list ->
+ com: Compiler ->
+ file: File ->
+ decl: Declaration ->
+ Declaration
+
+val transformFile: com: Compiler -> file: File -> File