mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -05:00
refactor(cli): migrate runtime compile/bundle to new infrastructure (#8192)
Fixes #8060
This commit is contained in:
parent
3558769d46
commit
fdcc78500c
23 changed files with 852 additions and 2770 deletions
|
@ -2,9 +2,11 @@
|
||||||
|
|
||||||
use crate::colors;
|
use crate::colors;
|
||||||
|
|
||||||
|
use deno_core::serde::Deserialize;
|
||||||
|
use deno_core::serde::Deserializer;
|
||||||
|
use deno_core::serde::Serialize;
|
||||||
|
use deno_core::serde::Serializer;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::Deserialize;
|
|
||||||
use serde::Deserializer;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
@ -157,6 +159,21 @@ impl<'de> Deserialize<'de> for DiagnosticCategory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for DiagnosticCategory {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let value = match self {
|
||||||
|
DiagnosticCategory::Warning => 0 as i32,
|
||||||
|
DiagnosticCategory::Error => 1 as i32,
|
||||||
|
DiagnosticCategory::Suggestion => 2 as i32,
|
||||||
|
DiagnosticCategory::Message => 3 as i32,
|
||||||
|
};
|
||||||
|
Serialize::serialize(&value, serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<i64> for DiagnosticCategory {
|
impl From<i64> for DiagnosticCategory {
|
||||||
fn from(value: i64) -> Self {
|
fn from(value: i64) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
@ -169,7 +186,7 @@ impl From<i64> for DiagnosticCategory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DiagnosticMessageChain {
|
pub struct DiagnosticMessageChain {
|
||||||
message_text: String,
|
message_text: String,
|
||||||
|
@ -196,14 +213,14 @@ impl DiagnosticMessageChain {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
pub line: u64,
|
pub line: u64,
|
||||||
pub character: u64,
|
pub character: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
|
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Diagnostic {
|
pub struct Diagnostic {
|
||||||
pub category: DiagnosticCategory,
|
pub category: DiagnosticCategory,
|
||||||
|
@ -367,6 +384,15 @@ impl<'de> Deserialize<'de> for Diagnostics {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for Diagnostics {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
Serialize::serialize(&self.0, serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Diagnostics {
|
impl fmt::Display for Diagnostics {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
27
cli/dts/lib.deno.unstable.d.ts
vendored
27
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -303,9 +303,6 @@ declare namespace Deno {
|
||||||
/** Provide full support for iterables in `for..of`, spread and
|
/** Provide full support for iterables in `for..of`, spread and
|
||||||
* destructuring when targeting ES5 or ES3. Defaults to `false`. */
|
* destructuring when targeting ES5 or ES3. Defaults to `false`. */
|
||||||
downlevelIteration?: boolean;
|
downlevelIteration?: boolean;
|
||||||
/** Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.
|
|
||||||
* Defaults to `false`. */
|
|
||||||
emitBOM?: boolean;
|
|
||||||
/** Only emit `.d.ts` declaration files. Defaults to `false`. */
|
/** Only emit `.d.ts` declaration files. Defaults to `false`. */
|
||||||
emitDeclarationOnly?: boolean;
|
emitDeclarationOnly?: boolean;
|
||||||
/** Emit design-type metadata for decorated declarations in source. See issue
|
/** Emit design-type metadata for decorated declarations in source. See issue
|
||||||
|
@ -316,8 +313,11 @@ declare namespace Deno {
|
||||||
* ecosystem compatibility and enable `allowSyntheticDefaultImports` for type
|
* ecosystem compatibility and enable `allowSyntheticDefaultImports` for type
|
||||||
* system compatibility. Defaults to `true`. */
|
* system compatibility. Defaults to `true`. */
|
||||||
esModuleInterop?: boolean;
|
esModuleInterop?: boolean;
|
||||||
/** Enables experimental support for ES decorators. Defaults to `false`. */
|
/** Enables experimental support for ES decorators. Defaults to `true`. */
|
||||||
experimentalDecorators?: boolean;
|
experimentalDecorators?: boolean;
|
||||||
|
/** Import emit helpers (e.g. `__extends`, `__rest`, etc..) from
|
||||||
|
* [tslib](https://www.npmjs.com/package/tslib). */
|
||||||
|
importHelpers?: boolean;
|
||||||
/** Emit a single file with source maps instead of having a separate file.
|
/** Emit a single file with source maps instead of having a separate file.
|
||||||
* Defaults to `false`. */
|
* Defaults to `false`. */
|
||||||
inlineSourceMap?: boolean;
|
inlineSourceMap?: boolean;
|
||||||
|
@ -325,7 +325,7 @@ declare namespace Deno {
|
||||||
* `inlineSourceMap` or `sourceMap` to be set. Defaults to `false`. */
|
* `inlineSourceMap` or `sourceMap` to be set. Defaults to `false`. */
|
||||||
inlineSources?: boolean;
|
inlineSources?: boolean;
|
||||||
/** Perform additional checks to ensure that transpile only would be safe.
|
/** Perform additional checks to ensure that transpile only would be safe.
|
||||||
* Defaults to `false`. */
|
* Defaults to `true`. */
|
||||||
isolatedModules?: boolean;
|
isolatedModules?: boolean;
|
||||||
/** Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`.
|
/** Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`.
|
||||||
* Defaults to `"react"`. */
|
* Defaults to `"react"`. */
|
||||||
|
@ -333,12 +333,12 @@ declare namespace Deno {
|
||||||
/** Specify the JSX factory function to use when targeting react JSX emit,
|
/** Specify the JSX factory function to use when targeting react JSX emit,
|
||||||
* e.g. `React.createElement` or `h`. Defaults to `React.createElement`. */
|
* e.g. `React.createElement` or `h`. Defaults to `React.createElement`. */
|
||||||
jsxFactory?: string;
|
jsxFactory?: string;
|
||||||
|
/** Specify the JSX fragment factory function to use when targeting react
|
||||||
|
* JSX emit, e.g. `Fragment`. Defaults to `React.Fragment`. */
|
||||||
|
jsxFragmentFactory?: string;
|
||||||
/** Resolve keyof to string valued property names only (no numbers or
|
/** Resolve keyof to string valued property names only (no numbers or
|
||||||
* symbols). Defaults to `false`. */
|
* symbols). Defaults to `false`. */
|
||||||
keyofStringsOnly?: string;
|
keyofStringsOnly?: string;
|
||||||
/** Emit class fields with ECMAScript-standard semantics. Defaults to `false`.
|
|
||||||
*/
|
|
||||||
useDefineForClassFields?: boolean;
|
|
||||||
/** List of library files to be included in the compilation. If omitted,
|
/** List of library files to be included in the compilation. If omitted,
|
||||||
* then the Deno main runtime libs are used. */
|
* then the Deno main runtime libs are used. */
|
||||||
lib?: string[];
|
lib?: string[];
|
||||||
|
@ -389,10 +389,6 @@ declare namespace Deno {
|
||||||
noUnusedLocals?: boolean;
|
noUnusedLocals?: boolean;
|
||||||
/** Report errors on unused parameters. Defaults to `false`. */
|
/** Report errors on unused parameters. Defaults to `false`. */
|
||||||
noUnusedParameters?: boolean;
|
noUnusedParameters?: boolean;
|
||||||
/** Redirect output structure to the directory. This only impacts
|
|
||||||
* `Deno.compile` and only changes the emitted file names. Defaults to
|
|
||||||
* `undefined`. */
|
|
||||||
outDir?: string;
|
|
||||||
/** List of path mapping entries for module names to locations relative to the
|
/** List of path mapping entries for module names to locations relative to the
|
||||||
* `baseUrl`. Defaults to `undefined`. */
|
* `baseUrl`. Defaults to `undefined`. */
|
||||||
paths?: Record<string, string[]>;
|
paths?: Record<string, string[]>;
|
||||||
|
@ -402,8 +398,6 @@ declare namespace Deno {
|
||||||
/** Remove all comments except copy-right header comments beginning with
|
/** Remove all comments except copy-right header comments beginning with
|
||||||
* `/*!`. Defaults to `true`. */
|
* `/*!`. Defaults to `true`. */
|
||||||
removeComments?: boolean;
|
removeComments?: boolean;
|
||||||
/** Include modules imported with `.json` extension. Defaults to `true`. */
|
|
||||||
resolveJsonModule?: boolean;
|
|
||||||
/** Specifies the root directory of input files. Only use to control the
|
/** Specifies the root directory of input files. Only use to control the
|
||||||
* output directory structure with `outDir`. Defaults to `undefined`. */
|
* output directory structure with `outDir`. Defaults to `undefined`. */
|
||||||
rootDir?: string;
|
rootDir?: string;
|
||||||
|
@ -418,6 +412,8 @@ declare namespace Deno {
|
||||||
* specified will be embedded in the sourceMap to direct the debugger where
|
* specified will be embedded in the sourceMap to direct the debugger where
|
||||||
* the source files will be located. Defaults to `undefined`. */
|
* the source files will be located. Defaults to `undefined`. */
|
||||||
sourceRoot?: string;
|
sourceRoot?: string;
|
||||||
|
/** Skip type checking of all declaration files (`*.d.ts`). */
|
||||||
|
skipLibCheck?: boolean;
|
||||||
/** Enable all strict type checking options. Enabling `strict` enables
|
/** Enable all strict type checking options. Enabling `strict` enables
|
||||||
* `noImplicitAny`, `noImplicitThis`, `alwaysStrict`, `strictBindCallApply`,
|
* `noImplicitAny`, `noImplicitThis`, `alwaysStrict`, `strictBindCallApply`,
|
||||||
* `strictNullChecks`, `strictFunctionTypes` and
|
* `strictNullChecks`, `strictFunctionTypes` and
|
||||||
|
@ -472,6 +468,9 @@ declare namespace Deno {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
types?: string[];
|
types?: string[];
|
||||||
|
/** Emit class fields with ECMAScript-standard semantics. Defaults to
|
||||||
|
* `false`. */
|
||||||
|
useDefineForClassFields?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** **UNSTABLE**: new API, yet to be vetted.
|
/** **UNSTABLE**: new API, yet to be vetted.
|
||||||
|
|
32
cli/main.rs
32
cli/main.rs
|
@ -35,7 +35,6 @@ mod lint;
|
||||||
mod lockfile;
|
mod lockfile;
|
||||||
mod media_type;
|
mod media_type;
|
||||||
mod metrics;
|
mod metrics;
|
||||||
mod module_graph;
|
|
||||||
mod module_graph2;
|
mod module_graph2;
|
||||||
mod module_loader;
|
mod module_loader;
|
||||||
mod op_fetch_asset;
|
mod op_fetch_asset;
|
||||||
|
@ -50,7 +49,6 @@ mod specifier_handler;
|
||||||
mod test_runner;
|
mod test_runner;
|
||||||
mod text_encoding;
|
mod text_encoding;
|
||||||
mod tokio_util;
|
mod tokio_util;
|
||||||
mod tsc;
|
|
||||||
mod tsc2;
|
mod tsc2;
|
||||||
mod tsc_config;
|
mod tsc_config;
|
||||||
mod upgrade;
|
mod upgrade;
|
||||||
|
@ -242,6 +240,11 @@ async fn cache_command(
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
files: Vec<String>,
|
files: Vec<String>,
|
||||||
) -> Result<(), AnyError> {
|
) -> Result<(), AnyError> {
|
||||||
|
let lib = if flags.unstable {
|
||||||
|
module_graph2::TypeLib::UnstableDenoWindow
|
||||||
|
} else {
|
||||||
|
module_graph2::TypeLib::DenoWindow
|
||||||
|
};
|
||||||
let program_state = ProgramState::new(flags)?;
|
let program_state = ProgramState::new(flags)?;
|
||||||
|
|
||||||
for file in files {
|
for file in files {
|
||||||
|
@ -249,7 +252,7 @@ async fn cache_command(
|
||||||
program_state
|
program_state
|
||||||
.prepare_module_load(
|
.prepare_module_load(
|
||||||
specifier,
|
specifier,
|
||||||
tsc::TargetLib::Main,
|
lib.clone(),
|
||||||
Permissions::allow_all(),
|
Permissions::allow_all(),
|
||||||
false,
|
false,
|
||||||
program_state.maybe_import_map.clone(),
|
program_state.maybe_import_map.clone(),
|
||||||
|
@ -343,21 +346,20 @@ async fn bundle_command(
|
||||||
module_graph2::TypeLib::DenoWindow
|
module_graph2::TypeLib::DenoWindow
|
||||||
};
|
};
|
||||||
let graph = graph.clone();
|
let graph = graph.clone();
|
||||||
let (stats, diagnostics, maybe_ignored_options) =
|
let result_info = graph.check(module_graph2::CheckOptions {
|
||||||
graph.check(module_graph2::CheckOptions {
|
debug,
|
||||||
debug,
|
emit: false,
|
||||||
emit: false,
|
lib,
|
||||||
lib,
|
maybe_config_path: flags.config_path.clone(),
|
||||||
maybe_config_path: flags.config_path.clone(),
|
reload: flags.reload,
|
||||||
reload: flags.reload,
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
debug!("{}", stats);
|
debug!("{}", result_info.stats);
|
||||||
if let Some(ignored_options) = maybe_ignored_options {
|
if let Some(ignored_options) = result_info.maybe_ignored_options {
|
||||||
eprintln!("{}", ignored_options);
|
eprintln!("{}", ignored_options);
|
||||||
}
|
}
|
||||||
if !diagnostics.is_empty() {
|
if !result_info.diagnostics.is_empty() {
|
||||||
return Err(generic_error(diagnostics.to_string()));
|
return Err(generic_error(result_info.diagnostics.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use deno_core::ModuleSpecifier;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::Serializer;
|
use serde::Serializer;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -60,6 +61,22 @@ impl<'a> From<&'a String> for MediaType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a ModuleSpecifier> for MediaType {
|
||||||
|
fn from(specifier: &'a ModuleSpecifier) -> Self {
|
||||||
|
let url = specifier.as_url();
|
||||||
|
let path = if url.scheme() == "file" {
|
||||||
|
if let Ok(path) = url.to_file_path() {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
PathBuf::from(url.path())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PathBuf::from(url.path())
|
||||||
|
};
|
||||||
|
MediaType::from_path(&path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for MediaType {
|
impl Default for MediaType {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
MediaType::Unknown
|
MediaType::Unknown
|
||||||
|
|
|
@ -1,968 +0,0 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
use crate::ast::Location;
|
|
||||||
use crate::checksum;
|
|
||||||
use crate::file_fetcher::SourceFile;
|
|
||||||
use crate::file_fetcher::SourceFileFetcher;
|
|
||||||
use crate::import_map::ImportMap;
|
|
||||||
use crate::media_type::MediaType;
|
|
||||||
use crate::permissions::Permissions;
|
|
||||||
use crate::tsc::pre_process_file;
|
|
||||||
use crate::tsc::ImportDesc;
|
|
||||||
use crate::tsc::TsReferenceDesc;
|
|
||||||
use crate::tsc::TsReferenceKind;
|
|
||||||
use crate::tsc::AVAILABLE_LIBS;
|
|
||||||
use crate::version;
|
|
||||||
use deno_core::error::custom_error;
|
|
||||||
use deno_core::error::generic_error;
|
|
||||||
use deno_core::error::AnyError;
|
|
||||||
use deno_core::futures::stream::FuturesUnordered;
|
|
||||||
use deno_core::futures::stream::StreamExt;
|
|
||||||
use deno_core::futures::Future;
|
|
||||||
use deno_core::futures::FutureExt;
|
|
||||||
use deno_core::ModuleSpecifier;
|
|
||||||
use serde::Serialize;
|
|
||||||
use serde::Serializer;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
// TODO(bartlomieju): it'd be great if this function returned
|
|
||||||
// more structured data and possibly format the same as TS diagnostics.
|
|
||||||
/// Decorate error with location of import that caused the error.
|
|
||||||
fn err_with_location(
|
|
||||||
e: AnyError,
|
|
||||||
maybe_location: Option<&Location>,
|
|
||||||
) -> AnyError {
|
|
||||||
if let Some(location) = maybe_location {
|
|
||||||
let location_str = format!(
|
|
||||||
"\nImported from \"{}:{}\"",
|
|
||||||
location.filename, location.line
|
|
||||||
);
|
|
||||||
let err_str = e.to_string();
|
|
||||||
generic_error(format!("{}{}", err_str, location_str))
|
|
||||||
} else {
|
|
||||||
e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Disallow http:// imports from modules loaded over https://
|
|
||||||
fn validate_no_downgrade(
|
|
||||||
module_specifier: &ModuleSpecifier,
|
|
||||||
maybe_referrer: Option<&ModuleSpecifier>,
|
|
||||||
maybe_location: Option<&Location>,
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
if let Some(referrer) = maybe_referrer.as_ref() {
|
|
||||||
if let "https" = referrer.as_url().scheme() {
|
|
||||||
if let "http" = module_specifier.as_url().scheme() {
|
|
||||||
let e = custom_error("PermissionDenied",
|
|
||||||
"Modules loaded over https:// are not allowed to import modules over http://"
|
|
||||||
);
|
|
||||||
return Err(err_with_location(e, maybe_location));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verify that remote file doesn't try to statically import local file.
|
|
||||||
fn validate_no_file_from_remote(
|
|
||||||
module_specifier: &ModuleSpecifier,
|
|
||||||
maybe_referrer: Option<&ModuleSpecifier>,
|
|
||||||
maybe_location: Option<&Location>,
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
if let Some(referrer) = maybe_referrer.as_ref() {
|
|
||||||
let referrer_url = referrer.as_url();
|
|
||||||
match referrer_url.scheme() {
|
|
||||||
"http" | "https" => {
|
|
||||||
let specifier_url = module_specifier.as_url();
|
|
||||||
match specifier_url.scheme() {
|
|
||||||
"http" | "https" => {}
|
|
||||||
_ => {
|
|
||||||
let e = custom_error(
|
|
||||||
"PermissionDenied",
|
|
||||||
"Remote modules are not allowed to statically import local \
|
|
||||||
modules. Use dynamic import instead.",
|
|
||||||
);
|
|
||||||
return Err(err_with_location(e, maybe_location));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(bartlomieju): handle imports/references in ambient contexts/TS modules
|
|
||||||
// https://github.com/denoland/deno/issues/6133
|
|
||||||
fn resolve_imports_and_references(
|
|
||||||
referrer: ModuleSpecifier,
|
|
||||||
maybe_import_map: Option<&ImportMap>,
|
|
||||||
import_descs: Vec<ImportDesc>,
|
|
||||||
ref_descs: Vec<TsReferenceDesc>,
|
|
||||||
) -> Result<(Vec<ImportDescriptor>, Vec<ReferenceDescriptor>), AnyError> {
|
|
||||||
let mut imports = vec![];
|
|
||||||
let mut references = vec![];
|
|
||||||
|
|
||||||
for import_desc in import_descs {
|
|
||||||
let maybe_resolved = if let Some(import_map) = maybe_import_map.as_ref() {
|
|
||||||
import_map.resolve(&import_desc.specifier, &referrer.to_string())?
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let resolved_specifier = if let Some(resolved) = maybe_resolved {
|
|
||||||
resolved
|
|
||||||
} else {
|
|
||||||
ModuleSpecifier::resolve_import(
|
|
||||||
&import_desc.specifier,
|
|
||||||
&referrer.to_string(),
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let resolved_type_directive =
|
|
||||||
if let Some(types_specifier) = import_desc.deno_types.as_ref() {
|
|
||||||
Some(ModuleSpecifier::resolve_import(
|
|
||||||
&types_specifier,
|
|
||||||
&referrer.to_string(),
|
|
||||||
)?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let import_descriptor = ImportDescriptor {
|
|
||||||
specifier: import_desc.specifier.to_string(),
|
|
||||||
resolved_specifier,
|
|
||||||
type_directive: import_desc.deno_types.clone(),
|
|
||||||
resolved_type_directive,
|
|
||||||
location: import_desc.location,
|
|
||||||
};
|
|
||||||
|
|
||||||
imports.push(import_descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
for ref_desc in ref_descs {
|
|
||||||
if AVAILABLE_LIBS.contains(&ref_desc.specifier.as_str()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let resolved_specifier = ModuleSpecifier::resolve_import(
|
|
||||||
&ref_desc.specifier,
|
|
||||||
&referrer.to_string(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let reference_descriptor = ReferenceDescriptor {
|
|
||||||
specifier: ref_desc.specifier.to_string(),
|
|
||||||
resolved_specifier,
|
|
||||||
kind: ref_desc.kind,
|
|
||||||
location: ref_desc.location,
|
|
||||||
};
|
|
||||||
|
|
||||||
references.push(reference_descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((imports, references))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_module_specifier<S>(
|
|
||||||
spec: &ModuleSpecifier,
|
|
||||||
s: S,
|
|
||||||
) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
s.serialize_str(&spec.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_option_module_specifier<S>(
|
|
||||||
maybe_spec: &Option<ModuleSpecifier>,
|
|
||||||
s: S,
|
|
||||||
) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
if let Some(spec) = maybe_spec {
|
|
||||||
serialize_module_specifier(spec, s)
|
|
||||||
} else {
|
|
||||||
s.serialize_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SUPPORTED_MEDIA_TYPES: [MediaType; 4] = [
|
|
||||||
MediaType::JavaScript,
|
|
||||||
MediaType::TypeScript,
|
|
||||||
MediaType::JSX,
|
|
||||||
MediaType::TSX,
|
|
||||||
];
|
|
||||||
|
|
||||||
pub type ModuleGraph = HashMap<String, ModuleGraphFile>;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, PartialEq)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ImportDescriptor {
|
|
||||||
pub specifier: String,
|
|
||||||
#[serde(serialize_with = "serialize_module_specifier")]
|
|
||||||
pub resolved_specifier: ModuleSpecifier,
|
|
||||||
// These two fields are for support of @deno-types directive
|
|
||||||
// directly prepending import statement
|
|
||||||
pub type_directive: Option<String>,
|
|
||||||
#[serde(serialize_with = "serialize_option_module_specifier")]
|
|
||||||
pub resolved_type_directive: Option<ModuleSpecifier>,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub location: Location,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ReferenceDescriptor {
|
|
||||||
pub specifier: String,
|
|
||||||
#[serde(serialize_with = "serialize_module_specifier")]
|
|
||||||
pub resolved_specifier: ModuleSpecifier,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub kind: TsReferenceKind,
|
|
||||||
#[serde(skip)]
|
|
||||||
pub location: Location,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ModuleGraphFile {
|
|
||||||
pub specifier: String,
|
|
||||||
pub url: String,
|
|
||||||
pub redirect: Option<String>,
|
|
||||||
pub filename: String,
|
|
||||||
pub version_hash: String,
|
|
||||||
pub imports: Vec<ImportDescriptor>,
|
|
||||||
pub referenced_files: Vec<ReferenceDescriptor>,
|
|
||||||
pub lib_directives: Vec<ReferenceDescriptor>,
|
|
||||||
pub types_directives: Vec<ReferenceDescriptor>,
|
|
||||||
pub type_headers: Vec<ReferenceDescriptor>,
|
|
||||||
pub media_type: MediaType,
|
|
||||||
pub source_code: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
type SourceFileFuture = Pin<
|
|
||||||
Box<dyn Future<Output = Result<(ModuleSpecifier, SourceFile), AnyError>>>,
|
|
||||||
>;
|
|
||||||
|
|
||||||
pub struct ModuleGraphLoader {
|
|
||||||
permissions: Permissions,
|
|
||||||
file_fetcher: SourceFileFetcher,
|
|
||||||
maybe_import_map: Option<ImportMap>,
|
|
||||||
pending_downloads: FuturesUnordered<SourceFileFuture>,
|
|
||||||
has_downloaded: HashSet<ModuleSpecifier>,
|
|
||||||
graph: ModuleGraph,
|
|
||||||
is_dyn_import: bool,
|
|
||||||
analyze_dynamic_imports: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModuleGraphLoader {
|
|
||||||
pub fn new(
|
|
||||||
file_fetcher: SourceFileFetcher,
|
|
||||||
maybe_import_map: Option<ImportMap>,
|
|
||||||
permissions: Permissions,
|
|
||||||
is_dyn_import: bool,
|
|
||||||
analyze_dynamic_imports: bool,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
file_fetcher,
|
|
||||||
permissions,
|
|
||||||
maybe_import_map,
|
|
||||||
pending_downloads: FuturesUnordered::new(),
|
|
||||||
has_downloaded: HashSet::new(),
|
|
||||||
graph: ModuleGraph::new(),
|
|
||||||
is_dyn_import,
|
|
||||||
analyze_dynamic_imports,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This method is used to add specified module and all of its
|
|
||||||
/// dependencies to the graph.
|
|
||||||
///
|
|
||||||
/// It resolves when all dependent modules have been fetched and analyzed.
|
|
||||||
///
|
|
||||||
/// This method can be called multiple times.
|
|
||||||
pub async fn add_to_graph(
|
|
||||||
&mut self,
|
|
||||||
specifier: &ModuleSpecifier,
|
|
||||||
maybe_referrer: Option<ModuleSpecifier>,
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
self.download_module(specifier.clone(), maybe_referrer, None)?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let (specifier, source_file) =
|
|
||||||
self.pending_downloads.next().await.unwrap()?;
|
|
||||||
self.visit_module(&specifier, source_file)?;
|
|
||||||
if self.pending_downloads.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This method is used to create a graph from in-memory files stored in
|
|
||||||
/// a hash map. Useful for creating module graph for code received from
|
|
||||||
/// the runtime.
|
|
||||||
pub fn build_local_graph(
|
|
||||||
&mut self,
|
|
||||||
_root_name: &str,
|
|
||||||
source_map: &HashMap<String, String>,
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
for (spec, source_code) in source_map.iter() {
|
|
||||||
self.visit_memory_module(spec.to_string(), source_code.to_string())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consumes the loader and returns created graph.
|
|
||||||
pub fn get_graph(self) -> ModuleGraph {
|
|
||||||
self.graph
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_memory_module(
|
|
||||||
&mut self,
|
|
||||||
specifier: String,
|
|
||||||
source_code: String,
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
let mut referenced_files = vec![];
|
|
||||||
let mut lib_directives = vec![];
|
|
||||||
let mut types_directives = vec![];
|
|
||||||
|
|
||||||
// FIXME(bartlomieju):
|
|
||||||
// The resolveModules op only handles fully qualified URLs for referrer.
|
|
||||||
// However we will have cases where referrer is "/foo.ts". We add this dummy
|
|
||||||
// prefix "memory://" in order to use resolution logic.
|
|
||||||
let module_specifier =
|
|
||||||
if let Ok(spec) = ModuleSpecifier::resolve_url(&specifier) {
|
|
||||||
spec
|
|
||||||
} else {
|
|
||||||
ModuleSpecifier::resolve_url(&format!("memory://{}", specifier))?
|
|
||||||
};
|
|
||||||
|
|
||||||
let (raw_imports, raw_references) = pre_process_file(
|
|
||||||
&module_specifier.to_string(),
|
|
||||||
MediaType::from(&specifier),
|
|
||||||
&source_code,
|
|
||||||
self.analyze_dynamic_imports,
|
|
||||||
)?;
|
|
||||||
let (imports, references) = resolve_imports_and_references(
|
|
||||||
module_specifier.clone(),
|
|
||||||
self.maybe_import_map.as_ref(),
|
|
||||||
raw_imports,
|
|
||||||
raw_references,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
for ref_descriptor in references {
|
|
||||||
match ref_descriptor.kind {
|
|
||||||
TsReferenceKind::Lib => {
|
|
||||||
lib_directives.push(ref_descriptor);
|
|
||||||
}
|
|
||||||
TsReferenceKind::Types => {
|
|
||||||
types_directives.push(ref_descriptor);
|
|
||||||
}
|
|
||||||
TsReferenceKind::Path => {
|
|
||||||
referenced_files.push(ref_descriptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.graph.insert(
|
|
||||||
module_specifier.to_string(),
|
|
||||||
ModuleGraphFile {
|
|
||||||
specifier: specifier.to_string(),
|
|
||||||
url: specifier.to_string(),
|
|
||||||
redirect: None,
|
|
||||||
version_hash: "".to_string(),
|
|
||||||
media_type: MediaType::from(&specifier),
|
|
||||||
filename: specifier,
|
|
||||||
source_code,
|
|
||||||
imports,
|
|
||||||
referenced_files,
|
|
||||||
lib_directives,
|
|
||||||
types_directives,
|
|
||||||
type_headers: vec![],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(bartlomieju): decorate errors with import location in the source code
|
|
||||||
// https://github.com/denoland/deno/issues/5080
|
|
||||||
fn download_module(
|
|
||||||
&mut self,
|
|
||||||
module_specifier: ModuleSpecifier,
|
|
||||||
maybe_referrer: Option<ModuleSpecifier>,
|
|
||||||
maybe_location: Option<Location>,
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
if self.has_downloaded.contains(&module_specifier) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_no_downgrade(
|
|
||||||
&module_specifier,
|
|
||||||
maybe_referrer.as_ref(),
|
|
||||||
maybe_location.as_ref(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if !self.is_dyn_import {
|
|
||||||
validate_no_file_from_remote(
|
|
||||||
&module_specifier,
|
|
||||||
maybe_referrer.as_ref(),
|
|
||||||
maybe_location.as_ref(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.has_downloaded.insert(module_specifier.clone());
|
|
||||||
let spec = module_specifier;
|
|
||||||
let file_fetcher = self.file_fetcher.clone();
|
|
||||||
let perms = self.permissions.clone();
|
|
||||||
|
|
||||||
let load_future = async move {
|
|
||||||
let spec_ = spec.clone();
|
|
||||||
let source_file = file_fetcher
|
|
||||||
.fetch_source_file(&spec_, maybe_referrer, perms)
|
|
||||||
.await
|
|
||||||
.map_err(|e| err_with_location(e, maybe_location.as_ref()))?;
|
|
||||||
|
|
||||||
Ok((spec_.clone(), source_file))
|
|
||||||
}
|
|
||||||
.boxed_local();
|
|
||||||
|
|
||||||
self.pending_downloads.push(load_future);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_module(
|
|
||||||
&mut self,
|
|
||||||
module_specifier: &ModuleSpecifier,
|
|
||||||
source_file: SourceFile,
|
|
||||||
) -> Result<(), AnyError> {
|
|
||||||
let mut imports = vec![];
|
|
||||||
let mut referenced_files = vec![];
|
|
||||||
let mut lib_directives = vec![];
|
|
||||||
let mut types_directives = vec![];
|
|
||||||
let mut type_headers = vec![];
|
|
||||||
|
|
||||||
// IMPORTANT: source_file.url might be different than requested
|
|
||||||
// module_specifier because of HTTP redirects. In such
|
|
||||||
// situation we add an "empty" ModuleGraphFile with 'redirect'
|
|
||||||
// field set that will be later used in TS worker when building
|
|
||||||
// map of available source file. It will perform substitution
|
|
||||||
// for proper URL point to redirect target.
|
|
||||||
if module_specifier.as_url() != &source_file.url {
|
|
||||||
// TODO(bartlomieju): refactor, this is a band-aid
|
|
||||||
self.graph.insert(
|
|
||||||
module_specifier.to_string(),
|
|
||||||
ModuleGraphFile {
|
|
||||||
specifier: module_specifier.to_string(),
|
|
||||||
url: module_specifier.to_string(),
|
|
||||||
redirect: Some(source_file.url.to_string()),
|
|
||||||
filename: source_file.filename.to_str().unwrap().to_string(),
|
|
||||||
version_hash: checksum::gen(&[
|
|
||||||
&source_file.source_code.as_bytes(),
|
|
||||||
&version::DENO.as_bytes(),
|
|
||||||
]),
|
|
||||||
media_type: source_file.media_type,
|
|
||||||
source_code: "".to_string(),
|
|
||||||
imports: vec![],
|
|
||||||
referenced_files: vec![],
|
|
||||||
lib_directives: vec![],
|
|
||||||
types_directives: vec![],
|
|
||||||
type_headers: vec![],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let module_specifier = ModuleSpecifier::from(source_file.url.clone());
|
|
||||||
let version_hash = checksum::gen(&[
|
|
||||||
&source_file.source_code.as_bytes(),
|
|
||||||
&version::DENO.as_bytes(),
|
|
||||||
]);
|
|
||||||
let source_code = source_file.source_code.clone();
|
|
||||||
|
|
||||||
if SUPPORTED_MEDIA_TYPES.contains(&source_file.media_type) {
|
|
||||||
if let Some(types_specifier) = source_file.types_header {
|
|
||||||
let type_header = ReferenceDescriptor {
|
|
||||||
specifier: types_specifier.to_string(),
|
|
||||||
resolved_specifier: ModuleSpecifier::resolve_import(
|
|
||||||
&types_specifier,
|
|
||||||
&module_specifier.to_string(),
|
|
||||||
)?,
|
|
||||||
kind: TsReferenceKind::Types,
|
|
||||||
// TODO(bartlomieju): location is not needed in here and constructing
|
|
||||||
// location by hand is bad
|
|
||||||
location: Location {
|
|
||||||
filename: module_specifier.to_string(),
|
|
||||||
line: 0,
|
|
||||||
col: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
self.download_module(
|
|
||||||
type_header.resolved_specifier.clone(),
|
|
||||||
Some(module_specifier.clone()),
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
type_headers.push(type_header);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (raw_imports, raw_refs) = pre_process_file(
|
|
||||||
&module_specifier.to_string(),
|
|
||||||
source_file.media_type,
|
|
||||||
&source_code,
|
|
||||||
self.analyze_dynamic_imports,
|
|
||||||
)?;
|
|
||||||
let (imports_, references) = resolve_imports_and_references(
|
|
||||||
module_specifier.clone(),
|
|
||||||
self.maybe_import_map.as_ref(),
|
|
||||||
raw_imports,
|
|
||||||
raw_refs,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
for import_descriptor in imports_ {
|
|
||||||
self.download_module(
|
|
||||||
import_descriptor.resolved_specifier.clone(),
|
|
||||||
Some(module_specifier.clone()),
|
|
||||||
Some(import_descriptor.location.clone()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if let Some(type_dir_url) =
|
|
||||||
import_descriptor.resolved_type_directive.as_ref()
|
|
||||||
{
|
|
||||||
self.download_module(
|
|
||||||
type_dir_url.clone(),
|
|
||||||
Some(module_specifier.clone()),
|
|
||||||
Some(import_descriptor.location.clone()),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
imports.push(import_descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
for ref_descriptor in references {
|
|
||||||
self.download_module(
|
|
||||||
ref_descriptor.resolved_specifier.clone(),
|
|
||||||
Some(module_specifier.clone()),
|
|
||||||
Some(ref_descriptor.location.clone()),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
match ref_descriptor.kind {
|
|
||||||
TsReferenceKind::Lib => {
|
|
||||||
lib_directives.push(ref_descriptor);
|
|
||||||
}
|
|
||||||
TsReferenceKind::Types => {
|
|
||||||
types_directives.push(ref_descriptor);
|
|
||||||
}
|
|
||||||
TsReferenceKind::Path => {
|
|
||||||
referenced_files.push(ref_descriptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.graph.insert(
|
|
||||||
module_specifier.to_string(),
|
|
||||||
ModuleGraphFile {
|
|
||||||
specifier: module_specifier.to_string(),
|
|
||||||
url: module_specifier.to_string(),
|
|
||||||
redirect: None,
|
|
||||||
version_hash,
|
|
||||||
filename: source_file.filename.to_str().unwrap().to_string(),
|
|
||||||
media_type: source_file.media_type,
|
|
||||||
source_code,
|
|
||||||
imports,
|
|
||||||
referenced_files,
|
|
||||||
lib_directives,
|
|
||||||
types_directives,
|
|
||||||
type_headers,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::program_state::ProgramState;
|
|
||||||
use deno_core::serde_json;
|
|
||||||
use deno_core::serde_json::json;
|
|
||||||
|
|
||||||
async fn build_graph(
|
|
||||||
module_specifier: &ModuleSpecifier,
|
|
||||||
) -> Result<ModuleGraph, AnyError> {
|
|
||||||
let program_state = ProgramState::new(Default::default()).unwrap();
|
|
||||||
let mut graph_loader = ModuleGraphLoader::new(
|
|
||||||
program_state.file_fetcher.clone(),
|
|
||||||
None,
|
|
||||||
Permissions::allow_all(),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
graph_loader.add_to_graph(&module_specifier, None).await?;
|
|
||||||
Ok(graph_loader.get_graph())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(bartlomieju): this test is flaky, because it's using 019_media_types
|
|
||||||
// file, reenable once Python server is replaced with Rust one.
|
|
||||||
#[ignore]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn source_graph_fetch() {
|
|
||||||
let _http_server_guard = test_util::http_server();
|
|
||||||
|
|
||||||
let module_specifier = ModuleSpecifier::resolve_url_or_path(
|
|
||||||
"http://localhost:4545/cli/tests/019_media_types.ts",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let graph = build_graph(&module_specifier)
|
|
||||||
.await
|
|
||||||
.expect("Failed to build graph");
|
|
||||||
|
|
||||||
let a = graph
|
|
||||||
.get("http://localhost:4545/cli/tests/019_media_types.ts")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(graph.contains_key(
|
|
||||||
"http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js"
|
|
||||||
));
|
|
||||||
assert!(graph.contains_key(
|
|
||||||
"http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts"
|
|
||||||
));
|
|
||||||
assert!(graph.contains_key("http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts"));
|
|
||||||
assert!(graph.contains_key(
|
|
||||||
"http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts"
|
|
||||||
));
|
|
||||||
assert!(graph.contains_key("http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js"));
|
|
||||||
assert!(graph.contains_key(
|
|
||||||
"http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js"
|
|
||||||
));
|
|
||||||
assert!(graph.contains_key(
|
|
||||||
"http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js"
|
|
||||||
));
|
|
||||||
assert!(graph.contains_key(
|
|
||||||
"http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts"
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
serde_json::to_value(&a.imports).unwrap(),
|
|
||||||
json!([
|
|
||||||
{
|
|
||||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn source_graph_type_references() {
|
|
||||||
let _http_server_guard = test_util::http_server();
|
|
||||||
|
|
||||||
let module_specifier = ModuleSpecifier::resolve_url_or_path(
|
|
||||||
"http://localhost:4545/cli/tests/type_definitions.ts",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let graph = build_graph(&module_specifier)
|
|
||||||
.await
|
|
||||||
.expect("Failed to build graph");
|
|
||||||
|
|
||||||
eprintln!("json {:#?}", serde_json::to_value(&graph).unwrap());
|
|
||||||
|
|
||||||
let a = graph
|
|
||||||
.get("http://localhost:4545/cli/tests/type_definitions.ts")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
serde_json::to_value(&a.imports).unwrap(),
|
|
||||||
json!([
|
|
||||||
{
|
|
||||||
"specifier": "./type_definitions/foo.js",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/type_definitions/foo.js",
|
|
||||||
"typeDirective": "./type_definitions/foo.d.ts",
|
|
||||||
"resolvedTypeDirective": "http://localhost:4545/cli/tests/type_definitions/foo.d.ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"specifier": "./type_definitions/fizz.js",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/type_definitions/fizz.js",
|
|
||||||
"typeDirective": "./type_definitions/fizz.d.ts",
|
|
||||||
"resolvedTypeDirective": "http://localhost:4545/cli/tests/type_definitions/fizz.d.ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"specifier": "./type_definitions/qat.ts",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/type_definitions/qat.ts",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
);
|
|
||||||
assert!(graph
|
|
||||||
.contains_key("http://localhost:4545/cli/tests/type_definitions/foo.js"));
|
|
||||||
assert!(graph.contains_key(
|
|
||||||
"http://localhost:4545/cli/tests/type_definitions/foo.d.ts"
|
|
||||||
));
|
|
||||||
assert!(graph.contains_key(
|
|
||||||
"http://localhost:4545/cli/tests/type_definitions/fizz.js"
|
|
||||||
));
|
|
||||||
assert!(graph.contains_key(
|
|
||||||
"http://localhost:4545/cli/tests/type_definitions/fizz.d.ts"
|
|
||||||
));
|
|
||||||
assert!(graph
|
|
||||||
.contains_key("http://localhost:4545/cli/tests/type_definitions/qat.ts"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn source_graph_type_references2() {
|
|
||||||
let _http_server_guard = test_util::http_server();
|
|
||||||
|
|
||||||
let module_specifier = ModuleSpecifier::resolve_url_or_path(
|
|
||||||
"http://localhost:4545/cli/tests/type_directives_02.ts",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let graph = build_graph(&module_specifier)
|
|
||||||
.await
|
|
||||||
.expect("Failed to build graph");
|
|
||||||
|
|
||||||
eprintln!("{:#?}", serde_json::to_value(&graph).unwrap());
|
|
||||||
|
|
||||||
let a = graph
|
|
||||||
.get("http://localhost:4545/cli/tests/type_directives_02.ts")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
serde_json::to_value(&a.imports).unwrap(),
|
|
||||||
json!([
|
|
||||||
{
|
|
||||||
"specifier": "./subdir/type_reference.js",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/type_reference.js",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(graph.contains_key(
|
|
||||||
"http://localhost:4545/cli/tests/subdir/type_reference.d.ts"
|
|
||||||
));
|
|
||||||
|
|
||||||
let b = graph
|
|
||||||
.get("http://localhost:4545/cli/tests/subdir/type_reference.js")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
serde_json::to_value(&b.types_directives).unwrap(),
|
|
||||||
json!([
|
|
||||||
{
|
|
||||||
"specifier": "./type_reference.d.ts",
|
|
||||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/type_reference.d.ts",
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn source_graph_type_references3() {
|
|
||||||
let _http_server_guard = test_util::http_server();
|
|
||||||
|
|
||||||
let module_specifier = ModuleSpecifier::resolve_url_or_path(
|
|
||||||
"http://localhost:4545/cli/tests/type_directives_01.ts",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let graph = build_graph(&module_specifier)
|
|
||||||
.await
|
|
||||||
.expect("Failed to build graph");
|
|
||||||
|
|
||||||
let ts = graph
|
|
||||||
.get("http://localhost:4545/cli/tests/type_directives_01.ts")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
serde_json::to_value(&ts.imports).unwrap(),
|
|
||||||
json!([
|
|
||||||
{
|
|
||||||
"specifier": "http://127.0.0.1:4545/xTypeScriptTypes.js",
|
|
||||||
"resolvedSpecifier": "http://127.0.0.1:4545/xTypeScriptTypes.js",
|
|
||||||
"typeDirective": null,
|
|
||||||
"resolvedTypeDirective": null,
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
let headers = graph
|
|
||||||
.get("http://127.0.0.1:4545/xTypeScriptTypes.js")
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
serde_json::to_value(&headers.type_headers).unwrap(),
|
|
||||||
json!([
|
|
||||||
{
|
|
||||||
"specifier": "./xTypeScriptTypes.d.ts",
|
|
||||||
"resolvedSpecifier": "http://127.0.0.1:4545/xTypeScriptTypes.d.ts"
|
|
||||||
}
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn source_graph_different_langs() {
|
|
||||||
let _http_server_guard = test_util::http_server();
|
|
||||||
|
|
||||||
// ModuleGraphLoader was mistakenly parsing this file as TSX
|
|
||||||
// https://github.com/denoland/deno/issues/5867
|
|
||||||
|
|
||||||
let module_specifier = ModuleSpecifier::resolve_url_or_path(
|
|
||||||
"http://localhost:4545/cli/tests/ts_with_generic.ts",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
build_graph(&module_specifier)
|
|
||||||
.await
|
|
||||||
.expect("Failed to build graph");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(bartlomieju): use baseline tests from TSC to ensure
|
|
||||||
// compatibility
|
|
||||||
#[test]
|
|
||||||
fn test_pre_process_file() {
|
|
||||||
let source = r#"
|
|
||||||
// This comment is placed to make sure that directives are parsed
|
|
||||||
// even when they start on non-first line
|
|
||||||
|
|
||||||
/// <reference lib="dom" />
|
|
||||||
/// <reference types="./type_reference.d.ts" />
|
|
||||||
/// <reference path="./type_reference/dep.ts" />
|
|
||||||
// @deno-types="./type_definitions/foo.d.ts"
|
|
||||||
import { foo } from "./type_definitions/foo.js";
|
|
||||||
// @deno-types="./type_definitions/fizz.d.ts"
|
|
||||||
import "./type_definitions/fizz.js";
|
|
||||||
|
|
||||||
/// <reference path="./type_reference/dep2.ts" />
|
|
||||||
|
|
||||||
import * as qat from "./type_definitions/qat.ts";
|
|
||||||
|
|
||||||
console.log(foo);
|
|
||||||
console.log(fizz);
|
|
||||||
console.log(qat.qat);
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let (imports, references) =
|
|
||||||
pre_process_file("some/file.ts", MediaType::TypeScript, source, true)
|
|
||||||
.expect("Failed to parse");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
imports,
|
|
||||||
vec![
|
|
||||||
ImportDesc {
|
|
||||||
specifier: "./type_definitions/foo.js".to_string(),
|
|
||||||
deno_types: Some("./type_definitions/foo.d.ts".to_string()),
|
|
||||||
location: Location {
|
|
||||||
filename: "some/file.ts".to_string(),
|
|
||||||
line: 9,
|
|
||||||
col: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ImportDesc {
|
|
||||||
specifier: "./type_definitions/fizz.js".to_string(),
|
|
||||||
deno_types: Some("./type_definitions/fizz.d.ts".to_string()),
|
|
||||||
location: Location {
|
|
||||||
filename: "some/file.ts".to_string(),
|
|
||||||
line: 11,
|
|
||||||
col: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ImportDesc {
|
|
||||||
specifier: "./type_definitions/qat.ts".to_string(),
|
|
||||||
deno_types: None,
|
|
||||||
location: Location {
|
|
||||||
filename: "some/file.ts".to_string(),
|
|
||||||
line: 15,
|
|
||||||
col: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// According to TS docs (https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html)
|
|
||||||
// directives that are not at the top of the file are ignored, so only
|
|
||||||
// 3 references should be captured instead of 4.
|
|
||||||
let file_specifier =
|
|
||||||
ModuleSpecifier::resolve_url_or_path("some/file.ts").unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
references,
|
|
||||||
vec![
|
|
||||||
TsReferenceDesc {
|
|
||||||
specifier: "dom".to_string(),
|
|
||||||
kind: TsReferenceKind::Lib,
|
|
||||||
location: Location {
|
|
||||||
filename: file_specifier.to_string(),
|
|
||||||
line: 5,
|
|
||||||
col: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TsReferenceDesc {
|
|
||||||
specifier: "./type_reference.d.ts".to_string(),
|
|
||||||
kind: TsReferenceKind::Types,
|
|
||||||
location: Location {
|
|
||||||
filename: file_specifier.to_string(),
|
|
||||||
line: 6,
|
|
||||||
col: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TsReferenceDesc {
|
|
||||||
specifier: "./type_reference/dep.ts".to_string(),
|
|
||||||
kind: TsReferenceKind::Path,
|
|
||||||
location: Location {
|
|
||||||
filename: file_specifier.to_string(),
|
|
||||||
line: 7,
|
|
||||||
col: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,9 +1,9 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use crate::ast;
|
||||||
use crate::ast::parse;
|
use crate::ast::parse;
|
||||||
use crate::ast::transpile_module;
|
use crate::ast::transpile_module;
|
||||||
use crate::ast::BundleHook;
|
use crate::ast::BundleHook;
|
||||||
use crate::ast::EmitOptions;
|
|
||||||
use crate::ast::Location;
|
use crate::ast::Location;
|
||||||
use crate::ast::ParsedModule;
|
use crate::ast::ParsedModule;
|
||||||
use crate::colors;
|
use crate::colors;
|
||||||
|
@ -22,8 +22,7 @@ use crate::specifier_handler::DependencyMap;
|
||||||
use crate::specifier_handler::Emit;
|
use crate::specifier_handler::Emit;
|
||||||
use crate::specifier_handler::FetchFuture;
|
use crate::specifier_handler::FetchFuture;
|
||||||
use crate::specifier_handler::SpecifierHandler;
|
use crate::specifier_handler::SpecifierHandler;
|
||||||
use crate::tsc2::exec;
|
use crate::tsc2;
|
||||||
use crate::tsc2::Request;
|
|
||||||
use crate::tsc_config::IgnoredCompilerOptions;
|
use crate::tsc_config::IgnoredCompilerOptions;
|
||||||
use crate::tsc_config::TsConfig;
|
use crate::tsc_config::TsConfig;
|
||||||
use crate::version;
|
use crate::version;
|
||||||
|
@ -35,6 +34,7 @@ use deno_core::futures::stream::StreamExt;
|
||||||
use deno_core::serde::Serialize;
|
use deno_core::serde::Serialize;
|
||||||
use deno_core::serde::Serializer;
|
use deno_core::serde::Serializer;
|
||||||
use deno_core::serde_json::json;
|
use deno_core::serde_json::json;
|
||||||
|
use deno_core::serde_json::Value;
|
||||||
use deno_core::ModuleResolutionError;
|
use deno_core::ModuleResolutionError;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
@ -121,13 +121,13 @@ impl Error for GraphError {}
|
||||||
struct BundleLoader<'a> {
|
struct BundleLoader<'a> {
|
||||||
cm: Rc<swc_common::SourceMap>,
|
cm: Rc<swc_common::SourceMap>,
|
||||||
graph: &'a Graph2,
|
graph: &'a Graph2,
|
||||||
emit_options: &'a EmitOptions,
|
emit_options: &'a ast::EmitOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BundleLoader<'a> {
|
impl<'a> BundleLoader<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
graph: &'a Graph2,
|
graph: &'a Graph2,
|
||||||
emit_options: &'a EmitOptions,
|
emit_options: &'a ast::EmitOptions,
|
||||||
cm: Rc<swc_common::SourceMap>,
|
cm: Rc<swc_common::SourceMap>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
BundleLoader {
|
BundleLoader {
|
||||||
|
@ -480,7 +480,7 @@ impl Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct Stats(pub Vec<(String, u128)>);
|
pub struct Stats(pub Vec<(String, u128)>);
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Stats {
|
impl<'de> Deserialize<'de> for Stats {
|
||||||
|
@ -504,6 +504,23 @@ impl fmt::Display for Stats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
/// 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.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum TypeLib {
|
pub enum TypeLib {
|
||||||
DenoWindow,
|
DenoWindow,
|
||||||
|
@ -539,7 +556,11 @@ impl Serialize for TypeLib {
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct BundleOptions {
|
pub struct BundleOptions {
|
||||||
|
/// If `true` then debug logging will be output from the isolate.
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
|
/// An optional string that points to a user supplied TypeScript configuration
|
||||||
|
/// file that augments the the default configuration passed to the TypeScript
|
||||||
|
/// compiler.
|
||||||
pub maybe_config_path: Option<String>,
|
pub maybe_config_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -560,6 +581,35 @@ pub struct CheckOptions {
|
||||||
pub reload: bool,
|
pub reload: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
pub enum BundleType {
|
||||||
|
/// Return the emitted contents of the program as a single "flattened" ES
|
||||||
|
/// module.
|
||||||
|
Esm,
|
||||||
|
// TODO(@kitsonk) once available in swc
|
||||||
|
// Iife,
|
||||||
|
/// 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 {
|
||||||
|
/// 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>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// A structure which provides options when transpiling modules.
|
/// A structure which provides options when transpiling modules.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct TranspileOptions {
|
pub struct TranspileOptions {
|
||||||
|
@ -647,47 +697,8 @@ impl Graph2 {
|
||||||
}));
|
}));
|
||||||
let maybe_ignored_options =
|
let maybe_ignored_options =
|
||||||
ts_config.merge_tsconfig(options.maybe_config_path)?;
|
ts_config.merge_tsconfig(options.maybe_config_path)?;
|
||||||
let emit_options: EmitOptions = ts_config.into();
|
|
||||||
let cm = Rc::new(swc_common::SourceMap::new(
|
|
||||||
swc_common::FilePathMapping::empty(),
|
|
||||||
));
|
|
||||||
let loader = BundleLoader::new(self, &emit_options, cm.clone());
|
|
||||||
let hook = Box::new(BundleHook);
|
|
||||||
let globals = swc_common::Globals::new();
|
|
||||||
let bundler = swc_bundler::Bundler::new(
|
|
||||||
&globals,
|
|
||||||
cm.clone(),
|
|
||||||
loader,
|
|
||||||
self,
|
|
||||||
swc_bundler::Config::default(),
|
|
||||||
hook,
|
|
||||||
);
|
|
||||||
let mut entries = HashMap::new();
|
|
||||||
entries.insert(
|
|
||||||
"bundle".to_string(),
|
|
||||||
swc_common::FileName::Custom(root_specifier.to_string()),
|
|
||||||
);
|
|
||||||
let output = bundler
|
|
||||||
.bundle(entries)
|
|
||||||
.context("Unable to output bundle during Graph2::bundle().")?;
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
{
|
|
||||||
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(
|
|
||||||
cm, "\n", &mut buf, None,
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
emitter
|
let s = self.emit_bundle(&root_specifier, &ts_config.into())?;
|
||||||
.emit_module(&output[0].module)
|
|
||||||
.context("Unable to emit bundle during Graph2::bundle().")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let s = String::from_utf8(buf)
|
|
||||||
.context("Emitted bundle is an invalid utf-8 string.")?;
|
|
||||||
let stats = Stats(vec![
|
let stats = Stats(vec![
|
||||||
("Files".to_string(), self.modules.len() as u128),
|
("Files".to_string(), self.modules.len() as u128),
|
||||||
("Total time".to_string(), start.elapsed().as_millis()),
|
("Total time".to_string(), start.elapsed().as_millis()),
|
||||||
|
@ -697,11 +708,7 @@ impl Graph2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type check the module graph, corresponding to the options provided.
|
/// Type check the module graph, corresponding to the options provided.
|
||||||
pub fn check(
|
pub fn check(self, options: CheckOptions) -> Result<ResultInfo, AnyError> {
|
||||||
self,
|
|
||||||
options: CheckOptions,
|
|
||||||
) -> Result<(Stats, Diagnostics, Option<IgnoredCompilerOptions>), AnyError>
|
|
||||||
{
|
|
||||||
let mut config = TsConfig::new(json!({
|
let mut config = TsConfig::new(json!({
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
// TODO(@kitsonk) is this really needed?
|
// TODO(@kitsonk) is this really needed?
|
||||||
|
@ -745,11 +752,10 @@ impl Graph2 {
|
||||||
&& (!options.reload || self.roots_dynamic))
|
&& (!options.reload || self.roots_dynamic))
|
||||||
{
|
{
|
||||||
debug!("graph does not need to be checked or emitted.");
|
debug!("graph does not need to be checked or emitted.");
|
||||||
return Ok((
|
return Ok(ResultInfo {
|
||||||
Stats(Vec::new()),
|
|
||||||
Diagnostics::default(),
|
|
||||||
maybe_ignored_options,
|
maybe_ignored_options,
|
||||||
));
|
..Default::default()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(@kitsonk) not totally happy with this here, but this is the first
|
// TODO(@kitsonk) not totally happy with this here, but this is the first
|
||||||
|
@ -760,26 +766,15 @@ impl Graph2 {
|
||||||
info!("{} {}", colors::green("Check"), specifier);
|
info!("{} {}", colors::green("Check"), specifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
let root_names: Vec<(ModuleSpecifier, MediaType)> = self
|
let root_names = self.get_root_names();
|
||||||
.roots
|
|
||||||
.iter()
|
|
||||||
.map(|ms| {
|
|
||||||
(
|
|
||||||
// root modules can be redirects, so before we pass it to tsc we need
|
|
||||||
// to resolve the redirect
|
|
||||||
self.resolve_specifier(ms).clone(),
|
|
||||||
self.get_media_type(ms).unwrap(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let maybe_tsbuildinfo = self.maybe_tsbuildinfo.clone();
|
let maybe_tsbuildinfo = self.maybe_tsbuildinfo.clone();
|
||||||
let hash_data =
|
let hash_data =
|
||||||
vec![config.as_bytes(), version::DENO.as_bytes().to_owned()];
|
vec![config.as_bytes(), version::DENO.as_bytes().to_owned()];
|
||||||
let graph = Rc::new(RefCell::new(self));
|
let graph = Rc::new(RefCell::new(self));
|
||||||
|
|
||||||
let response = exec(
|
let response = tsc2::exec(
|
||||||
js::compiler_isolate_init(),
|
js::compiler_isolate_init(),
|
||||||
Request {
|
tsc2::Request {
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
debug: options.debug,
|
debug: options.debug,
|
||||||
graph: graph.clone(),
|
graph: graph.clone(),
|
||||||
|
@ -837,7 +832,11 @@ impl Graph2 {
|
||||||
}
|
}
|
||||||
graph.flush()?;
|
graph.flush()?;
|
||||||
|
|
||||||
Ok((response.stats, response.diagnostics, maybe_ignored_options))
|
Ok(ResultInfo {
|
||||||
|
diagnostics: response.diagnostics,
|
||||||
|
maybe_ignored_options,
|
||||||
|
stats: response.stats,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains_module(&self, specifier: &ModuleSpecifier) -> bool {
|
fn contains_module(&self, specifier: &ModuleSpecifier) -> bool {
|
||||||
|
@ -845,6 +844,165 @@ impl Graph2 {
|
||||||
self.modules.contains_key(s)
|
self.modules.contains_key(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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(
|
||||||
|
self,
|
||||||
|
options: EmitOptions,
|
||||||
|
) -> Result<(HashMap<String, String>, ResultInfo), AnyError> {
|
||||||
|
let mut config = TsConfig::new(json!({
|
||||||
|
"allowJs": true,
|
||||||
|
// TODO(@kitsonk) consider enabling this by default
|
||||||
|
// see: https://github.com/denoland/deno/issues/7732
|
||||||
|
"emitDecoratorMetadata": false,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"lib": TypeLib::DenoWindow,
|
||||||
|
"module": "esnext",
|
||||||
|
"strict": true,
|
||||||
|
"target": "esnext",
|
||||||
|
}));
|
||||||
|
let opts = match options.bundle_type {
|
||||||
|
BundleType::Esm => json!({
|
||||||
|
"checkJs": false,
|
||||||
|
"inlineSourceMap": false,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsxFactory": "React.createElement",
|
||||||
|
"jsxFragmentFactory": "React.Fragment",
|
||||||
|
}),
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
let root_names = self.get_root_names();
|
||||||
|
let hash_data =
|
||||||
|
vec![config.as_bytes(), version::DENO.as_bytes().to_owned()];
|
||||||
|
let graph = Rc::new(RefCell::new(self));
|
||||||
|
|
||||||
|
let response = tsc2::exec(
|
||||||
|
js::compiler_isolate_init(),
|
||||||
|
tsc2::Request {
|
||||||
|
config: config.clone(),
|
||||||
|
debug: options.debug,
|
||||||
|
graph: graph.clone(),
|
||||||
|
hash_data,
|
||||||
|
maybe_tsbuildinfo: None,
|
||||||
|
root_names,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut emitted_files = HashMap::new();
|
||||||
|
match options.bundle_type {
|
||||||
|
BundleType::Esm => {
|
||||||
|
assert!(
|
||||||
|
response.emitted_files.is_empty(),
|
||||||
|
"No files should have been emitted from tsc."
|
||||||
|
);
|
||||||
|
let graph = graph.borrow();
|
||||||
|
assert_eq!(
|
||||||
|
graph.roots.len(),
|
||||||
|
1,
|
||||||
|
"Only a single root module supported."
|
||||||
|
);
|
||||||
|
let specifier = &graph.roots[0];
|
||||||
|
let s = graph.emit_bundle(specifier, &config.into())?;
|
||||||
|
emitted_files.insert("deno:///bundle.js".to_string(), s);
|
||||||
|
}
|
||||||
|
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",
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let key = format!("{}{}", specifier, extension);
|
||||||
|
emitted_files.insert(key, emitted_file.data.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
emitted_files,
|
||||||
|
ResultInfo {
|
||||||
|
diagnostics: response.diagnostics,
|
||||||
|
maybe_ignored_options,
|
||||||
|
stats: response.stats,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared between `bundle()` and `emit()`.
|
||||||
|
fn emit_bundle(
|
||||||
|
&self,
|
||||||
|
specifier: &ModuleSpecifier,
|
||||||
|
emit_options: &ast::EmitOptions,
|
||||||
|
) -> Result<String, AnyError> {
|
||||||
|
let cm = Rc::new(swc_common::SourceMap::new(
|
||||||
|
swc_common::FilePathMapping::empty(),
|
||||||
|
));
|
||||||
|
let loader = BundleLoader::new(self, emit_options, cm.clone());
|
||||||
|
let hook = Box::new(BundleHook);
|
||||||
|
let globals = swc_common::Globals::new();
|
||||||
|
let bundler = swc_bundler::Bundler::new(
|
||||||
|
&globals,
|
||||||
|
cm.clone(),
|
||||||
|
loader,
|
||||||
|
self,
|
||||||
|
swc_bundler::Config::default(),
|
||||||
|
hook,
|
||||||
|
);
|
||||||
|
let mut entries = HashMap::new();
|
||||||
|
entries.insert(
|
||||||
|
"bundle".to_string(),
|
||||||
|
swc_common::FileName::Custom(specifier.to_string()),
|
||||||
|
);
|
||||||
|
let output = bundler
|
||||||
|
.bundle(entries)
|
||||||
|
.context("Unable to output bundle during Graph2::bundle().")?;
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
{
|
||||||
|
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(
|
||||||
|
cm, "\n", &mut buf, None,
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
emitter
|
||||||
|
.emit_module(&output[0].module)
|
||||||
|
.context("Unable to emit bundle during Graph2::bundle().")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::from_utf8(buf).context("Emitted bundle is an invalid utf-8 string.")
|
||||||
|
}
|
||||||
|
|
||||||
/// Update the handler with any modules that are marked as _dirty_ and update
|
/// Update the handler with any modules that are marked as _dirty_ and update
|
||||||
/// any build info if present.
|
/// any build info if present.
|
||||||
fn flush(&mut self) -> Result<(), AnyError> {
|
fn flush(&mut self) -> Result<(), AnyError> {
|
||||||
|
@ -963,22 +1121,6 @@ impl Graph2 {
|
||||||
self.modules.get(s)
|
self.modules.get(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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> {
|
|
||||||
if let Some(module) = self.get_module(specifier) {
|
|
||||||
Some(module.source.clone())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_module_mut(
|
fn get_module_mut(
|
||||||
&mut self,
|
&mut self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
|
@ -993,6 +1135,41 @@ impl Graph2 {
|
||||||
self.modules.get_mut(s)
|
self.modules.get_mut(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// that upfront before feeding it to `tsc`.
|
||||||
|
fn get_root_names(&self) -> Vec<(ModuleSpecifier, MediaType)> {
|
||||||
|
self
|
||||||
|
.roots
|
||||||
|
.iter()
|
||||||
|
.map(|ms| {
|
||||||
|
(
|
||||||
|
// root modules can be redirects, so before we pass it to tsc we need
|
||||||
|
// to resolve the redirect
|
||||||
|
self.resolve_specifier(ms).clone(),
|
||||||
|
self.get_media_type(ms).unwrap(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
if let Some(module) = self.get_module(specifier) {
|
||||||
|
Some(module.source.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a structure which provides information about the module graph and
|
/// Return a structure which provides information about the module graph and
|
||||||
/// the relationship of the modules in the graph. This structure is used to
|
/// the relationship of the modules in the graph. This structure is used to
|
||||||
/// provide information for the `info` subcommand.
|
/// provide information for the `info` subcommand.
|
||||||
|
@ -1209,7 +1386,7 @@ impl Graph2 {
|
||||||
let maybe_ignored_options =
|
let maybe_ignored_options =
|
||||||
ts_config.merge_tsconfig(options.maybe_config_path)?;
|
ts_config.merge_tsconfig(options.maybe_config_path)?;
|
||||||
|
|
||||||
let emit_options: EmitOptions = ts_config.clone().into();
|
let emit_options: ast::EmitOptions = ts_config.clone().into();
|
||||||
|
|
||||||
let mut emit_count: u128 = 0;
|
let mut emit_count: u128 = 0;
|
||||||
let config = ts_config.as_bytes();
|
let config = ts_config.as_bytes();
|
||||||
|
@ -1434,12 +1611,25 @@ impl GraphBuilder2 {
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use crate::specifier_handler::MemoryHandler;
|
||||||
use deno_core::futures::future;
|
use deno_core::futures::future;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
macro_rules! map (
|
||||||
|
{ $($key:expr => $value:expr),+ } => {
|
||||||
|
{
|
||||||
|
let mut m = ::std::collections::HashMap::new();
|
||||||
|
$(
|
||||||
|
m.insert($key, $value);
|
||||||
|
)+
|
||||||
|
m
|
||||||
|
}
|
||||||
|
};
|
||||||
|
);
|
||||||
|
|
||||||
/// This is a testing mock for `SpecifierHandler` that uses a special file
|
/// This is a testing mock for `SpecifierHandler` that uses a special file
|
||||||
/// system renaming to mock local and remote modules as well as provides
|
/// system renaming to mock local and remote modules as well as provides
|
||||||
/// "spies" for the critical methods for testing purposes.
|
/// "spies" for the critical methods for testing purposes.
|
||||||
|
@ -1465,20 +1655,7 @@ pub mod tests {
|
||||||
.replace("://", "_")
|
.replace("://", "_")
|
||||||
.replace("/", "-");
|
.replace("/", "-");
|
||||||
let source_path = self.fixtures.join(specifier_text);
|
let source_path = self.fixtures.join(specifier_text);
|
||||||
let media_type = match source_path.extension().unwrap().to_str().unwrap()
|
let media_type = MediaType::from(&source_path);
|
||||||
{
|
|
||||||
"ts" => {
|
|
||||||
if source_path.to_string_lossy().ends_with(".d.ts") {
|
|
||||||
MediaType::Dts
|
|
||||||
} else {
|
|
||||||
MediaType::TypeScript
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"tsx" => MediaType::TSX,
|
|
||||||
"js" => MediaType::JavaScript,
|
|
||||||
"jsx" => MediaType::JSX,
|
|
||||||
_ => MediaType::Unknown,
|
|
||||||
};
|
|
||||||
let source = fs::read_to_string(&source_path)?;
|
let source = fs::read_to_string(&source_path)?;
|
||||||
let is_remote = specifier.as_url().scheme() != "file";
|
let is_remote = specifier.as_url().scheme() != "file";
|
||||||
|
|
||||||
|
@ -1572,6 +1749,24 @@ pub mod tests {
|
||||||
(builder.get_graph(), handler)
|
(builder.get_graph(), handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn setup_memory(
|
||||||
|
specifier: ModuleSpecifier,
|
||||||
|
sources: HashMap<&str, &str>,
|
||||||
|
) -> Graph2 {
|
||||||
|
let sources: HashMap<String, String> = sources
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
|
.collect();
|
||||||
|
let handler = Rc::new(RefCell::new(MemoryHandler::new(sources)));
|
||||||
|
let mut builder = GraphBuilder2::new(handler.clone(), None, None);
|
||||||
|
builder
|
||||||
|
.add(&specifier, false)
|
||||||
|
.await
|
||||||
|
.expect("module not inserted");
|
||||||
|
|
||||||
|
builder.get_graph()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_version() {
|
fn test_get_version() {
|
||||||
let doc_a = "console.log(42);";
|
let doc_a = "console.log(42);";
|
||||||
|
@ -1694,7 +1889,7 @@ pub mod tests {
|
||||||
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
|
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
|
||||||
.expect("could not resolve module");
|
.expect("could not resolve module");
|
||||||
let (graph, handler) = setup(specifier).await;
|
let (graph, handler) = setup(specifier).await;
|
||||||
let (stats, diagnostics, maybe_ignored_options) = graph
|
let result_info = graph
|
||||||
.check(CheckOptions {
|
.check(CheckOptions {
|
||||||
debug: false,
|
debug: false,
|
||||||
emit: true,
|
emit: true,
|
||||||
|
@ -1703,9 +1898,9 @@ pub mod tests {
|
||||||
reload: false,
|
reload: false,
|
||||||
})
|
})
|
||||||
.expect("should have checked");
|
.expect("should have checked");
|
||||||
assert!(maybe_ignored_options.is_none());
|
assert!(result_info.maybe_ignored_options.is_none());
|
||||||
assert_eq!(stats.0.len(), 12);
|
assert_eq!(result_info.stats.0.len(), 12);
|
||||||
assert!(diagnostics.is_empty());
|
assert!(result_info.diagnostics.is_empty());
|
||||||
let h = handler.borrow();
|
let h = handler.borrow();
|
||||||
assert_eq!(h.cache_calls.len(), 2);
|
assert_eq!(h.cache_calls.len(), 2);
|
||||||
assert_eq!(h.tsbuildinfo_calls.len(), 1);
|
assert_eq!(h.tsbuildinfo_calls.len(), 1);
|
||||||
|
@ -1717,7 +1912,7 @@ pub mod tests {
|
||||||
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
|
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
|
||||||
.expect("could not resolve module");
|
.expect("could not resolve module");
|
||||||
let (graph, handler) = setup(specifier).await;
|
let (graph, handler) = setup(specifier).await;
|
||||||
let (stats, diagnostics, maybe_ignored_options) = graph
|
let result_info = graph
|
||||||
.check(CheckOptions {
|
.check(CheckOptions {
|
||||||
debug: false,
|
debug: false,
|
||||||
emit: false,
|
emit: false,
|
||||||
|
@ -1726,9 +1921,9 @@ pub mod tests {
|
||||||
reload: false,
|
reload: false,
|
||||||
})
|
})
|
||||||
.expect("should have checked");
|
.expect("should have checked");
|
||||||
assert!(maybe_ignored_options.is_none());
|
assert!(result_info.maybe_ignored_options.is_none());
|
||||||
assert_eq!(stats.0.len(), 12);
|
assert_eq!(result_info.stats.0.len(), 12);
|
||||||
assert!(diagnostics.is_empty());
|
assert!(result_info.diagnostics.is_empty());
|
||||||
let h = handler.borrow();
|
let h = handler.borrow();
|
||||||
assert_eq!(h.cache_calls.len(), 0);
|
assert_eq!(h.cache_calls.len(), 0);
|
||||||
assert_eq!(h.tsbuildinfo_calls.len(), 1);
|
assert_eq!(h.tsbuildinfo_calls.len(), 1);
|
||||||
|
@ -1740,7 +1935,7 @@ pub mod tests {
|
||||||
ModuleSpecifier::resolve_url_or_path("file:///tests/checkwithconfig.ts")
|
ModuleSpecifier::resolve_url_or_path("file:///tests/checkwithconfig.ts")
|
||||||
.expect("could not resolve module");
|
.expect("could not resolve module");
|
||||||
let (graph, handler) = setup(specifier.clone()).await;
|
let (graph, handler) = setup(specifier.clone()).await;
|
||||||
let (_, diagnostics, maybe_ignored_options) = graph
|
let result_info = graph
|
||||||
.check(CheckOptions {
|
.check(CheckOptions {
|
||||||
debug: false,
|
debug: false,
|
||||||
emit: true,
|
emit: true,
|
||||||
|
@ -1751,8 +1946,8 @@ pub mod tests {
|
||||||
reload: true,
|
reload: true,
|
||||||
})
|
})
|
||||||
.expect("should have checked");
|
.expect("should have checked");
|
||||||
assert!(maybe_ignored_options.is_none());
|
assert!(result_info.maybe_ignored_options.is_none());
|
||||||
assert!(diagnostics.is_empty());
|
assert!(result_info.diagnostics.is_empty());
|
||||||
let h = handler.borrow();
|
let h = handler.borrow();
|
||||||
assert_eq!(h.version_calls.len(), 2);
|
assert_eq!(h.version_calls.len(), 2);
|
||||||
let ver0 = h.version_calls[0].1.clone();
|
let ver0 = h.version_calls[0].1.clone();
|
||||||
|
@ -1760,7 +1955,7 @@ pub mod tests {
|
||||||
|
|
||||||
// let's do it all over again to ensure that the versions are determinstic
|
// let's do it all over again to ensure that the versions are determinstic
|
||||||
let (graph, handler) = setup(specifier).await;
|
let (graph, handler) = setup(specifier).await;
|
||||||
let (_, diagnostics, maybe_ignored_options) = graph
|
let result_info = graph
|
||||||
.check(CheckOptions {
|
.check(CheckOptions {
|
||||||
debug: false,
|
debug: false,
|
||||||
emit: true,
|
emit: true,
|
||||||
|
@ -1771,14 +1966,89 @@ pub mod tests {
|
||||||
reload: true,
|
reload: true,
|
||||||
})
|
})
|
||||||
.expect("should have checked");
|
.expect("should have checked");
|
||||||
assert!(maybe_ignored_options.is_none());
|
assert!(result_info.maybe_ignored_options.is_none());
|
||||||
assert!(diagnostics.is_empty());
|
assert!(result_info.diagnostics.is_empty());
|
||||||
let h = handler.borrow();
|
let h = handler.borrow();
|
||||||
assert_eq!(h.version_calls.len(), 2);
|
assert_eq!(h.version_calls.len(), 2);
|
||||||
assert!(h.version_calls[0].1 == ver0 || h.version_calls[0].1 == ver1);
|
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);
|
assert!(h.version_calls[1].1 == ver0 || h.version_calls[1].1 == ver1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_graph_emit() {
|
||||||
|
let specifier =
|
||||||
|
ModuleSpecifier::resolve_url_or_path("file:///a.ts").unwrap();
|
||||||
|
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 {
|
||||||
|
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() {
|
||||||
|
let specifier =
|
||||||
|
ModuleSpecifier::resolve_url_or_path("file:///a.ts").unwrap();
|
||||||
|
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 {
|
||||||
|
bundle_type: BundleType::Esm,
|
||||||
|
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(), 1);
|
||||||
|
let actual = emitted_files.get("deno:///bundle.js");
|
||||||
|
assert!(actual.is_some());
|
||||||
|
let actual = actual.unwrap();
|
||||||
|
assert!(actual.contains("const b = \"b\";"));
|
||||||
|
assert!(actual.contains("console.log(b);"));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_graph_info() {
|
async fn test_graph_info() {
|
||||||
let specifier =
|
let specifier =
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
use crate::import_map::ImportMap;
|
use crate::import_map::ImportMap;
|
||||||
|
use crate::module_graph2::TypeLib;
|
||||||
use crate::permissions::Permissions;
|
use crate::permissions::Permissions;
|
||||||
use crate::program_state::ProgramState;
|
use crate::program_state::ProgramState;
|
||||||
use crate::tsc::TargetLib;
|
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::futures::future::FutureExt;
|
use deno_core::futures::future::FutureExt;
|
||||||
use deno_core::futures::Future;
|
use deno_core::futures::Future;
|
||||||
|
@ -21,7 +21,7 @@ pub struct CliModuleLoader {
|
||||||
/// When flags contains a `.import_map_path` option, the content of the
|
/// When flags contains a `.import_map_path` option, the content of the
|
||||||
/// import map file will be resolved and set.
|
/// import map file will be resolved and set.
|
||||||
pub import_map: Option<ImportMap>,
|
pub import_map: Option<ImportMap>,
|
||||||
pub target_lib: TargetLib,
|
pub lib: TypeLib,
|
||||||
pub is_main: bool,
|
pub is_main: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ impl CliModuleLoader {
|
||||||
pub fn new(maybe_import_map: Option<ImportMap>) -> Rc<Self> {
|
pub fn new(maybe_import_map: Option<ImportMap>) -> Rc<Self> {
|
||||||
Rc::new(CliModuleLoader {
|
Rc::new(CliModuleLoader {
|
||||||
import_map: maybe_import_map,
|
import_map: maybe_import_map,
|
||||||
target_lib: TargetLib::Main,
|
lib: TypeLib::DenoWindow,
|
||||||
is_main: true,
|
is_main: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ impl CliModuleLoader {
|
||||||
pub fn new_for_worker() -> Rc<Self> {
|
pub fn new_for_worker() -> Rc<Self> {
|
||||||
Rc::new(CliModuleLoader {
|
Rc::new(CliModuleLoader {
|
||||||
import_map: None,
|
import_map: None,
|
||||||
target_lib: TargetLib::Worker,
|
lib: TypeLib::DenoWorker,
|
||||||
is_main: false,
|
is_main: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -117,13 +117,21 @@ impl ModuleLoader for CliModuleLoader {
|
||||||
is_dynamic: bool,
|
is_dynamic: bool,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
|
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
|
||||||
let specifier = specifier.clone();
|
let specifier = specifier.clone();
|
||||||
let target_lib = self.target_lib.clone();
|
|
||||||
let maybe_import_map = self.import_map.clone();
|
let maybe_import_map = self.import_map.clone();
|
||||||
let state = op_state.borrow();
|
let state = op_state.borrow();
|
||||||
|
|
||||||
// The permissions that should be applied to any dynamically imported module
|
// The permissions that should be applied to any dynamically imported module
|
||||||
let dynamic_permissions = state.borrow::<Permissions>().clone();
|
let dynamic_permissions = state.borrow::<Permissions>().clone();
|
||||||
let program_state = state.borrow::<Arc<ProgramState>>().clone();
|
let program_state = state.borrow::<Arc<ProgramState>>().clone();
|
||||||
|
let lib = if program_state.flags.unstable {
|
||||||
|
if self.lib == TypeLib::DenoWindow {
|
||||||
|
TypeLib::UnstableDenoWindow
|
||||||
|
} else {
|
||||||
|
TypeLib::UnstableDenoWorker
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.lib.clone()
|
||||||
|
};
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
// TODO(bartlomieju): `prepare_module_load` should take `load_id` param
|
// TODO(bartlomieju): `prepare_module_load` should take `load_id` param
|
||||||
|
@ -131,7 +139,7 @@ impl ModuleLoader for CliModuleLoader {
|
||||||
program_state
|
program_state
|
||||||
.prepare_module_load(
|
.prepare_module_load(
|
||||||
specifier,
|
specifier,
|
||||||
target_lib,
|
lib,
|
||||||
dynamic_permissions,
|
dynamic_permissions,
|
||||||
is_dynamic,
|
is_dynamic,
|
||||||
maybe_import_map,
|
maybe_import_map,
|
||||||
|
|
|
@ -17,8 +17,6 @@ pub fn get_asset(name: &str) -> Option<&'static str> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
match name {
|
match name {
|
||||||
"system_loader.js" => Some(include_str!("system_loader.js")),
|
|
||||||
"system_loader_es5.js" => Some(include_str!("system_loader_es5.js")),
|
|
||||||
"bootstrap.ts" => Some("console.log(\"hello deno\");"),
|
"bootstrap.ts" => Some("console.log(\"hello deno\");"),
|
||||||
"typescript.d.ts" => inc!("typescript.d.ts"),
|
"typescript.d.ts" => inc!("typescript.d.ts"),
|
||||||
"lib.dom.d.ts" => inc!("lib.dom.d.ts"),
|
"lib.dom.d.ts" => inc!("lib.dom.d.ts"),
|
||||||
|
@ -85,6 +83,11 @@ pub fn get_asset(name: &str) -> Option<&'static str> {
|
||||||
|
|
||||||
/// Warning: Returns a non-JSON op dispatcher. Must be manually attached to
|
/// Warning: Returns a non-JSON op dispatcher. Must be manually attached to
|
||||||
/// JsRuntime.
|
/// JsRuntime.
|
||||||
|
///
|
||||||
|
/// TODO(@kitsonk) this is only used when building the snapshot, and needs to
|
||||||
|
/// be refactored somewhere else. It is no longer used by `main.rs` and
|
||||||
|
/// therefore requires the allow unused.
|
||||||
|
#[allow(unused)]
|
||||||
pub fn op_fetch_asset(
|
pub fn op_fetch_asset(
|
||||||
custom_assets: HashMap<String, PathBuf>,
|
custom_assets: HashMap<String, PathBuf>,
|
||||||
) -> impl Fn(Rc<RefCell<OpState>>, BufVec) -> Op {
|
) -> impl Fn(Rc<RefCell<OpState>>, BufVec) -> Op {
|
||||||
|
|
|
@ -3,17 +3,23 @@
|
||||||
use crate::ast;
|
use crate::ast;
|
||||||
use crate::colors;
|
use crate::colors;
|
||||||
use crate::media_type::MediaType;
|
use crate::media_type::MediaType;
|
||||||
|
use crate::module_graph2::BundleType;
|
||||||
|
use crate::module_graph2::EmitOptions;
|
||||||
|
use crate::module_graph2::GraphBuilder2;
|
||||||
use crate::permissions::Permissions;
|
use crate::permissions::Permissions;
|
||||||
use crate::tsc::runtime_bundle;
|
use crate::specifier_handler::FetchHandler;
|
||||||
use crate::tsc::runtime_compile;
|
use crate::specifier_handler::MemoryHandler;
|
||||||
|
use crate::specifier_handler::SpecifierHandler;
|
||||||
use crate::tsc_config;
|
use crate::tsc_config;
|
||||||
|
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::futures::FutureExt;
|
use deno_core::error::Context;
|
||||||
use deno_core::serde::Serialize;
|
use deno_core::serde::Serialize;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::serde_json::json;
|
use deno_core::serde_json::json;
|
||||||
use deno_core::serde_json::Value;
|
use deno_core::serde_json::Value;
|
||||||
use deno_core::BufVec;
|
use deno_core::BufVec;
|
||||||
|
use deno_core::ModuleSpecifier;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
@ -39,35 +45,53 @@ async fn op_compile(
|
||||||
args: Value,
|
args: Value,
|
||||||
_data: BufVec,
|
_data: BufVec,
|
||||||
) -> Result<Value, AnyError> {
|
) -> Result<Value, AnyError> {
|
||||||
super::check_unstable2(&state, "Deno.compile");
|
|
||||||
let args: CompileArgs = serde_json::from_value(args)?;
|
let args: CompileArgs = serde_json::from_value(args)?;
|
||||||
let cli_state = super::global_state2(&state);
|
if args.bundle {
|
||||||
let program_state = cli_state.clone();
|
super::check_unstable2(&state, "Deno.bundle");
|
||||||
let permissions = {
|
} else {
|
||||||
|
super::check_unstable2(&state, "Deno.compile");
|
||||||
|
}
|
||||||
|
let program_state = super::global_state2(&state);
|
||||||
|
let runtime_permissions = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
state.borrow::<Permissions>().clone()
|
state.borrow::<Permissions>().clone()
|
||||||
};
|
};
|
||||||
let fut = if args.bundle {
|
let handler: Rc<RefCell<dyn SpecifierHandler>> =
|
||||||
runtime_bundle(
|
if let Some(sources) = args.sources {
|
||||||
&program_state,
|
Rc::new(RefCell::new(MemoryHandler::new(sources)))
|
||||||
permissions,
|
} else {
|
||||||
&args.root_name,
|
Rc::new(RefCell::new(FetchHandler::new(
|
||||||
&args.sources,
|
&program_state,
|
||||||
&args.options,
|
runtime_permissions,
|
||||||
)
|
)?))
|
||||||
.boxed_local()
|
};
|
||||||
|
let mut builder = GraphBuilder2::new(handler, None, None);
|
||||||
|
let specifier = ModuleSpecifier::resolve_url_or_path(&args.root_name)
|
||||||
|
.context("The root specifier is invalid.")?;
|
||||||
|
builder.add(&specifier, false).await?;
|
||||||
|
let graph = builder.get_graph();
|
||||||
|
let bundle_type = if args.bundle {
|
||||||
|
BundleType::Esm
|
||||||
} else {
|
} else {
|
||||||
runtime_compile(
|
BundleType::None
|
||||||
&program_state,
|
|
||||||
permissions,
|
|
||||||
&args.root_name,
|
|
||||||
&args.sources,
|
|
||||||
&args.options,
|
|
||||||
)
|
|
||||||
.boxed_local()
|
|
||||||
};
|
};
|
||||||
let result = fut.await?;
|
let debug = program_state.flags.log_level == Some(log::Level::Debug);
|
||||||
Ok(result)
|
let maybe_user_config: Option<HashMap<String, Value>> =
|
||||||
|
if let Some(options) = args.options {
|
||||||
|
Some(serde_json::from_str(&options)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let (emitted_files, result_info) = graph.emit(EmitOptions {
|
||||||
|
bundle_type,
|
||||||
|
debug,
|
||||||
|
maybe_user_config,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(json!({
|
||||||
|
"emittedFiles": emitted_files,
|
||||||
|
"diagnostics": result_info.diagnostics,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
|
|
@ -15,9 +15,6 @@ use crate::module_graph2::TypeLib;
|
||||||
use crate::permissions::Permissions;
|
use crate::permissions::Permissions;
|
||||||
use crate::source_maps::SourceMapGetter;
|
use crate::source_maps::SourceMapGetter;
|
||||||
use crate::specifier_handler::FetchHandler;
|
use crate::specifier_handler::FetchHandler;
|
||||||
use crate::tsc::CompiledModule;
|
|
||||||
use crate::tsc::TargetLib;
|
|
||||||
use crate::tsc::TsCompiler;
|
|
||||||
|
|
||||||
use deno_core::error::generic_error;
|
use deno_core::error::generic_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
|
@ -37,6 +34,12 @@ pub fn exit_unstable(api_name: &str) {
|
||||||
std::process::exit(70);
|
std::process::exit(70);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(@kitsonk) probably can refactor this better with the graph.
|
||||||
|
pub struct CompiledModule {
|
||||||
|
pub code: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// This structure represents state of single "deno" program.
|
/// This structure represents state of single "deno" program.
|
||||||
///
|
///
|
||||||
/// It is shared by all created workers (thus V8 isolates).
|
/// It is shared by all created workers (thus V8 isolates).
|
||||||
|
@ -47,7 +50,6 @@ pub struct ProgramState {
|
||||||
pub permissions: Permissions,
|
pub permissions: Permissions,
|
||||||
pub dir: deno_dir::DenoDir,
|
pub dir: deno_dir::DenoDir,
|
||||||
pub file_fetcher: SourceFileFetcher,
|
pub file_fetcher: SourceFileFetcher,
|
||||||
pub ts_compiler: TsCompiler,
|
|
||||||
pub lockfile: Option<Arc<Mutex<Lockfile>>>,
|
pub lockfile: Option<Arc<Mutex<Lockfile>>>,
|
||||||
pub maybe_import_map: Option<ImportMap>,
|
pub maybe_import_map: Option<ImportMap>,
|
||||||
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
|
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
|
||||||
|
@ -70,12 +72,6 @@ impl ProgramState {
|
||||||
ca_file.as_deref(),
|
ca_file.as_deref(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let ts_compiler = TsCompiler::new(
|
|
||||||
file_fetcher.clone(),
|
|
||||||
flags.clone(),
|
|
||||||
dir.gen_cache.clone(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let lockfile = if let Some(filename) = &flags.lock {
|
let lockfile = if let Some(filename) = &flags.lock {
|
||||||
let lockfile = Lockfile::new(filename.clone(), flags.lock_write)?;
|
let lockfile = Lockfile::new(filename.clone(), flags.lock_write)?;
|
||||||
Some(Arc::new(Mutex::new(lockfile)))
|
Some(Arc::new(Mutex::new(lockfile)))
|
||||||
|
@ -105,7 +101,6 @@ impl ProgramState {
|
||||||
permissions: Permissions::from_flags(&flags),
|
permissions: Permissions::from_flags(&flags),
|
||||||
flags,
|
flags,
|
||||||
file_fetcher,
|
file_fetcher,
|
||||||
ts_compiler,
|
|
||||||
lockfile,
|
lockfile,
|
||||||
maybe_import_map,
|
maybe_import_map,
|
||||||
maybe_inspector_server,
|
maybe_inspector_server,
|
||||||
|
@ -120,7 +115,7 @@ impl ProgramState {
|
||||||
pub async fn prepare_module_load(
|
pub async fn prepare_module_load(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
specifier: ModuleSpecifier,
|
specifier: ModuleSpecifier,
|
||||||
target_lib: TargetLib,
|
lib: TypeLib,
|
||||||
runtime_permissions: Permissions,
|
runtime_permissions: Permissions,
|
||||||
is_dynamic: bool,
|
is_dynamic: bool,
|
||||||
maybe_import_map: Option<ImportMap>,
|
maybe_import_map: Option<ImportMap>,
|
||||||
|
@ -129,7 +124,7 @@ impl ProgramState {
|
||||||
// Workers are subject to the current runtime permissions. We do the
|
// Workers are subject to the current runtime permissions. We do the
|
||||||
// permission check here early to avoid "wasting" time building a module
|
// permission check here early to avoid "wasting" time building a module
|
||||||
// graph for a module that cannot be loaded.
|
// graph for a module that cannot be loaded.
|
||||||
if target_lib == TargetLib::Worker {
|
if lib == TypeLib::DenoWorker || lib == TypeLib::UnstableDenoWorker {
|
||||||
runtime_permissions.check_specifier(&specifier)?;
|
runtime_permissions.check_specifier(&specifier)?;
|
||||||
}
|
}
|
||||||
let handler =
|
let handler =
|
||||||
|
@ -153,37 +148,20 @@ impl ProgramState {
|
||||||
eprintln!("{}", ignored_options);
|
eprintln!("{}", ignored_options);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let lib = match target_lib {
|
let result_info = graph.check(CheckOptions {
|
||||||
TargetLib::Main => {
|
debug,
|
||||||
if self.flags.unstable {
|
emit: true,
|
||||||
TypeLib::UnstableDenoWindow
|
lib,
|
||||||
} else {
|
maybe_config_path,
|
||||||
TypeLib::DenoWindow
|
reload: self.flags.reload,
|
||||||
}
|
})?;
|
||||||
}
|
|
||||||
TargetLib::Worker => {
|
|
||||||
if self.flags.unstable {
|
|
||||||
TypeLib::UnstableDenoWorker
|
|
||||||
} else {
|
|
||||||
TypeLib::DenoWorker
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let (stats, diagnostics, maybe_ignored_options) =
|
|
||||||
graph.check(CheckOptions {
|
|
||||||
debug,
|
|
||||||
emit: true,
|
|
||||||
lib,
|
|
||||||
maybe_config_path,
|
|
||||||
reload: self.flags.reload,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
debug!("{}", stats);
|
debug!("{}", result_info.stats);
|
||||||
if let Some(ignored_options) = maybe_ignored_options {
|
if let Some(ignored_options) = result_info.maybe_ignored_options {
|
||||||
eprintln!("{}", ignored_options);
|
eprintln!("{}", ignored_options);
|
||||||
}
|
}
|
||||||
if !diagnostics.is_empty() {
|
if !result_info.diagnostics.is_empty() {
|
||||||
return Err(generic_error(diagnostics.to_string()));
|
return Err(generic_error(result_info.diagnostics.to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -52,19 +52,14 @@
|
||||||
sources: !!sources,
|
sources: !!sources,
|
||||||
options,
|
options,
|
||||||
});
|
});
|
||||||
|
/** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
|
||||||
const result = await opCompile(payload);
|
const result = await opCompile(payload);
|
||||||
util.assert(result.emitMap);
|
util.assert(result.emittedFiles);
|
||||||
const maybeDiagnostics = result.diagnostics.length === 0
|
const maybeDiagnostics = result.diagnostics.length === 0
|
||||||
? undefined
|
? undefined
|
||||||
: result.diagnostics;
|
: result.diagnostics;
|
||||||
|
|
||||||
const emitMap = {};
|
return [maybeDiagnostics, result.emittedFiles];
|
||||||
|
|
||||||
for (const [key, emittedSource] of Object.entries(result.emitMap)) {
|
|
||||||
emitMap[key] = emittedSource.contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [maybeDiagnostics, emitMap];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(bartlomieju): change return type to interface?
|
// TODO(bartlomieju): change return type to interface?
|
||||||
|
@ -84,12 +79,14 @@
|
||||||
sources: !!sources,
|
sources: !!sources,
|
||||||
options,
|
options,
|
||||||
});
|
});
|
||||||
|
/** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
|
||||||
const result = await opCompile(payload);
|
const result = await opCompile(payload);
|
||||||
util.assert(result.output);
|
let output = result.emittedFiles["deno:///bundle.js"];
|
||||||
|
util.assert(output);
|
||||||
const maybeDiagnostics = result.diagnostics.length === 0
|
const maybeDiagnostics = result.diagnostics.length === 0
|
||||||
? undefined
|
? undefined
|
||||||
: result.diagnostics;
|
: result.diagnostics;
|
||||||
return [maybeDiagnostics, result.output];
|
return [maybeDiagnostics, output];
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__bootstrap.compilerApi = {
|
window.__bootstrap.compilerApi = {
|
||||||
|
|
|
@ -8,7 +8,9 @@ use crate::media_type::MediaType;
|
||||||
use crate::permissions::Permissions;
|
use crate::permissions::Permissions;
|
||||||
use crate::program_state::ProgramState;
|
use crate::program_state::ProgramState;
|
||||||
|
|
||||||
|
use deno_core::error::custom_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
|
use deno_core::futures::future;
|
||||||
use deno_core::futures::Future;
|
use deno_core::futures::Future;
|
||||||
use deno_core::futures::FutureExt;
|
use deno_core::futures::FutureExt;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
|
@ -61,7 +63,6 @@ pub struct CachedModule {
|
||||||
pub specifier: ModuleSpecifier,
|
pub specifier: ModuleSpecifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl Default for CachedModule {
|
impl Default for CachedModule {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let specifier = ModuleSpecifier::resolve_url("file:///example.js").unwrap();
|
let specifier = ModuleSpecifier::resolve_url("file:///example.js").unwrap();
|
||||||
|
@ -422,12 +423,119 @@ impl SpecifierHandler for FetchHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct MemoryHandler {
|
||||||
|
sources: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryHandler {
|
||||||
|
pub fn new(sources: HashMap<String, String>) -> Self {
|
||||||
|
Self { sources }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpecifierHandler for MemoryHandler {
|
||||||
|
fn fetch(
|
||||||
|
&mut self,
|
||||||
|
specifier: ModuleSpecifier,
|
||||||
|
_maybe_referrer: Option<Location>,
|
||||||
|
_is_dynamic: bool,
|
||||||
|
) -> FetchFuture {
|
||||||
|
let mut specifier_text = specifier.to_string();
|
||||||
|
if !self.sources.contains_key(&specifier_text) {
|
||||||
|
specifier_text = specifier_text.replace("file:///", "/");
|
||||||
|
if !self.sources.contains_key(&specifier_text) {
|
||||||
|
// Convert `C:/a/path/file.ts` to `/a/path/file.ts`
|
||||||
|
specifier_text = specifier_text[3..].to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = if let Some(source) = self.sources.get(&specifier_text) {
|
||||||
|
let media_type = MediaType::from(&specifier);
|
||||||
|
let is_remote = specifier.as_url().scheme() != "file";
|
||||||
|
|
||||||
|
Ok(CachedModule {
|
||||||
|
source: source.to_string(),
|
||||||
|
requested_specifier: specifier.clone(),
|
||||||
|
specifier,
|
||||||
|
media_type,
|
||||||
|
is_remote,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(custom_error(
|
||||||
|
"NotFound",
|
||||||
|
format!("Unable to find specifier in sources: {}", specifier),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::pin(future::ready(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_tsbuildinfo(
|
||||||
|
&self,
|
||||||
|
_specifier: &ModuleSpecifier,
|
||||||
|
) -> Result<Option<String>, AnyError> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cache(
|
||||||
|
&mut self,
|
||||||
|
_specifier: &ModuleSpecifier,
|
||||||
|
_emit: &Emit,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_types(
|
||||||
|
&mut self,
|
||||||
|
_specifier: &ModuleSpecifier,
|
||||||
|
_types: String,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_tsbuildinfo(
|
||||||
|
&mut self,
|
||||||
|
_specifier: &ModuleSpecifier,
|
||||||
|
_tsbuildinfo: String,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_deps(
|
||||||
|
&mut self,
|
||||||
|
_specifier: &ModuleSpecifier,
|
||||||
|
_dependencies: DependencyMap,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_version(
|
||||||
|
&mut self,
|
||||||
|
_specifier: &ModuleSpecifier,
|
||||||
|
_version: String,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http_cache::HttpCache;
|
use crate::http_cache::HttpCache;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
macro_rules! map (
|
||||||
|
{ $($key:expr => $value:expr),+ } => {
|
||||||
|
{
|
||||||
|
let mut m = ::std::collections::HashMap::new();
|
||||||
|
$(
|
||||||
|
m.insert($key, $value);
|
||||||
|
)+
|
||||||
|
m
|
||||||
|
}
|
||||||
|
};
|
||||||
|
);
|
||||||
|
|
||||||
fn setup() -> (TempDir, FetchHandler) {
|
fn setup() -> (TempDir, FetchHandler) {
|
||||||
let temp_dir = TempDir::new().expect("could not setup");
|
let temp_dir = TempDir::new().expect("could not setup");
|
||||||
let deno_dir = DenoDir::new(Some(temp_dir.path().to_path_buf()))
|
let deno_dir = DenoDir::new(Some(temp_dir.path().to_path_buf()))
|
||||||
|
@ -522,4 +630,111 @@ pub mod tests {
|
||||||
file_fetcher.fetch(specifier, None, false).await.unwrap();
|
file_fetcher.fetch(specifier, None, false).await.unwrap();
|
||||||
assert_eq!(cached_module.is_remote, false);
|
assert_eq!(cached_module.is_remote, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_memory_handler_fetch() {
|
||||||
|
let a_src = r#"
|
||||||
|
import * as b from "./b.ts";
|
||||||
|
console.log(b);
|
||||||
|
"#;
|
||||||
|
let b_src = r#"
|
||||||
|
export const b = "b";
|
||||||
|
"#;
|
||||||
|
let c_src = r#"
|
||||||
|
export const c = "c";
|
||||||
|
"#;
|
||||||
|
let d_src = r#"
|
||||||
|
export const d: string;
|
||||||
|
"#;
|
||||||
|
let sources = map!(
|
||||||
|
"/a.ts" => a_src,
|
||||||
|
"/b.ts" => b_src,
|
||||||
|
"https://deno.land/x/c.js" => c_src,
|
||||||
|
"https://deno.land/x/d.d.ts" => d_src
|
||||||
|
);
|
||||||
|
let sources: HashMap<String, String> = sources
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
|
.collect();
|
||||||
|
let mut handler = MemoryHandler::new(sources);
|
||||||
|
let specifier =
|
||||||
|
ModuleSpecifier::resolve_url_or_path("file:///a.ts").unwrap();
|
||||||
|
let actual: CachedModule = handler
|
||||||
|
.fetch(specifier.clone(), None, false)
|
||||||
|
.await
|
||||||
|
.expect("could not fetch module");
|
||||||
|
assert_eq!(actual.source, a_src.to_string());
|
||||||
|
assert_eq!(actual.requested_specifier, specifier);
|
||||||
|
assert_eq!(actual.specifier, specifier);
|
||||||
|
assert_eq!(actual.media_type, MediaType::TypeScript);
|
||||||
|
assert_eq!(actual.is_remote, false);
|
||||||
|
|
||||||
|
let specifier =
|
||||||
|
ModuleSpecifier::resolve_url_or_path("file:///b.ts").unwrap();
|
||||||
|
let actual: CachedModule = handler
|
||||||
|
.fetch(specifier.clone(), None, false)
|
||||||
|
.await
|
||||||
|
.expect("could not fetch module");
|
||||||
|
assert_eq!(actual.source, b_src.to_string());
|
||||||
|
assert_eq!(actual.requested_specifier, specifier);
|
||||||
|
assert_eq!(actual.specifier, specifier);
|
||||||
|
assert_eq!(actual.media_type, MediaType::TypeScript);
|
||||||
|
assert_eq!(actual.is_remote, false);
|
||||||
|
|
||||||
|
let specifier =
|
||||||
|
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/c.js").unwrap();
|
||||||
|
let actual: CachedModule = handler
|
||||||
|
.fetch(specifier.clone(), None, false)
|
||||||
|
.await
|
||||||
|
.expect("could not fetch module");
|
||||||
|
assert_eq!(actual.source, c_src.to_string());
|
||||||
|
assert_eq!(actual.requested_specifier, specifier);
|
||||||
|
assert_eq!(actual.specifier, specifier);
|
||||||
|
assert_eq!(actual.media_type, MediaType::JavaScript);
|
||||||
|
assert_eq!(actual.is_remote, true);
|
||||||
|
|
||||||
|
let specifier =
|
||||||
|
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/d.d.ts")
|
||||||
|
.unwrap();
|
||||||
|
let actual: CachedModule = handler
|
||||||
|
.fetch(specifier.clone(), None, false)
|
||||||
|
.await
|
||||||
|
.expect("could not fetch module");
|
||||||
|
assert_eq!(actual.source, d_src.to_string());
|
||||||
|
assert_eq!(actual.requested_specifier, specifier);
|
||||||
|
assert_eq!(actual.specifier, specifier);
|
||||||
|
assert_eq!(actual.media_type, MediaType::Dts);
|
||||||
|
assert_eq!(actual.is_remote, true);
|
||||||
|
|
||||||
|
let specifier =
|
||||||
|
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/missing.ts")
|
||||||
|
.unwrap();
|
||||||
|
handler
|
||||||
|
.fetch(specifier.clone(), None, false)
|
||||||
|
.await
|
||||||
|
.expect_err("should have errored");
|
||||||
|
|
||||||
|
let specifier = ModuleSpecifier::resolve_url_or_path("/a.ts").unwrap();
|
||||||
|
let actual: CachedModule = handler
|
||||||
|
.fetch(specifier.clone(), None, false)
|
||||||
|
.await
|
||||||
|
.expect("could not fetch module");
|
||||||
|
assert_eq!(actual.source, a_src.to_string());
|
||||||
|
assert_eq!(actual.requested_specifier, specifier);
|
||||||
|
assert_eq!(actual.specifier, specifier);
|
||||||
|
assert_eq!(actual.media_type, MediaType::TypeScript);
|
||||||
|
assert_eq!(actual.is_remote, false);
|
||||||
|
|
||||||
|
let specifier =
|
||||||
|
ModuleSpecifier::resolve_url_or_path("file:///C:/a.ts").unwrap();
|
||||||
|
let actual: CachedModule = handler
|
||||||
|
.fetch(specifier.clone(), None, false)
|
||||||
|
.await
|
||||||
|
.expect("could not fetch module");
|
||||||
|
assert_eq!(actual.source, a_src.to_string());
|
||||||
|
assert_eq!(actual.requested_specifier, specifier);
|
||||||
|
assert_eq!(actual.specifier, specifier);
|
||||||
|
assert_eq!(actual.media_type, MediaType::TypeScript);
|
||||||
|
assert_eq!(actual.is_remote, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,11 @@ Deno.test({
|
||||||
});
|
});
|
||||||
assert(diagnostics == null);
|
assert(diagnostics == null);
|
||||||
assert(actual);
|
assert(actual);
|
||||||
assertEquals(Object.keys(actual), [
|
const keys = Object.keys(actual).sort();
|
||||||
"/bar.js.map",
|
assert(keys[0].endsWith("/bar.ts.js"));
|
||||||
"/bar.js",
|
assert(keys[1].endsWith("/bar.ts.js.map"));
|
||||||
"/foo.js.map",
|
assert(keys[2].endsWith("/foo.ts.js"));
|
||||||
"/foo.js",
|
assert(keys[3].endsWith("/foo.ts.js.map"));
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -29,10 +28,10 @@ Deno.test({
|
||||||
const [diagnostics, actual] = await Deno.compile("./subdir/mod1.ts");
|
const [diagnostics, actual] = await Deno.compile("./subdir/mod1.ts");
|
||||||
assert(diagnostics == null);
|
assert(diagnostics == null);
|
||||||
assert(actual);
|
assert(actual);
|
||||||
const keys = Object.keys(actual);
|
const keys = Object.keys(actual).sort();
|
||||||
assertEquals(keys.length, 6);
|
assertEquals(keys.length, 6);
|
||||||
assert(keys[0].endsWith("print_hello.js.map"));
|
assert(keys[0].endsWith("cli/tests/subdir/mod1.ts.js"));
|
||||||
assert(keys[1].endsWith("print_hello.js"));
|
assert(keys[1].endsWith("cli/tests/subdir/mod1.ts.js.map"));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -51,8 +50,11 @@ Deno.test({
|
||||||
);
|
);
|
||||||
assert(diagnostics == null);
|
assert(diagnostics == null);
|
||||||
assert(actual);
|
assert(actual);
|
||||||
assertEquals(Object.keys(actual), ["/foo.js"]);
|
const keys = Object.keys(actual);
|
||||||
assert(actual["/foo.js"].startsWith("define("));
|
assertEquals(keys.length, 1);
|
||||||
|
const key = keys[0];
|
||||||
|
assert(key.endsWith("/foo.ts.js"));
|
||||||
|
assert(actual[key].startsWith("define("));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,9 +62,9 @@ Deno.test({
|
||||||
name: "Deno.compile() - pass lib in compiler options",
|
name: "Deno.compile() - pass lib in compiler options",
|
||||||
async fn() {
|
async fn() {
|
||||||
const [diagnostics, actual] = await Deno.compile(
|
const [diagnostics, actual] = await Deno.compile(
|
||||||
"/foo.ts",
|
"file:///foo.ts",
|
||||||
{
|
{
|
||||||
"/foo.ts": `console.log(document.getElementById("foo"));
|
"file:///foo.ts": `console.log(document.getElementById("foo"));
|
||||||
console.log(Deno.args);`,
|
console.log(Deno.args);`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -71,45 +73,37 @@ Deno.test({
|
||||||
);
|
);
|
||||||
assert(diagnostics == null);
|
assert(diagnostics == null);
|
||||||
assert(actual);
|
assert(actual);
|
||||||
assertEquals(Object.keys(actual), ["/foo.js.map", "/foo.js"]);
|
assertEquals(
|
||||||
|
Object.keys(actual).sort(),
|
||||||
|
["file:///foo.ts.js", "file:///foo.ts.js.map"],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test({
|
// TODO(@kitsonk) figure the "right way" to restore support for types
|
||||||
name: "Deno.compile() - pass outDir in compiler options",
|
// Deno.test({
|
||||||
async fn() {
|
// name: "Deno.compile() - properly handles .d.ts files",
|
||||||
const [diagnostics, actual] = await Deno.compile(
|
// async fn() {
|
||||||
"src/foo.ts",
|
// const [diagnostics, actual] = await Deno.compile(
|
||||||
{
|
// "/foo.ts",
|
||||||
"src/foo.ts": "console.log('Hello world')",
|
// {
|
||||||
},
|
// "/foo.ts": `console.log(Foo.bar);`,
|
||||||
{
|
// "/foo_types.d.ts": `declare namespace Foo {
|
||||||
outDir: "./lib",
|
// const bar: string;
|
||||||
},
|
// }`,
|
||||||
);
|
// },
|
||||||
assert(diagnostics == null);
|
// {
|
||||||
assert(actual);
|
// types: ["/foo_types.d.ts"],
|
||||||
assertEquals(Object.keys(actual), ["lib/foo.js.map", "lib/foo.js"]);
|
// },
|
||||||
},
|
// );
|
||||||
});
|
// assert(diagnostics == null);
|
||||||
|
// assert(actual);
|
||||||
Deno.test({
|
// assertEquals(
|
||||||
name: "Deno.compile() - properly handles .d.ts files",
|
// Object.keys(actual).sort(),
|
||||||
async fn() {
|
// ["file:///foo.ts.js", "file:///file.ts.js.map"],
|
||||||
const [diagnostics, actual] = await Deno.compile(
|
// );
|
||||||
"/foo.ts",
|
// },
|
||||||
{
|
// });
|
||||||
"/foo.ts": `console.log(Foo.bar);`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
types: ["./subdir/foo_types.d.ts"],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
assert(diagnostics == null);
|
|
||||||
assert(actual);
|
|
||||||
assertEquals(Object.keys(actual), ["/foo.js.map", "/foo.js"]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test({
|
Deno.test({
|
||||||
name: "Deno.transpileOnly()",
|
name: "Deno.transpileOnly()",
|
||||||
|
@ -150,8 +144,7 @@ Deno.test({
|
||||||
"/bar.ts": `export const bar = "bar";\n`,
|
"/bar.ts": `export const bar = "bar";\n`,
|
||||||
});
|
});
|
||||||
assert(diagnostics == null);
|
assert(diagnostics == null);
|
||||||
assert(actual.includes(`__instantiate("foo", false)`));
|
assert(actual.includes(`const bar = "bar"`));
|
||||||
assert(actual.includes(`__exp["bar"]`));
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -160,26 +153,7 @@ Deno.test({
|
||||||
async fn() {
|
async fn() {
|
||||||
const [diagnostics, actual] = await Deno.bundle("./subdir/mod1.ts");
|
const [diagnostics, actual] = await Deno.bundle("./subdir/mod1.ts");
|
||||||
assert(diagnostics == null);
|
assert(diagnostics == null);
|
||||||
assert(actual.includes(`__instantiate("mod1", false)`));
|
assert(actual.length);
|
||||||
assert(actual.includes(`__exp["printHello3"]`));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test({
|
|
||||||
name: "Deno.bundle() - compiler config effects emit",
|
|
||||||
async fn() {
|
|
||||||
const [diagnostics, actual] = await Deno.bundle(
|
|
||||||
"/foo.ts",
|
|
||||||
{
|
|
||||||
"/foo.ts": `// random comment\nexport * from "./bar.ts";\n`,
|
|
||||||
"/bar.ts": `export const bar = "bar";\n`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
removeComments: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
assert(diagnostics == null);
|
|
||||||
assert(!actual.includes(`random`));
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -191,22 +165,7 @@ Deno.test({
|
||||||
"/bar.js": `export const bar = "bar";\n`,
|
"/bar.js": `export const bar = "bar";\n`,
|
||||||
});
|
});
|
||||||
assert(diagnostics == null);
|
assert(diagnostics == null);
|
||||||
assert(actual.includes(`System.register("bar",`));
|
assert(actual.includes(`const bar = "bar"`));
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Deno.test({
|
|
||||||
name: "Deno.bundle - pre ES2017 uses ES5 loader",
|
|
||||||
async fn() {
|
|
||||||
const [diagnostics, actual] = await Deno.bundle(
|
|
||||||
"/foo.ts",
|
|
||||||
{
|
|
||||||
"/foo.ts": `console.log("hello world!")\n`,
|
|
||||||
},
|
|
||||||
{ target: "es2015" },
|
|
||||||
);
|
|
||||||
assert(diagnostics == null);
|
|
||||||
assert(actual.includes(`var __awaiter = `));
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -226,8 +185,8 @@ Deno.test({
|
||||||
name: "Deno.compile() - SWC diagnostics",
|
name: "Deno.compile() - SWC diagnostics",
|
||||||
async fn() {
|
async fn() {
|
||||||
await assertThrowsAsync(async () => {
|
await assertThrowsAsync(async () => {
|
||||||
await Deno.compile("main.js", {
|
await Deno.compile("/main.js", {
|
||||||
"main.js": `
|
"/main.js": `
|
||||||
export class Foo {
|
export class Foo {
|
||||||
constructor() {
|
constructor() {
|
||||||
console.log("foo");
|
console.log("foo");
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Deno.compile("main.js", { "main.js": "console.log(foo);" });
|
|
|
@ -1,4 +0,0 @@
|
||||||
Check [WILDCARD]compiler_js_error.ts
|
|
||||||
error: Uncaught (in promise) Error: Error in TS compiler:
|
|
||||||
AssertionError: Unexpected skip of the emit.
|
|
||||||
[WILDCARD]
|
|
|
@ -3001,12 +3001,6 @@ itest!(deno_doc_import_map {
|
||||||
output: "doc/use_import_map.out",
|
output: "doc/use_import_map.out",
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(compiler_js_error {
|
|
||||||
args: "run --unstable compiler_js_error.ts",
|
|
||||||
output: "compiler_js_error.ts.out",
|
|
||||||
exit_code: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
itest!(import_file_with_colon {
|
itest!(import_file_with_colon {
|
||||||
args: "run --quiet --reload import_file_with_colon.ts",
|
args: "run --quiet --reload import_file_with_colon.ts",
|
||||||
output: "import_file_with_colon.ts.out",
|
output: "import_file_with_colon.ts.out",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const [errors, program] = await Deno.compile(
|
const [errors, program] = await Deno.compile(
|
||||||
"main.ts",
|
"/main.ts",
|
||||||
{
|
{
|
||||||
"main.ts":
|
"/main.ts":
|
||||||
`/// <reference lib="dom" />\n\ndocument.getElementById("foo");\nDeno.args;`,
|
`/// <reference lib="dom" />\n\ndocument.getElementById("foo");\nDeno.args;`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -11,4 +11,4 @@ const [errors, program] = await Deno.compile(
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(errors);
|
console.log(errors);
|
||||||
console.log(Object.keys(program));
|
console.log(Object.keys(program).sort());
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
undefined
|
undefined
|
||||||
[ "main.js.map", "main.js" ]
|
[ "file:///[WILDCARD]main.ts.js", "file:///[WILDCARD]main.ts.js.map" ]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const [errors, program] = await Deno.compile(
|
const [errors, program] = await Deno.compile(
|
||||||
"main.ts",
|
"/main.ts",
|
||||||
{
|
{
|
||||||
"main.ts": `document.getElementById("foo");`,
|
"/main.ts": `document.getElementById("foo");`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
lib: ["dom", "esnext"],
|
lib: ["dom", "esnext"],
|
||||||
|
@ -9,4 +9,4 @@ const [errors, program] = await Deno.compile(
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(errors);
|
console.log(errors);
|
||||||
console.log(Object.keys(program));
|
console.log(Object.keys(program).sort());
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
undefined
|
undefined
|
||||||
[ "main.js.map", "main.js" ]
|
[ "file:///[WILDCARD]main.ts.js", "file:///[WILDCARD]main.ts.js.map" ]
|
||||||
|
|
1005
cli/tsc.rs
1005
cli/tsc.rs
File diff suppressed because it is too large
Load diff
|
@ -118,9 +118,6 @@ delete Object.prototype.__proto__;
|
||||||
return core.decode(sourceCodeBytes);
|
return core.decode(sourceCodeBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constants used by `normalizeString` and `resolvePath`
|
|
||||||
const CHAR_DOT = 46; /* . */
|
|
||||||
const CHAR_FORWARD_SLASH = 47; /* / */
|
|
||||||
// Using incremental compile APIs requires that all
|
// Using incremental compile APIs requires that all
|
||||||
// paths must be either relative or absolute. Since
|
// paths must be either relative or absolute. Since
|
||||||
// analysis in Rust operates on fully resolved URLs,
|
// analysis in Rust operates on fully resolved URLs,
|
||||||
|
@ -218,18 +215,6 @@ delete Object.prototype.__proto__;
|
||||||
*/
|
*/
|
||||||
const RESOLVED_SPECIFIER_CACHE = new Map();
|
const RESOLVED_SPECIFIER_CACHE = new Map();
|
||||||
|
|
||||||
function parseCompilerOptions(compilerOptions) {
|
|
||||||
const { options, errors } = ts.convertCompilerOptionsFromJson(
|
|
||||||
compilerOptions,
|
|
||||||
"",
|
|
||||||
"tsconfig.json",
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
options,
|
|
||||||
diagnostics: errors.length ? errors : undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class SourceFile {
|
class SourceFile {
|
||||||
constructor(json) {
|
constructor(json) {
|
||||||
this.processed = false;
|
this.processed = false;
|
||||||
|
@ -541,95 +526,6 @@ delete Object.prototype.__proto__;
|
||||||
host,
|
host,
|
||||||
});
|
});
|
||||||
|
|
||||||
// This function is called only during snapshotting process
|
|
||||||
const SYSTEM_LOADER = getAsset("system_loader.js");
|
|
||||||
const SYSTEM_LOADER_ES5 = getAsset("system_loader_es5.js");
|
|
||||||
|
|
||||||
function buildLocalSourceFileCache(sourceFileMap) {
|
|
||||||
for (const entry of Object.values(sourceFileMap)) {
|
|
||||||
assert(entry.sourceCode.length > 0);
|
|
||||||
SourceFile.addToCache({
|
|
||||||
url: entry.url,
|
|
||||||
filename: entry.url,
|
|
||||||
mediaType: entry.mediaType,
|
|
||||||
sourceCode: entry.sourceCode,
|
|
||||||
versionHash: entry.versionHash,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const importDesc of entry.imports) {
|
|
||||||
let mappedUrl = importDesc.resolvedSpecifier;
|
|
||||||
const importedFile = sourceFileMap[importDesc.resolvedSpecifier];
|
|
||||||
assert(importedFile);
|
|
||||||
const isJsOrJsx = importedFile.mediaType === MediaType.JavaScript ||
|
|
||||||
importedFile.mediaType === MediaType.JSX;
|
|
||||||
// If JS or JSX perform substitution for types if available
|
|
||||||
if (isJsOrJsx) {
|
|
||||||
// @deno-types has highest precedence, followed by
|
|
||||||
// X-TypeScript-Types header
|
|
||||||
if (importDesc.resolvedTypeDirective) {
|
|
||||||
mappedUrl = importDesc.resolvedTypeDirective;
|
|
||||||
} else if (importedFile.typeHeaders.length > 0) {
|
|
||||||
const typeHeaders = importedFile.typeHeaders[0];
|
|
||||||
mappedUrl = typeHeaders.resolvedSpecifier;
|
|
||||||
} else if (importedFile.typesDirectives.length > 0) {
|
|
||||||
const typeDirective = importedFile.typesDirectives[0];
|
|
||||||
mappedUrl = typeDirective.resolvedSpecifier;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mappedUrl = mappedUrl.replace("memory://", "");
|
|
||||||
SourceFile.cacheResolvedUrl(mappedUrl, importDesc.specifier, entry.url);
|
|
||||||
}
|
|
||||||
for (const fileRef of entry.referencedFiles) {
|
|
||||||
SourceFile.cacheResolvedUrl(
|
|
||||||
fileRef.resolvedSpecifier.replace("memory://", ""),
|
|
||||||
fileRef.specifier,
|
|
||||||
entry.url,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (const fileRef of entry.libDirectives) {
|
|
||||||
SourceFile.cacheResolvedUrl(
|
|
||||||
fileRef.resolvedSpecifier.replace("memory://", ""),
|
|
||||||
fileRef.specifier,
|
|
||||||
entry.url,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning! The values in this enum are duplicated in `cli/msg.rs`
|
|
||||||
// Update carefully!
|
|
||||||
const CompilerRequestType = {
|
|
||||||
RuntimeCompile: 2,
|
|
||||||
RuntimeBundle: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
function createBundleWriteFile(state) {
|
|
||||||
return function writeFile(_fileName, data, sourceFiles) {
|
|
||||||
assert(sourceFiles != null);
|
|
||||||
assert(state.options);
|
|
||||||
// we only support single root names for bundles
|
|
||||||
assert(state.rootNames.length === 1);
|
|
||||||
state.bundleOutput = buildBundle(
|
|
||||||
state.rootNames[0],
|
|
||||||
data,
|
|
||||||
sourceFiles,
|
|
||||||
state.options.target ?? ts.ScriptTarget.ESNext,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRuntimeCompileWriteFile(state) {
|
|
||||||
return function writeFile(fileName, data, sourceFiles) {
|
|
||||||
assert(sourceFiles);
|
|
||||||
assert(sourceFiles.length === 1);
|
|
||||||
state.emitMap[fileName] = {
|
|
||||||
filename: sourceFiles[0].fileName,
|
|
||||||
contents: data,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const IGNORED_DIAGNOSTICS = [
|
const IGNORED_DIAGNOSTICS = [
|
||||||
// TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is
|
// TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is
|
||||||
// not a module.
|
// not a module.
|
||||||
|
@ -674,7 +570,6 @@ delete Object.prototype.__proto__;
|
||||||
|
|
||||||
function performanceStart() {
|
function performanceStart() {
|
||||||
stats.length = 0;
|
stats.length = 0;
|
||||||
// TODO(kitsonk) replace with performance.mark() when landed
|
|
||||||
statsStart = new Date();
|
statsStart = new Date();
|
||||||
ts.performance.enable();
|
ts.performance.enable();
|
||||||
}
|
}
|
||||||
|
@ -716,317 +611,6 @@ delete Object.prototype.__proto__;
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeString(path) {
|
|
||||||
let res = "";
|
|
||||||
let lastSegmentLength = 0;
|
|
||||||
let lastSlash = -1;
|
|
||||||
let dots = 0;
|
|
||||||
let code;
|
|
||||||
for (let i = 0, len = path.length; i <= len; ++i) {
|
|
||||||
if (i < len) code = path.charCodeAt(i);
|
|
||||||
else if (code === CHAR_FORWARD_SLASH) break;
|
|
||||||
else code = CHAR_FORWARD_SLASH;
|
|
||||||
|
|
||||||
if (code === CHAR_FORWARD_SLASH) {
|
|
||||||
if (lastSlash === i - 1 || dots === 1) {
|
|
||||||
// NOOP
|
|
||||||
} else if (lastSlash !== i - 1 && dots === 2) {
|
|
||||||
if (
|
|
||||||
res.length < 2 ||
|
|
||||||
lastSegmentLength !== 2 ||
|
|
||||||
res.charCodeAt(res.length - 1) !== CHAR_DOT ||
|
|
||||||
res.charCodeAt(res.length - 2) !== CHAR_DOT
|
|
||||||
) {
|
|
||||||
if (res.length > 2) {
|
|
||||||
const lastSlashIndex = res.lastIndexOf("/");
|
|
||||||
if (lastSlashIndex === -1) {
|
|
||||||
res = "";
|
|
||||||
lastSegmentLength = 0;
|
|
||||||
} else {
|
|
||||||
res = res.slice(0, lastSlashIndex);
|
|
||||||
lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
|
|
||||||
}
|
|
||||||
lastSlash = i;
|
|
||||||
dots = 0;
|
|
||||||
continue;
|
|
||||||
} else if (res.length === 2 || res.length === 1) {
|
|
||||||
res = "";
|
|
||||||
lastSegmentLength = 0;
|
|
||||||
lastSlash = i;
|
|
||||||
dots = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (res.length > 0) res += "/" + path.slice(lastSlash + 1, i);
|
|
||||||
else res = path.slice(lastSlash + 1, i);
|
|
||||||
lastSegmentLength = i - lastSlash - 1;
|
|
||||||
}
|
|
||||||
lastSlash = i;
|
|
||||||
dots = 0;
|
|
||||||
} else if (code === CHAR_DOT && dots !== -1) {
|
|
||||||
++dots;
|
|
||||||
} else {
|
|
||||||
dots = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
function commonPath(paths, sep = "/") {
|
|
||||||
const [first = "", ...remaining] = paths;
|
|
||||||
if (first === "" || remaining.length === 0) {
|
|
||||||
return first.substring(0, first.lastIndexOf(sep) + 1);
|
|
||||||
}
|
|
||||||
const parts = first.split(sep);
|
|
||||||
|
|
||||||
let endOfPrefix = parts.length;
|
|
||||||
for (const path of remaining) {
|
|
||||||
const compare = path.split(sep);
|
|
||||||
for (let i = 0; i < endOfPrefix; i++) {
|
|
||||||
if (compare[i] !== parts[i]) {
|
|
||||||
endOfPrefix = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endOfPrefix === 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const prefix = parts.slice(0, endOfPrefix).join(sep);
|
|
||||||
return prefix.endsWith(sep) ? prefix : `${prefix}${sep}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rootExports;
|
|
||||||
|
|
||||||
function normalizeUrl(rootName) {
|
|
||||||
const match = /^(\S+:\/{2,3})(.+)$/.exec(rootName);
|
|
||||||
if (match) {
|
|
||||||
const [, protocol, path] = match;
|
|
||||||
return `${protocol}${normalizeString(path)}`;
|
|
||||||
} else {
|
|
||||||
return rootName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildBundle(rootName, data, sourceFiles, target) {
|
|
||||||
// when outputting to AMD and a single outfile, TypeScript makes up the module
|
|
||||||
// specifiers which are used to define the modules, and doesn't expose them
|
|
||||||
// publicly, so we have to try to replicate
|
|
||||||
const sources = sourceFiles.map((sf) => sf.fileName);
|
|
||||||
const sharedPath = commonPath(sources);
|
|
||||||
rootName = normalizeUrl(rootName)
|
|
||||||
.replace(sharedPath, "")
|
|
||||||
.replace(/\.\w+$/i, "");
|
|
||||||
// If one of the modules requires support for top-level-await, TypeScript will
|
|
||||||
// emit the execute function as an async function. When this is the case we
|
|
||||||
// need to bubble up the TLA to the instantiation, otherwise we instantiate
|
|
||||||
// synchronously.
|
|
||||||
const hasTla = data.match(/execute:\sasync\sfunction\s/);
|
|
||||||
let instantiate;
|
|
||||||
if (rootExports && rootExports.length) {
|
|
||||||
instantiate = hasTla
|
|
||||||
? `const __exp = await __instantiate("${rootName}", true);\n`
|
|
||||||
: `const __exp = __instantiate("${rootName}", false);\n`;
|
|
||||||
for (const rootExport of rootExports) {
|
|
||||||
if (rootExport === "default") {
|
|
||||||
instantiate += `export default __exp["${rootExport}"];\n`;
|
|
||||||
} else {
|
|
||||||
instantiate +=
|
|
||||||
`export const ${rootExport} = __exp["${rootExport}"];\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
instantiate = hasTla
|
|
||||||
? `await __instantiate("${rootName}", true);\n`
|
|
||||||
: `__instantiate("${rootName}", false);\n`;
|
|
||||||
}
|
|
||||||
const es5Bundle = target === ts.ScriptTarget.ES3 ||
|
|
||||||
target === ts.ScriptTarget.ES5 ||
|
|
||||||
target === ts.ScriptTarget.ES2015 ||
|
|
||||||
target === ts.ScriptTarget.ES2016;
|
|
||||||
return `${
|
|
||||||
es5Bundle ? SYSTEM_LOADER_ES5 : SYSTEM_LOADER
|
|
||||||
}\n${data}\n${instantiate}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setRootExports(program, rootModule) {
|
|
||||||
// get a reference to the type checker, this will let us find symbols from
|
|
||||||
// the AST.
|
|
||||||
const checker = program.getTypeChecker();
|
|
||||||
// get a reference to the main source file for the bundle
|
|
||||||
const mainSourceFile = program.getSourceFile(rootModule);
|
|
||||||
assert(mainSourceFile);
|
|
||||||
// retrieve the internal TypeScript symbol for this AST node
|
|
||||||
const mainSymbol = checker.getSymbolAtLocation(mainSourceFile);
|
|
||||||
if (!mainSymbol) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rootExports = checker
|
|
||||||
.getExportsOfModule(mainSymbol)
|
|
||||||
// .getExportsOfModule includes type only symbols which are exported from
|
|
||||||
// the module, so we need to try to filter those out. While not critical
|
|
||||||
// someone looking at the bundle would think there is runtime code behind
|
|
||||||
// that when there isn't. There appears to be no clean way of figuring that
|
|
||||||
// out, so inspecting SymbolFlags that might be present that are type only
|
|
||||||
.filter(
|
|
||||||
(sym) =>
|
|
||||||
sym.flags & ts.SymbolFlags.Class ||
|
|
||||||
!(
|
|
||||||
sym.flags & ts.SymbolFlags.Interface ||
|
|
||||||
sym.flags & ts.SymbolFlags.TypeLiteral ||
|
|
||||||
sym.flags & ts.SymbolFlags.Signature ||
|
|
||||||
sym.flags & ts.SymbolFlags.TypeParameter ||
|
|
||||||
sym.flags & ts.SymbolFlags.TypeAlias ||
|
|
||||||
sym.flags & ts.SymbolFlags.Type ||
|
|
||||||
sym.flags & ts.SymbolFlags.Namespace ||
|
|
||||||
sym.flags & ts.SymbolFlags.InterfaceExcludes ||
|
|
||||||
sym.flags & ts.SymbolFlags.TypeParameterExcludes ||
|
|
||||||
sym.flags & ts.SymbolFlags.TypeAliasExcludes
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.map((sym) => sym.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
function runtimeCompile(request) {
|
|
||||||
const { compilerOptions, rootNames, target, sourceFileMap } = request;
|
|
||||||
|
|
||||||
debug(">>> runtime compile start", {
|
|
||||||
rootNames,
|
|
||||||
});
|
|
||||||
|
|
||||||
// if there are options, convert them into TypeScript compiler options,
|
|
||||||
// and resolve any external file references
|
|
||||||
const result = parseCompilerOptions(
|
|
||||||
compilerOptions,
|
|
||||||
);
|
|
||||||
const options = result.options;
|
|
||||||
// TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
|
|
||||||
// however stuff breaks if it's not passed (type_directives_js_main.js, compiler_js_error.ts)
|
|
||||||
options.allowNonTsExtensions = true;
|
|
||||||
|
|
||||||
buildLocalSourceFileCache(sourceFileMap);
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
rootNames,
|
|
||||||
emitMap: {},
|
|
||||||
};
|
|
||||||
legacyHostState.target = target;
|
|
||||||
legacyHostState.writeFile = createRuntimeCompileWriteFile(state);
|
|
||||||
const program = ts.createProgram({
|
|
||||||
rootNames,
|
|
||||||
options,
|
|
||||||
host,
|
|
||||||
});
|
|
||||||
|
|
||||||
const diagnostics = ts
|
|
||||||
.getPreEmitDiagnostics(program)
|
|
||||||
.filter(({ code }) =>
|
|
||||||
!IGNORED_DIAGNOSTICS.includes(code) &&
|
|
||||||
!IGNORED_COMPILE_DIAGNOSTICS.includes(code)
|
|
||||||
);
|
|
||||||
|
|
||||||
const emitResult = program.emit();
|
|
||||||
assert(emitResult.emitSkipped === false, "Unexpected skip of the emit.");
|
|
||||||
|
|
||||||
debug("<<< runtime compile finish", {
|
|
||||||
rootNames,
|
|
||||||
emitMap: Object.keys(state.emitMap),
|
|
||||||
});
|
|
||||||
|
|
||||||
const maybeDiagnostics = diagnostics.length
|
|
||||||
? fromTypeScriptDiagnostic(diagnostics)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
diagnostics: maybeDiagnostics,
|
|
||||||
emitMap: state.emitMap,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function runtimeBundle(request) {
|
|
||||||
const { compilerOptions, rootNames, target, sourceFileMap } = request;
|
|
||||||
|
|
||||||
debug(">>> runtime bundle start", {
|
|
||||||
rootNames,
|
|
||||||
});
|
|
||||||
|
|
||||||
// if there are options, convert them into TypeScript compiler options,
|
|
||||||
// and resolve any external file references
|
|
||||||
const result = parseCompilerOptions(
|
|
||||||
compilerOptions,
|
|
||||||
);
|
|
||||||
const options = result.options;
|
|
||||||
// TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
|
|
||||||
// however stuff breaks if it's not passed (type_directives_js_main.js, compiler_js_error.ts)
|
|
||||||
options.allowNonTsExtensions = true;
|
|
||||||
|
|
||||||
buildLocalSourceFileCache(sourceFileMap);
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
rootNames,
|
|
||||||
bundleOutput: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
legacyHostState.target = target;
|
|
||||||
legacyHostState.writeFile = createBundleWriteFile(state);
|
|
||||||
state.options = options;
|
|
||||||
|
|
||||||
const program = ts.createProgram({
|
|
||||||
rootNames,
|
|
||||||
options,
|
|
||||||
host,
|
|
||||||
});
|
|
||||||
|
|
||||||
setRootExports(program, rootNames[0]);
|
|
||||||
const diagnostics = ts
|
|
||||||
.getPreEmitDiagnostics(program)
|
|
||||||
.filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code));
|
|
||||||
|
|
||||||
const emitResult = program.emit();
|
|
||||||
|
|
||||||
assert(emitResult.emitSkipped === false, "Unexpected skip of the emit.");
|
|
||||||
|
|
||||||
debug("<<< runtime bundle finish", {
|
|
||||||
rootNames,
|
|
||||||
});
|
|
||||||
|
|
||||||
const maybeDiagnostics = diagnostics.length
|
|
||||||
? fromTypeScriptDiagnostic(diagnostics)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
diagnostics: maybeDiagnostics,
|
|
||||||
output: state.bundleOutput,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function opCompilerRespond(msg) {
|
|
||||||
core.jsonOpSync("op_compiler_respond", msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
function tsCompilerOnMessage(msg) {
|
|
||||||
const request = msg.data;
|
|
||||||
switch (request.type) {
|
|
||||||
case CompilerRequestType.RuntimeCompile: {
|
|
||||||
const result = runtimeCompile(request);
|
|
||||||
opCompilerRespond(result);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CompilerRequestType.RuntimeBundle: {
|
|
||||||
const result = runtimeBundle(request);
|
|
||||||
opCompilerRespond(result);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`!!! unhandled CompilerRequestType: ${request.type} (${
|
|
||||||
CompilerRequestType[request.type]
|
|
||||||
})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} Request
|
* @typedef {object} Request
|
||||||
* @property {Record<string, any>} config
|
* @property {Record<string, any>} config
|
||||||
|
@ -1094,6 +678,4 @@ delete Object.prototype.__proto__;
|
||||||
|
|
||||||
globalThis.startup = startup;
|
globalThis.startup = startup;
|
||||||
globalThis.exec = exec;
|
globalThis.exec = exec;
|
||||||
// TODO(@kitsonk) remove when converted from legacy tsc
|
|
||||||
globalThis.tsCompilerOnMessage = tsCompilerOnMessage;
|
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
@ -52,37 +52,49 @@ impl fmt::Display for IgnoredCompilerOptions {
|
||||||
/// A static slice of all the compiler options that should be ignored that
|
/// A static slice of all the compiler options that should be ignored that
|
||||||
/// either have no effect on the compilation or would cause the emit to not work
|
/// either have no effect on the compilation or would cause the emit to not work
|
||||||
/// in Deno.
|
/// in Deno.
|
||||||
const IGNORED_COMPILER_OPTIONS: [&str; 10] = [
|
const IGNORED_COMPILER_OPTIONS: &[&str] = &[
|
||||||
"allowSyntheticDefaultImports",
|
"allowSyntheticDefaultImports",
|
||||||
|
"allowUmdGlobalAccess",
|
||||||
|
"baseUrl",
|
||||||
|
"declaration",
|
||||||
|
"declarationMap",
|
||||||
|
"downlevelIteration",
|
||||||
"esModuleInterop",
|
"esModuleInterop",
|
||||||
|
"emitDeclarationOnly",
|
||||||
|
"importHelpers",
|
||||||
"inlineSourceMap",
|
"inlineSourceMap",
|
||||||
"inlineSources",
|
"inlineSources",
|
||||||
// TODO(nayeemrmn): Add "isolatedModules" here for 1.6.0.
|
// TODO(nayeemrmn): Add "isolatedModules" here for 1.6.0.
|
||||||
"module",
|
"module",
|
||||||
|
"noEmitHelpers",
|
||||||
"noLib",
|
"noLib",
|
||||||
|
"noResolve",
|
||||||
|
"outDir",
|
||||||
|
"paths",
|
||||||
"preserveConstEnums",
|
"preserveConstEnums",
|
||||||
"reactNamespace",
|
"reactNamespace",
|
||||||
|
"rootDir",
|
||||||
|
"rootDirs",
|
||||||
|
"skipLibCheck",
|
||||||
"sourceMap",
|
"sourceMap",
|
||||||
|
"sourceRoot",
|
||||||
"target",
|
"target",
|
||||||
|
"types",
|
||||||
|
"useDefineForClassFields",
|
||||||
];
|
];
|
||||||
|
|
||||||
const IGNORED_RUNTIME_COMPILER_OPTIONS: [&str; 50] = [
|
const IGNORED_RUNTIME_COMPILER_OPTIONS: &[&str] = &[
|
||||||
"allowUmdGlobalAccess",
|
|
||||||
"assumeChangesOnlyAffectDirectDependencies",
|
"assumeChangesOnlyAffectDirectDependencies",
|
||||||
"baseUrl",
|
|
||||||
"build",
|
"build",
|
||||||
|
"charset",
|
||||||
"composite",
|
"composite",
|
||||||
"declaration",
|
|
||||||
"declarationMap",
|
|
||||||
"diagnostics",
|
"diagnostics",
|
||||||
"downlevelIteration",
|
"disableSizeLimit",
|
||||||
"emitBOM",
|
"emitBOM",
|
||||||
"emitDeclarationOnly",
|
|
||||||
"extendedDiagnostics",
|
"extendedDiagnostics",
|
||||||
"forceConsistentCasingInFileNames",
|
"forceConsistentCasingInFileNames",
|
||||||
"generateCpuProfile",
|
"generateCpuProfile",
|
||||||
"help",
|
"help",
|
||||||
"importHelpers",
|
|
||||||
"incremental",
|
"incremental",
|
||||||
"init",
|
"init",
|
||||||
"listEmittedFiles",
|
"listEmittedFiles",
|
||||||
|
@ -92,29 +104,21 @@ const IGNORED_RUNTIME_COMPILER_OPTIONS: [&str; 50] = [
|
||||||
"moduleResolution",
|
"moduleResolution",
|
||||||
"newLine",
|
"newLine",
|
||||||
"noEmit",
|
"noEmit",
|
||||||
"noEmitHelpers",
|
|
||||||
"noEmitOnError",
|
"noEmitOnError",
|
||||||
"noResolve",
|
|
||||||
"out",
|
"out",
|
||||||
"outDir",
|
"outDir",
|
||||||
"outFile",
|
"outFile",
|
||||||
"paths",
|
|
||||||
"preserveSymlinks",
|
"preserveSymlinks",
|
||||||
"preserveWatchOutput",
|
"preserveWatchOutput",
|
||||||
"pretty",
|
"pretty",
|
||||||
|
"project",
|
||||||
"resolveJsonModule",
|
"resolveJsonModule",
|
||||||
"rootDir",
|
|
||||||
"rootDirs",
|
|
||||||
"showConfig",
|
"showConfig",
|
||||||
"skipDefaultLibCheck",
|
"skipDefaultLibCheck",
|
||||||
"skipLibCheck",
|
|
||||||
"sourceRoot",
|
|
||||||
"stripInternal",
|
"stripInternal",
|
||||||
"traceResolution",
|
"traceResolution",
|
||||||
"tsBuildInfoFile",
|
"tsBuildInfoFile",
|
||||||
"types",
|
|
||||||
"typeRoots",
|
"typeRoots",
|
||||||
"useDefineForClassFields",
|
|
||||||
"version",
|
"version",
|
||||||
"watch",
|
"watch",
|
||||||
];
|
];
|
||||||
|
@ -170,12 +174,6 @@ struct TSConfigJson {
|
||||||
type_acquisition: Option<Value>,
|
type_acquisition: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_raw_config(config_text: &str) -> Result<Value, AnyError> {
|
|
||||||
assert!(!config_text.is_empty());
|
|
||||||
let jsonc = jsonc_parser::parse_to_value(config_text)?.unwrap();
|
|
||||||
Ok(jsonc_to_serde(jsonc))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_compiler_options(
|
fn parse_compiler_options(
|
||||||
compiler_options: &HashMap<String, Value>,
|
compiler_options: &HashMap<String, Value>,
|
||||||
maybe_path: Option<PathBuf>,
|
maybe_path: Option<PathBuf>,
|
||||||
|
@ -394,18 +392,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_raw_config() {
|
|
||||||
let invalid_config_text = r#"{
|
|
||||||
"compilerOptions": {
|
|
||||||
// comments are allowed
|
|
||||||
}"#;
|
|
||||||
let errbox = parse_raw_config(invalid_config_text).unwrap_err();
|
|
||||||
assert!(errbox
|
|
||||||
.to_string()
|
|
||||||
.starts_with("Unterminated object on line 1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tsconfig_as_bytes() {
|
fn test_tsconfig_as_bytes() {
|
||||||
let mut tsconfig1 = TsConfig::new(json!({
|
let mut tsconfig1 = TsConfig::new(json!({
|
||||||
|
|
Loading…
Reference in a new issue