2021-01-11 12:13:41 -05:00
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
2020-11-01 21:51:56 -05:00
use crate ::ast ;
2020-09-24 18:31:17 -04:00
use crate ::ast ::parse ;
2020-10-19 23:10:42 -04:00
use crate ::ast ::transpile_module ;
use crate ::ast ::BundleHook ;
2020-09-24 18:31:17 -04:00
use crate ::ast ::Location ;
use crate ::ast ::ParsedModule ;
2021-03-01 06:49:58 -05:00
use crate ::checksum ;
2020-10-22 20:50:15 -04:00
use crate ::colors ;
2021-05-10 12:16:39 -04:00
use crate ::config_file ::ConfigFile ;
use crate ::config_file ::IgnoredCompilerOptions ;
use crate ::config_file ::TsConfig ;
2020-10-22 20:50:15 -04:00
use crate ::diagnostics ::Diagnostics ;
2020-09-24 18:31:17 -04:00
use crate ::import_map ::ImportMap ;
2021-05-30 20:20:34 -04:00
use crate ::import_map ::ImportMapError ;
2021-03-01 06:49:58 -05:00
use crate ::info ;
2020-09-24 18:31:17 -04:00
use crate ::lockfile ::Lockfile ;
use crate ::media_type ::MediaType ;
use crate ::specifier_handler ::CachedModule ;
2020-10-22 20:50:15 -04:00
use crate ::specifier_handler ::Dependency ;
2020-09-24 18:31:17 -04:00
use crate ::specifier_handler ::DependencyMap ;
2020-10-13 06:54:28 -04:00
use crate ::specifier_handler ::Emit ;
2020-09-24 18:31:17 -04:00
use crate ::specifier_handler ::FetchFuture ;
use crate ::specifier_handler ::SpecifierHandler ;
2020-11-02 14:41:20 -05:00
use crate ::tsc ;
2020-09-30 07:46:42 -04:00
use crate ::version ;
2020-12-15 00:52:55 -05:00
use deno_core ::error ::anyhow ;
use deno_core ::error ::custom_error ;
2021-01-17 16:58:23 -05:00
use deno_core ::error ::get_custom_error_class ;
2021-03-26 12:34:25 -04:00
use deno_core ::error ::AnyError ;
2020-10-19 23:10:42 -04:00
use deno_core ::error ::Context ;
2020-09-24 18:31:17 -04:00
use deno_core ::futures ::stream ::FuturesUnordered ;
use deno_core ::futures ::stream ::StreamExt ;
2021-02-17 13:47:18 -05:00
use deno_core ::resolve_url_or_path ;
2020-12-07 05:46:39 -05:00
use deno_core ::serde ::Deserialize ;
use deno_core ::serde ::Deserializer ;
2020-10-22 20:50:15 -04:00
use deno_core ::serde ::Serialize ;
use deno_core ::serde ::Serializer ;
2020-09-24 18:31:17 -04:00
use deno_core ::serde_json ::json ;
2020-11-01 21:51:56 -05:00
use deno_core ::serde_json ::Value ;
2021-06-01 02:45:37 -04:00
use deno_core ::url ::Url ;
2020-10-22 20:50:15 -04:00
use deno_core ::ModuleResolutionError ;
2020-12-15 00:52:55 -05:00
use deno_core ::ModuleSource ;
2020-09-24 18:31:17 -04:00
use deno_core ::ModuleSpecifier ;
2021-03-26 12:34:25 -04:00
use log ::debug ;
2020-09-24 18:31:17 -04:00
use regex ::Regex ;
2021-03-01 06:49:58 -05:00
use std ::collections ::HashMap ;
2020-09-24 18:31:17 -04:00
use std ::collections ::HashSet ;
use std ::error ::Error ;
use std ::fmt ;
2020-10-11 22:25:27 -04:00
use std ::path ::PathBuf ;
2020-09-24 18:31:17 -04:00
use std ::rc ::Rc ;
use std ::result ;
2020-10-23 17:01:54 -04:00
use std ::sync ::Arc ;
2020-09-24 18:31:17 -04:00
use std ::sync ::Mutex ;
use std ::time ::Instant ;
2021-05-24 22:34:01 -04:00
use swc_common ::comments ::Comment ;
use swc_common ::BytePos ;
use swc_common ::Span ;
2020-09-24 18:31:17 -04:00
2021-03-26 12:34:25 -04:00
lazy_static ::lazy_static! {
2020-09-24 18:31:17 -04:00
/// Matched the `@deno-types` pragma.
static ref DENO_TYPES_RE : Regex =
Regex ::new ( r # "(?i)^\s*@deno-types\s*=\s*(?:["']([^"']+)["']|(\S+))"# )
. unwrap ( ) ;
/// Matches a `/// <reference ... />` comment reference.
static ref TRIPLE_SLASH_REFERENCE_RE : Regex =
Regex ::new ( r "(?i)^/\s*<reference\s.*?/>" ) . unwrap ( ) ;
/// Matches a path reference, which adds a dependency to a module
static ref PATH_REFERENCE_RE : Regex =
Regex ::new ( r # "(?i)\spath\s*=\s*["']([^"']*)["']"# ) . unwrap ( ) ;
/// Matches a types reference, which for JavaScript files indicates the
/// location of types to use when type checking a program that includes it as
/// a dependency.
static ref TYPES_REFERENCE_RE : Regex =
Regex ::new ( r # "(?i)\stypes\s*=\s*["']([^"']*)["']"# ) . unwrap ( ) ;
}
/// A group of errors that represent errors that can occur when interacting with
/// a module graph.
#[ derive(Debug, Clone, Eq, PartialEq) ]
pub enum GraphError {
/// A module using the HTTPS protocol is trying to import a module with an
/// HTTP schema.
InvalidDowngrade ( ModuleSpecifier , Location ) ,
/// A remote module is trying to import a local module.
InvalidLocalImport ( ModuleSpecifier , Location ) ,
2020-10-22 20:50:15 -04:00
/// The source code is invalid, as it does not match the expected hash in the
/// lockfile.
InvalidSource ( ModuleSpecifier , PathBuf ) ,
2020-09-24 18:31:17 -04:00
/// An unexpected dependency was requested for a module.
MissingDependency ( ModuleSpecifier , String ) ,
/// An unexpected specifier was requested.
MissingSpecifier ( ModuleSpecifier ) ,
/// The current feature is not supported.
NotSupported ( String ) ,
2020-10-22 20:50:15 -04:00
/// A unsupported media type was attempted to be imported as a module.
UnsupportedImportType ( ModuleSpecifier , MediaType ) ,
2020-09-24 18:31:17 -04:00
}
impl fmt ::Display for GraphError {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self {
2020-10-22 20:50:15 -04:00
GraphError ::InvalidDowngrade ( ref specifier , ref location ) = > write! ( f , " Modules imported via https are not allowed to import http modules. \n Importing: {} \n at {} " , specifier , location ) ,
GraphError ::InvalidLocalImport ( ref specifier , ref location ) = > write! ( f , " Remote modules are not allowed to import local modules. Consider using a dynamic import instead. \n Importing: {} \n at {} " , specifier , location ) ,
GraphError ::InvalidSource ( ref specifier , ref lockfile ) = > write! ( f , " The source code is invalid, as it does not match the expected hash in the lock file. \n Specifier: {} \n Lock file: {} " , specifier , lockfile . to_str ( ) . unwrap ( ) ) ,
GraphError ::MissingDependency ( ref referrer , specifier ) = > write! (
2020-09-24 18:31:17 -04:00
f ,
" The graph is missing a dependency. \n Specifier: {} from {} " ,
specifier , referrer
) ,
2020-10-22 20:50:15 -04:00
GraphError ::MissingSpecifier ( ref specifier ) = > write! (
2020-09-24 18:31:17 -04:00
f ,
" The graph is missing a specifier. \n Specifier: {} " ,
specifier
) ,
2020-10-22 20:50:15 -04:00
GraphError ::NotSupported ( ref msg ) = > write! ( f , " {} " , msg ) ,
GraphError ::UnsupportedImportType ( ref specifier , ref media_type ) = > write! ( f , " An unsupported media type was attempted to be imported as a module. \n Specifier: {} \n MediaType: {} " , specifier , media_type ) ,
2020-09-24 18:31:17 -04:00
}
}
}
impl Error for GraphError { }
2020-10-19 23:10:42 -04:00
/// A structure for handling bundle loading, which is implemented here, to
/// avoid a circular dependency with `ast`.
struct BundleLoader < ' a > {
cm : Rc < swc_common ::SourceMap > ,
2020-11-01 21:51:56 -05:00
emit_options : & ' a ast ::EmitOptions ,
2020-11-02 06:33:43 -05:00
globals : & ' a swc_common ::Globals ,
2020-11-02 14:41:20 -05:00
graph : & ' a Graph ,
2020-10-19 23:10:42 -04:00
}
impl < ' a > BundleLoader < ' a > {
pub fn new (
2020-11-02 14:41:20 -05:00
graph : & ' a Graph ,
2020-11-01 21:51:56 -05:00
emit_options : & ' a ast ::EmitOptions ,
2020-11-02 06:33:43 -05:00
globals : & ' a swc_common ::Globals ,
2020-10-19 23:10:42 -04:00
cm : Rc < swc_common ::SourceMap > ,
) -> Self {
BundleLoader {
cm ,
emit_options ,
2020-11-02 06:33:43 -05:00
globals ,
graph ,
2020-10-19 23:10:42 -04:00
}
}
}
impl swc_bundler ::Load for BundleLoader < '_ > {
fn load (
& self ,
file : & swc_common ::FileName ,
2020-11-08 17:27:36 -05:00
) -> Result < swc_bundler ::ModuleData , AnyError > {
2020-10-19 23:10:42 -04:00
match file {
swc_common ::FileName ::Custom ( filename ) = > {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( filename )
2020-10-19 23:10:42 -04:00
. context ( " Failed to convert swc FileName to ModuleSpecifier. " ) ? ;
if let Some ( src ) = self . graph . get_source ( & specifier ) {
let media_type = self
. graph
. get_media_type ( & specifier )
. context ( " Looking up media type during bundling. " ) ? ;
2020-11-08 17:27:36 -05:00
let ( source_file , module ) = transpile_module (
2020-10-19 23:10:42 -04:00
filename ,
& src ,
& media_type ,
self . emit_options ,
2020-11-02 06:33:43 -05:00
self . globals ,
2020-10-19 23:10:42 -04:00
self . cm . clone ( ) ,
2020-11-08 17:27:36 -05:00
) ? ;
Ok ( swc_bundler ::ModuleData {
fm : source_file ,
module ,
helpers : Default ::default ( ) ,
} )
2020-10-19 23:10:42 -04:00
} else {
2020-10-22 20:50:15 -04:00
Err (
GraphError ::MissingDependency ( specifier , " <bundle> " . to_string ( ) )
. into ( ) ,
)
2020-10-19 23:10:42 -04:00
}
}
_ = > unreachable! ( " Received request for unsupported filename {:?} " , file ) ,
}
}
}
2020-09-24 18:31:17 -04:00
/// An enum which represents the parsed out values of references in source code.
#[ derive(Debug, Clone, Eq, PartialEq) ]
2020-12-07 05:46:39 -05:00
pub enum TypeScriptReference {
2020-09-24 18:31:17 -04:00
Path ( String ) ,
Types ( String ) ,
}
2021-05-24 22:34:01 -04:00
fn match_to_span ( comment : & Comment , m : & regex ::Match ) -> Span {
Span {
lo : comment . span . lo + BytePos ( ( m . start ( ) + 1 ) as u32 ) ,
hi : comment . span . lo + BytePos ( ( m . end ( ) + 1 ) as u32 ) ,
ctxt : comment . span . ctxt ,
}
}
2020-09-24 18:31:17 -04:00
/// Determine if a comment contains a triple slash reference and optionally
/// return its kind and value.
2021-05-24 22:34:01 -04:00
pub fn parse_ts_reference (
comment : & Comment ,
) -> Option < ( TypeScriptReference , Span ) > {
if ! TRIPLE_SLASH_REFERENCE_RE . is_match ( & comment . text ) {
2020-09-24 18:31:17 -04:00
None
2021-05-24 22:34:01 -04:00
} else if let Some ( captures ) = PATH_REFERENCE_RE . captures ( & comment . text ) {
let m = captures . get ( 1 ) . unwrap ( ) ;
Some ( (
TypeScriptReference ::Path ( m . as_str ( ) . to_string ( ) ) ,
match_to_span ( comment , & m ) ,
2020-09-24 18:31:17 -04:00
) )
} else {
2021-05-24 22:34:01 -04:00
TYPES_REFERENCE_RE . captures ( & comment . text ) . map ( | captures | {
let m = captures . get ( 1 ) . unwrap ( ) ;
(
TypeScriptReference ::Types ( m . as_str ( ) . to_string ( ) ) ,
match_to_span ( comment , & m ) ,
)
2021-03-25 14:17:37 -04:00
} )
2020-09-24 18:31:17 -04:00
}
}
/// Determine if a comment contains a `@deno-types` pragma and optionally return
/// its value.
2021-05-24 22:34:01 -04:00
pub fn parse_deno_types ( comment : & Comment ) -> Option < ( String , Span ) > {
let captures = DENO_TYPES_RE . captures ( & comment . text ) ? ;
if let Some ( m ) = captures . get ( 1 ) {
Some ( ( m . as_str ( ) . to_string ( ) , match_to_span ( comment , & m ) ) )
} else if let Some ( m ) = captures . get ( 2 ) {
Some ( ( m . as_str ( ) . to_string ( ) , match_to_span ( comment , & m ) ) )
2020-09-24 18:31:17 -04:00
} else {
2021-05-24 22:34:01 -04:00
unreachable! ( ) ;
2020-09-24 18:31:17 -04:00
}
}
2020-09-30 07:46:42 -04:00
/// A hashing function that takes the source code, version and optionally a
/// user provided config and generates a string hash which can be stored to
/// determine if the cached emit is valid or not.
2020-10-07 07:43:44 -04:00
fn get_version ( source : & str , version : & str , config : & [ u8 ] ) -> String {
crate ::checksum ::gen ( & [ source . as_bytes ( ) , version . as_bytes ( ) , config ] )
2020-09-30 07:46:42 -04:00
}
2020-09-24 18:31:17 -04:00
/// A logical representation of a module within a graph.
#[ derive(Debug, Clone) ]
2020-12-07 05:46:39 -05:00
pub struct Module {
pub dependencies : DependencyMap ,
2020-09-24 18:31:17 -04:00
is_dirty : bool ,
is_parsed : bool ,
2020-10-13 06:54:28 -04:00
maybe_emit : Option < Emit > ,
maybe_emit_path : Option < ( PathBuf , Option < PathBuf > ) > ,
2020-12-29 23:17:17 -05:00
maybe_import_map : Option < Arc < Mutex < ImportMap > > > ,
2020-09-24 18:31:17 -04:00
maybe_types : Option < ( String , ModuleSpecifier ) > ,
2020-09-30 07:46:42 -04:00
maybe_version : Option < String > ,
2020-09-24 18:31:17 -04:00
media_type : MediaType ,
specifier : ModuleSpecifier ,
2020-10-07 07:43:44 -04:00
source : String ,
2020-10-11 22:25:27 -04:00
source_path : PathBuf ,
2020-09-24 18:31:17 -04:00
}
impl Default for Module {
fn default ( ) -> Self {
Module {
dependencies : HashMap ::new ( ) ,
is_dirty : false ,
is_parsed : false ,
2020-10-13 06:54:28 -04:00
maybe_emit : None ,
2020-10-11 22:25:27 -04:00
maybe_emit_path : None ,
2020-09-24 18:31:17 -04:00
maybe_import_map : None ,
maybe_types : None ,
2020-09-30 07:46:42 -04:00
maybe_version : None ,
2020-09-24 18:31:17 -04:00
media_type : MediaType ::Unknown ,
2021-02-17 13:47:18 -05:00
specifier : deno_core ::resolve_url ( " file:///example.js " ) . unwrap ( ) ,
2020-10-07 07:43:44 -04:00
source : " " . to_string ( ) ,
2020-10-11 22:25:27 -04:00
source_path : PathBuf ::new ( ) ,
2020-09-24 18:31:17 -04:00
}
}
}
impl Module {
pub fn new (
2020-10-15 19:34:55 -04:00
cached_module : CachedModule ,
2020-10-22 20:50:15 -04:00
is_root : bool ,
2020-12-29 23:17:17 -05:00
maybe_import_map : Option < Arc < Mutex < ImportMap > > > ,
2020-09-24 18:31:17 -04:00
) -> Self {
2020-10-22 20:50:15 -04:00
// If this is a local root file, and its media type is unknown, set the
// media type to JavaScript. This allows easier ability to create "shell"
// scripts with Deno.
let media_type = if is_root
& & ! cached_module . is_remote
& & cached_module . media_type = = MediaType ::Unknown
{
MediaType ::JavaScript
} else {
cached_module . media_type
} ;
2020-10-15 19:34:55 -04:00
let mut module = Module {
specifier : cached_module . specifier ,
2020-09-24 18:31:17 -04:00
maybe_import_map ,
2020-10-22 20:50:15 -04:00
media_type ,
2020-10-15 19:34:55 -04:00
source : cached_module . source ,
source_path : cached_module . source_path ,
maybe_emit : cached_module . maybe_emit ,
maybe_emit_path : cached_module . maybe_emit_path ,
maybe_version : cached_module . maybe_version ,
is_dirty : false ,
.. Self ::default ( )
} ;
if module . maybe_import_map . is_none ( ) {
2020-09-24 18:31:17 -04:00
if let Some ( dependencies ) = cached_module . maybe_dependencies {
2020-10-15 19:34:55 -04:00
module . dependencies = dependencies ;
module . is_parsed = true ;
2020-09-24 18:31:17 -04:00
}
}
2021-03-25 14:17:37 -04:00
module . maybe_types = cached_module . maybe_types . map ( | specifier | {
(
2020-09-24 18:31:17 -04:00
specifier . clone ( ) ,
2020-10-15 19:34:55 -04:00
module
2020-09-24 18:31:17 -04:00
. resolve_import ( & specifier , None )
. expect ( " could not resolve module " ) ,
2021-03-25 14:17:37 -04:00
)
} ) ;
2020-10-15 19:34:55 -04:00
module
}
/// Return `true` if the current hash of the module matches the stored
/// version.
pub fn is_emit_valid ( & self , config : & [ u8 ] ) -> bool {
if let Some ( version ) = self . maybe_version . clone ( ) {
2020-11-25 05:30:14 -05:00
version = = get_version ( & self . source , & version ::deno ( ) , config )
2020-10-15 19:34:55 -04:00
} else {
false
}
2020-09-24 18:31:17 -04:00
}
2020-10-22 20:50:15 -04:00
/// Parse a module, populating the structure with data retrieved from the
/// source of the module.
2020-12-29 23:17:17 -05:00
pub fn parse ( & mut self ) -> Result < ParsedModule , AnyError > {
2020-10-26 09:03:03 -04:00
let parsed_module =
parse ( self . specifier . as_str ( ) , & self . source , & self . media_type ) ? ;
2020-09-24 18:31:17 -04:00
// parse out any triple slash references
for comment in parsed_module . get_leading_comments ( ) . iter ( ) {
2021-05-24 22:34:01 -04:00
if let Some ( ( ts_reference , _ ) ) = parse_ts_reference ( & comment ) {
2020-10-22 20:50:15 -04:00
let location = parsed_module . get_location ( & comment . span ) ;
2020-09-24 18:31:17 -04:00
match ts_reference {
TypeScriptReference ::Path ( import ) = > {
2020-10-22 20:50:15 -04:00
let specifier =
self . resolve_import ( & import , Some ( location . clone ( ) ) ) ? ;
let dep = self
. dependencies
. entry ( import )
. or_insert_with ( | | Dependency ::new ( location ) ) ;
2020-09-24 18:31:17 -04:00
dep . maybe_code = Some ( specifier ) ;
}
TypeScriptReference ::Types ( import ) = > {
2020-10-22 20:50:15 -04:00
let specifier =
self . resolve_import ( & import , Some ( location . clone ( ) ) ) ? ;
2020-09-24 18:31:17 -04:00
if self . media_type = = MediaType ::JavaScript
2021-03-25 14:17:37 -04:00
| | self . media_type = = MediaType ::Jsx
2020-09-24 18:31:17 -04:00
{
// TODO(kitsonk) we need to specifically update the cache when
// this value changes
self . maybe_types = Some ( ( import . clone ( ) , specifier ) ) ;
} else {
2020-10-22 20:50:15 -04:00
let dep = self
. dependencies
. entry ( import )
. or_insert_with ( | | Dependency ::new ( location ) ) ;
2020-09-24 18:31:17 -04:00
dep . maybe_type = Some ( specifier ) ;
}
}
}
}
}
// Parse out all the syntactical dependencies for a module
let dependencies = parsed_module . analyze_dependencies ( ) ;
2020-10-19 23:10:42 -04:00
for desc in dependencies . iter ( ) . filter ( | desc | {
desc . kind ! = swc_ecmascript ::dep_graph ::DependencyKind ::Require
} ) {
2020-09-24 18:31:17 -04:00
let location = Location {
filename : self . specifier . to_string ( ) ,
col : desc . col ,
line : desc . line ,
} ;
2020-10-22 20:50:15 -04:00
// In situations where there is a potential issue with resolving the
// import specifier, that ends up being a module resolution error for a
// code dependency, we should not throw in the `ModuleGraph` but instead
// wait until runtime and throw there, as with dynamic imports they need
// to be catchable, which means they need to be resolved at runtime.
let maybe_specifier =
match self . resolve_import ( & desc . specifier , Some ( location . clone ( ) ) ) {
Ok ( specifier ) = > Some ( specifier ) ,
Err ( any_error ) = > {
match any_error . downcast_ref ::< ModuleResolutionError > ( ) {
2021-06-01 02:45:37 -04:00
Some ( ModuleResolutionError ::ImportPrefixMissing ( .. ) ) = > {
Some ( Url ::parse ( & format! ( " bare: {} " , & desc . specifier ) ) . unwrap ( ) )
}
2021-05-30 20:20:34 -04:00
_ = > match any_error . downcast_ref ::< ImportMapError > ( ) {
2021-06-01 02:45:37 -04:00
Some ( ImportMapError ::UnmappedBareSpecifier ( .. ) ) = > Some (
Url ::parse ( & format! ( " bare: {} " , & desc . specifier ) ) . unwrap ( ) ,
) ,
2021-05-30 20:20:34 -04:00
_ = > {
return Err ( any_error ) ;
}
} ,
2020-10-22 20:50:15 -04:00
}
}
} ;
2020-09-24 18:31:17 -04:00
// Parse out any `@deno-types` pragmas and modify dependency
2020-10-22 20:50:15 -04:00
let maybe_type = if ! desc . leading_comments . is_empty ( ) {
2020-09-24 18:31:17 -04:00
let comment = desc . leading_comments . last ( ) . unwrap ( ) ;
2021-05-24 22:34:01 -04:00
if let Some ( ( deno_types , _ ) ) = parse_deno_types ( & comment ) . as_ref ( ) {
2020-10-22 20:50:15 -04:00
Some ( self . resolve_import ( deno_types , Some ( location . clone ( ) ) ) ? )
2020-09-24 18:31:17 -04:00
} else {
None
}
} else {
None
} ;
let dep = self
. dependencies
. entry ( desc . specifier . to_string ( ) )
2020-10-22 20:50:15 -04:00
. or_insert_with ( | | Dependency ::new ( location ) ) ;
dep . is_dynamic = desc . is_dynamic ;
if let Some ( specifier ) = maybe_specifier {
if desc . kind = = swc_ecmascript ::dep_graph ::DependencyKind ::ExportType
| | desc . kind = = swc_ecmascript ::dep_graph ::DependencyKind ::ImportType
{
dep . maybe_type = Some ( specifier ) ;
} else {
dep . maybe_code = Some ( specifier ) ;
}
2020-09-24 18:31:17 -04:00
}
2020-10-22 20:50:15 -04:00
// If the dependency wasn't a type only dependency already, and there is
// a `@deno-types` comment, then we will set the `maybe_type` dependency.
if maybe_type . is_some ( ) & & dep . maybe_type . is_none ( ) {
dep . maybe_type = maybe_type ;
2020-09-24 18:31:17 -04:00
}
}
2020-12-29 23:17:17 -05:00
Ok ( parsed_module )
2020-09-24 18:31:17 -04:00
}
fn resolve_import (
& self ,
specifier : & str ,
maybe_location : Option < Location > ,
2020-09-29 03:16:12 -04:00
) -> Result < ModuleSpecifier , AnyError > {
2020-09-24 18:31:17 -04:00
let maybe_resolve = if let Some ( import_map ) = self . maybe_import_map . clone ( )
{
2021-05-30 20:20:34 -04:00
let import_map = import_map . lock ( ) . unwrap ( ) ;
Some ( import_map . resolve ( specifier , self . specifier . as_str ( ) ) ? )
2020-09-24 18:31:17 -04:00
} else {
None
} ;
2020-11-06 23:04:22 -05:00
let mut remapped_import = false ;
2020-09-24 18:31:17 -04:00
let specifier = if let Some ( module_specifier ) = maybe_resolve {
2020-11-06 23:04:22 -05:00
remapped_import = true ;
2020-09-24 18:31:17 -04:00
module_specifier
} else {
2021-02-17 13:47:18 -05:00
deno_core ::resolve_import ( specifier , self . specifier . as_str ( ) ) ?
2020-09-24 18:31:17 -04:00
} ;
2021-02-17 13:47:18 -05:00
let referrer_scheme = self . specifier . scheme ( ) ;
let specifier_scheme = specifier . scheme ( ) ;
2020-09-24 18:31:17 -04:00
let location = maybe_location . unwrap_or ( Location {
filename : self . specifier . to_string ( ) ,
line : 0 ,
col : 0 ,
} ) ;
// Disallow downgrades from HTTPS to HTTP
if referrer_scheme = = " https " & & specifier_scheme = = " http " {
2020-10-22 20:50:15 -04:00
return Err (
GraphError ::InvalidDowngrade ( specifier . clone ( ) , location ) . into ( ) ,
) ;
2020-09-24 18:31:17 -04:00
}
2020-11-06 23:04:22 -05:00
// Disallow a remote URL from trying to import a local URL, unless it is a
// remapped import via the import map
2020-09-24 18:31:17 -04:00
if ( referrer_scheme = = " https " | | referrer_scheme = = " http " )
& & ! ( specifier_scheme = = " https " | | specifier_scheme = = " http " )
2020-11-06 23:04:22 -05:00
& & ! remapped_import
2020-09-24 18:31:17 -04:00
{
2020-10-22 20:50:15 -04:00
return Err (
GraphError ::InvalidLocalImport ( specifier . clone ( ) , location ) . into ( ) ,
) ;
2020-09-24 18:31:17 -04:00
}
Ok ( specifier )
}
2020-09-30 07:46:42 -04:00
2020-12-15 00:52:55 -05:00
pub fn set_emit ( & mut self , code : String , maybe_map : Option < String > ) {
self . maybe_emit = Some ( Emit ::Cli ( ( code , maybe_map ) ) ) ;
}
2020-09-30 07:46:42 -04:00
/// Calculate the hashed version of the module and update the `maybe_version`.
pub fn set_version ( & mut self , config : & [ u8 ] ) {
2020-11-25 05:30:14 -05:00
self . maybe_version =
Some ( get_version ( & self . source , & version ::deno ( ) , config ) )
2020-09-30 07:46:42 -04:00
}
2020-10-11 22:25:27 -04:00
pub fn size ( & self ) -> usize {
self . source . as_bytes ( ) . len ( )
}
2020-09-24 18:31:17 -04:00
}
2020-11-01 21:51:56 -05:00
#[ derive(Clone, Debug, Default, Eq, PartialEq) ]
2020-12-31 16:43:54 -05:00
pub struct Stats ( pub Vec < ( String , u32 ) > ) ;
2020-09-24 18:31:17 -04:00
impl < ' de > Deserialize < ' de > for Stats {
fn deserialize < D > ( deserializer : D ) -> result ::Result < Self , D ::Error >
where
D : Deserializer < ' de > ,
{
2020-12-31 16:43:54 -05:00
let items : Vec < ( String , u32 ) > = Deserialize ::deserialize ( deserializer ) ? ;
2020-09-24 18:31:17 -04:00
Ok ( Stats ( items ) )
}
}
2020-12-31 16:43:54 -05:00
impl Serialize for Stats {
fn serialize < S > ( & self , serializer : S ) -> Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
Serialize ::serialize ( & self . 0 , serializer )
}
}
2020-09-24 18:31:17 -04:00
impl fmt ::Display for Stats {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
2020-10-22 20:50:15 -04:00
writeln! ( f , " Compilation statistics: " ) ? ;
2020-09-24 18:31:17 -04:00
for ( key , value ) in self . 0. clone ( ) {
2020-10-22 20:50:15 -04:00
writeln! ( f , " {}: {} " , key , value ) ? ;
2020-09-24 18:31:17 -04:00
}
Ok ( ( ) )
}
}
2020-11-01 21:51:56 -05:00
/// A structure that provides information about a module graph result.
#[ derive(Debug, Default) ]
pub struct ResultInfo {
/// A structure which provides diagnostic information (usually from `tsc`)
/// about the code in the module graph.
pub diagnostics : Diagnostics ,
2020-12-15 00:52:55 -05:00
/// A map of specifiers to the result of their resolution in the module graph.
pub loadable_modules :
HashMap < ModuleSpecifier , Result < ModuleSource , AnyError > > ,
2020-11-01 21:51:56 -05:00
/// Optionally ignored compiler options that represent any options that were
/// ignored if there was a user provided configuration.
pub maybe_ignored_options : Option < IgnoredCompilerOptions > ,
/// A structure providing key metrics around the operation performed, in
/// milliseconds.
pub stats : Stats ,
}
/// Represents the "default" type library that should be used when type
/// checking the code in the module graph. Note that a user provided config
/// of `"lib"` would override this value.
2020-10-22 20:50:15 -04:00
#[ derive(Debug, Clone, Eq, PartialEq) ]
pub enum TypeLib {
DenoWindow ,
DenoWorker ,
UnstableDenoWindow ,
UnstableDenoWorker ,
}
impl Default for TypeLib {
fn default ( ) -> Self {
TypeLib ::DenoWindow
}
}
impl Serialize for TypeLib {
fn serialize < S > ( & self , serializer : S ) -> Result < S ::Ok , S ::Error >
where
S : Serializer ,
{
let value = match self {
TypeLib ::DenoWindow = > vec! [ " deno.window " . to_string ( ) ] ,
TypeLib ::DenoWorker = > vec! [ " deno.worker " . to_string ( ) ] ,
TypeLib ::UnstableDenoWindow = > {
vec! [ " deno.window " . to_string ( ) , " deno.unstable " . to_string ( ) ]
}
TypeLib ::UnstableDenoWorker = > {
2020-11-05 20:10:19 -05:00
vec! [ " deno.worker " . to_string ( ) , " deno.unstable " . to_string ( ) ]
2020-10-22 20:50:15 -04:00
}
} ;
Serialize ::serialize ( & value , serializer )
}
}
2020-10-19 23:10:42 -04:00
#[ derive(Debug, Default) ]
pub struct BundleOptions {
2020-11-01 21:51:56 -05:00
/// If `true` then debug logging will be output from the isolate.
2020-10-19 23:10:42 -04:00
pub debug : bool ,
2021-05-10 12:16:39 -04:00
/// An optional config file with user supplied TypeScript configuration
/// that augments the the default configuration passed to the TypeScript
2020-11-01 21:51:56 -05:00
/// compiler.
2021-05-10 12:16:39 -04:00
pub maybe_config_file : Option < ConfigFile > ,
2020-10-19 23:10:42 -04:00
}
2020-10-22 20:50:15 -04:00
#[ derive(Debug, Default) ]
pub struct CheckOptions {
/// If `true` then debug logging will be output from the isolate.
pub debug : bool ,
/// Utilise the emit from `tsc` to update the emitted code for modules.
pub emit : bool ,
/// The base type libraries that should be used when type checking.
pub lib : TypeLib ,
2021-05-10 12:16:39 -04:00
/// An optional config file with user supplied TypeScript configuration
/// that augments the the default configuration passed to the TypeScript
2020-10-22 20:50:15 -04:00
/// compiler.
2021-05-10 12:16:39 -04:00
pub maybe_config_file : Option < ConfigFile > ,
2020-10-22 20:50:15 -04:00
/// Ignore any previously emits and ensure that all files are emitted from
/// source.
pub reload : bool ,
}
2020-11-01 21:51:56 -05:00
#[ derive(Debug, Eq, PartialEq) ]
pub enum BundleType {
/// Return the emitted contents of the program as a single "flattened" ES
/// module.
2021-04-25 16:54:57 -04:00
Module ,
2021-02-15 20:02:00 -05:00
/// Return the emitted contents of the program as a single script that
/// executes the program using an immediately invoked function execution
/// (IIFE).
2021-04-25 16:54:57 -04:00
Classic ,
2020-11-01 21:51:56 -05:00
/// Do not bundle the emit, instead returning each of the modules that are
/// part of the program as individual files.
None ,
}
impl Default for BundleType {
fn default ( ) -> Self {
BundleType ::None
}
}
#[ derive(Debug, Default) ]
pub struct EmitOptions {
2020-12-31 16:43:54 -05:00
/// If true, then code will be type checked, otherwise type checking will be
/// skipped. If false, then swc will be used for the emit, otherwise tsc will
/// be used.
pub check : bool ,
2020-11-01 21:51:56 -05:00
/// Indicate the form the result of the emit should take.
pub bundle_type : BundleType ,
/// If `true` then debug logging will be output from the isolate.
pub debug : bool ,
/// An optional map that contains user supplied TypeScript compiler
/// configuration options that are passed to the TypeScript compiler.
pub maybe_user_config : Option < HashMap < String , Value > > ,
}
2020-09-24 18:31:17 -04:00
/// A structure which provides options when transpiling modules.
#[ derive(Debug, Default) ]
pub struct TranspileOptions {
/// If `true` then debug logging will be output from the isolate.
pub debug : bool ,
2021-05-10 12:16:39 -04:00
/// An optional config file with user supplied TypeScript configuration
/// that augments the the default configuration passed to the TypeScript
2020-09-29 03:16:12 -04:00
/// compiler.
2021-05-10 12:16:39 -04:00
pub maybe_config_file : Option < ConfigFile > ,
2020-10-22 20:50:15 -04:00
/// Ignore any previously emits and ensure that all files are emitted from
/// source.
pub reload : bool ,
2020-09-24 18:31:17 -04:00
}
2020-12-15 00:52:55 -05:00
#[ derive(Debug, Clone) ]
enum ModuleSlot {
/// The module fetch resulted in a non-recoverable error.
2020-12-29 23:17:17 -05:00
Err ( Arc < AnyError > ) ,
2020-12-15 00:52:55 -05:00
/// The the fetch resulted in a module.
Module ( Box < Module > ) ,
/// Used to denote a module that isn't part of the graph.
None ,
/// The fetch of the module is pending.
Pending ,
}
2020-09-24 18:31:17 -04:00
/// A dependency graph of modules, were the modules that have been inserted via
/// the builder will be loaded into the graph. Also provides an interface to
/// be able to manipulate and handle the graph.
2020-10-23 07:05:41 -04:00
#[ derive(Debug, Clone) ]
2020-11-02 14:41:20 -05:00
pub struct Graph {
2020-10-22 20:50:15 -04:00
/// A reference to the specifier handler that will retrieve and cache modules
/// for the graph.
2020-12-29 23:17:17 -05:00
handler : Arc < Mutex < dyn SpecifierHandler > > ,
2020-10-22 20:50:15 -04:00
/// Optional TypeScript build info that will be passed to `tsc` if `tsc` is
/// invoked.
maybe_tsbuildinfo : Option < String > ,
/// The modules that are part of the graph.
2020-12-15 00:52:55 -05:00
modules : HashMap < ModuleSpecifier , ModuleSlot > ,
2020-10-22 20:50:15 -04:00
/// A map of redirects, where a module specifier is redirected to another
/// module specifier by the handler. All modules references should be
/// resolved internally via this, before attempting to access the module via
/// the handler, to make sure the correct modules is being dealt with.
2020-10-15 19:34:55 -04:00
redirects : HashMap < ModuleSpecifier , ModuleSpecifier > ,
2020-10-22 20:50:15 -04:00
/// The module specifiers that have been uniquely added to the graph, which
/// does not include any transient dependencies.
2020-09-24 18:31:17 -04:00
roots : Vec < ModuleSpecifier > ,
2020-10-22 20:50:15 -04:00
/// If all of the root modules are dynamically imported, then this is true.
/// This is used to ensure correct `--reload` behavior, where subsequent
/// calls to a module graph where the emit is already valid do not cause the
/// graph to re-emit.
roots_dynamic : bool ,
2020-10-23 17:01:54 -04:00
// A reference to lock file that will be used to check module integrity.
maybe_lockfile : Option < Arc < Mutex < Lockfile > > > ,
2020-09-24 18:31:17 -04:00
}
2020-12-15 00:52:55 -05:00
/// Convert a specifier and a module slot in a result to the module source which
/// is needed by Deno core for loading the module.
fn to_module_result (
( specifier , module_slot ) : ( & ModuleSpecifier , & ModuleSlot ) ,
) -> ( ModuleSpecifier , Result < ModuleSource , AnyError > ) {
match module_slot {
ModuleSlot ::Err ( err ) = > ( specifier . clone ( ) , Err ( anyhow! ( err . to_string ( ) ) ) ) ,
ModuleSlot ::Module ( module ) = > (
specifier . clone ( ) ,
if let Some ( emit ) = & module . maybe_emit {
match emit {
Emit ::Cli ( ( code , _ ) ) = > Ok ( ModuleSource {
code : code . clone ( ) ,
module_url_found : module . specifier . to_string ( ) ,
module_url_specified : specifier . to_string ( ) ,
} ) ,
}
} else {
match module . media_type {
MediaType ::JavaScript | MediaType ::Unknown = > Ok ( ModuleSource {
code : module . source . clone ( ) ,
module_url_found : module . specifier . to_string ( ) ,
module_url_specified : specifier . to_string ( ) ,
} ) ,
_ = > Err ( custom_error (
" NotFound " ,
format! ( " Compiled module not found \" {} \" " , specifier ) ,
) ) ,
}
} ,
) ,
_ = > (
specifier . clone ( ) ,
Err ( anyhow! ( " Module \" {} \" unavailable. " , specifier ) ) ,
) ,
}
}
2020-11-02 14:41:20 -05:00
impl Graph {
2020-09-24 18:31:17 -04:00
/// Create a new instance of a graph, ready to have modules loaded it.
///
/// The argument `handler` is an instance of a structure that implements the
/// `SpecifierHandler` trait.
///
2020-10-23 17:01:54 -04:00
pub fn new (
2020-12-29 23:17:17 -05:00
handler : Arc < Mutex < dyn SpecifierHandler > > ,
2020-10-23 17:01:54 -04:00
maybe_lockfile : Option < Arc < Mutex < Lockfile > > > ,
) -> Self {
2020-11-02 14:41:20 -05:00
Graph {
2020-09-24 18:31:17 -04:00
handler ,
2020-10-22 20:50:15 -04:00
maybe_tsbuildinfo : None ,
2020-09-24 18:31:17 -04:00
modules : HashMap ::new ( ) ,
2020-10-15 19:34:55 -04:00
redirects : HashMap ::new ( ) ,
2020-09-24 18:31:17 -04:00
roots : Vec ::new ( ) ,
2020-10-22 20:50:15 -04:00
roots_dynamic : true ,
2020-10-23 17:01:54 -04:00
maybe_lockfile ,
2020-09-24 18:31:17 -04:00
}
}
2020-10-19 23:10:42 -04:00
/// Transform the module graph into a single JavaScript module which is
/// returned as a `String` in the result.
pub fn bundle (
& self ,
options : BundleOptions ,
) -> Result < ( String , Stats , Option < IgnoredCompilerOptions > ) , AnyError > {
if self . roots . is_empty ( ) | | self . roots . len ( ) > 1 {
2020-10-22 20:50:15 -04:00
return Err ( GraphError ::NotSupported ( format! ( " Bundling is only supported when there is a single root module in the graph. Found: {} " , self . roots . len ( ) ) ) . into ( ) ) ;
2020-10-19 23:10:42 -04:00
}
let start = Instant ::now ( ) ;
let root_specifier = self . roots [ 0 ] . clone ( ) ;
let mut ts_config = TsConfig ::new ( json! ( {
" checkJs " : false ,
" emitDecoratorMetadata " : false ,
2021-03-07 15:40:11 -05:00
" importsNotUsedAsValues " : " remove " ,
2021-05-19 00:18:01 -04:00
" inlineSourceMap " : false ,
" sourceMap " : false ,
2020-10-19 23:10:42 -04:00
" jsx " : " react " ,
" jsxFactory " : " React.createElement " ,
" jsxFragmentFactory " : " React.Fragment " ,
} ) ) ;
2021-05-10 12:16:39 -04:00
let maybe_ignored_options = ts_config
. merge_tsconfig_from_config_file ( options . maybe_config_file . as_ref ( ) ) ? ;
2020-10-19 23:10:42 -04:00
2021-05-19 00:18:01 -04:00
let ( src , _ ) = self . emit_bundle (
2021-04-25 16:54:57 -04:00
& root_specifier ,
& ts_config . into ( ) ,
& BundleType ::Module ,
) ? ;
2020-10-19 23:10:42 -04:00
let stats = Stats ( vec! [
2020-12-31 16:43:54 -05:00
( " Files " . to_string ( ) , self . modules . len ( ) as u32 ) ,
( " Total time " . to_string ( ) , start . elapsed ( ) . as_millis ( ) as u32 ) ,
2020-10-19 23:10:42 -04:00
] ) ;
2021-05-19 00:18:01 -04:00
Ok ( ( src , stats , maybe_ignored_options ) )
2020-10-19 23:10:42 -04:00
}
2020-10-22 20:50:15 -04:00
/// Type check the module graph, corresponding to the options provided.
2020-11-01 21:51:56 -05:00
pub fn check ( self , options : CheckOptions ) -> Result < ResultInfo , AnyError > {
2021-01-27 14:54:20 -05:00
self . validate ( ) ? ;
2020-10-22 20:50:15 -04:00
let mut config = TsConfig ::new ( json! ( {
" allowJs " : true ,
// TODO(@kitsonk) is this really needed?
" esModuleInterop " : true ,
// Enabled by default to align to transpile/swc defaults
" experimentalDecorators " : true ,
" incremental " : true ,
2021-02-05 06:01:48 -05:00
" jsx " : " react " ,
2020-10-23 06:38:35 -04:00
" isolatedModules " : true ,
2020-10-22 20:50:15 -04:00
" lib " : options . lib ,
" module " : " esnext " ,
" strict " : true ,
" target " : " esnext " ,
" tsBuildInfoFile " : " deno:///.tsbuildinfo " ,
2021-04-10 17:56:40 -04:00
" useDefineForClassFields " : true ,
2020-10-22 20:50:15 -04:00
} ) ) ;
if options . emit {
config . merge ( & json! ( {
// TODO(@kitsonk) consider enabling this by default
// see: https://github.com/denoland/deno/issues/7732
" emitDecoratorMetadata " : false ,
2021-03-07 15:40:11 -05:00
" importsNotUsedAsValues " : " remove " ,
2020-10-22 20:50:15 -04:00
" inlineSourceMap " : true ,
" outDir " : " deno:// " ,
" removeComments " : true ,
} ) ) ;
} else {
config . merge ( & json! ( {
" noEmit " : true ,
} ) ) ;
}
2021-05-10 12:16:39 -04:00
let maybe_ignored_options = config
. merge_tsconfig_from_config_file ( options . maybe_config_file . as_ref ( ) ) ? ;
2020-10-22 20:50:15 -04:00
// Short circuit if none of the modules require an emit, or all of the
// modules that require an emit have a valid emit. There is also an edge
// case where there are multiple imports of a dynamic module during a
// single invocation, if that is the case, even if there is a reload, we
// will simply look at if the emit is invalid, to avoid two checks for the
// same programme.
if ! self . needs_emit ( & config )
| | ( self . is_emit_valid ( & config )
& & ( ! options . reload | | self . roots_dynamic ) )
{
debug! ( " graph does not need to be checked or emitted. " ) ;
2020-11-01 21:51:56 -05:00
return Ok ( ResultInfo {
2020-10-22 20:50:15 -04:00
maybe_ignored_options ,
2020-12-15 00:52:55 -05:00
loadable_modules : self . get_loadable_modules ( ) ,
2020-11-01 21:51:56 -05:00
.. Default ::default ( )
} ) ;
2020-10-22 20:50:15 -04:00
}
// TODO(@kitsonk) not totally happy with this here, but this is the first
// point where we know we are actually going to check the program. If we
// moved it out of here, we wouldn't know until after the check has already
// happened, which isn't informative to the users.
for specifier in & self . roots {
2021-03-26 12:34:25 -04:00
log ::info! ( " {} {} " , colors ::green ( " Check " ) , specifier ) ;
2020-10-22 20:50:15 -04:00
}
2021-01-17 16:58:23 -05:00
let root_names = self . get_root_names ( ! config . get_check_js ( ) ) ? ;
2020-10-22 20:50:15 -04:00
let maybe_tsbuildinfo = self . maybe_tsbuildinfo . clone ( ) ;
let hash_data =
2020-11-25 05:30:14 -05:00
vec! [ config . as_bytes ( ) , version ::deno ( ) . as_bytes ( ) . to_owned ( ) ] ;
2020-12-29 23:17:17 -05:00
let graph = Arc ::new ( Mutex ::new ( self ) ) ;
2020-10-22 20:50:15 -04:00
2021-01-05 20:38:23 -05:00
let response = tsc ::exec ( tsc ::Request {
config : config . clone ( ) ,
debug : options . debug ,
graph : graph . clone ( ) ,
hash_data ,
maybe_tsbuildinfo ,
root_names ,
} ) ? ;
2020-10-22 20:50:15 -04:00
2020-12-29 23:17:17 -05:00
let mut graph = graph . lock ( ) . unwrap ( ) ;
2020-10-22 20:50:15 -04:00
graph . maybe_tsbuildinfo = response . maybe_tsbuildinfo ;
// Only process changes to the graph if there are no diagnostics and there
// were files emitted.
2020-11-09 05:21:49 -05:00
if response . diagnostics . is_empty ( ) {
if ! response . emitted_files . is_empty ( ) {
let mut codes = HashMap ::new ( ) ;
let mut maps = HashMap ::new ( ) ;
let check_js = config . get_check_js ( ) ;
for emit in & response . emitted_files {
if let Some ( specifiers ) = & emit . maybe_specifiers {
assert! ( specifiers . len ( ) = = 1 , " Unexpected specifier length " ) ;
// The specifier emitted might not be the redirected specifier, and
// therefore we need to ensure it is the correct one.
let specifier = graph . resolve_specifier ( & specifiers [ 0 ] ) ;
// Sometimes if tsc sees a CommonJS file it will _helpfully_ output it
// to ESM, which we don't really want unless someone has enabled the
// check_js option.
if ! check_js
& & graph . get_media_type ( & specifier ) = = Some ( MediaType ::JavaScript )
{
debug! ( " skipping emit for {} " , specifier ) ;
continue ;
2020-10-22 20:50:15 -04:00
}
2020-11-09 05:21:49 -05:00
match emit . media_type {
MediaType ::JavaScript = > {
codes . insert ( specifier . clone ( ) , emit . data . clone ( ) ) ;
}
MediaType ::SourceMap = > {
maps . insert ( specifier . clone ( ) , emit . data . clone ( ) ) ;
}
_ = > unreachable! ( ) ,
2020-10-22 20:50:15 -04:00
}
}
}
2020-11-09 05:21:49 -05:00
let config = config . as_bytes ( ) ;
for ( specifier , code ) in codes . iter ( ) {
2020-12-15 00:52:55 -05:00
if let ModuleSlot ::Module ( module ) =
graph . get_module_mut ( specifier ) . unwrap ( )
{
module . set_emit ( code . clone ( ) , maps . get ( specifier ) . cloned ( ) ) ;
2020-11-09 05:21:49 -05:00
module . set_version ( & config ) ;
module . is_dirty = true ;
} else {
return Err ( GraphError ::MissingSpecifier ( specifier . clone ( ) ) . into ( ) ) ;
}
2020-10-22 20:50:15 -04:00
}
}
2020-11-09 05:21:49 -05:00
graph . flush ( ) ? ;
2020-10-22 20:50:15 -04:00
}
2020-11-01 21:51:56 -05:00
Ok ( ResultInfo {
diagnostics : response . diagnostics ,
2020-12-15 00:52:55 -05:00
loadable_modules : graph . get_loadable_modules ( ) ,
2020-11-01 21:51:56 -05:00
maybe_ignored_options ,
stats : response . stats ,
} )
2020-10-22 20:50:15 -04:00
}
2020-11-01 21:51:56 -05:00
/// Emit the module graph in a specific format. This is specifically designed
/// to be an "all-in-one" API for access by the runtime, allowing both
/// emitting single modules as well as bundles, using Deno module resolution
/// or supplied sources.
pub fn emit (
2020-12-31 16:43:54 -05:00
mut self ,
2020-11-01 21:51:56 -05:00
options : EmitOptions ,
) -> Result < ( HashMap < String , String > , ResultInfo ) , AnyError > {
let mut config = TsConfig ::new ( json! ( {
" allowJs " : true ,
2020-12-31 16:43:54 -05:00
" checkJs " : false ,
2020-11-01 21:51:56 -05:00
// TODO(@kitsonk) consider enabling this by default
// see: https://github.com/denoland/deno/issues/7732
" emitDecoratorMetadata " : false ,
" esModuleInterop " : true ,
" experimentalDecorators " : true ,
2021-03-07 15:40:11 -05:00
" importsNotUsedAsValues " : " remove " ,
2020-12-31 16:43:54 -05:00
" inlineSourceMap " : false ,
2021-05-19 00:18:01 -04:00
" sourceMap " : false ,
2020-11-01 21:51:56 -05:00
" isolatedModules " : true ,
" jsx " : " react " ,
2020-12-31 16:43:54 -05:00
" jsxFactory " : " React.createElement " ,
" jsxFragmentFactory " : " React.Fragment " ,
2020-11-01 21:51:56 -05:00
" lib " : TypeLib ::DenoWindow ,
" module " : " esnext " ,
" strict " : true ,
" target " : " esnext " ,
2021-04-10 17:56:40 -04:00
" useDefineForClassFields " : true ,
2020-11-01 21:51:56 -05:00
} ) ) ;
let opts = match options . bundle_type {
2021-04-25 16:54:57 -04:00
BundleType ::Module | BundleType ::Classic = > json! ( {
2020-11-01 21:51:56 -05:00
" noEmit " : true ,
2021-05-19 00:18:01 -04:00
" removeComments " : true ,
" sourceMap " : true ,
2020-11-01 21:51:56 -05:00
} ) ,
BundleType ::None = > json! ( {
" outDir " : " deno:// " ,
" removeComments " : true ,
" sourceMap " : true ,
} ) ,
} ;
config . merge ( & opts ) ;
let maybe_ignored_options =
if let Some ( user_options ) = & options . maybe_user_config {
config . merge_user_config ( user_options ) ?
} else {
None
} ;
2020-12-31 16:43:54 -05:00
if ! options . check & & config . get_declaration ( ) {
return Err ( anyhow! ( " The option of `check` is false, but the compiler option of `declaration` is true which is not currently supported. " ) ) ;
}
if options . bundle_type ! = BundleType ::None & & config . get_declaration ( ) {
return Err ( anyhow! ( " The bundle option is set, but the compiler option of `declaration` is true which is not currently supported. " ) ) ;
}
2020-11-01 21:51:56 -05:00
let mut emitted_files = HashMap ::new ( ) ;
2020-12-31 16:43:54 -05:00
if options . check {
2021-01-17 16:58:23 -05:00
let root_names = self . get_root_names ( ! config . get_check_js ( ) ) ? ;
2020-12-31 16:43:54 -05:00
let hash_data =
vec! [ config . as_bytes ( ) , version ::deno ( ) . as_bytes ( ) . to_owned ( ) ] ;
let graph = Arc ::new ( Mutex ::new ( self ) ) ;
2021-01-05 20:38:23 -05:00
let response = tsc ::exec ( tsc ::Request {
config : config . clone ( ) ,
debug : options . debug ,
graph : graph . clone ( ) ,
hash_data ,
maybe_tsbuildinfo : None ,
root_names ,
} ) ? ;
2020-12-31 16:43:54 -05:00
let graph = graph . lock ( ) . unwrap ( ) ;
match options . bundle_type {
2021-04-25 16:54:57 -04:00
BundleType ::Module | BundleType ::Classic = > {
2020-11-01 21:51:56 -05:00
assert! (
2020-12-31 16:43:54 -05:00
response . emitted_files . is_empty ( ) ,
" No files should have been emitted from tsc. "
2020-11-01 21:51:56 -05:00
) ;
assert_eq! (
2020-12-31 16:43:54 -05:00
graph . roots . len ( ) ,
2020-11-01 21:51:56 -05:00
1 ,
2020-12-31 16:43:54 -05:00
" Only a single root module supported. "
2020-11-01 21:51:56 -05:00
) ;
2020-12-31 16:43:54 -05:00
let specifier = & graph . roots [ 0 ] ;
2021-05-19 00:18:01 -04:00
let ( src , maybe_src_map ) = graph . emit_bundle (
2021-02-15 20:02:00 -05:00
specifier ,
& config . into ( ) ,
& options . bundle_type ,
) ? ;
2021-05-19 00:18:01 -04:00
emitted_files . insert ( " deno:///bundle.js " . to_string ( ) , src ) ;
if let Some ( src_map ) = maybe_src_map {
emitted_files . insert ( " deno:///bundle.js.map " . to_string ( ) , src_map ) ;
}
2020-12-31 16:43:54 -05:00
}
BundleType ::None = > {
for emitted_file in & response . emitted_files {
assert! (
emitted_file . maybe_specifiers . is_some ( ) ,
" Orphaned file emitted. "
) ;
let specifiers = emitted_file . maybe_specifiers . clone ( ) . unwrap ( ) ;
assert_eq! (
specifiers . len ( ) ,
1 ,
" An unexpected number of specifiers associated with emitted file. "
) ;
let specifier = specifiers [ 0 ] . clone ( ) ;
let extension = match emitted_file . media_type {
MediaType ::JavaScript = > " .js " ,
MediaType ::SourceMap = > " .js.map " ,
MediaType ::Dts = > " .d.ts " ,
_ = > unreachable! ( ) ,
} ;
let key = format! ( " {} {} " , specifier , extension ) ;
emitted_files . insert ( key , emitted_file . data . clone ( ) ) ;
}
}
} ;
Ok ( (
emitted_files ,
ResultInfo {
diagnostics : response . diagnostics ,
loadable_modules : graph . get_loadable_modules ( ) ,
maybe_ignored_options ,
stats : response . stats ,
} ,
) )
} else {
let start = Instant ::now ( ) ;
let mut emit_count = 0_ u32 ;
match options . bundle_type {
2021-04-25 16:54:57 -04:00
BundleType ::Module | BundleType ::Classic = > {
2020-12-31 16:43:54 -05:00
assert_eq! (
self . roots . len ( ) ,
1 ,
" Only a single root module supported. "
) ;
let specifier = & self . roots [ 0 ] ;
2021-05-19 00:18:01 -04:00
let ( src , maybe_src_map ) = self . emit_bundle (
2021-02-15 20:02:00 -05:00
specifier ,
& config . into ( ) ,
& options . bundle_type ,
) ? ;
2020-12-31 16:43:54 -05:00
emit_count + = 1 ;
2021-05-19 00:18:01 -04:00
emitted_files . insert ( " deno:///bundle.js " . to_string ( ) , src ) ;
if let Some ( src_map ) = maybe_src_map {
emitted_files . insert ( " deno:///bundle.js.map " . to_string ( ) , src_map ) ;
}
2020-12-31 16:43:54 -05:00
}
BundleType ::None = > {
let emit_options : ast ::EmitOptions = config . into ( ) ;
for ( _ , module_slot ) in self . modules . iter_mut ( ) {
if let ModuleSlot ::Module ( module ) = module_slot {
if ! ( emit_options . check_js
2021-03-25 14:17:37 -04:00
| | module . media_type = = MediaType ::Jsx
| | module . media_type = = MediaType ::Tsx
2020-12-31 16:43:54 -05:00
| | module . media_type = = MediaType ::TypeScript )
{
emitted_files
. insert ( module . specifier . to_string ( ) , module . source . clone ( ) ) ;
}
let parsed_module = module . parse ( ) ? ;
let ( code , maybe_map ) = parsed_module . transpile ( & emit_options ) ? ;
emit_count + = 1 ;
emitted_files . insert ( format! ( " {} .js " , module . specifier ) , code ) ;
if let Some ( map ) = maybe_map {
emitted_files
. insert ( format! ( " {} .js.map " , module . specifier ) , map ) ;
}
}
}
self . flush ( ) ? ;
2020-11-01 21:51:56 -05:00
}
}
2020-12-31 16:43:54 -05:00
let stats = Stats ( vec! [
( " Files " . to_string ( ) , self . modules . len ( ) as u32 ) ,
( " Emitted " . to_string ( ) , emit_count ) ,
( " Total time " . to_string ( ) , start . elapsed ( ) . as_millis ( ) as u32 ) ,
] ) ;
Ok ( (
emitted_files ,
ResultInfo {
diagnostics : Default ::default ( ) ,
loadable_modules : self . get_loadable_modules ( ) ,
maybe_ignored_options ,
stats ,
} ,
) )
}
2020-11-01 21:51:56 -05:00
}
/// Shared between `bundle()` and `emit()`.
fn emit_bundle (
& self ,
specifier : & ModuleSpecifier ,
emit_options : & ast ::EmitOptions ,
2021-02-15 20:02:00 -05:00
bundle_type : & BundleType ,
2021-05-19 00:18:01 -04:00
) -> Result < ( String , Option < String > ) , AnyError > {
2020-11-01 21:51:56 -05:00
let cm = Rc ::new ( swc_common ::SourceMap ::new (
swc_common ::FilePathMapping ::empty ( ) ,
) ) ;
let globals = swc_common ::Globals ::new ( ) ;
2020-11-02 06:33:43 -05:00
let loader = BundleLoader ::new ( self , emit_options , & globals , cm . clone ( ) ) ;
let hook = Box ::new ( BundleHook ) ;
2021-02-15 20:02:00 -05:00
let module = match bundle_type {
2021-04-25 16:54:57 -04:00
BundleType ::Module = > swc_bundler ::ModuleType ::Es ,
BundleType ::Classic = > swc_bundler ::ModuleType ::Iife ,
2021-02-15 20:02:00 -05:00
_ = > unreachable! ( " invalid bundle type " ) ,
} ;
2020-11-01 21:51:56 -05:00
let bundler = swc_bundler ::Bundler ::new (
& globals ,
cm . clone ( ) ,
loader ,
self ,
2021-02-15 20:02:00 -05:00
swc_bundler ::Config {
module ,
.. Default ::default ( )
} ,
2020-11-01 21:51:56 -05:00
hook ,
) ;
let mut entries = HashMap ::new ( ) ;
entries . insert (
" bundle " . to_string ( ) ,
swc_common ::FileName ::Custom ( specifier . to_string ( ) ) ,
) ;
let output = bundler
. bundle ( entries )
2020-11-02 14:41:20 -05:00
. context ( " Unable to output bundle during Graph::bundle(). " ) ? ;
2020-11-01 21:51:56 -05:00
let mut buf = Vec ::new ( ) ;
2021-05-19 00:18:01 -04:00
let mut src_map_buf = Vec ::new ( ) ;
2020-11-01 21:51:56 -05:00
{
let mut emitter = swc_ecmascript ::codegen ::Emitter {
cfg : swc_ecmascript ::codegen ::Config { minify : false } ,
cm : cm . clone ( ) ,
comments : None ,
wr : Box ::new ( swc_ecmascript ::codegen ::text_writer ::JsWriter ::new (
2021-05-19 00:18:01 -04:00
cm . clone ( ) ,
" \n " ,
& mut buf ,
Some ( & mut src_map_buf ) ,
2020-11-01 21:51:56 -05:00
) ) ,
} ;
emitter
. emit_module ( & output [ 0 ] . module )
2020-11-02 14:41:20 -05:00
. context ( " Unable to emit bundle during Graph::bundle(). " ) ? ;
2020-11-01 21:51:56 -05:00
}
2021-05-19 00:18:01 -04:00
let mut src = String ::from_utf8 ( buf )
. context ( " Emitted bundle is an invalid utf-8 string. " ) ? ;
let mut map : Option < String > = None ;
{
let mut buf = Vec ::new ( ) ;
cm . build_source_map_from ( & mut src_map_buf , None )
. to_writer ( & mut buf ) ? ;
if emit_options . inline_source_map {
src . push_str ( " //# sourceMappingURL=data:application/json;base64, " ) ;
let encoded_map = base64 ::encode ( buf ) ;
src . push_str ( & encoded_map ) ;
} else if emit_options . source_map {
map = Some ( String ::from_utf8 ( buf ) ? ) ;
}
}
2020-11-01 21:51:56 -05:00
2021-05-19 00:18:01 -04:00
Ok ( ( src , map ) )
2020-11-01 21:51:56 -05:00
}
2020-10-15 19:34:55 -04:00
/// Update the handler with any modules that are marked as _dirty_ and update
/// any build info if present.
fn flush ( & mut self ) -> Result < ( ) , AnyError > {
2020-12-29 23:17:17 -05:00
let mut handler = self . handler . lock ( ) . unwrap ( ) ;
2020-12-15 00:52:55 -05:00
for ( _ , module_slot ) in self . modules . iter_mut ( ) {
if let ModuleSlot ::Module ( module ) = module_slot {
if module . is_dirty {
if let Some ( emit ) = & module . maybe_emit {
handler . set_cache ( & module . specifier , emit ) ? ;
}
if let Some ( version ) = & module . maybe_version {
handler . set_version ( & module . specifier , version . clone ( ) ) ? ;
}
module . is_dirty = false ;
2020-10-15 19:34:55 -04:00
}
}
}
for root_specifier in self . roots . iter ( ) {
2020-10-22 20:50:15 -04:00
if let Some ( tsbuildinfo ) = & self . maybe_tsbuildinfo {
handler . set_tsbuildinfo ( root_specifier , tsbuildinfo . to_owned ( ) ) ? ;
2020-10-15 19:34:55 -04:00
}
}
Ok ( ( ) )
}
2020-12-15 00:52:55 -05:00
/// Retrieve a map that contains a representation of each module in the graph
/// which can be used to provide code to a module loader without holding all
/// the state to be able to operate on the graph.
pub fn get_loadable_modules (
& self ,
) -> HashMap < ModuleSpecifier , Result < ModuleSource , AnyError > > {
let mut loadable_modules : HashMap <
ModuleSpecifier ,
Result < ModuleSource , AnyError > ,
> = self . modules . iter ( ) . map ( to_module_result ) . collect ( ) ;
for ( specifier , _ ) in self . redirects . iter ( ) {
if let Some ( module_slot ) =
self . modules . get ( self . resolve_specifier ( specifier ) )
{
let ( _ , result ) = to_module_result ( ( specifier , module_slot ) ) ;
loadable_modules . insert ( specifier . clone ( ) , result ) ;
}
}
loadable_modules
}
2020-10-15 19:34:55 -04:00
pub fn get_media_type (
& self ,
specifier : & ModuleSpecifier ,
) -> Option < MediaType > {
2020-12-15 00:52:55 -05:00
if let ModuleSlot ::Module ( module ) = self . get_module ( specifier ) {
2020-10-15 19:34:55 -04:00
Some ( module . media_type )
} else {
None
}
}
2020-12-15 00:52:55 -05:00
fn get_module ( & self , specifier : & ModuleSpecifier ) -> & ModuleSlot {
2020-10-15 19:34:55 -04:00
let s = self . resolve_specifier ( specifier ) ;
2020-12-15 00:52:55 -05:00
if let Some ( module_slot ) = self . modules . get ( s ) {
module_slot
} else {
& ModuleSlot ::None
}
2020-10-15 19:34:55 -04:00
}
2020-10-22 20:50:15 -04:00
fn get_module_mut (
& mut self ,
specifier : & ModuleSpecifier ,
2020-12-15 00:52:55 -05:00
) -> Option < & mut ModuleSlot > {
2020-10-22 20:50:15 -04:00
// this is duplicated code because `.resolve_specifier` requires an
// immutable borrow, but if `.resolve_specifier` is mut, then everything
// that calls it is is mut
let mut s = specifier ;
while let Some ( redirect ) = self . redirects . get ( s ) {
s = redirect ;
}
self . modules . get_mut ( s )
}
2021-05-10 02:06:13 -04:00
pub fn get_specifier (
& self ,
specifier : & ModuleSpecifier ,
) -> Result < & Module , AnyError > {
let s = self . resolve_specifier ( specifier ) ;
match self . get_module ( s ) {
ModuleSlot ::Module ( m ) = > Ok ( m . as_ref ( ) ) ,
ModuleSlot ::Err ( e ) = > Err ( anyhow! ( e . to_string ( ) ) ) ,
_ = > Err ( GraphError ::MissingSpecifier ( specifier . clone ( ) ) . into ( ) ) ,
}
}
2020-11-01 21:51:56 -05:00
/// Consume graph and return list of all module specifiers contained in the
/// graph.
pub fn get_modules ( & self ) -> Vec < ModuleSpecifier > {
self . modules . keys ( ) . map ( | s | s . to_owned ( ) ) . collect ( )
}
/// Transform `self.roots` into something that works for `tsc`, because `tsc`
/// doesn't like root names without extensions that match its expectations,
/// nor does it have any concept of redirection, so we have to resolve all
2020-11-07 15:00:42 -05:00
/// that upfront before feeding it to `tsc`. In addition, if checkJs is not
/// true, we should pass all emittable files in as the roots, so that `tsc`
/// type checks them and potentially emits them.
fn get_root_names (
& self ,
include_emittable : bool ,
2021-01-17 16:58:23 -05:00
) -> Result < Vec < ( ModuleSpecifier , MediaType ) > , AnyError > {
2020-11-07 15:00:42 -05:00
let root_names : Vec < ModuleSpecifier > = if include_emittable {
// in situations where there is `allowJs` with tsc, but not `checkJs`,
// then tsc will not parse the whole module graph, meaning that any
// JavaScript importing TypeScript will get ignored, meaning that those
// files will not get emitted. To counter act that behavior, we will
// include all modules that are emittable.
let mut specifiers = HashSet ::< & ModuleSpecifier > ::new ( ) ;
2020-12-15 00:52:55 -05:00
for ( _ , module_slot ) in self . modules . iter ( ) {
if let ModuleSlot ::Module ( module ) = module_slot {
2021-03-25 14:17:37 -04:00
if module . media_type = = MediaType ::Jsx
2020-12-15 00:52:55 -05:00
| | module . media_type = = MediaType ::TypeScript
2021-03-25 14:17:37 -04:00
| | module . media_type = = MediaType ::Tsx
2020-12-15 00:52:55 -05:00
{
specifiers . insert ( & module . specifier ) ;
}
2020-11-07 15:00:42 -05:00
}
}
// We should include all the original roots as well.
for specifier in self . roots . iter ( ) {
specifiers . insert ( specifier ) ;
}
specifiers . into_iter ( ) . cloned ( ) . collect ( )
} else {
self . roots . clone ( )
} ;
2021-01-17 16:58:23 -05:00
let mut root_types = vec! [ ] ;
for ms in root_names {
// if the root module has a types specifier, we should be sending that
// to tsc instead of the original specifier
let specifier = self . resolve_specifier ( & ms ) ;
let module = match self . get_module ( specifier ) {
ModuleSlot ::Module ( module ) = > module ,
ModuleSlot ::Err ( error ) = > {
// It would be great if we could just clone the error here...
if let Some ( class ) = get_custom_error_class ( error ) {
return Err ( custom_error ( class , error . to_string ( ) ) ) ;
2020-12-15 00:52:55 -05:00
} else {
2021-01-17 16:58:23 -05:00
panic! ( " unsupported ModuleSlot error " ) ;
}
}
_ = > {
panic! ( " missing module " ) ;
}
} ;
let specifier = if let Some ( ( _ , types_specifier ) ) = & module . maybe_types {
self . resolve_specifier ( types_specifier )
} else {
specifier
} ;
root_types . push ( (
// root modules can be redirects, so before we pass it to tsc we need
// to resolve the redirect
specifier . clone ( ) ,
self . get_media_type ( specifier ) . unwrap ( ) ,
) ) ;
}
Ok ( root_types )
2020-11-01 21:51:56 -05:00
}
/// Get the source for a given module specifier. If the module is not part
/// of the graph, the result will be `None`.
pub fn get_source ( & self , specifier : & ModuleSpecifier ) -> Option < String > {
2020-12-15 00:52:55 -05:00
if let ModuleSlot ::Module ( module ) = self . get_module ( specifier ) {
2020-11-01 21:51:56 -05:00
Some ( module . source . clone ( ) )
} else {
None
}
}
2020-10-11 22:25:27 -04:00
/// Return a structure which provides information about the module graph and
/// the relationship of the modules in the graph. This structure is used to
/// provide information for the `info` subcommand.
2021-03-01 06:49:58 -05:00
pub fn info ( & self ) -> Result < info ::ModuleGraphInfo , AnyError > {
2020-10-11 22:25:27 -04:00
if self . roots . is_empty ( ) | | self . roots . len ( ) > 1 {
2020-10-22 20:50:15 -04:00
return Err ( GraphError ::NotSupported ( format! ( " Info is only supported when there is a single root module in the graph. Found: {} " , self . roots . len ( ) ) ) . into ( ) ) ;
2020-10-11 22:25:27 -04:00
}
2021-03-01 06:49:58 -05:00
let root = self . resolve_specifier ( & self . roots [ 0 ] ) . clone ( ) ;
let mut modules : Vec < info ::ModuleGraphInfoMod > = self
2020-12-15 00:52:55 -05:00
. modules
. iter ( )
2021-03-01 06:49:58 -05:00
. filter_map ( | ( sp , sl ) | match sl {
ModuleSlot ::Module ( module ) = > {
let mut dependencies : Vec < info ::ModuleGraphInfoDep > = module
. dependencies
. iter ( )
. map ( | ( k , v ) | info ::ModuleGraphInfoDep {
specifier : k . clone ( ) ,
is_dynamic : v . is_dynamic ,
maybe_code : v
. maybe_code
. clone ( )
. map ( | s | self . resolve_specifier ( & s ) . clone ( ) ) ,
maybe_type : v
. maybe_type
. clone ( )
. map ( | s | self . resolve_specifier ( & s ) . clone ( ) ) ,
} )
. collect ( ) ;
dependencies . sort ( ) ;
let ( emit , map ) =
if let Some ( ( emit , maybe_map ) ) = & module . maybe_emit_path {
( Some ( emit . clone ( ) ) , maybe_map . clone ( ) )
} else {
( None , None )
} ;
Some ( info ::ModuleGraphInfoMod {
specifier : sp . clone ( ) ,
dependencies ,
size : Some ( module . size ( ) ) ,
media_type : Some ( module . media_type ) ,
local : Some ( module . source_path . clone ( ) ) ,
checksum : Some ( checksum ::gen ( & [ module . source . as_bytes ( ) ] ) ) ,
emit ,
map ,
.. Default ::default ( )
} )
}
ModuleSlot ::Err ( err ) = > Some ( info ::ModuleGraphInfoMod {
specifier : sp . clone ( ) ,
error : Some ( err . to_string ( ) ) ,
.. Default ::default ( )
} ) ,
2020-12-15 00:52:55 -05:00
_ = > None ,
} )
2021-03-01 06:49:58 -05:00
. collect ( ) ;
modules . sort ( ) ;
let size = modules . iter ( ) . fold ( 0_ usize , | acc , m | {
if let Some ( size ) = & m . size {
acc + size
} else {
acc
}
} ) ;
Ok ( info ::ModuleGraphInfo {
root ,
modules ,
size ,
2020-10-11 22:25:27 -04:00
} )
}
2020-10-22 20:50:15 -04:00
/// Determines if all of the modules in the graph that require an emit have
/// a valid emit. Returns `true` if all the modules have a valid emit,
/// otherwise false.
fn is_emit_valid ( & self , config : & TsConfig ) -> bool {
let check_js = config . get_check_js ( ) ;
let config = config . as_bytes ( ) ;
self . modules . iter ( ) . all ( | ( _ , m ) | {
2020-12-15 00:52:55 -05:00
if let ModuleSlot ::Module ( m ) = m {
let needs_emit = match m . media_type {
2021-03-25 14:17:37 -04:00
MediaType ::TypeScript | MediaType ::Tsx | MediaType ::Jsx = > true ,
2020-12-15 00:52:55 -05:00
MediaType ::JavaScript = > check_js ,
_ = > false ,
} ;
if needs_emit {
m . is_emit_valid ( & config )
} else {
true
}
2020-10-22 20:50:15 -04:00
} else {
2021-01-27 06:25:33 -05:00
true
2020-10-22 20:50:15 -04:00
}
} )
}
2020-09-24 18:31:17 -04:00
/// Verify the subresource integrity of the graph based upon the optional
/// lockfile, updating the lockfile with any missing resources. This will
/// error if any of the resources do not match their lock status.
2020-10-23 17:01:54 -04:00
pub fn lock ( & self ) {
if let Some ( lf ) = self . maybe_lockfile . as_ref ( ) {
2020-09-24 18:31:17 -04:00
let mut lockfile = lf . lock ( ) . unwrap ( ) ;
2020-12-15 00:52:55 -05:00
for ( ms , module_slot ) in self . modules . iter ( ) {
if let ModuleSlot ::Module ( module ) = module_slot {
let specifier = module . specifier . to_string ( ) ;
let valid = lockfile . check_or_insert ( & specifier , & module . source ) ;
if ! valid {
eprintln! (
" {} " ,
GraphError ::InvalidSource ( ms . clone ( ) , lockfile . filename . clone ( ) )
) ;
std ::process ::exit ( 10 ) ;
}
2020-09-24 18:31:17 -04:00
}
}
}
2020-10-22 20:50:15 -04:00
}
2020-09-24 18:31:17 -04:00
2020-10-22 20:50:15 -04:00
/// Determines if any of the modules in the graph are required to be emitted.
/// This is similar to `emit_valid()` except that the actual emit isn't
/// checked to determine if it is valid.
fn needs_emit ( & self , config : & TsConfig ) -> bool {
let check_js = config . get_check_js ( ) ;
2020-12-15 00:52:55 -05:00
self . modules . iter ( ) . any ( | ( _ , m ) | match m {
ModuleSlot ::Module ( m ) = > match m . media_type {
2021-03-25 14:17:37 -04:00
MediaType ::TypeScript | MediaType ::Tsx | MediaType ::Jsx = > true ,
2020-12-15 00:52:55 -05:00
MediaType ::JavaScript = > check_js ,
_ = > false ,
} ,
2020-10-22 20:50:15 -04:00
_ = > false ,
} )
2020-09-24 18:31:17 -04:00
}
2020-10-13 19:52:49 -04:00
/// Given a string specifier and a referring module specifier, provide the
/// resulting module specifier and media type for the module that is part of
/// the graph.
2020-10-22 20:50:15 -04:00
///
/// # Arguments
///
/// * `specifier` - The string form of the module specifier that needs to be
/// resolved.
/// * `referrer` - The referring `ModuleSpecifier`.
/// * `prefer_types` - When resolving to a module specifier, determine if a
/// type dependency is preferred over a code dependency. This is set to
/// `true` when resolving module names for `tsc` as it needs the type
/// dependency over the code, while other consumers do not handle type only
/// dependencies.
2020-10-13 19:52:49 -04:00
pub fn resolve (
& self ,
specifier : & str ,
referrer : & ModuleSpecifier ,
2020-10-22 20:50:15 -04:00
prefer_types : bool ,
2020-10-13 19:52:49 -04:00
) -> Result < ModuleSpecifier , AnyError > {
2020-12-15 00:52:55 -05:00
let module = if let ModuleSlot ::Module ( module ) = self . get_module ( referrer ) {
module
} else {
return Err ( GraphError ::MissingSpecifier ( referrer . clone ( ) ) . into ( ) ) ;
} ;
2020-10-13 19:52:49 -04:00
if ! module . dependencies . contains_key ( specifier ) {
return Err (
2020-10-22 20:50:15 -04:00
GraphError ::MissingDependency (
referrer . to_owned ( ) ,
specifier . to_owned ( ) ,
)
. into ( ) ,
2020-10-13 19:52:49 -04:00
) ;
}
let dependency = module . dependencies . get ( specifier ) . unwrap ( ) ;
// If there is a @deno-types pragma that impacts the dependency, then the
// maybe_type property will be set with that specifier, otherwise we use the
// specifier that point to the runtime code.
2020-10-22 20:50:15 -04:00
let resolved_specifier = if prefer_types & & dependency . maybe_type . is_some ( )
{
dependency . maybe_type . clone ( ) . unwrap ( )
} else if let Some ( code_specifier ) = dependency . maybe_code . clone ( ) {
code_specifier
} else {
return Err (
GraphError ::MissingDependency (
referrer . to_owned ( ) ,
specifier . to_owned ( ) ,
)
. into ( ) ,
) ;
} ;
2020-12-15 00:52:55 -05:00
let dep_module = if let ModuleSlot ::Module ( dep_module ) =
self . get_module ( & resolved_specifier )
{
dep_module
} else {
2020-10-13 19:52:49 -04:00
return Err (
2020-10-22 20:50:15 -04:00
GraphError ::MissingDependency (
referrer . to_owned ( ) ,
resolved_specifier . to_string ( ) ,
)
. into ( ) ,
2020-10-13 19:52:49 -04:00
) ;
2020-12-15 00:52:55 -05:00
} ;
2020-10-13 19:52:49 -04:00
// In the case that there is a X-TypeScript-Types or a triple-slash types,
// then the `maybe_types` specifier will be populated and we should use that
// instead.
2020-10-22 20:50:15 -04:00
let result = if prefer_types & & dep_module . maybe_types . is_some ( ) {
let ( _ , types ) = dep_module . maybe_types . clone ( ) . unwrap ( ) ;
2020-10-28 05:38:09 -04:00
// It is possible that `types` points to a redirected specifier, so we
// need to ensure it resolves to the final specifier in the graph.
self . resolve_specifier ( & types ) . clone ( )
2020-10-13 19:52:49 -04:00
} else {
2020-10-28 05:38:09 -04:00
dep_module . specifier . clone ( )
2020-10-13 19:52:49 -04:00
} ;
Ok ( result )
}
2020-10-15 19:34:55 -04:00
/// Takes a module specifier and returns the "final" specifier, accounting for
/// any redirects that may have occurred.
fn resolve_specifier < ' a > (
& ' a self ,
specifier : & ' a ModuleSpecifier ,
) -> & ' a ModuleSpecifier {
let mut s = specifier ;
let mut seen = HashSet ::new ( ) ;
seen . insert ( s . clone ( ) ) ;
while let Some ( redirect ) = self . redirects . get ( s ) {
if ! seen . insert ( redirect . clone ( ) ) {
eprintln! ( " An infinite loop of module redirections detected. \n Original specifier: {} " , specifier ) ;
break ;
}
s = redirect ;
if seen . len ( ) > 5 {
eprintln! ( " An excessive number of module redirections detected. \n Original specifier: {} " , specifier ) ;
break ;
}
}
s
}
2020-09-24 18:31:17 -04:00
/// Transpile (only transform) the graph, updating any emitted modules
/// with the specifier handler. The result contains any performance stats
/// from the compiler and optionally any user provided configuration compiler
/// options that were ignored.
///
/// # Arguments
///
2020-10-22 20:50:15 -04:00
/// * `options` - A structure of options which impact how the code is
2020-09-24 18:31:17 -04:00
/// transpiled.
///
pub fn transpile (
& mut self ,
options : TranspileOptions ,
2020-12-15 00:52:55 -05:00
) -> Result < ResultInfo , AnyError > {
2020-09-24 18:31:17 -04:00
let start = Instant ::now ( ) ;
2020-09-29 03:16:12 -04:00
let mut ts_config = TsConfig ::new ( json! ( {
2020-09-24 18:31:17 -04:00
" checkJs " : false ,
" emitDecoratorMetadata " : false ,
2021-03-07 15:40:11 -05:00
" importsNotUsedAsValues " : " remove " ,
2020-10-26 09:03:03 -04:00
" inlineSourceMap " : true ,
2021-05-19 00:18:01 -04:00
" sourceMap " : false ,
2020-09-24 18:31:17 -04:00
" jsx " : " react " ,
" jsxFactory " : " React.createElement " ,
" jsxFragmentFactory " : " React.Fragment " ,
2020-09-29 03:16:12 -04:00
} ) ) ;
2020-09-24 18:31:17 -04:00
2021-05-10 12:16:39 -04:00
let maybe_ignored_options = ts_config
. merge_tsconfig_from_config_file ( options . maybe_config_file . as_ref ( ) ) ? ;
2020-09-24 18:31:17 -04:00
2020-10-22 20:50:15 -04:00
let config = ts_config . as_bytes ( ) ;
2020-12-31 16:43:54 -05:00
let emit_options : ast ::EmitOptions = ts_config . into ( ) ;
let mut emit_count = 0_ u32 ;
2020-12-15 00:52:55 -05:00
for ( _ , module_slot ) in self . modules . iter_mut ( ) {
if let ModuleSlot ::Module ( module ) = module_slot {
// TODO(kitsonk) a lot of this logic should be refactored into `Module` as
// we start to support other methods on the graph. Especially managing
// the dirty state is something the module itself should "own".
// if the module is a Dts file we should skip it
if module . media_type = = MediaType ::Dts {
continue ;
}
// if we don't have check_js enabled, we won't touch non TypeScript or JSX
// modules
if ! ( emit_options . check_js
2021-03-25 14:17:37 -04:00
| | module . media_type = = MediaType ::Jsx
| | module . media_type = = MediaType ::Tsx
2020-12-15 00:52:55 -05:00
| | module . media_type = = MediaType ::TypeScript )
{
continue ;
}
// skip modules that already have a valid emit
if ! options . reload & & module . is_emit_valid ( & config ) {
continue ;
}
2020-12-29 23:17:17 -05:00
let parsed_module = module . parse ( ) ? ;
2020-12-15 00:52:55 -05:00
let emit = parsed_module . transpile ( & emit_options ) ? ;
emit_count + = 1 ;
module . maybe_emit = Some ( Emit ::Cli ( emit ) ) ;
module . set_version ( & config ) ;
module . is_dirty = true ;
2020-09-24 18:31:17 -04:00
}
}
2020-10-13 06:54:28 -04:00
self . flush ( ) ? ;
2020-09-24 18:31:17 -04:00
let stats = Stats ( vec! [
2020-12-31 16:43:54 -05:00
( " Files " . to_string ( ) , self . modules . len ( ) as u32 ) ,
2020-09-24 18:31:17 -04:00
( " Emitted " . to_string ( ) , emit_count ) ,
2020-12-31 16:43:54 -05:00
( " Total time " . to_string ( ) , start . elapsed ( ) . as_millis ( ) as u32 ) ,
2020-09-24 18:31:17 -04:00
] ) ;
2020-12-15 00:52:55 -05:00
Ok ( ResultInfo {
diagnostics : Default ::default ( ) ,
loadable_modules : self . get_loadable_modules ( ) ,
maybe_ignored_options ,
stats ,
} )
2020-09-24 18:31:17 -04:00
}
2021-01-27 14:54:20 -05:00
/// Validate that the module graph is "valid" in that there are not module
/// slots that have errorred that should be available to be able to statically
/// analyze. In certain situations, we can spin up tsc with an "invalid"
/// graph.
fn validate ( & self ) -> Result < ( ) , AnyError > {
fn validate_module < F > (
specifier : & ModuleSpecifier ,
seen : & mut HashSet < ModuleSpecifier > ,
get_module : & F ,
) -> Result < ( ) , AnyError >
where
F : Fn ( & ModuleSpecifier ) -> ModuleSlot ,
{
if seen . contains ( specifier ) {
return Ok ( ( ) ) ;
}
seen . insert ( specifier . clone ( ) ) ;
match get_module ( specifier ) {
ModuleSlot ::Err ( err ) = > Err ( anyhow! ( err . to_string ( ) ) ) ,
ModuleSlot ::Module ( module ) = > {
for ( _ , dep ) in module . dependencies . iter ( ) {
// a dynamic import should be skipped, because while it might not
// be available to statically analyze, it might be available at
// runtime.
if ! dep . is_dynamic {
if let Some ( code_specifier ) = & dep . maybe_code {
validate_module ( code_specifier , seen , get_module ) ? ;
}
if let Some ( type_specifier ) = & dep . maybe_type {
validate_module ( type_specifier , seen , get_module ) ? ;
}
}
}
Ok ( ( ) )
} ,
ModuleSlot ::None = > Err ( custom_error ( " NotFound " , format! ( " The specifier \" {} \" is unexpectedly not in the module graph. " , specifier ) ) ) ,
ModuleSlot ::Pending = > Err ( custom_error ( " InvalidState " , format! ( " The specifier \" {} \" is in an unexpected state in the module graph. " , specifier ) ) ) ,
}
}
let mut seen = HashSet ::new ( ) ;
for specifier in & self . roots {
validate_module ( specifier , & mut seen , & | s | self . get_module ( s ) . clone ( ) ) ? ;
}
Ok ( ( ) )
}
2020-09-24 18:31:17 -04:00
}
2020-11-02 14:41:20 -05:00
impl swc_bundler ::Resolve for Graph {
2020-10-19 23:10:42 -04:00
fn resolve (
& self ,
referrer : & swc_common ::FileName ,
specifier : & str ,
) -> Result < swc_common ::FileName , AnyError > {
let referrer = if let swc_common ::FileName ::Custom ( referrer ) = referrer {
2021-02-17 13:47:18 -05:00
resolve_url_or_path ( referrer )
2020-10-19 23:10:42 -04:00
. context ( " Cannot resolve swc FileName to a module specifier " ) ?
} else {
unreachable! (
" An unexpected referrer was passed when bundling: {:?} " ,
referrer
)
} ;
2020-10-22 20:50:15 -04:00
let specifier = self . resolve ( specifier , & referrer , false ) ? ;
2020-10-19 23:10:42 -04:00
Ok ( swc_common ::FileName ::Custom ( specifier . to_string ( ) ) )
}
}
2020-09-24 18:31:17 -04:00
/// A structure for building a dependency graph of modules.
2020-11-02 14:41:20 -05:00
pub struct GraphBuilder {
graph : Graph ,
2020-12-29 23:17:17 -05:00
maybe_import_map : Option < Arc < Mutex < ImportMap > > > ,
2020-09-24 18:31:17 -04:00
pending : FuturesUnordered < FetchFuture > ,
}
2020-11-02 14:41:20 -05:00
impl GraphBuilder {
2020-09-24 18:31:17 -04:00
pub fn new (
2020-12-29 23:17:17 -05:00
handler : Arc < Mutex < dyn SpecifierHandler > > ,
2020-09-24 18:31:17 -04:00
maybe_import_map : Option < ImportMap > ,
2020-10-23 17:01:54 -04:00
maybe_lockfile : Option < Arc < Mutex < Lockfile > > > ,
2020-09-24 18:31:17 -04:00
) -> Self {
2021-03-25 14:17:37 -04:00
let internal_import_map =
maybe_import_map . map ( | import_map | Arc ::new ( Mutex ::new ( import_map ) ) ) ;
2020-11-02 14:41:20 -05:00
GraphBuilder {
graph : Graph ::new ( handler , maybe_lockfile ) ,
2020-09-24 18:31:17 -04:00
maybe_import_map : internal_import_map ,
pending : FuturesUnordered ::new ( ) ,
}
}
2020-10-22 20:50:15 -04:00
/// Add a module into the graph based on a module specifier. The module
/// and any dependencies will be fetched from the handler. The module will
/// also be treated as a _root_ module in the graph.
pub async fn add (
& mut self ,
specifier : & ModuleSpecifier ,
is_dynamic : bool ,
) -> Result < ( ) , AnyError > {
2020-12-15 00:52:55 -05:00
self . fetch ( specifier , & None , is_dynamic ) ;
2020-10-22 20:50:15 -04:00
loop {
2020-12-15 00:52:55 -05:00
match self . pending . next ( ) . await {
Some ( Err ( ( specifier , err ) ) ) = > {
self
. graph
. modules
2020-12-29 23:17:17 -05:00
. insert ( specifier , ModuleSlot ::Err ( Arc ::new ( err ) ) ) ;
2020-12-15 00:52:55 -05:00
}
Some ( Ok ( cached_module ) ) = > {
let is_root = & cached_module . specifier = = specifier ;
2021-05-17 03:44:38 -04:00
self . visit ( cached_module , is_root , is_dynamic ) ? ;
2020-12-15 00:52:55 -05:00
}
_ = > { }
}
2020-10-22 20:50:15 -04:00
if self . pending . is_empty ( ) {
break ;
}
}
if ! self . graph . roots . contains ( specifier ) {
self . graph . roots . push ( specifier . clone ( ) ) ;
self . graph . roots_dynamic = self . graph . roots_dynamic & & is_dynamic ;
if self . graph . maybe_tsbuildinfo . is_none ( ) {
2020-12-29 23:17:17 -05:00
let handler = self . graph . handler . lock ( ) . unwrap ( ) ;
2020-10-22 20:50:15 -04:00
self . graph . maybe_tsbuildinfo = handler . get_tsbuildinfo ( specifier ) ? ;
}
}
Ok ( ( ) )
}
2020-09-24 18:31:17 -04:00
/// Request a module to be fetched from the handler and queue up its future
/// to be awaited to be resolved.
2020-10-22 20:50:15 -04:00
fn fetch (
& mut self ,
specifier : & ModuleSpecifier ,
maybe_referrer : & Option < Location > ,
is_dynamic : bool ,
2020-12-15 00:52:55 -05:00
) {
if ! self . graph . modules . contains_key ( & specifier ) {
self
. graph
. modules
. insert ( specifier . clone ( ) , ModuleSlot ::Pending ) ;
2020-12-29 23:17:17 -05:00
let mut handler = self . graph . handler . lock ( ) . unwrap ( ) ;
let future =
handler . fetch ( specifier . clone ( ) , maybe_referrer . clone ( ) , is_dynamic ) ;
2020-12-15 00:52:55 -05:00
self . pending . push ( future ) ;
2020-09-24 18:31:17 -04:00
}
}
/// Visit a module that has been fetched, hydrating the module, analyzing its
/// dependencies if required, fetching those dependencies, and inserting the
/// module into the graph.
2020-10-22 20:50:15 -04:00
fn visit (
& mut self ,
cached_module : CachedModule ,
is_root : bool ,
2021-05-17 03:44:38 -04:00
is_root_dynamic : bool ,
2020-10-22 20:50:15 -04:00
) -> Result < ( ) , AnyError > {
2020-09-24 18:31:17 -04:00
let specifier = cached_module . specifier . clone ( ) ;
2020-10-15 19:34:55 -04:00
let requested_specifier = cached_module . requested_specifier . clone ( ) ;
2020-10-22 20:50:15 -04:00
let mut module =
Module ::new ( cached_module , is_root , self . maybe_import_map . clone ( ) ) ;
match module . media_type {
MediaType ::Json
| MediaType ::SourceMap
| MediaType ::TsBuildInfo
| MediaType ::Unknown = > {
return Err (
GraphError ::UnsupportedImportType (
module . specifier ,
module . media_type ,
)
. into ( ) ,
) ;
}
_ = > ( ) ,
}
2020-09-24 18:31:17 -04:00
if ! module . is_parsed {
let has_types = module . maybe_types . is_some ( ) ;
module . parse ( ) ? ;
if self . maybe_import_map . is_none ( ) {
2020-12-29 23:17:17 -05:00
let mut handler = self . graph . handler . lock ( ) . unwrap ( ) ;
2020-09-24 18:31:17 -04:00
handler . set_deps ( & specifier , module . dependencies . clone ( ) ) ? ;
if ! has_types {
if let Some ( ( types , _ ) ) = module . maybe_types . clone ( ) {
handler . set_types ( & specifier , types ) ? ;
}
}
}
}
for ( _ , dep ) in module . dependencies . iter ( ) {
2020-10-22 20:50:15 -04:00
let maybe_referrer = Some ( dep . location . clone ( ) ) ;
2021-06-01 02:45:37 -04:00
for maybe_specifier in & [ dep . maybe_code . as_ref ( ) , dep . maybe_type . as_ref ( ) ]
{
if let Some ( & dep_specifier ) = maybe_specifier . as_ref ( ) {
if dep_specifier . scheme ( ) = = " bare " {
self . graph . modules . insert (
dep_specifier . clone ( ) ,
ModuleSlot ::Err ( Arc ::new (
ModuleResolutionError ::ImportPrefixMissing (
dep_specifier . path ( ) . to_string ( ) ,
Some ( specifier . to_string ( ) ) ,
)
. into ( ) ,
) ) ,
) ;
} else {
self . fetch (
dep_specifier ,
& maybe_referrer ,
is_root_dynamic | | dep . is_dynamic ,
) ;
}
}
2020-09-24 18:31:17 -04:00
}
}
if let Some ( ( _ , specifier ) ) = module . maybe_types . as_ref ( ) {
2021-05-17 03:44:38 -04:00
self . fetch ( specifier , & None , is_root_dynamic ) ;
2020-09-24 18:31:17 -04:00
}
2020-10-15 19:34:55 -04:00
if specifier ! = requested_specifier {
self
. graph
. redirects
. insert ( requested_specifier , specifier . clone ( ) ) ;
}
2020-12-15 00:52:55 -05:00
self
. graph
. modules
. insert ( specifier , ModuleSlot ::Module ( Box ::new ( module ) ) ) ;
2020-09-24 18:31:17 -04:00
Ok ( ( ) )
}
/// Move out the graph from the builder to be utilized further. An optional
/// lockfile can be provided, where if the sources in the graph do not match
2020-10-22 20:50:15 -04:00
/// the expected lockfile, an error will be logged and the process will exit.
2020-11-02 14:41:20 -05:00
pub fn get_graph ( self ) -> Graph {
2020-10-23 17:01:54 -04:00
self . graph . lock ( ) ;
2020-10-22 20:50:15 -04:00
self . graph
2020-09-24 18:31:17 -04:00
}
}
#[ cfg(test) ]
2020-10-13 19:52:49 -04:00
pub mod tests {
2020-09-24 18:31:17 -04:00
use super ::* ;
2020-11-01 21:51:56 -05:00
use crate ::specifier_handler ::MemoryHandler ;
2020-10-07 01:24:15 -04:00
use deno_core ::futures ::future ;
2020-09-24 18:31:17 -04:00
use std ::env ;
2020-10-07 01:24:15 -04:00
use std ::fs ;
2020-09-24 18:31:17 -04:00
use std ::path ::PathBuf ;
use std ::sync ::Mutex ;
2020-11-01 21:51:56 -05:00
macro_rules ! map (
{ $( $key :expr = > $value :expr ) , + } = > {
{
let mut m = ::std ::collections ::HashMap ::new ( ) ;
$(
m . insert ( $key , $value ) ;
) +
m
}
} ;
) ;
2020-10-07 01:24:15 -04:00
/// This is a testing mock for `SpecifierHandler` that uses a special file
/// system renaming to mock local and remote modules as well as provides
/// "spies" for the critical methods for testing purposes.
#[ derive(Debug, Default) ]
pub struct MockSpecifierHandler {
pub fixtures : PathBuf ,
2020-10-22 20:50:15 -04:00
pub maybe_tsbuildinfo : Option < String > ,
pub tsbuildinfo_calls : Vec < ( ModuleSpecifier , String ) > ,
2020-10-13 06:54:28 -04:00
pub cache_calls : Vec < ( ModuleSpecifier , Emit ) > ,
2020-10-07 01:24:15 -04:00
pub deps_calls : Vec < ( ModuleSpecifier , DependencyMap ) > ,
pub types_calls : Vec < ( ModuleSpecifier , String ) > ,
pub version_calls : Vec < ( ModuleSpecifier , String ) > ,
}
impl MockSpecifierHandler {
fn get_cache (
& self ,
specifier : ModuleSpecifier ,
2020-12-15 00:52:55 -05:00
) -> Result < CachedModule , ( ModuleSpecifier , AnyError ) > {
2020-10-07 01:24:15 -04:00
let specifier_text = specifier
. to_string ( )
. replace ( " :/// " , " _ " )
. replace ( " :// " , " _ " )
. replace ( " / " , " - " ) ;
2020-10-11 22:25:27 -04:00
let source_path = self . fixtures . join ( specifier_text ) ;
2020-11-01 21:51:56 -05:00
let media_type = MediaType ::from ( & source_path ) ;
2020-12-15 00:52:55 -05:00
let source = fs ::read_to_string ( & source_path )
. map_err ( | err | ( specifier . clone ( ) , err . into ( ) ) ) ? ;
2021-02-17 13:47:18 -05:00
let is_remote = specifier . scheme ( ) ! = " file " ;
2020-10-07 01:24:15 -04:00
Ok ( CachedModule {
source ,
2020-10-15 19:34:55 -04:00
requested_specifier : specifier . clone ( ) ,
2020-10-11 22:25:27 -04:00
source_path ,
2020-10-07 01:24:15 -04:00
specifier ,
media_type ,
2020-10-22 20:50:15 -04:00
is_remote ,
2020-10-07 01:24:15 -04:00
.. CachedModule ::default ( )
} )
}
}
impl SpecifierHandler for MockSpecifierHandler {
2020-10-22 20:50:15 -04:00
fn fetch (
& mut self ,
specifier : ModuleSpecifier ,
_maybe_referrer : Option < Location > ,
_is_dynamic : bool ,
) -> FetchFuture {
2020-10-07 01:24:15 -04:00
Box ::pin ( future ::ready ( self . get_cache ( specifier ) ) )
}
2020-10-22 20:50:15 -04:00
fn get_tsbuildinfo (
2020-10-07 01:24:15 -04:00
& self ,
2020-10-13 06:54:28 -04:00
_specifier : & ModuleSpecifier ,
2020-10-07 07:43:44 -04:00
) -> Result < Option < String > , AnyError > {
2020-10-22 20:50:15 -04:00
Ok ( self . maybe_tsbuildinfo . clone ( ) )
2020-10-07 01:24:15 -04:00
}
fn set_cache (
& mut self ,
specifier : & ModuleSpecifier ,
2020-10-13 06:54:28 -04:00
emit : & Emit ,
2020-10-07 01:24:15 -04:00
) -> Result < ( ) , AnyError > {
2020-10-13 06:54:28 -04:00
self . cache_calls . push ( ( specifier . clone ( ) , emit . clone ( ) ) ) ;
2020-10-07 01:24:15 -04:00
Ok ( ( ) )
}
fn set_types (
& mut self ,
specifier : & ModuleSpecifier ,
types : String ,
) -> Result < ( ) , AnyError > {
self . types_calls . push ( ( specifier . clone ( ) , types ) ) ;
Ok ( ( ) )
}
2020-10-22 20:50:15 -04:00
fn set_tsbuildinfo (
2020-10-07 01:24:15 -04:00
& mut self ,
specifier : & ModuleSpecifier ,
2020-10-22 20:50:15 -04:00
tsbuildinfo : String ,
2020-10-07 01:24:15 -04:00
) -> Result < ( ) , AnyError > {
2020-10-22 20:50:15 -04:00
self . maybe_tsbuildinfo = Some ( tsbuildinfo . clone ( ) ) ;
2020-10-07 01:24:15 -04:00
self
2020-10-22 20:50:15 -04:00
. tsbuildinfo_calls
. push ( ( specifier . clone ( ) , tsbuildinfo ) ) ;
2020-10-07 01:24:15 -04:00
Ok ( ( ) )
}
fn set_deps (
& mut self ,
specifier : & ModuleSpecifier ,
dependencies : DependencyMap ,
) -> Result < ( ) , AnyError > {
self . deps_calls . push ( ( specifier . clone ( ) , dependencies ) ) ;
Ok ( ( ) )
}
fn set_version (
& mut self ,
specifier : & ModuleSpecifier ,
version : String ,
) -> Result < ( ) , AnyError > {
self . version_calls . push ( ( specifier . clone ( ) , version ) ) ;
Ok ( ( ) )
}
}
2020-10-22 20:50:15 -04:00
async fn setup (
specifier : ModuleSpecifier ,
2020-12-29 23:17:17 -05:00
) -> ( Graph , Arc < Mutex < MockSpecifierHandler > > ) {
2020-10-22 20:50:15 -04:00
let c = PathBuf ::from ( env ::var_os ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) ) ;
let fixtures = c . join ( " tests/module_graph " ) ;
2020-12-29 23:17:17 -05:00
let handler = Arc ::new ( Mutex ::new ( MockSpecifierHandler {
2020-10-22 20:50:15 -04:00
fixtures ,
.. MockSpecifierHandler ::default ( )
} ) ) ;
2020-11-02 14:41:20 -05:00
let mut builder = GraphBuilder ::new ( handler . clone ( ) , None , None ) ;
2020-10-22 20:50:15 -04:00
builder
. add ( & specifier , false )
. await
. expect ( " module not inserted " ) ;
2020-10-23 17:01:54 -04:00
( builder . get_graph ( ) , handler )
2020-10-22 20:50:15 -04:00
}
2020-11-01 21:51:56 -05:00
async fn setup_memory (
specifier : ModuleSpecifier ,
sources : HashMap < & str , & str > ,
2020-11-02 14:41:20 -05:00
) -> Graph {
2020-11-01 21:51:56 -05:00
let sources : HashMap < String , String > = sources
. iter ( )
. map ( | ( k , v ) | ( k . to_string ( ) , v . to_string ( ) ) )
. collect ( ) ;
2020-12-29 23:17:17 -05:00
let handler = Arc ::new ( Mutex ::new ( MemoryHandler ::new ( sources ) ) ) ;
2020-11-02 14:41:20 -05:00
let mut builder = GraphBuilder ::new ( handler . clone ( ) , None , None ) ;
2020-11-01 21:51:56 -05:00
builder
. add ( & specifier , false )
. await
. expect ( " module not inserted " ) ;
builder . get_graph ( )
}
2020-09-30 07:46:42 -04:00
#[ test ]
fn test_get_version ( ) {
2020-10-07 07:43:44 -04:00
let doc_a = " console.log(42); " ;
2020-09-30 07:46:42 -04:00
let version_a = get_version ( & doc_a , " 1.2.3 " , b " " ) ;
2020-10-07 07:43:44 -04:00
let doc_b = " console.log(42); " ;
2020-09-30 07:46:42 -04:00
let version_b = get_version ( & doc_b , " 1.2.3 " , b " " ) ;
assert_eq! ( version_a , version_b ) ;
let version_c = get_version ( & doc_a , " 1.2.3 " , b " options " ) ;
assert_ne! ( version_a , version_c ) ;
let version_d = get_version ( & doc_b , " 1.2.3 " , b " options " ) ;
assert_eq! ( version_c , version_d ) ;
let version_e = get_version ( & doc_a , " 1.2.4 " , b " " ) ;
assert_ne! ( version_a , version_e ) ;
let version_f = get_version ( & doc_b , " 1.2.4 " , b " " ) ;
assert_eq! ( version_e , version_f ) ;
}
#[ test ]
fn test_module_emit_valid ( ) {
2020-10-07 07:43:44 -04:00
let source = " console.log(42); " . to_string ( ) ;
2020-11-25 05:30:14 -05:00
let maybe_version = Some ( get_version ( & source , & version ::deno ( ) , b " " ) ) ;
2020-09-30 07:46:42 -04:00
let module = Module {
maybe_version ,
2021-03-25 14:17:37 -04:00
source ,
2020-09-30 07:46:42 -04:00
.. Module ::default ( )
} ;
2020-10-15 19:34:55 -04:00
assert! ( module . is_emit_valid ( b " " ) ) ;
2020-09-30 07:46:42 -04:00
2020-10-07 07:43:44 -04:00
let source = " console.log(42); " . to_string ( ) ;
let old_source = " console.log(43); " ;
2020-11-25 05:30:14 -05:00
let maybe_version = Some ( get_version ( old_source , & version ::deno ( ) , b " " ) ) ;
2020-09-30 07:46:42 -04:00
let module = Module {
maybe_version ,
2021-03-25 14:17:37 -04:00
source ,
2020-09-30 07:46:42 -04:00
.. Module ::default ( )
} ;
2020-10-15 19:34:55 -04:00
assert! ( ! module . is_emit_valid ( b " " ) ) ;
2020-09-30 07:46:42 -04:00
2020-10-07 07:43:44 -04:00
let source = " console.log(42); " . to_string ( ) ;
2020-09-30 07:46:42 -04:00
let maybe_version = Some ( get_version ( & source , " 0.0.0 " , b " " ) ) ;
let module = Module {
maybe_version ,
2021-03-25 14:17:37 -04:00
source ,
2020-09-30 07:46:42 -04:00
.. Module ::default ( )
} ;
2020-10-15 19:34:55 -04:00
assert! ( ! module . is_emit_valid ( b " " ) ) ;
2020-09-30 07:46:42 -04:00
2020-10-07 07:43:44 -04:00
let source = " console.log(42); " . to_string ( ) ;
2020-09-30 07:46:42 -04:00
let module = Module {
source ,
.. Module ::default ( )
} ;
2020-10-15 19:34:55 -04:00
assert! ( ! module . is_emit_valid ( b " " ) ) ;
2020-09-30 07:46:42 -04:00
}
#[ test ]
fn test_module_set_version ( ) {
2020-10-07 07:43:44 -04:00
let source = " console.log(42); " . to_string ( ) ;
2020-11-25 05:30:14 -05:00
let expected = Some ( get_version ( & source , & version ::deno ( ) , b " " ) ) ;
2020-09-30 07:46:42 -04:00
let mut module = Module {
source ,
.. Module ::default ( )
} ;
assert! ( module . maybe_version . is_none ( ) ) ;
module . set_version ( b " " ) ;
assert_eq! ( module . maybe_version , expected ) ;
}
2020-10-19 23:10:42 -04:00
#[ tokio::test ]
async fn test_graph_bundle ( ) {
let tests = vec! [
( " file:///tests/fixture01.ts " , " fixture01.out " ) ,
( " file:///tests/fixture02.ts " , " fixture02.out " ) ,
( " file:///tests/fixture03.ts " , " fixture03.out " ) ,
( " file:///tests/fixture04.ts " , " fixture04.out " ) ,
( " file:///tests/fixture05.ts " , " fixture05.out " ) ,
( " file:///tests/fixture06.ts " , " fixture06.out " ) ,
( " file:///tests/fixture07.ts " , " fixture07.out " ) ,
( " file:///tests/fixture08.ts " , " fixture08.out " ) ,
( " file:///tests/fixture09.ts " , " fixture09.out " ) ,
( " file:///tests/fixture10.ts " , " fixture10.out " ) ,
( " file:///tests/fixture11.ts " , " fixture11.out " ) ,
( " file:///tests/fixture12.ts " , " fixture12.out " ) ,
( " file:///tests/fixture13.ts " , " fixture13.out " ) ,
( " file:///tests/fixture14.ts " , " fixture14.out " ) ,
2020-11-22 18:22:13 -05:00
( " file:///tests/fixture15.ts " , " fixture15.out " ) ,
2020-10-19 23:10:42 -04:00
] ;
let c = PathBuf ::from ( env ::var_os ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) ) ;
let fixtures = c . join ( " tests/bundle " ) ;
for ( specifier , expected_str ) in tests {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( specifier ) . unwrap ( ) ;
2020-12-29 23:17:17 -05:00
let handler = Arc ::new ( Mutex ::new ( MockSpecifierHandler {
2020-10-19 23:10:42 -04:00
fixtures : fixtures . clone ( ) ,
.. MockSpecifierHandler ::default ( )
} ) ) ;
2020-11-02 14:41:20 -05:00
let mut builder = GraphBuilder ::new ( handler . clone ( ) , None , None ) ;
2020-10-19 23:10:42 -04:00
builder
2020-10-22 20:50:15 -04:00
. add ( & specifier , false )
2020-10-19 23:10:42 -04:00
. await
. expect ( " module not inserted " ) ;
2020-10-23 17:01:54 -04:00
let graph = builder . get_graph ( ) ;
2020-10-19 23:10:42 -04:00
let ( actual , stats , maybe_ignored_options ) = graph
. bundle ( BundleOptions ::default ( ) )
. expect ( " could not bundle " ) ;
assert_eq! ( stats . 0. len ( ) , 2 ) ;
assert_eq! ( maybe_ignored_options , None ) ;
let expected_path = fixtures . join ( expected_str ) ;
let expected = fs ::read_to_string ( expected_path ) . unwrap ( ) ;
assert_eq! ( actual , expected , " fixture: {} " , specifier ) ;
}
}
2020-10-22 20:50:15 -04:00
#[ tokio::test ]
async fn test_graph_check_emit ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/main.ts " )
. expect ( " could not resolve module " ) ;
2020-10-22 20:50:15 -04:00
let ( graph , handler ) = setup ( specifier ) . await ;
2020-11-01 21:51:56 -05:00
let result_info = graph
2020-10-22 20:50:15 -04:00
. check ( CheckOptions {
debug : false ,
emit : true ,
lib : TypeLib ::DenoWindow ,
2021-05-10 12:16:39 -04:00
maybe_config_file : None ,
2020-10-22 20:50:15 -04:00
reload : false ,
} )
. expect ( " should have checked " ) ;
2020-11-01 21:51:56 -05:00
assert! ( result_info . maybe_ignored_options . is_none ( ) ) ;
assert_eq! ( result_info . stats . 0. len ( ) , 12 ) ;
assert! ( result_info . diagnostics . is_empty ( ) ) ;
2020-12-29 23:17:17 -05:00
let h = handler . lock ( ) . unwrap ( ) ;
2020-10-22 20:50:15 -04:00
assert_eq! ( h . cache_calls . len ( ) , 2 ) ;
assert_eq! ( h . tsbuildinfo_calls . len ( ) , 1 ) ;
}
2020-11-09 18:18:43 -05:00
#[ tokio::test ]
async fn test_graph_check_ignores_dynamic_import_errors ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/dynamicimport.ts " )
. expect ( " could not resolve module " ) ;
2020-11-09 18:18:43 -05:00
let ( graph , _ ) = setup ( specifier ) . await ;
let result_info = graph
. check ( CheckOptions {
debug : false ,
emit : false ,
lib : TypeLib ::DenoWindow ,
2021-05-10 12:16:39 -04:00
maybe_config_file : None ,
2020-11-09 18:18:43 -05:00
reload : false ,
} )
. expect ( " should have checked " ) ;
assert! ( result_info . diagnostics . is_empty ( ) ) ;
}
2020-11-09 05:21:49 -05:00
#[ tokio::test ]
async fn fix_graph_check_emit_diagnostics ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/diag.ts " )
. expect ( " could not resolve module " ) ;
2020-11-09 05:21:49 -05:00
let ( graph , handler ) = setup ( specifier ) . await ;
let result_info = graph
. check ( CheckOptions {
debug : false ,
emit : true ,
lib : TypeLib ::DenoWindow ,
2021-05-10 12:16:39 -04:00
maybe_config_file : None ,
2020-11-09 05:21:49 -05:00
reload : false ,
} )
. expect ( " should have checked " ) ;
assert! ( result_info . maybe_ignored_options . is_none ( ) ) ;
assert_eq! ( result_info . stats . 0. len ( ) , 12 ) ;
assert! ( ! result_info . diagnostics . is_empty ( ) ) ;
2020-12-29 23:17:17 -05:00
let h = handler . lock ( ) . unwrap ( ) ;
2020-11-09 05:21:49 -05:00
// we shouldn't cache any files or write out tsbuildinfo if there are
// diagnostic errors
assert_eq! ( h . cache_calls . len ( ) , 0 ) ;
assert_eq! ( h . tsbuildinfo_calls . len ( ) , 0 ) ;
}
2020-10-22 20:50:15 -04:00
#[ tokio::test ]
async fn test_graph_check_no_emit ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/main.ts " )
. expect ( " could not resolve module " ) ;
2020-10-22 20:50:15 -04:00
let ( graph , handler ) = setup ( specifier ) . await ;
2020-11-01 21:51:56 -05:00
let result_info = graph
2020-10-22 20:50:15 -04:00
. check ( CheckOptions {
debug : false ,
emit : false ,
lib : TypeLib ::DenoWindow ,
2021-05-10 12:16:39 -04:00
maybe_config_file : None ,
2020-10-22 20:50:15 -04:00
reload : false ,
} )
. expect ( " should have checked " ) ;
2020-11-01 21:51:56 -05:00
assert! ( result_info . maybe_ignored_options . is_none ( ) ) ;
assert_eq! ( result_info . stats . 0. len ( ) , 12 ) ;
assert! ( result_info . diagnostics . is_empty ( ) ) ;
2020-12-29 23:17:17 -05:00
let h = handler . lock ( ) . unwrap ( ) ;
2020-10-22 20:50:15 -04:00
assert_eq! ( h . cache_calls . len ( ) , 0 ) ;
assert_eq! ( h . tsbuildinfo_calls . len ( ) , 1 ) ;
}
2020-11-06 14:53:37 -05:00
#[ tokio::test ]
2020-11-09 18:10:41 -05:00
async fn fix_graph_check_mjs_root ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/a.mjs " )
2020-11-09 18:10:41 -05:00
. expect ( " could not resolve module " ) ;
let ( graph , handler ) = setup ( specifier ) . await ;
let result_info = graph
. check ( CheckOptions {
debug : false ,
emit : true ,
lib : TypeLib ::DenoWindow ,
2021-05-10 12:16:39 -04:00
maybe_config_file : None ,
2020-11-09 18:10:41 -05:00
reload : false ,
} )
. expect ( " should have checked " ) ;
assert! ( result_info . maybe_ignored_options . is_none ( ) ) ;
assert! ( result_info . diagnostics . is_empty ( ) ) ;
2020-12-29 23:17:17 -05:00
let h = handler . lock ( ) . unwrap ( ) ;
2020-11-09 18:10:41 -05:00
assert_eq! ( h . cache_calls . len ( ) , 1 ) ;
assert_eq! ( h . tsbuildinfo_calls . len ( ) , 1 ) ;
}
#[ tokio::test ]
2020-11-06 14:53:37 -05:00
async fn fix_graph_check_types_root ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///typesref.js " )
2020-11-06 14:53:37 -05:00
. expect ( " could not resolve module " ) ;
let ( graph , _ ) = setup ( specifier ) . await ;
let result_info = graph
. check ( CheckOptions {
debug : false ,
emit : false ,
lib : TypeLib ::DenoWindow ,
2021-05-10 12:16:39 -04:00
maybe_config_file : None ,
2020-11-06 14:53:37 -05:00
reload : false ,
} )
. expect ( " should have checked " ) ;
assert! ( result_info . diagnostics . is_empty ( ) ) ;
}
2020-10-29 06:18:18 -04:00
#[ tokio::test ]
async fn test_graph_check_user_config ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/checkwithconfig.ts " )
. expect ( " could not resolve module " ) ;
2020-10-29 06:18:18 -04:00
let ( graph , handler ) = setup ( specifier . clone ( ) ) . await ;
2021-05-10 12:16:39 -04:00
let config_file =
ConfigFile ::read ( " tests/module_graph/tsconfig_01.json " ) . unwrap ( ) ;
2020-11-01 21:51:56 -05:00
let result_info = graph
2020-10-29 06:18:18 -04:00
. check ( CheckOptions {
debug : false ,
emit : true ,
lib : TypeLib ::DenoWindow ,
2021-05-10 12:16:39 -04:00
maybe_config_file : Some ( config_file ) ,
2020-10-29 06:18:18 -04:00
reload : true ,
} )
. expect ( " should have checked " ) ;
2020-11-01 21:51:56 -05:00
assert! ( result_info . maybe_ignored_options . is_none ( ) ) ;
assert! ( result_info . diagnostics . is_empty ( ) ) ;
2021-01-02 07:52:42 -05:00
let ( ver0 , ver1 ) = {
let h = handler . lock ( ) . unwrap ( ) ;
assert_eq! ( h . version_calls . len ( ) , 2 ) ;
( h . version_calls [ 0 ] . 1. clone ( ) , h . version_calls [ 1 ] . 1. clone ( ) )
} ;
2020-10-29 06:18:18 -04:00
// let's do it all over again to ensure that the versions are determinstic
let ( graph , handler ) = setup ( specifier ) . await ;
2021-05-10 12:16:39 -04:00
let config_file =
ConfigFile ::read ( " tests/module_graph/tsconfig_01.json " ) . unwrap ( ) ;
2020-11-01 21:51:56 -05:00
let result_info = graph
2020-10-29 06:18:18 -04:00
. check ( CheckOptions {
debug : false ,
emit : true ,
lib : TypeLib ::DenoWindow ,
2021-05-10 12:16:39 -04:00
maybe_config_file : Some ( config_file ) ,
2020-10-29 06:18:18 -04:00
reload : true ,
} )
. expect ( " should have checked " ) ;
2020-11-01 21:51:56 -05:00
assert! ( result_info . maybe_ignored_options . is_none ( ) ) ;
assert! ( result_info . diagnostics . is_empty ( ) ) ;
2020-12-29 23:17:17 -05:00
let h = handler . lock ( ) . unwrap ( ) ;
2020-10-29 06:18:18 -04:00
assert_eq! ( h . version_calls . len ( ) , 2 ) ;
assert! ( h . version_calls [ 0 ] . 1 = = ver0 | | h . version_calls [ 0 ] . 1 = = ver1 ) ;
assert! ( h . version_calls [ 1 ] . 1 = = ver0 | | h . version_calls [ 1 ] . 1 = = ver1 ) ;
}
2020-11-01 21:51:56 -05:00
#[ tokio::test ]
async fn test_graph_emit ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///a.ts " ) . unwrap ( ) ;
2020-11-01 21:51:56 -05:00
let graph = setup_memory (
specifier ,
map! (
" /a.ts " = > r #"
import * as b from " ./b.ts " ;
console . log ( b ) ;
" #,
" /b.ts " = > r #"
export const b = " b " ;
" #
) ,
)
. await ;
let ( emitted_files , result_info ) = graph
. emit ( EmitOptions {
2020-12-31 16:43:54 -05:00
check : true ,
2020-11-01 21:51:56 -05:00
bundle_type : BundleType ::None ,
debug : false ,
maybe_user_config : None ,
} )
. expect ( " should have emitted " ) ;
assert! ( result_info . diagnostics . is_empty ( ) ) ;
assert! ( result_info . maybe_ignored_options . is_none ( ) ) ;
assert_eq! ( emitted_files . len ( ) , 4 ) ;
let out_a = emitted_files . get ( " file:///a.ts.js " ) ;
assert! ( out_a . is_some ( ) ) ;
let out_a = out_a . unwrap ( ) ;
assert! ( out_a . starts_with ( " import * as b from " ) ) ;
assert! ( emitted_files . contains_key ( " file:///a.ts.js.map " ) ) ;
let out_b = emitted_files . get ( " file:///b.ts.js " ) ;
assert! ( out_b . is_some ( ) ) ;
let out_b = out_b . unwrap ( ) ;
assert! ( out_b . starts_with ( " export const b = \" b \" ; " ) ) ;
assert! ( emitted_files . contains_key ( " file:///b.ts.js.map " ) ) ;
}
#[ tokio::test ]
async fn test_graph_emit_bundle ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///a.ts " ) . unwrap ( ) ;
2020-11-01 21:51:56 -05:00
let graph = setup_memory (
specifier ,
map! (
" /a.ts " = > r #"
import * as b from " ./b.ts " ;
console . log ( b ) ;
" #,
" /b.ts " = > r #"
export const b = " b " ;
" #
) ,
)
. await ;
let ( emitted_files , result_info ) = graph
. emit ( EmitOptions {
2020-12-31 16:43:54 -05:00
check : true ,
2021-04-25 16:54:57 -04:00
bundle_type : BundleType ::Module ,
2020-11-01 21:51:56 -05:00
debug : false ,
maybe_user_config : None ,
} )
. expect ( " should have emitted " ) ;
assert! ( result_info . diagnostics . is_empty ( ) ) ;
assert! ( result_info . maybe_ignored_options . is_none ( ) ) ;
2021-05-19 00:18:01 -04:00
assert_eq! ( emitted_files . len ( ) , 2 ) ;
2020-11-01 21:51:56 -05:00
let actual = emitted_files . get ( " deno:///bundle.js " ) ;
assert! ( actual . is_some ( ) ) ;
2021-05-19 00:18:01 -04:00
assert! ( emitted_files . contains_key ( " deno:///bundle.js.map " ) ) ;
2020-11-01 21:51:56 -05:00
let actual = actual . unwrap ( ) ;
assert! ( actual . contains ( " const b = \" b \" ; " ) ) ;
2020-11-21 07:17:42 -05:00
assert! ( actual . contains ( " console.log(mod); " ) ) ;
2020-11-01 21:51:56 -05:00
}
2020-11-09 14:49:15 -05:00
#[ tokio::test ]
async fn fix_graph_emit_declaration ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///a.ts " ) . unwrap ( ) ;
2020-11-09 14:49:15 -05:00
let graph = setup_memory (
specifier ,
map! (
" /a.ts " = > r #"
import * as b from " ./b.ts " ;
console . log ( b ) ;
" #,
" /b.ts " = > r #"
export const b = " b " ;
" #
) ,
)
. await ;
let mut user_config = HashMap ::< String , Value > ::new ( ) ;
user_config . insert ( " declaration " . to_string ( ) , json! ( true ) ) ;
let ( emitted_files , result_info ) = graph
. emit ( EmitOptions {
2020-12-31 16:43:54 -05:00
check : true ,
2020-11-09 14:49:15 -05:00
bundle_type : BundleType ::None ,
debug : false ,
maybe_user_config : Some ( user_config ) ,
} )
. expect ( " should have emitted " ) ;
assert! ( result_info . diagnostics . is_empty ( ) ) ;
assert! ( result_info . maybe_ignored_options . is_none ( ) ) ;
assert_eq! ( emitted_files . len ( ) , 6 ) ;
let out_a = emitted_files . get ( " file:///a.ts.js " ) ;
assert! ( out_a . is_some ( ) ) ;
let out_a = out_a . unwrap ( ) ;
assert! ( out_a . starts_with ( " import * as b from " ) ) ;
assert! ( emitted_files . contains_key ( " file:///a.ts.js.map " ) ) ;
assert! ( emitted_files . contains_key ( " file:///a.ts.d.ts " ) ) ;
let out_b = emitted_files . get ( " file:///b.ts.js " ) ;
assert! ( out_b . is_some ( ) ) ;
let out_b = out_b . unwrap ( ) ;
assert! ( out_b . starts_with ( " export const b = \" b \" ; " ) ) ;
assert! ( emitted_files . contains_key ( " file:///b.ts.js.map " ) ) ;
assert! ( emitted_files . contains_key ( " file:///b.ts.d.ts " ) ) ;
}
2020-10-11 22:25:27 -04:00
#[ tokio::test ]
async fn test_graph_info ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/main.ts " )
. expect ( " could not resolve module " ) ;
2021-03-01 06:49:58 -05:00
let ( graph , _ ) = setup ( specifier . clone ( ) ) . await ;
2020-10-11 22:25:27 -04:00
let info = graph . info ( ) . expect ( " could not get info " ) ;
2021-03-01 06:49:58 -05:00
assert_eq! ( info . root , specifier ) ;
assert_eq! ( info . modules . len ( ) , 7 ) ;
assert_eq! ( info . size , 518 ) ;
2020-10-11 22:25:27 -04:00
}
2020-10-22 20:50:15 -04:00
#[ tokio::test ]
async fn test_graph_import_json ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/importjson.ts " )
. expect ( " could not resolve module " ) ;
2020-10-22 20:50:15 -04:00
let c = PathBuf ::from ( env ::var_os ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) ) ;
let fixtures = c . join ( " tests/module_graph " ) ;
2020-12-29 23:17:17 -05:00
let handler = Arc ::new ( Mutex ::new ( MockSpecifierHandler {
2020-10-22 20:50:15 -04:00
fixtures ,
.. MockSpecifierHandler ::default ( )
} ) ) ;
2020-11-02 14:41:20 -05:00
let mut builder = GraphBuilder ::new ( handler . clone ( ) , None , None ) ;
2020-10-22 20:50:15 -04:00
builder
. add ( & specifier , false )
. await
. expect_err ( " should have errored " ) ;
}
2020-09-24 18:31:17 -04:00
#[ tokio::test ]
async fn test_graph_transpile ( ) {
// This is a complex scenario of transpiling, where we have TypeScript
// importing a JavaScript file (with type definitions) which imports
// TypeScript, JavaScript, and JavaScript with type definitions.
// For scenarios where we transpile, we only want the TypeScript files
// to be actually emitted.
//
// This also exercises "@deno-types" and type references.
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/main.ts " )
. expect ( " could not resolve module " ) ;
2020-10-22 20:50:15 -04:00
let ( mut graph , handler ) = setup ( specifier ) . await ;
2020-12-15 00:52:55 -05:00
let result_info = graph . transpile ( TranspileOptions ::default ( ) ) . unwrap ( ) ;
assert_eq! ( result_info . stats . 0. len ( ) , 3 ) ;
assert_eq! ( result_info . maybe_ignored_options , None ) ;
2020-12-29 23:17:17 -05:00
let h = handler . lock ( ) . unwrap ( ) ;
2020-09-24 18:31:17 -04:00
assert_eq! ( h . cache_calls . len ( ) , 2 ) ;
2020-10-13 06:54:28 -04:00
match & h . cache_calls [ 0 ] . 1 {
Emit ::Cli ( ( code , maybe_map ) ) = > {
assert! (
code . contains ( " # sourceMappingURL=data:application/json;base64, " )
) ;
assert! ( maybe_map . is_none ( ) ) ;
}
} ;
match & h . cache_calls [ 1 ] . 1 {
Emit ::Cli ( ( code , maybe_map ) ) = > {
assert! (
code . contains ( " # sourceMappingURL=data:application/json;base64, " )
) ;
assert! ( maybe_map . is_none ( ) ) ;
}
} ;
2020-09-24 18:31:17 -04:00
assert_eq! ( h . deps_calls . len ( ) , 7 ) ;
assert_eq! (
h . deps_calls [ 0 ] . 0 ,
2021-02-17 13:47:18 -05:00
resolve_url_or_path ( " file:///tests/main.ts " ) . unwrap ( )
2020-09-24 18:31:17 -04:00
) ;
assert_eq! ( h . deps_calls [ 0 ] . 1. len ( ) , 1 ) ;
assert_eq! (
h . deps_calls [ 1 ] . 0 ,
2021-02-17 13:47:18 -05:00
resolve_url_or_path ( " https://deno.land/x/lib/mod.js " ) . unwrap ( )
2020-09-24 18:31:17 -04:00
) ;
assert_eq! ( h . deps_calls [ 1 ] . 1. len ( ) , 3 ) ;
assert_eq! (
h . deps_calls [ 2 ] . 0 ,
2021-02-17 13:47:18 -05:00
resolve_url_or_path ( " https://deno.land/x/lib/mod.d.ts " ) . unwrap ( )
2020-09-24 18:31:17 -04:00
) ;
assert_eq! ( h . deps_calls [ 2 ] . 1. len ( ) , 3 , " should have 3 dependencies " ) ;
// sometimes the calls are not deterministic, and so checking the contents
// can cause some failures
assert_eq! ( h . deps_calls [ 3 ] . 1. len ( ) , 0 , " should have no dependencies " ) ;
assert_eq! ( h . deps_calls [ 4 ] . 1. len ( ) , 0 , " should have no dependencies " ) ;
assert_eq! ( h . deps_calls [ 5 ] . 1. len ( ) , 0 , " should have no dependencies " ) ;
assert_eq! ( h . deps_calls [ 6 ] . 1. len ( ) , 0 , " should have no dependencies " ) ;
}
#[ tokio::test ]
async fn test_graph_transpile_user_config ( ) {
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " https://deno.land/x/transpile.tsx " )
. expect ( " could not resolve module " ) ;
2020-10-22 20:50:15 -04:00
let ( mut graph , handler ) = setup ( specifier ) . await ;
2021-05-10 12:16:39 -04:00
let config_file =
ConfigFile ::read ( " tests/module_graph/tsconfig.json " ) . unwrap ( ) ;
2020-12-15 00:52:55 -05:00
let result_info = graph
2020-09-24 18:31:17 -04:00
. transpile ( TranspileOptions {
debug : false ,
2021-05-10 12:16:39 -04:00
maybe_config_file : Some ( config_file ) ,
2020-10-22 20:50:15 -04:00
reload : false ,
2020-09-24 18:31:17 -04:00
} )
. unwrap ( ) ;
assert_eq! (
2020-12-15 00:52:55 -05:00
result_info . maybe_ignored_options . unwrap ( ) . items ,
2020-09-29 03:16:12 -04:00
vec! [ " target " . to_string ( ) ] ,
2020-09-24 18:31:17 -04:00
" the 'target' options should have been ignored "
) ;
2020-12-29 23:17:17 -05:00
let h = handler . lock ( ) . unwrap ( ) ;
2020-09-24 18:31:17 -04:00
assert_eq! ( h . cache_calls . len ( ) , 1 , " only one file should be emitted " ) ;
2020-10-02 07:51:37 -04:00
// FIXME(bartlomieju): had to add space in `<div>`, probably a quirk in swc_ecma_codegen
2020-10-13 06:54:28 -04:00
match & h . cache_calls [ 0 ] . 1 {
Emit ::Cli ( ( code , _ ) ) = > {
assert! (
code . contains ( " <div >Hello world!</div> " ) ,
" jsx should have been preserved "
) ;
}
}
2020-09-24 18:31:17 -04:00
}
2020-11-06 23:04:22 -05:00
#[ tokio::test ]
async fn test_graph_import_map_remote_to_local ( ) {
let c = PathBuf ::from ( env ::var_os ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) ) ;
let fixtures = c . join ( " tests/module_graph " ) ;
let maybe_import_map = Some (
ImportMap ::from_json (
" file:///tests/importmap.json " ,
r #" {
" imports " : {
" https://deno.land/x/b/mod.js " : " ./b/mod.js "
}
}
" #,
)
. expect ( " could not parse import map " ) ,
) ;
2020-12-29 23:17:17 -05:00
let handler = Arc ::new ( Mutex ::new ( MockSpecifierHandler {
2020-11-06 23:04:22 -05:00
fixtures ,
.. Default ::default ( )
} ) ) ;
let mut builder = GraphBuilder ::new ( handler , maybe_import_map , None ) ;
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/importremap.ts " )
. expect ( " could not resolve module " ) ;
2020-11-06 23:04:22 -05:00
builder . add ( & specifier , false ) . await . expect ( " could not add " ) ;
builder . get_graph ( ) ;
}
2020-09-24 18:31:17 -04:00
#[ tokio::test ]
async fn test_graph_with_lockfile ( ) {
let c = PathBuf ::from ( env ::var_os ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) ) ;
let fixtures = c . join ( " tests/module_graph " ) ;
let lockfile_path = fixtures . join ( " lockfile.json " ) ;
let lockfile =
2020-10-19 15:19:20 -04:00
Lockfile ::new ( lockfile_path , false ) . expect ( " could not load lockfile " ) ;
2020-10-23 17:01:54 -04:00
let maybe_lockfile = Some ( Arc ::new ( Mutex ::new ( lockfile ) ) ) ;
2020-12-29 23:17:17 -05:00
let handler = Arc ::new ( Mutex ::new ( MockSpecifierHandler {
2020-09-24 18:31:17 -04:00
fixtures ,
.. MockSpecifierHandler ::default ( )
} ) ) ;
2020-11-02 14:41:20 -05:00
let mut builder = GraphBuilder ::new ( handler . clone ( ) , None , maybe_lockfile ) ;
2021-02-17 13:47:18 -05:00
let specifier = resolve_url_or_path ( " file:///tests/main.ts " )
. expect ( " could not resolve module " ) ;
2020-09-24 18:31:17 -04:00
builder
2020-10-22 20:50:15 -04:00
. add ( & specifier , false )
2020-09-24 18:31:17 -04:00
. await
. expect ( " module not inserted " ) ;
2020-10-23 17:01:54 -04:00
builder . get_graph ( ) ;
2020-09-24 18:31:17 -04:00
}
}