mirror of
https://github.com/denoland/deno.git
synced 2024-11-26 16:09:27 -05:00
250 lines
7.7 KiB
Rust
250 lines
7.7 KiB
Rust
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use crate::cache;
|
|
use crate::config_file::IgnoredCompilerOptions;
|
|
use crate::diagnostics::Diagnostics;
|
|
use crate::emit;
|
|
use crate::errors::get_error_class_name;
|
|
use crate::proc_state::ProcState;
|
|
use crate::resolver::ImportMapResolver;
|
|
|
|
use deno_core::error::custom_error;
|
|
use deno_core::error::generic_error;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::error::Context;
|
|
use deno_core::resolve_url_or_path;
|
|
use deno_core::serde_json;
|
|
use deno_core::serde_json::Value;
|
|
use deno_core::ModuleSpecifier;
|
|
use deno_core::OpState;
|
|
use deno_graph;
|
|
use deno_runtime::permissions::Permissions;
|
|
use import_map::ImportMap;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::collections::HashSet;
|
|
use std::rc::Rc;
|
|
use std::sync::Arc;
|
|
|
|
pub fn init(rt: &mut deno_core::JsRuntime) {
|
|
super::reg_async(rt, "op_emit", op_emit);
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
enum RuntimeBundleType {
|
|
#[serde(rename = "module")]
|
|
Module,
|
|
#[serde(rename = "classic")]
|
|
Classic,
|
|
}
|
|
|
|
impl<'a> From<&'a RuntimeBundleType> for emit::BundleType {
|
|
fn from(bundle_type: &'a RuntimeBundleType) -> Self {
|
|
match bundle_type {
|
|
RuntimeBundleType::Classic => Self::Classic,
|
|
RuntimeBundleType::Module => Self::Module,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct EmitArgs {
|
|
bundle: Option<RuntimeBundleType>,
|
|
check: Option<bool>,
|
|
compiler_options: Option<HashMap<String, Value>>,
|
|
import_map: Option<Value>,
|
|
import_map_path: Option<String>,
|
|
root_specifier: String,
|
|
sources: Option<HashMap<String, Arc<String>>>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct EmitResult {
|
|
diagnostics: Diagnostics,
|
|
files: HashMap<String, String>,
|
|
#[serde(rename = "ignoredOptions")]
|
|
maybe_ignored_options: Option<IgnoredCompilerOptions>,
|
|
stats: emit::Stats,
|
|
}
|
|
|
|
fn to_maybe_imports(
|
|
referrer: &ModuleSpecifier,
|
|
maybe_options: Option<&HashMap<String, Value>>,
|
|
) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> {
|
|
let options = maybe_options.as_ref()?;
|
|
let types_value = options.get("types")?;
|
|
let types: Vec<String> = serde_json::from_value(types_value.clone()).ok()?;
|
|
Some(vec![(referrer.clone(), types)])
|
|
}
|
|
|
|
async fn op_emit(
|
|
state: Rc<RefCell<OpState>>,
|
|
args: EmitArgs,
|
|
_: (),
|
|
) -> Result<EmitResult, AnyError> {
|
|
deno_runtime::ops::check_unstable2(&state, "Deno.emit");
|
|
let root_specifier = args.root_specifier;
|
|
let state = state.borrow();
|
|
let ps = state.borrow::<ProcState>();
|
|
let mut runtime_permissions = { state.borrow::<Permissions>().clone() };
|
|
let mut cache: Box<dyn cache::CacherLoader> =
|
|
if let Some(sources) = &args.sources {
|
|
Box::new(cache::MemoryCacher::new(sources.clone()))
|
|
} else {
|
|
Box::new(cache::FetchCacher::new(
|
|
ps.dir.gen_cache.clone(),
|
|
ps.file_fetcher.clone(),
|
|
runtime_permissions.clone(),
|
|
runtime_permissions.clone(),
|
|
))
|
|
};
|
|
let maybe_import_map = if let Some(import_map_str) = args.import_map_path {
|
|
let import_map_specifier = resolve_url_or_path(&import_map_str)
|
|
.context(format!("Bad URL (\"{}\") for import map.", import_map_str))?;
|
|
let import_map = if let Some(value) = args.import_map {
|
|
ImportMap::from_json(import_map_specifier.as_str(), &value.to_string())?
|
|
} else {
|
|
let file = ps
|
|
.file_fetcher
|
|
.fetch(&import_map_specifier, &mut runtime_permissions)
|
|
.await
|
|
.map_err(|e| {
|
|
generic_error(format!(
|
|
"Unable to load '{}' import map: {}",
|
|
import_map_specifier, e
|
|
))
|
|
})?;
|
|
ImportMap::from_json(import_map_specifier.as_str(), &file.source)?
|
|
};
|
|
Some(import_map)
|
|
} else if args.import_map.is_some() {
|
|
return Err(generic_error("An importMap was specified, but no importMapPath was provided, which is required."));
|
|
} else {
|
|
None
|
|
};
|
|
let roots = vec![resolve_url_or_path(&root_specifier)?];
|
|
let maybe_imports =
|
|
to_maybe_imports(&roots[0], args.compiler_options.as_ref());
|
|
let maybe_resolver = maybe_import_map.as_ref().map(ImportMapResolver::new);
|
|
let graph = Arc::new(
|
|
deno_graph::create_graph(
|
|
roots,
|
|
true,
|
|
maybe_imports,
|
|
cache.as_mut_loader(),
|
|
maybe_resolver.as_ref().map(|r| r.as_resolver()),
|
|
None,
|
|
None,
|
|
)
|
|
.await,
|
|
);
|
|
// There are certain graph errors that we want to return as an error of an op,
|
|
// versus something that gets returned as a diagnostic of the op, this is
|
|
// handled here.
|
|
if let Err(err) = graph.valid() {
|
|
let err: AnyError = err.into();
|
|
if get_error_class_name(&err) == "PermissionDenied" {
|
|
return Err(err);
|
|
}
|
|
}
|
|
let check = args.check.unwrap_or(true);
|
|
let debug = ps.flags.log_level == Some(log::Level::Debug);
|
|
let tsc_emit = check && args.bundle.is_none();
|
|
let (ts_config, maybe_ignored_options) = emit::get_ts_config(
|
|
emit::ConfigType::RuntimeEmit { tsc_emit },
|
|
None,
|
|
args.compiler_options.as_ref(),
|
|
)?;
|
|
let (files, mut diagnostics, stats) = if check && args.bundle.is_none() {
|
|
let (diagnostics, stats) = if args.sources.is_none()
|
|
&& emit::valid_emit(
|
|
graph.as_ref(),
|
|
cache.as_cacher(),
|
|
&ts_config,
|
|
ps.flags.reload,
|
|
&HashSet::default(),
|
|
) {
|
|
log::debug!(
|
|
"cache is valid for \"{}\", skipping check/emit",
|
|
root_specifier
|
|
);
|
|
(Diagnostics::default(), emit::Stats::default())
|
|
} else {
|
|
let emit_result = emit::check_and_maybe_emit(
|
|
graph.clone(),
|
|
cache.as_mut_cacher(),
|
|
emit::CheckOptions {
|
|
debug,
|
|
emit_with_diagnostics: true,
|
|
maybe_config_specifier: None,
|
|
ts_config,
|
|
},
|
|
)?;
|
|
(emit_result.diagnostics, emit_result.stats)
|
|
};
|
|
let files = emit::to_file_map(graph.as_ref(), cache.as_mut_cacher());
|
|
(files, diagnostics, stats)
|
|
} else if let Some(bundle) = &args.bundle {
|
|
let (diagnostics, stats) = if check {
|
|
if ts_config.get_declaration() {
|
|
return Err(custom_error("TypeError", "The bundle option is set, but the compiler option of `declaration` is true which is not currently supported."));
|
|
}
|
|
let emit_result = emit::check_and_maybe_emit(
|
|
graph.clone(),
|
|
cache.as_mut_cacher(),
|
|
emit::CheckOptions {
|
|
debug,
|
|
emit_with_diagnostics: true,
|
|
maybe_config_specifier: None,
|
|
ts_config: ts_config.clone(),
|
|
},
|
|
)?;
|
|
(emit_result.diagnostics, emit_result.stats)
|
|
} else {
|
|
(Diagnostics::default(), Default::default())
|
|
};
|
|
let (emit, maybe_map) = emit::bundle(
|
|
graph.as_ref(),
|
|
emit::BundleOptions {
|
|
bundle_type: bundle.into(),
|
|
ts_config,
|
|
},
|
|
)?;
|
|
let mut files = HashMap::new();
|
|
files.insert("deno:///bundle.js".to_string(), emit);
|
|
if let Some(map) = maybe_map {
|
|
files.insert("deno:///bundle.js.map".to_string(), map);
|
|
}
|
|
(files, diagnostics, stats)
|
|
} else {
|
|
if ts_config.get_declaration() {
|
|
return Err(custom_error("TypeError", "The option of `check` is false, but the compiler option of `declaration` is true which is not currently supported."));
|
|
}
|
|
let emit_result = emit::emit(
|
|
graph.as_ref(),
|
|
cache.as_mut_cacher(),
|
|
emit::EmitOptions {
|
|
reload: ps.flags.reload,
|
|
ts_config,
|
|
reload_exclusions: HashSet::default(),
|
|
},
|
|
)?;
|
|
let files = emit::to_file_map(graph.as_ref(), cache.as_mut_cacher());
|
|
(files, emit_result.diagnostics, emit_result.stats)
|
|
};
|
|
|
|
// we want to add any errors that were returned as an `Err` earlier by adding
|
|
// them to the diagnostics.
|
|
diagnostics.extend_graph_errors(graph.errors());
|
|
|
|
Ok(EmitResult {
|
|
diagnostics,
|
|
files,
|
|
maybe_ignored_options,
|
|
stats,
|
|
})
|
|
}
|