feat(cli): support React 17 JSX transforms (#12631)

Closes #8440
This commit is contained in:
Kitson Kelly 2021-11-09 12:26:39 +11:00 committed by GitHub
parent 45425c1146
commit f5eb177f50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1115 additions and 173 deletions

8
Cargo.lock generated
View file

@ -782,9 +782,9 @@ dependencies = [
[[package]] [[package]]
name = "deno_doc" name = "deno_doc"
version = "0.19.0" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08abadd9f3ede74c5ba6e3d9a688ecfe160cf7fb2988ae133ef4e3d591d091e7" checksum = "6d2d76b6b75a6fbfda0f529e310fc3cab960f4219403280b430ce93dcf8cf9a2"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"deno_ast", "deno_ast",
@ -827,9 +827,9 @@ dependencies = [
[[package]] [[package]]
name = "deno_graph" name = "deno_graph"
version = "0.10.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6df7e1b135780d9424ce4fb9a8927983d27d2c094922cb84b5fd5d72a4c85b82" checksum = "f6d84ddee0cf83bf295721be792b6769b92214983bda29d52c2b05a89d1e968f"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cfg-if 1.0.0", "cfg-if 1.0.0",

View file

@ -41,8 +41,8 @@ winres = "0.1.11"
[dependencies] [dependencies]
deno_ast = { version = "0.5.0", features = ["bundler", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] } deno_ast = { version = "0.5.0", features = ["bundler", "codegen", "dep_graph", "module_specifier", "proposal", "react", "sourcemap", "transforms", "typescript", "view", "visit"] }
deno_core = { version = "0.105.0", path = "../core" } deno_core = { version = "0.105.0", path = "../core" }
deno_doc = "0.19.0" deno_doc = "0.20.0"
deno_graph = "0.10.0" deno_graph = "0.11.1"
deno_lint = { version = "0.19.0", features = ["docs"] } deno_lint = { version = "0.19.0", features = ["docs"] }
deno_runtime = { version = "0.31.0", path = "../runtime" } deno_runtime = { version = "0.31.0", path = "../runtime" }
deno_tls = { version = "0.10.0", path = "../ext/tls" } deno_tls = { version = "0.10.0", path = "../ext/tls" }

View file

@ -122,15 +122,26 @@ pub struct EmitOptions {
pub inline_source_map: bool, pub inline_source_map: bool,
/// Should the sources be inlined in the source map. Defaults to `true`. /// Should the sources be inlined in the source map. Defaults to `true`.
pub inline_sources: bool, pub inline_sources: bool,
// Should a corresponding .map file be created for the output. This should be /// Should a corresponding .map file be created for the output. This should be
// false if inline_source_map is true. Defaults to `false`. /// false if inline_source_map is true. Defaults to `false`.
pub source_map: bool, pub source_map: bool,
/// `true` if the program should use an implicit JSX import source/the "new"
/// JSX transforms.
pub jsx_automatic: bool,
/// If JSX is automatic, if it is in development mode, meaning that it should
/// import `jsx-dev-runtime` and transform JSX using `jsxDEV` import from the
/// JSX import source as well as provide additional debug information to the
/// JSX factory.
pub jsx_development: bool,
/// When transforming JSX, what value should be used for the JSX factory. /// When transforming JSX, what value should be used for the JSX factory.
/// Defaults to `React.createElement`. /// Defaults to `React.createElement`.
pub jsx_factory: String, pub jsx_factory: String,
/// When transforming JSX, what value should be used for the JSX fragment /// When transforming JSX, what value should be used for the JSX fragment
/// factory. Defaults to `React.Fragment`. /// factory. Defaults to `React.Fragment`.
pub jsx_fragment_factory: String, pub jsx_fragment_factory: String,
/// The string module specifier to implicitly import JSX factories from when
/// transpiling JSX.
pub jsx_import_source: Option<String>,
/// Should JSX be transformed or preserved. Defaults to `true`. /// Should JSX be transformed or preserved. Defaults to `true`.
pub transform_jsx: bool, pub transform_jsx: bool,
/// Should import declarations be transformed to variable declarations. /// Should import declarations be transformed to variable declarations.
@ -146,8 +157,11 @@ impl Default for EmitOptions {
inline_source_map: true, inline_source_map: true,
inline_sources: true, inline_sources: true,
source_map: false, source_map: false,
jsx_automatic: false,
jsx_development: false,
jsx_factory: "React.createElement".into(), jsx_factory: "React.createElement".into(),
jsx_fragment_factory: "React.Fragment".into(), jsx_fragment_factory: "React.Fragment".into(),
jsx_import_source: None,
transform_jsx: true, transform_jsx: true,
repl_imports: false, repl_imports: false,
} }
@ -164,15 +178,25 @@ impl From<config_file::TsConfig> for EmitOptions {
"error" => ImportsNotUsedAsValues::Error, "error" => ImportsNotUsedAsValues::Error,
_ => ImportsNotUsedAsValues::Remove, _ => ImportsNotUsedAsValues::Remove,
}; };
let (transform_jsx, jsx_automatic, jsx_development) =
match options.jsx.as_str() {
"react" => (true, false, false),
"react-jsx" => (true, true, false),
"react-jsxdev" => (true, true, true),
_ => (false, false, false),
};
EmitOptions { EmitOptions {
emit_metadata: options.emit_decorator_metadata, emit_metadata: options.emit_decorator_metadata,
imports_not_used_as_values, imports_not_used_as_values,
inline_source_map: options.inline_source_map, inline_source_map: options.inline_source_map,
inline_sources: options.inline_sources, inline_sources: options.inline_sources,
source_map: options.source_map, source_map: options.source_map,
jsx_automatic,
jsx_development,
jsx_factory: options.jsx_factory, jsx_factory: options.jsx_factory,
jsx_fragment_factory: options.jsx_fragment_factory, jsx_fragment_factory: options.jsx_fragment_factory,
transform_jsx: options.jsx == "react", jsx_import_source: options.jsx_import_source,
transform_jsx,
repl_imports: false, repl_imports: false,
} }
} }
@ -355,6 +379,13 @@ fn fold_program(
// this will use `Object.assign()` instead of the `_extends` helper // this will use `Object.assign()` instead of the `_extends` helper
// when spreading props. // when spreading props.
use_builtins: true, use_builtins: true,
runtime: if options.jsx_automatic {
Some(react::Runtime::Automatic)
} else {
None
},
development: options.jsx_development,
import_source: options.jsx_import_source.clone().unwrap_or_default(),
..Default::default() ..Default::default()
}, },
top_level_mark, top_level_mark,
@ -495,6 +526,112 @@ function App() {
assert_eq!(&code[..expected.len()], expected); assert_eq!(&code[..expected.len()], expected);
} }
#[test]
fn test_transpile_jsx_import_source_pragma() {
let specifier = resolve_url_or_path("https://deno.land/x/mod.tsx")
.expect("could not resolve specifier");
let source = r#"
/** @jsxImportSource jsx_lib */
function App() {
return (
<div><></></div>
);
}"#;
let module = parse_module(ParseParams {
specifier: specifier.as_str().to_string(),
source: SourceTextInfo::from_string(source.to_string()),
media_type: deno_ast::MediaType::Jsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let (code, _) = transpile(&module, &EmitOptions::default()).unwrap();
let expected = r#"import { jsx as _jsx, Fragment as _Fragment } from "jsx_lib/jsx-runtime";
/** @jsxImportSource jsx_lib */ function App() {
return(/*#__PURE__*/ _jsx("div", {
children: /*#__PURE__*/ _jsx(_Fragment, {
})
}));
"#;
assert_eq!(&code[..expected.len()], expected);
}
#[test]
fn test_transpile_jsx_import_source_no_pragma() {
let specifier = resolve_url_or_path("https://deno.land/x/mod.tsx")
.expect("could not resolve specifier");
let source = r#"
function App() {
return (
<div><></></div>
);
}"#;
let module = parse_module(ParseParams {
specifier: specifier.as_str().to_string(),
source: SourceTextInfo::from_string(source.to_string()),
media_type: deno_ast::MediaType::Jsx,
capture_tokens: false,
maybe_syntax: None,
scope_analysis: true,
})
.unwrap();
let emit_options = EmitOptions {
jsx_automatic: true,
jsx_import_source: Some("jsx_lib".to_string()),
..Default::default()
};
let (code, _) = transpile(&module, &emit_options).unwrap();
let expected = r#"import { jsx as _jsx, Fragment as _Fragment } from "jsx_lib/jsx-runtime";
function App() {
return(/*#__PURE__*/ _jsx("div", {
children: /*#__PURE__*/ _jsx(_Fragment, {
})
}));
}
"#;
assert_eq!(&code[..expected.len()], expected);
}
// TODO(@kitsonk) https://github.com/swc-project/swc/issues/2656
// #[test]
// fn test_transpile_jsx_import_source_no_pragma_dev() {
// let specifier = resolve_url_or_path("https://deno.land/x/mod.tsx")
// .expect("could not resolve specifier");
// let source = r#"
// function App() {
// return (
// <div><></></div>
// );
// }"#;
// let module = parse_module(ParseParams {
// specifier: specifier.as_str().to_string(),
// source: SourceTextInfo::from_string(source.to_string()),
// media_type: deno_ast::MediaType::Jsx,
// capture_tokens: false,
// maybe_syntax: None,
// scope_analysis: true,
// })
// .unwrap();
// let emit_options = EmitOptions {
// jsx_automatic: true,
// jsx_import_source: Some("jsx_lib".to_string()),
// jsx_development: true,
// ..Default::default()
// };
// let (code, _) = transpile(&module, &emit_options).unwrap();
// let expected = r#"import { jsx as _jsx, Fragment as _Fragment } from "jsx_lib/jsx-dev-runtime";
// function App() {
// return(/*#__PURE__*/ _jsx("div", {
// children: /*#__PURE__*/ _jsx(_Fragment, {
// })
// }));
// }
// "#;
// assert_eq!(&code[..expected.len()], expected);
// }
#[test] #[test]
fn test_transpile_decorators() { fn test_transpile_decorators() {
let specifier = resolve_url_or_path("https://deno.land/x/mod.ts") let specifier = resolve_url_or_path("https://deno.land/x/mod.ts")

View file

@ -14,12 +14,12 @@ use regex::Regex;
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct NodeEsmResolver<'a> { pub(crate) struct NodeEsmResolver {
maybe_import_map_resolver: Option<ImportMapResolver<'a>>, maybe_import_map_resolver: Option<ImportMapResolver>,
} }
impl<'a> NodeEsmResolver<'a> { impl NodeEsmResolver {
pub fn new(maybe_import_map_resolver: Option<ImportMapResolver<'a>>) -> Self { pub fn new(maybe_import_map_resolver: Option<ImportMapResolver>) -> Self {
Self { Self {
maybe_import_map_resolver, maybe_import_map_resolver,
} }
@ -30,7 +30,7 @@ impl<'a> NodeEsmResolver<'a> {
} }
} }
impl Resolver for NodeEsmResolver<'_> { impl Resolver for NodeEsmResolver {
fn resolve( fn resolve(
&self, &self,
specifier: &str, specifier: &str,

View file

@ -1,7 +1,9 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::fs_util::canonicalize_path; use crate::fs_util::canonicalize_path;
use deno_core::error::anyhow; use deno_core::error::anyhow;
use deno_core::error::custom_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::error::Context; use deno_core::error::Context;
use deno_core::serde::Deserialize; use deno_core::serde::Deserialize;
@ -17,6 +19,9 @@ use std::fmt;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
pub(crate) type MaybeImportsResult =
Result<Option<Vec<(ModuleSpecifier, Vec<String>)>>, AnyError>;
/// The transpile options that are significant out of a user provided tsconfig /// The transpile options that are significant out of a user provided tsconfig
/// file, that we want to deserialize out of the final config for a transpile. /// file, that we want to deserialize out of the final config for a transpile.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -31,6 +36,7 @@ pub struct EmitConfigOptions {
pub jsx: String, pub jsx: String,
pub jsx_factory: String, pub jsx_factory: String,
pub jsx_fragment_factory: String, pub jsx_fragment_factory: String,
pub jsx_import_source: Option<String>,
} }
/// There are certain compiler options that can impact what modules are part of /// There are certain compiler options that can impact what modules are part of
@ -38,6 +44,8 @@ pub struct EmitConfigOptions {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CompilerOptions { pub struct CompilerOptions {
pub jsx: Option<String>,
pub jsx_import_source: Option<String>,
pub types: Option<Vec<String>>, pub types: Option<Vec<String>>,
} }
@ -404,15 +412,50 @@ impl ConfigFile {
/// If the configuration file contains "extra" modules (like TypeScript /// If the configuration file contains "extra" modules (like TypeScript
/// `"types"`) options, return them as imports to be added to a module graph. /// `"types"`) options, return them as imports to be added to a module graph.
pub fn to_maybe_imports( pub fn to_maybe_imports(&self) -> MaybeImportsResult {
&self, let mut imports = Vec::new();
) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> { let compiler_options_value =
if let Some(value) = self.json.compiler_options.as_ref() {
value
} else {
return Ok(None);
};
let compiler_options: CompilerOptions =
serde_json::from_value(compiler_options_value.clone())?;
let referrer = ModuleSpecifier::from_file_path(&self.path)
.map_err(|_| custom_error("TypeError", "bad config file specifier"))?;
if let Some(types) = compiler_options.types {
imports.extend(types);
}
if compiler_options.jsx == Some("react-jsx".to_string()) {
imports.push(format!(
"{}/jsx-runtime",
compiler_options.jsx_import_source.ok_or_else(|| custom_error("TypeError", "Compiler option 'jsx' set to 'react-jsx', but no 'jsxImportSource' defined."))?
));
} else if compiler_options.jsx == Some("react-jsxdev".to_string()) {
imports.push(format!(
"{}/jsx-dev-runtime",
compiler_options.jsx_import_source.ok_or_else(|| custom_error("TypeError", "Compiler option 'jsx' set to 'react-jsxdev', but no 'jsxImportSource' defined."))?
));
}
if !imports.is_empty() {
Ok(Some(vec![(referrer, imports)]))
} else {
Ok(None)
}
}
/// Based on the compiler options in the configuration file, return the
/// implied JSX import source module.
pub fn to_maybe_jsx_import_source_module(&self) -> Option<String> {
let compiler_options_value = self.json.compiler_options.as_ref()?; let compiler_options_value = self.json.compiler_options.as_ref()?;
let compiler_options: CompilerOptions = let compiler_options: CompilerOptions =
serde_json::from_value(compiler_options_value.clone()).ok()?; serde_json::from_value(compiler_options_value.clone()).ok()?;
let referrer = ModuleSpecifier::from_file_path(&self.path).ok()?; match compiler_options.jsx.as_deref() {
let types = compiler_options.types?; Some("react-jsx") => Some("jsx-runtime".to_string()),
Some(vec![(referrer, types)]) Some("react-jsxdev") => Some("jsx-dev-runtime".to_string()),
_ => None,
}
} }
pub fn to_fmt_config(&self) -> Result<Option<FmtConfig>, AnyError> { pub fn to_fmt_config(&self) -> Result<Option<FmtConfig>, AnyError> {

View file

@ -277,15 +277,20 @@ declare namespace Deno {
/** Emit the source alongside the source maps within a single file; requires /** Emit the source alongside the source maps within a single file; requires
* `inlineSourceMap` or `sourceMap` to be set. Defaults to `false`. */ * `inlineSourceMap` or `sourceMap` to be set. Defaults to `false`. */
inlineSources?: boolean; inlineSources?: boolean;
/** Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`. /** Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`,
* `"react-jsx", `"react-jsxdev"`.
* Defaults to `"react"`. */ * Defaults to `"react"`. */
jsx?: "react" | "preserve" | "react-native"; jsx?: "react" | "preserve" | "react-native" | "react-jsx" | "react-jsx-dev";
/** 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 /** Specify the JSX fragment factory function to use when targeting react
* JSX emit, e.g. `Fragment`. Defaults to `React.Fragment`. */ * JSX emit, e.g. `Fragment`. Defaults to `React.Fragment`. */
jsxFragmentFactory?: string; jsxFragmentFactory?: string;
/** Declares the module specifier to be used for importing the `jsx` and
* `jsxs` factory functions when using jsx as `"react-jsx"` or
* `"react-jsxdev"`. Defaults to `"react"`. */
jsxImportSource?: 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;

View file

@ -1,6 +1,7 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::auth_tokens::AuthToken; use crate::auth_tokens::AuthToken;
use deno_core::error::custom_error;
use deno_core::error::generic_error; use deno_core::error::generic_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::url::Url; use deno_core::url::Url;
@ -123,11 +124,18 @@ pub async fn fetch_once(
if response.status().is_client_error() || response.status().is_server_error() if response.status().is_client_error() || response.status().is_server_error()
{ {
let err = generic_error(format!( let err = if response.status() == StatusCode::NOT_FOUND {
"Import '{}' failed: {}", custom_error(
args.url, "NotFound",
response.status() format!("Import '{}' failed, not found.", args.url),
)); )
} else {
generic_error(format!(
"Import '{}' failed: {}",
args.url,
response.status()
))
};
return Err(err); return Err(err);
} }

View file

@ -2,9 +2,11 @@
use crate::cache::CacherLoader; use crate::cache::CacherLoader;
use crate::cache::FetchCacher; use crate::cache::FetchCacher;
use crate::config_file::ConfigFile;
use crate::flags::Flags; use crate::flags::Flags;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver; use crate::resolver::ImportMapResolver;
use crate::resolver::JsxResolver;
use deno_core::error::anyhow; use deno_core::error::anyhow;
use deno_core::error::AnyError; use deno_core::error::AnyError;
@ -13,6 +15,7 @@ use deno_runtime::permissions::Permissions;
use deno_runtime::tokio_util::create_basic_runtime; use deno_runtime::tokio_util::create_basic_runtime;
use import_map::ImportMap; use import_map::ImportMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc;
use std::thread; use std::thread;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::oneshot; use tokio::sync::oneshot;
@ -27,7 +30,8 @@ pub(crate) struct CacheServer(mpsc::UnboundedSender<Request>);
impl CacheServer { impl CacheServer {
pub async fn new( pub async fn new(
maybe_cache_path: Option<PathBuf>, maybe_cache_path: Option<PathBuf>,
maybe_import_map: Option<ImportMap>, maybe_import_map: Option<Arc<ImportMap>>,
maybe_config_file: Option<ConfigFile>,
) -> Self { ) -> Self {
let (tx, mut rx) = mpsc::unbounded_channel::<Request>(); let (tx, mut rx) = mpsc::unbounded_channel::<Request>();
let _join_handle = thread::spawn(move || { let _join_handle = thread::spawn(move || {
@ -39,8 +43,26 @@ impl CacheServer {
}) })
.await .await
.unwrap(); .unwrap();
let maybe_resolver = let maybe_import_map_resolver =
maybe_import_map.as_ref().map(ImportMapResolver::new); maybe_import_map.map(ImportMapResolver::new);
let maybe_jsx_resolver = maybe_config_file
.as_ref()
.map(|cf| {
cf.to_maybe_jsx_import_source_module()
.map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
})
.flatten();
let maybe_resolver = if maybe_jsx_resolver.is_some() {
maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
} else {
maybe_import_map_resolver
.as_ref()
.map(|im| im.as_resolver())
};
let maybe_imports = maybe_config_file
.map(|cf| cf.to_maybe_imports().ok())
.flatten()
.flatten();
let mut cache = FetchCacher::new( let mut cache = FetchCacher::new(
ps.dir.gen_cache.clone(), ps.dir.gen_cache.clone(),
ps.file_fetcher.clone(), ps.file_fetcher.clone(),
@ -52,9 +74,9 @@ impl CacheServer {
let graph = deno_graph::create_graph( let graph = deno_graph::create_graph(
roots, roots,
false, false,
None, maybe_imports.clone(),
cache.as_mut_loader(), cache.as_mut_loader(),
maybe_resolver.as_ref().map(|r| r.as_resolver()), maybe_resolver,
None, None,
None, None,
) )

View file

@ -1,14 +1,16 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use super::resolver::ImportMapResolver;
use super::text::LineIndex; use super::text::LineIndex;
use super::tsc; use super::tsc;
use crate::config_file::ConfigFile;
use crate::file_fetcher::get_source_from_bytes; use crate::file_fetcher::get_source_from_bytes;
use crate::file_fetcher::map_content_type; use crate::file_fetcher::map_content_type;
use crate::file_fetcher::SUPPORTED_SCHEMES; use crate::file_fetcher::SUPPORTED_SCHEMES;
use crate::http_cache; use crate::http_cache;
use crate::http_cache::HttpCache; use crate::http_cache::HttpCache;
use crate::resolver::ImportMapResolver;
use crate::resolver::JsxResolver;
use crate::text_encoding; use crate::text_encoding;
use deno_ast::MediaType; use deno_ast::MediaType;
@ -19,6 +21,7 @@ use deno_core::parking_lot::Mutex;
use deno_core::url; use deno_core::url;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use lspower::lsp; use lspower::lsp;
use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs; use std::fs;
@ -131,6 +134,59 @@ impl IndexValid {
} }
} }
// TODO(@kitsonk) expose the synthetic module from deno_graph
#[derive(Debug)]
struct SyntheticModule {
dependencies: BTreeMap<String, deno_graph::Resolved>,
specifier: ModuleSpecifier,
}
impl SyntheticModule {
pub fn new(
specifier: ModuleSpecifier,
dependencies: Vec<(String, Option<lsp::Range>)>,
maybe_resolver: Option<&dyn deno_graph::source::Resolver>,
) -> Self {
let dependencies = dependencies
.iter()
.map(|(dep, maybe_range)| {
let range = to_deno_graph_range(&specifier, maybe_range.as_ref());
let result = if let Some(resolver) = maybe_resolver {
resolver.resolve(dep, &specifier).map_err(|err| {
if let Some(specifier_error) =
err.downcast_ref::<deno_graph::SpecifierError>()
{
deno_graph::ResolutionError::InvalidSpecifier(
specifier_error.clone(),
range.clone(),
)
} else {
deno_graph::ResolutionError::ResolverError(
Arc::new(err),
dep.to_string(),
range.clone(),
)
}
})
} else {
deno_core::resolve_import(dep, specifier.as_str()).map_err(|err| {
deno_graph::ResolutionError::ResolverError(
Arc::new(err.into()),
dep.to_string(),
range.clone(),
)
})
};
(dep.to_string(), Some(result.map(|s| (s, range))))
})
.collect();
Self {
dependencies,
specifier,
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Document { pub(crate) struct Document {
line_index: Arc<LineIndex>, line_index: Arc<LineIndex>,
@ -347,6 +403,32 @@ pub(crate) fn to_lsp_range(range: &deno_graph::Range) -> lsp::Range {
} }
} }
fn to_deno_graph_range(
specifier: &ModuleSpecifier,
maybe_range: Option<&lsp::Range>,
) -> deno_graph::Range {
let specifier = specifier.clone();
if let Some(range) = maybe_range {
deno_graph::Range {
specifier,
start: deno_graph::Position {
line: range.start.line as usize,
character: range.start.character as usize,
},
end: deno_graph::Position {
line: range.end.line as usize,
character: range.end.character as usize,
},
}
} else {
deno_graph::Range {
specifier,
start: deno_graph::Position::zeroed(),
end: deno_graph::Position::zeroed(),
}
}
}
/// Recurse and collect specifiers that appear in the dependent map. /// Recurse and collect specifiers that appear in the dependent map.
fn recurse_dependents( fn recurse_dependents(
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
@ -376,8 +458,13 @@ struct Inner {
/// A map of documents that can either be "open" in the language server, or /// A map of documents that can either be "open" in the language server, or
/// just present on disk. /// just present on disk.
docs: HashMap<ModuleSpecifier, Document>, docs: HashMap<ModuleSpecifier, Document>,
/// Any imports to the context supplied by configuration files. This is like
/// the imports into the a module graph in CLI.
imports: HashMap<ModuleSpecifier, SyntheticModule>,
/// The optional import map that should be used when resolving dependencies. /// The optional import map that should be used when resolving dependencies.
maybe_import_map: Option<ImportMapResolver>, maybe_import_map: Option<ImportMapResolver>,
/// The optional JSX resolver, which is used when JSX imports are configured.
maybe_jsx_resolver: Option<JsxResolver>,
redirects: HashMap<ModuleSpecifier, ModuleSpecifier>, redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
} }
@ -388,7 +475,9 @@ impl Inner {
dirty: true, dirty: true,
dependents_map: HashMap::default(), dependents_map: HashMap::default(),
docs: HashMap::default(), docs: HashMap::default(),
imports: HashMap::default(),
maybe_import_map: None, maybe_import_map: None,
maybe_jsx_resolver: None,
redirects: HashMap::default(), redirects: HashMap::default(),
} }
} }
@ -407,7 +496,7 @@ impl Inner {
version, version,
None, None,
content, content,
self.maybe_import_map.as_ref().map(|r| r.as_resolver()), self.get_maybe_resolver(),
) )
} else { } else {
let cache_filename = self.cache.get_cache_filename(&specifier)?; let cache_filename = self.cache.get_cache_filename(&specifier)?;
@ -421,7 +510,7 @@ impl Inner {
version, version,
maybe_headers, maybe_headers,
content, content,
self.maybe_import_map.as_ref().map(|r| r.as_resolver()), self.get_maybe_resolver(),
) )
}; };
self.dirty = true; self.dirty = true;
@ -481,6 +570,14 @@ impl Inner {
version: i32, version: i32,
changes: Vec<lsp::TextDocumentContentChangeEvent>, changes: Vec<lsp::TextDocumentContentChangeEvent>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
// this duplicates the .get_resolver() method, because there is no easy
// way to avoid the double borrow of self that occurs here with getting the
// mut doc out.
let maybe_resolver = if self.maybe_jsx_resolver.is_some() {
self.maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
} else {
self.maybe_import_map.as_ref().map(|im| im.as_resolver())
};
let doc = self.docs.get_mut(specifier).map_or_else( let doc = self.docs.get_mut(specifier).map_or_else(
|| { || {
Err(custom_error( Err(custom_error(
@ -491,11 +588,7 @@ impl Inner {
Ok, Ok,
)?; )?;
self.dirty = true; self.dirty = true;
doc.change( doc.change(version, changes, maybe_resolver)
version,
changes,
self.maybe_import_map.as_ref().map(|r| r.as_resolver()),
)
} }
fn close(&mut self, specifier: &ModuleSpecifier) -> Result<(), AnyError> { fn close(&mut self, specifier: &ModuleSpecifier) -> Result<(), AnyError> {
@ -518,8 +611,7 @@ impl Inner {
specifier: &str, specifier: &str,
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
) -> bool { ) -> bool {
let maybe_resolver = let maybe_resolver = self.get_maybe_resolver();
self.maybe_import_map.as_ref().map(|im| im.as_resolver());
let maybe_specifier = if let Some(resolver) = maybe_resolver { let maybe_specifier = if let Some(resolver) = maybe_resolver {
resolver.resolve(specifier, referrer).ok() resolver.resolve(specifier, referrer).ok()
} else { } else {
@ -604,6 +696,14 @@ impl Inner {
}) })
} }
fn get_maybe_resolver(&self) -> Option<&dyn deno_graph::source::Resolver> {
if self.maybe_jsx_resolver.is_some() {
self.maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
} else {
self.maybe_import_map.as_ref().map(|im| im.as_resolver())
}
}
fn get_maybe_types_for_dependency( fn get_maybe_types_for_dependency(
&mut self, &mut self,
dependency: &deno_graph::Dependency, dependency: &deno_graph::Dependency,
@ -706,12 +806,13 @@ impl Inner {
language_id: LanguageId, language_id: LanguageId,
content: Arc<String>, content: Arc<String>,
) { ) {
let maybe_resolver = self.get_maybe_resolver();
let document_data = Document::open( let document_data = Document::open(
specifier.clone(), specifier.clone(),
version, version,
language_id, language_id,
content, content,
self.maybe_import_map.as_ref().map(|r| r.as_resolver()), maybe_resolver,
); );
self.docs.insert(specifier, document_data); self.docs.insert(specifier, document_data);
self.dirty = true; self.dirty = true;
@ -758,6 +859,12 @@ impl Inner {
} else { } else {
results.push(None); results.push(None);
} }
} else if let Some(Some(Ok((specifier, _)))) =
self.resolve_imports_dependency(&specifier)
{
// clone here to avoid double borrow of self
let specifier = specifier.clone();
results.push(self.resolve_dependency(&specifier));
} else { } else {
results.push(None); results.push(None);
} }
@ -790,6 +897,22 @@ impl Inner {
} }
} }
/// Iterate through any "imported" modules, checking to see if a dependency
/// is available. This is used to provide "global" imports like the JSX import
/// source.
fn resolve_imports_dependency(
&self,
specifier: &str,
) -> Option<&deno_graph::Resolved> {
for module in self.imports.values() {
let maybe_dep = module.dependencies.get(specifier);
if maybe_dep.is_some() {
return maybe_dep;
}
}
None
}
fn resolve_remote_specifier( fn resolve_remote_specifier(
&self, &self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
@ -832,15 +955,6 @@ impl Inner {
} }
} }
fn set_import_map(
&mut self,
maybe_import_map: Option<Arc<import_map::ImportMap>>,
) {
// TODO update resolved dependencies?
self.maybe_import_map = maybe_import_map.map(ImportMapResolver::new);
self.dirty = true;
}
fn set_location(&mut self, location: PathBuf) { fn set_location(&mut self, location: PathBuf) {
// TODO update resolved dependencies? // TODO update resolved dependencies?
self.cache = HttpCache::new(&location); self.cache = HttpCache::new(&location);
@ -886,6 +1000,36 @@ impl Inner {
self.get(specifier).map(|d| d.source.clone()) self.get(specifier).map(|d| d.source.clone())
} }
fn update_config(
&mut self,
maybe_import_map: Option<Arc<import_map::ImportMap>>,
maybe_config_file: Option<&ConfigFile>,
) {
// TODO(@kitsonk) update resolved dependencies?
self.maybe_import_map = maybe_import_map.map(ImportMapResolver::new);
self.maybe_jsx_resolver = maybe_config_file
.map(|cf| {
cf.to_maybe_jsx_import_source_module()
.map(|im| JsxResolver::new(im, self.maybe_import_map.clone()))
})
.flatten();
if let Some(Ok(Some(imports))) =
maybe_config_file.map(|cf| cf.to_maybe_imports())
{
for (referrer, dependencies) in imports {
let dependencies =
dependencies.into_iter().map(|s| (s, None)).collect();
let module = SyntheticModule::new(
referrer.clone(),
dependencies,
self.get_maybe_resolver(),
);
self.imports.insert(referrer, module);
}
}
self.dirty = true;
}
fn version(&mut self, specifier: &ModuleSpecifier) -> Option<String> { fn version(&mut self, specifier: &ModuleSpecifier) -> Option<String> {
self.get(specifier).map(|d| { self.get(specifier).map(|d| {
d.maybe_lsp_version d.maybe_lsp_version
@ -1050,14 +1194,6 @@ impl Documents {
self.0.lock().resolve(specifiers, referrer) self.0.lock().resolve(specifiers, referrer)
} }
/// Set the optional import map for the document cache.
pub fn set_import_map(
&self,
maybe_import_map: Option<Arc<import_map::ImportMap>>,
) {
self.0.lock().set_import_map(maybe_import_map);
}
/// Update the location of the on disk cache for the document store. /// Update the location of the on disk cache for the document store.
pub fn set_location(&self, location: PathBuf) { pub fn set_location(&self, location: PathBuf) {
self.0.lock().set_location(location) self.0.lock().set_location(location)
@ -1095,6 +1231,17 @@ impl Documents {
self.0.lock().text_info(specifier) self.0.lock().text_info(specifier)
} }
pub fn update_config(
&self,
maybe_import_map: Option<Arc<import_map::ImportMap>>,
maybe_config_file: Option<&ConfigFile>,
) {
self
.0
.lock()
.update_config(maybe_import_map, maybe_config_file)
}
/// Return the version of a document in the document cache. /// Return the version of a document in the document cache.
pub fn version(&self, specifier: &ModuleSpecifier) -> Option<String> { pub fn version(&self, specifier: &ModuleSpecifier) -> Option<String> {
self.0.lock().version(specifier) self.0.lock().version(specifier)

View file

@ -116,7 +116,7 @@ pub(crate) struct Inner {
/// file which will be used by the Deno LSP. /// file which will be used by the Deno LSP.
maybe_config_uri: Option<Url>, maybe_config_uri: Option<Url>,
/// An optional import map which is used to resolve modules. /// An optional import map which is used to resolve modules.
pub(crate) maybe_import_map: Option<ImportMap>, pub(crate) maybe_import_map: Option<Arc<ImportMap>>,
/// The URL for the import map which is used to determine relative imports. /// The URL for the import map which is used to determine relative imports.
maybe_import_map_uri: Option<Url>, maybe_import_map_uri: Option<Url>,
/// A collection of measurements which instrument that performance of the LSP. /// A collection of measurements which instrument that performance of the LSP.
@ -481,13 +481,13 @@ impl Inner {
) )
})? })?
}; };
let import_map = let import_map = Arc::new(ImportMap::from_json(
ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?; &import_map_url.to_string(),
&import_map_json,
)?);
self.maybe_import_map_uri = Some(import_map_url); self.maybe_import_map_uri = Some(import_map_url);
self.maybe_import_map = Some(import_map.clone()); self.maybe_import_map = Some(import_map);
self.documents.set_import_map(Some(Arc::new(import_map)));
} else { } else {
self.documents.set_import_map(None);
self.maybe_import_map = None; self.maybe_import_map = None;
} }
self.performance.measure(mark); self.performance.measure(mark);
@ -700,6 +700,10 @@ impl Inner {
if let Err(err) = self.update_registries().await { if let Err(err) = self.update_registries().await {
self.client.show_message(MessageType::Warning, err).await; self.client.show_message(MessageType::Warning, err).await;
} }
self.documents.update_config(
self.maybe_import_map.clone(),
self.maybe_config_file.as_ref(),
);
self.performance.measure(mark); self.performance.measure(mark);
Ok(InitializeResult { Ok(InitializeResult {
@ -908,6 +912,10 @@ impl Inner {
if let Err(err) = self.diagnostics_server.update() { if let Err(err) = self.diagnostics_server.update() {
error!("{}", err); error!("{}", err);
} }
self.documents.update_config(
self.maybe_import_map.clone(),
self.maybe_config_file.as_ref(),
);
self.performance.measure(mark); self.performance.measure(mark);
} }
@ -942,6 +950,10 @@ impl Inner {
} }
} }
if touched { if touched {
self.documents.update_config(
self.maybe_import_map.clone(),
self.maybe_config_file.as_ref(),
);
self.diagnostics_server.invalidate_all().await; self.diagnostics_server.invalidate_all().await;
if let Err(err) = self.diagnostics_server.update() { if let Err(err) = self.diagnostics_server.update() {
error!("Cannot update diagnostics: {}", err); error!("Cannot update diagnostics: {}", err);
@ -2624,6 +2636,7 @@ impl Inner {
CacheServer::new( CacheServer::new(
self.maybe_cache_path.clone(), self.maybe_cache_path.clone(),
self.maybe_import_map.clone(), self.maybe_import_map.clone(),
self.maybe_config_file.clone(),
) )
.await, .await,
); );

View file

@ -19,7 +19,6 @@ mod path_to_regex;
mod performance; mod performance;
mod refactor; mod refactor;
mod registries; mod registries;
mod resolver;
mod semantic_tokens; mod semantic_tokens;
mod text; mod text;
mod tsc; mod tsc;

View file

@ -1,33 +0,0 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError;
use deno_core::ModuleSpecifier;
use deno_graph::source::Resolver;
use import_map::ImportMap;
use std::sync::Arc;
#[derive(Debug)]
pub(crate) struct ImportMapResolver(Arc<ImportMap>);
impl ImportMapResolver {
pub fn new(import_map: Arc<ImportMap>) -> Self {
Self(import_map)
}
pub fn as_resolver(&self) -> &dyn Resolver {
self
}
}
impl Resolver for ImportMapResolver {
fn resolve(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
) -> Result<ModuleSpecifier, AnyError> {
self
.0
.resolve(specifier, referrer.as_str())
.map_err(|err| err.into())
}
}

View file

@ -60,6 +60,7 @@ use crate::fmt_errors::PrettyJsError;
use crate::module_loader::CliModuleLoader; use crate::module_loader::CliModuleLoader;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver; use crate::resolver::ImportMapResolver;
use crate::resolver::JsxResolver;
use crate::source_maps::apply_source_map; use crate::source_maps::apply_source_map;
use crate::tools::installer::infer_name_from_url; use crate::tools::installer::infer_name_from_url;
use deno_ast::MediaType; use deno_ast::MediaType;
@ -468,14 +469,29 @@ async fn info_command(
Permissions::allow_all(), Permissions::allow_all(),
); );
let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone()); let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
let maybe_resolver = let maybe_import_map_resolver =
ps.maybe_import_map.as_ref().map(ImportMapResolver::new); ps.maybe_import_map.clone().map(ImportMapResolver::new);
let maybe_jsx_resolver = ps
.maybe_config_file
.as_ref()
.map(|cf| {
cf.to_maybe_jsx_import_source_module()
.map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
})
.flatten();
let maybe_resolver = if maybe_jsx_resolver.is_some() {
maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
} else {
maybe_import_map_resolver
.as_ref()
.map(|im| im.as_resolver())
};
let graph = deno_graph::create_graph( let graph = deno_graph::create_graph(
vec![specifier], vec![specifier],
false, false,
None, None,
&mut cache, &mut cache,
maybe_resolver.as_ref().map(|r| r.as_resolver()), maybe_resolver,
maybe_locker, maybe_locker,
None, None,
) )
@ -637,19 +653,35 @@ async fn create_graph_and_maybe_check(
Permissions::allow_all(), Permissions::allow_all(),
); );
let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone()); let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
let maybe_imports = ps let maybe_imports = if let Some(config_file) = &ps.maybe_config_file {
config_file.to_maybe_imports()?
} else {
None
};
let maybe_import_map_resolver =
ps.maybe_import_map.clone().map(ImportMapResolver::new);
let maybe_jsx_resolver = ps
.maybe_config_file .maybe_config_file
.as_ref() .as_ref()
.map(|cf| cf.to_maybe_imports()) .map(|cf| {
cf.to_maybe_jsx_import_source_module()
.map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
})
.flatten(); .flatten();
let maybe_resolver = ps.maybe_import_map.as_ref().map(ImportMapResolver::new); let maybe_resolver = if maybe_jsx_resolver.is_some() {
maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
} else {
maybe_import_map_resolver
.as_ref()
.map(|im| im.as_resolver())
};
let graph = Arc::new( let graph = Arc::new(
deno_graph::create_graph( deno_graph::create_graph(
vec![root], vec![root],
false, false,
maybe_imports, maybe_imports,
&mut cache, &mut cache,
maybe_resolver.as_ref().map(|r| r.as_resolver()), maybe_resolver,
maybe_locker, maybe_locker,
None, None,
) )
@ -965,19 +997,34 @@ async fn run_with_watch(flags: Flags, script: String) -> Result<(), AnyError> {
Permissions::allow_all(), Permissions::allow_all(),
); );
let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone()); let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
let maybe_imports = ps let maybe_imports = if let Some(config_file) = &ps.maybe_config_file {
config_file.to_maybe_imports()?
} else {
None
};
let maybe_import_map_resolver =
ps.maybe_import_map.clone().map(ImportMapResolver::new);
let maybe_jsx_resolver = ps
.maybe_config_file .maybe_config_file
.as_ref() .as_ref()
.map(|cf| cf.to_maybe_imports()) .map(|cf| {
cf.to_maybe_jsx_import_source_module()
.map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
})
.flatten(); .flatten();
let maybe_resolver = let maybe_resolver = if maybe_jsx_resolver.is_some() {
ps.maybe_import_map.as_ref().map(ImportMapResolver::new); maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
} else {
maybe_import_map_resolver
.as_ref()
.map(|im| im.as_resolver())
};
let graph = deno_graph::create_graph( let graph = deno_graph::create_graph(
vec![main_module.clone()], vec![main_module.clone()],
false, false,
maybe_imports, maybe_imports,
&mut cache, &mut cache,
maybe_resolver.as_ref().map(|r| r.as_resolver()), maybe_resolver,
maybe_locker, maybe_locker,
None, None,
) )

View file

@ -7,6 +7,7 @@ use crate::emit;
use crate::errors::get_error_class_name; use crate::errors::get_error_class_name;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver; use crate::resolver::ImportMapResolver;
use crate::resolver::JsxResolver;
use deno_core::error::custom_error; use deno_core::error::custom_error;
use deno_core::error::generic_error; use deno_core::error::generic_error;
@ -71,14 +72,66 @@ struct EmitResult {
stats: emit::Stats, stats: emit::Stats,
} }
/// Provides inferred imported modules from configuration options, like the
/// `"types"` and `"jsxImportSource"` imports.
fn to_maybe_imports( fn to_maybe_imports(
referrer: &ModuleSpecifier, referrer: &ModuleSpecifier,
maybe_options: Option<&HashMap<String, Value>>, maybe_options: Option<&HashMap<String, Value>>,
) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> { ) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> {
let options = maybe_options.as_ref()?; let options = maybe_options?;
let types_value = options.get("types")?; let mut imports = Vec::new();
let types: Vec<String> = serde_json::from_value(types_value.clone()).ok()?; if let Some(types_value) = options.get("types") {
Some(vec![(referrer.clone(), types)]) if let Ok(types) =
serde_json::from_value::<Vec<String>>(types_value.clone())
{
imports.extend(types);
}
}
if let Some(jsx_value) = options.get("jsx") {
if let Ok(jsx) = serde_json::from_value::<String>(jsx_value.clone()) {
let jsx_import_source =
if let Some(jsx_import_source_value) = options.get("jsxImportSource") {
if let Ok(jsx_import_source) =
serde_json::from_value::<String>(jsx_import_source_value.clone())
{
jsx_import_source
} else {
"react".to_string()
}
} else {
"react".to_string()
};
match jsx.as_str() {
"react-jsx" => {
imports.push(format!("{}/jsx-runtime", jsx_import_source));
}
"react-jsxdev" => {
imports.push(format!("{}/jsx-dev-runtime", jsx_import_source));
}
_ => (),
}
}
}
if !imports.is_empty() {
Some(vec![(referrer.clone(), imports)])
} else {
None
}
}
/// Converts the compiler options to the JSX import source module that will be
/// loaded when transpiling JSX.
fn to_maybe_jsx_import_source_module(
maybe_options: Option<&HashMap<String, Value>>,
) -> Option<String> {
let options = maybe_options?;
let jsx_value = options.get("jsx")?;
let jsx: String = serde_json::from_value(jsx_value.clone()).ok()?;
match jsx.as_str() {
"react-jsx" => Some("jsx-runtime".to_string()),
"react-jsxdev" => Some("jsx-dev-runtime".to_string()),
_ => None,
}
} }
async fn op_emit( async fn op_emit(
@ -108,7 +161,9 @@ async fn op_emit(
runtime_permissions.clone(), runtime_permissions.clone(),
)) ))
}; };
let maybe_import_map = if let Some(import_map_str) = args.import_map_path { let maybe_import_map_resolver = if let Some(import_map_str) =
args.import_map_path
{
let import_map_specifier = resolve_url_or_path(&import_map_str) let import_map_specifier = resolve_url_or_path(&import_map_str)
.context(format!("Bad URL (\"{}\") for import map.", import_map_str))?; .context(format!("Bad URL (\"{}\") for import map.", import_map_str))?;
let import_map = if let Some(value) = args.import_map { let import_map = if let Some(value) = args.import_map {
@ -126,23 +181,32 @@ async fn op_emit(
})?; })?;
ImportMap::from_json(import_map_specifier.as_str(), &file.source)? ImportMap::from_json(import_map_specifier.as_str(), &file.source)?
}; };
Some(import_map) Some(ImportMapResolver::new(Arc::new(import_map)))
} else if args.import_map.is_some() { } else if args.import_map.is_some() {
return Err(generic_error("An importMap was specified, but no importMapPath was provided, which is required.")); return Err(generic_error("An importMap was specified, but no importMapPath was provided, which is required."));
} else { } else {
None None
}; };
let maybe_jsx_resolver =
to_maybe_jsx_import_source_module(args.compiler_options.as_ref())
.map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()));
let maybe_resolver = if maybe_jsx_resolver.is_some() {
maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
} else {
maybe_import_map_resolver
.as_ref()
.map(|imr| imr.as_resolver())
};
let roots = vec![resolve_url_or_path(&root_specifier)?]; let roots = vec![resolve_url_or_path(&root_specifier)?];
let maybe_imports = let maybe_imports =
to_maybe_imports(&roots[0], args.compiler_options.as_ref()); 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( let graph = Arc::new(
deno_graph::create_graph( deno_graph::create_graph(
roots, roots,
true, true,
maybe_imports, maybe_imports,
cache.as_mut_loader(), cache.as_mut_loader(),
maybe_resolver.as_ref().map(|r| r.as_resolver()), maybe_resolver,
None, None,
None, None,
) )

View file

@ -5,6 +5,7 @@ use crate::colors;
use crate::compat; use crate::compat;
use crate::compat::NodeEsmResolver; use crate::compat::NodeEsmResolver;
use crate::config_file::ConfigFile; use crate::config_file::ConfigFile;
use crate::config_file::MaybeImportsResult;
use crate::deno_dir; use crate::deno_dir;
use crate::emit; use crate::emit;
use crate::errors::get_module_graph_error_class; use crate::errors::get_module_graph_error_class;
@ -15,6 +16,7 @@ use crate::http_cache;
use crate::lockfile::as_maybe_locker; use crate::lockfile::as_maybe_locker;
use crate::lockfile::Lockfile; use crate::lockfile::Lockfile;
use crate::resolver::ImportMapResolver; use crate::resolver::ImportMapResolver;
use crate::resolver::JsxResolver;
use crate::source_maps::SourceMapGetter; use crate::source_maps::SourceMapGetter;
use crate::version; use crate::version;
@ -79,7 +81,7 @@ pub struct Inner {
graph_data: Arc<Mutex<GraphData>>, graph_data: Arc<Mutex<GraphData>>,
pub lockfile: Option<Arc<Mutex<Lockfile>>>, pub lockfile: Option<Arc<Mutex<Lockfile>>>,
pub maybe_config_file: Option<ConfigFile>, pub maybe_config_file: Option<ConfigFile>,
pub maybe_import_map: Option<ImportMap>, pub maybe_import_map: Option<Arc<ImportMap>>,
pub maybe_inspector_server: Option<Arc<InspectorServer>>, pub maybe_inspector_server: Option<Arc<InspectorServer>>,
pub root_cert_store: Option<RootCertStore>, pub root_cert_store: Option<RootCertStore>,
pub blob_store: BlobStore, pub blob_store: BlobStore,
@ -200,7 +202,7 @@ impl ProcState {
None None
}; };
let maybe_import_map: Option<ImportMap> = let maybe_import_map: Option<Arc<ImportMap>> =
match flags.import_map_path.as_ref() { match flags.import_map_path.as_ref() {
None => None, None => None,
Some(import_map_url) => { Some(import_map_url) => {
@ -218,7 +220,7 @@ impl ProcState {
))?; ))?;
let import_map = let import_map =
ImportMap::from_json(import_map_specifier.as_str(), &file.source)?; ImportMap::from_json(import_map_specifier.as_str(), &file.source)?;
Some(import_map) Some(Arc::new(import_map))
} }
}; };
@ -258,10 +260,10 @@ impl ProcState {
/// Return any imports that should be brought into the scope of the module /// Return any imports that should be brought into the scope of the module
/// graph. /// graph.
fn get_maybe_imports(&self) -> Option<Vec<(ModuleSpecifier, Vec<String>)>> { fn get_maybe_imports(&self) -> MaybeImportsResult {
let mut imports = Vec::new(); let mut imports = Vec::new();
if let Some(config_file) = &self.maybe_config_file { if let Some(config_file) = &self.maybe_config_file {
if let Some(config_imports) = config_file.to_maybe_imports() { if let Some(config_imports) = config_file.to_maybe_imports()? {
imports.extend(config_imports); imports.extend(config_imports);
} }
} }
@ -269,9 +271,9 @@ impl ProcState {
imports.extend(compat::get_node_imports()); imports.extend(compat::get_node_imports());
} }
if imports.is_empty() { if imports.is_empty() {
None Ok(None)
} else { } else {
Some(imports) Ok(Some(imports))
} }
} }
@ -297,16 +299,30 @@ impl ProcState {
dynamic_permissions.clone(), dynamic_permissions.clone(),
); );
let maybe_locker = as_maybe_locker(self.lockfile.clone()); let maybe_locker = as_maybe_locker(self.lockfile.clone());
let maybe_imports = self.get_maybe_imports(); let maybe_imports = self.get_maybe_imports()?;
let node_resolver = NodeEsmResolver::new( let node_resolver = NodeEsmResolver::new(
self.maybe_import_map.as_ref().map(ImportMapResolver::new), self.maybe_import_map.clone().map(ImportMapResolver::new),
); );
let import_map_resolver = let maybe_import_map_resolver =
self.maybe_import_map.as_ref().map(ImportMapResolver::new); self.maybe_import_map.clone().map(ImportMapResolver::new);
let maybe_jsx_resolver = self
.maybe_config_file
.as_ref()
.map(|cf| {
cf.to_maybe_jsx_import_source_module()
.map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
})
.flatten();
let maybe_resolver = if self.flags.compat { let maybe_resolver = if self.flags.compat {
Some(node_resolver.as_resolver()) Some(node_resolver.as_resolver())
} else if maybe_jsx_resolver.is_some() {
// the JSX resolver offloads to the import map if present, otherwise uses
// the default Deno explicit import resolution.
maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
} else { } else {
import_map_resolver.as_ref().map(|im| im.as_resolver()) maybe_import_map_resolver
.as_ref()
.map(|im| im.as_resolver())
}; };
// TODO(bartlomieju): this is very make-shift, is there an existing API // TODO(bartlomieju): this is very make-shift, is there an existing API
// that we could include it like with "maybe_imports"? // that we could include it like with "maybe_imports"?

View file

@ -1,27 +1,29 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::resolve_import;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_graph::source::Resolver; use deno_graph::source::Resolver;
use import_map::ImportMap; use import_map::ImportMap;
use std::sync::Arc;
/// Wraps an import map to be used when building a deno_graph module graph. /// Wraps an import map to be used when building a deno_graph module graph.
/// This is done to avoid having `import_map` be a direct dependency of /// This is done to avoid having `import_map` be a direct dependency of
/// `deno_graph`. /// `deno_graph`.
#[derive(Debug)] #[derive(Debug, Clone)]
pub(crate) struct ImportMapResolver<'a>(&'a ImportMap); pub(crate) struct ImportMapResolver(Arc<ImportMap>);
impl<'a> ImportMapResolver<'a> { impl ImportMapResolver {
pub fn new(import_map: &'a ImportMap) -> Self { pub fn new(import_map: Arc<ImportMap>) -> Self {
Self(import_map) Self(import_map)
} }
pub fn as_resolver(&'a self) -> &'a dyn Resolver { pub fn as_resolver(&self) -> &dyn Resolver {
self self
} }
} }
impl Resolver for ImportMapResolver<'_> { impl Resolver for ImportMapResolver {
fn resolve( fn resolve(
&self, &self,
specifier: &str, specifier: &str,
@ -33,3 +35,42 @@ impl Resolver for ImportMapResolver<'_> {
.map_err(|err| err.into()) .map_err(|err| err.into())
} }
} }
#[derive(Debug, Default, Clone)]
pub(crate) struct JsxResolver {
jsx_import_source_module: String,
maybe_import_map_resolver: Option<ImportMapResolver>,
}
impl JsxResolver {
pub fn new(
jsx_import_source_module: String,
maybe_import_map_resolver: Option<ImportMapResolver>,
) -> Self {
Self {
jsx_import_source_module,
maybe_import_map_resolver,
}
}
pub fn as_resolver(&self) -> &dyn Resolver {
self
}
}
impl Resolver for JsxResolver {
fn jsx_import_source_module(&self) -> &str {
self.jsx_import_source_module.as_str()
}
fn resolve(
&self,
specifier: &str,
referrer: &ModuleSpecifier,
) -> Result<ModuleSpecifier, AnyError> {
self.maybe_import_map_resolver.as_ref().map_or_else(
|| resolve_import(specifier, referrer.as_str()).map_err(|err| err.into()),
|r| r.resolve(specifier, referrer),
)
}
}

View file

@ -63,6 +63,12 @@
"default": "React.Fragment", "default": "React.Fragment",
"markdownDescription": "Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'.\n\nSee more: https://www.typescriptlang.org/tsconfig#jsxFragmentFactory" "markdownDescription": "Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'.\n\nSee more: https://www.typescriptlang.org/tsconfig#jsxFragmentFactory"
}, },
"jsxImportSource": {
"description": "Specify module specifier used to import the JSX factory functions when using jsx: 'react-jsx*'.",
"type": "string",
"default": "react",
"markdownDescription": "Specify module specifier used to import the JSX factory functions when using jsx: `react-jsx*`.\n\nSee more: https://www.typescriptlang.org/tsconfig/#jsxImportSource"
},
"keyofStringsOnly": { "keyofStringsOnly": {
"description": "Make keyof only return strings instead of string, numbers or symbols. Legacy option.", "description": "Make keyof only return strings instead of string, numbers or symbols. Legacy option.",
"type": "boolean", "type": "boolean",
@ -73,7 +79,9 @@
"description": "Specify a set of bundled library declaration files that describe the target runtime environment.", "description": "Specify a set of bundled library declaration files that describe the target runtime environment.",
"type": "array", "type": "array",
"uniqueItems": true, "uniqueItems": true,
"default": ["deno.window"], "default": [
"deno.window"
],
"items": { "items": {
"type": "string" "type": "string"
}, },

View file

@ -3684,3 +3684,82 @@ fn lsp_lint_with_config() {
} }
shutdown(&mut client); shutdown(&mut client);
} }
#[test]
fn lsp_jsx_import_source_pragma() {
let _g = http_server();
let mut client = init("initialize_params.json");
did_open(
&mut client,
json!({
"textDocument": {
"uri": "file:///a/file.tsx",
"languageId": "typescriptreact",
"version": 1,
"text":
"/** @jsxImportSource http://localhost:4545/jsx */
function A() {
return \"hello\";
}
export function B() {
return <A></A>;
}
",
}
}),
);
let (maybe_res, maybe_err) = client
.write_request::<_, _, Value>(
"deno/cache",
json!({
"referrer": {
"uri": "file:///a/file.tsx",
},
"uris": [
{
"uri": "http://127.0.0.1:4545/jsx/jsx-runtime",
}
],
}),
)
.unwrap();
assert!(maybe_err.is_none());
assert!(maybe_res.is_some());
let (maybe_res, maybe_err) = client
.write_request::<_, _, Value>(
"textDocument/hover",
json!({
"textDocument": {
"uri": "file:///a/file.tsx"
},
"position": {
"line": 0,
"character": 25
}
}),
)
.unwrap();
assert!(maybe_err.is_none());
assert_eq!(
maybe_res,
Some(json!({
"contents": {
"kind": "markdown",
"value": "**Resolved Dependency**\n\n**Code**: http&#8203;://localhost:4545/jsx/jsx-runtime\n",
},
"range": {
"start": {
"line": 0,
"character": 21
},
"end": {
"line": 0,
"character": 46
}
}
}))
);
shutdown(&mut client);
}

View file

@ -1100,7 +1100,7 @@ fn basic_auth_tokens() {
eprintln!("{}", stderr_str); eprintln!("{}", stderr_str);
assert!(stderr_str.contains( assert!(stderr_str.contains(
"Import 'http://127.0.0.1:4554/001_hello.js' failed: 404 Not Found" "Import 'http://127.0.0.1:4554/001_hello.js' failed, not found."
)); ));
let output = util::deno_cmd() let output = util::deno_cmd()

View file

@ -1215,6 +1215,118 @@ itest!(jsx_import_from_ts {
output: "jsx_import_from_ts.ts.out", output: "jsx_import_from_ts.ts.out",
}); });
itest!(jsx_import_source_pragma {
args: "run --reload jsx_import_source_pragma.tsx",
output: "jsx_import_source.out",
http_server: true,
});
itest!(jsx_import_source_pragma_with_config {
args: "run --reload --config jsx/deno-jsx.jsonc jsx_import_source_pragma.tsx",
output: "jsx_import_source.out",
http_server: true,
});
itest!(jsx_import_source_pragma_with_dev_config {
args:
"run --reload --config jsx/deno-jsxdev.jsonc jsx_import_source_pragma.tsx",
output: "jsx_import_source_dev.out",
http_server: true,
});
itest!(jsx_import_source_no_pragma {
args:
"run --reload --config jsx/deno-jsx.jsonc jsx_import_source_no_pragma.tsx",
output: "jsx_import_source.out",
http_server: true,
});
itest!(jsx_import_source_no_pragma_dev {
args: "run --reload --config jsx/deno-jsxdev.jsonc jsx_import_source_no_pragma.tsx",
output: "jsx_import_source_dev.out",
http_server: true,
});
itest!(jsx_import_source_pragma_import_map {
args: "run --reload --import-map jsx/import-map.json jsx_import_source_pragma_import_map.tsx",
output: "jsx_import_source_import_map.out",
http_server: true,
});
itest!(jsx_import_source_pragma_import_map_dev {
args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsxdev-import-map.jsonc jsx_import_source_pragma_import_map.tsx",
output: "jsx_import_source_import_map_dev.out",
http_server: true,
});
itest!(jsx_import_source_import_map {
args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsx-import-map.jsonc jsx_import_source_no_pragma.tsx",
output: "jsx_import_source_import_map.out",
http_server: true,
});
itest!(jsx_import_source_import_map_dev {
args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsxdev-import-map.jsonc jsx_import_source_no_pragma.tsx",
output: "jsx_import_source_import_map_dev.out",
http_server: true,
});
itest!(jsx_import_source_pragma_no_check {
args: "run --reload --no-check jsx_import_source_pragma.tsx",
output: "jsx_import_source.out",
http_server: true,
});
itest!(jsx_import_source_pragma_with_config_no_check {
args: "run --reload --config jsx/deno-jsx.jsonc --no-check jsx_import_source_pragma.tsx",
output: "jsx_import_source.out",
http_server: true,
});
// itest!(jsx_import_source_pragma_with_dev_config_no_check {
// args:
// "run --reload --config jsx/deno-jsxdev.jsonc --no-check jsx_import_source_pragma.tsx",
// output: "jsx_import_source_dev.out",
// http_server: true,
// });
itest!(jsx_import_source_no_pragma_no_check {
args:
"run --reload --config jsx/deno-jsx.jsonc --no-check jsx_import_source_no_pragma.tsx",
output: "jsx_import_source.out",
http_server: true,
});
// itest!(jsx_import_source_no_pragma_dev_no_check {
// args: "run --reload --config jsx/deno-jsxdev.jsonc --no-check jsx_import_source_no_pragma.tsx",
// output: "jsx_import_source_dev.out",
// http_server: true,
// });
itest!(jsx_import_source_pragma_import_map_no_check {
args: "run --reload --import-map jsx/import-map.json --no-check jsx_import_source_pragma_import_map.tsx",
output: "jsx_import_source_import_map.out",
http_server: true,
});
// itest!(jsx_import_source_pragma_import_map_dev_no_check {
// args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsxdev-import-map.jsonc --no-check jsx_import_source_pragma_import_map.tsx",
// output: "jsx_import_source_import_map_dev.out",
// http_server: true,
// });
itest!(jsx_import_source_import_map_no_check {
args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsx-import-map.jsonc --no-check jsx_import_source_no_pragma.tsx",
output: "jsx_import_source_import_map.out",
http_server: true,
});
// itest!(jsx_import_source_import_map_dev_no_check {
// args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsxdev-import-map.jsonc --no-check jsx_import_source_no_pragma.tsx",
// output: "jsx_import_source_import_map_dev.out",
// http_server: true,
// });
// TODO(#11128): Flaky. Re-enable later. // TODO(#11128): Flaky. Re-enable later.
// itest!(single_compile_with_reload { // itest!(single_compile_with_reload {
// args: "run --reload --allow-read single_compile_with_reload.ts", // args: "run --reload --allow-read single_compile_with_reload.ts",

View file

@ -557,3 +557,81 @@ Deno.test({
assertEquals(sourceMap.sourcesContent.length, 1); assertEquals(sourceMap.sourcesContent.length, 1);
}, },
}); });
Deno.test({
name: "Deno.emit() - JSX import source pragma",
async fn() {
const { files } = await Deno.emit(
"file:///a.tsx",
{
sources: {
"file:///a.tsx": `/** @jsxImportSource https://example.com/jsx */
export function App() {
return (
<div><></></div>
);
}`,
"https://example.com/jsx/jsx-runtime": `export function jsx(
_type,
_props,
_key,
_source,
_self,
) {}
export const jsxs = jsx;
export const jsxDEV = jsx;
export const Fragment = Symbol("Fragment");
console.log("imported", import.meta.url);
`,
},
},
);
assert(files["file:///a.tsx.js"]);
assert(
files["file:///a.tsx.js"].startsWith(
`import { Fragment as _Fragment, jsx as _jsx } from "https://example.com/jsx/jsx-runtime";\n`,
),
);
},
});
Deno.test({
name: "Deno.emit() - JSX import source no pragma",
async fn() {
const { files } = await Deno.emit(
"file:///a.tsx",
{
compilerOptions: {
jsx: "react-jsx",
jsxImportSource: "https://example.com/jsx",
},
sources: {
"file:///a.tsx": `export function App() {
return (
<div><></></div>
);
}`,
"https://example.com/jsx/jsx-runtime": `export function jsx(
_type,
_props,
_key,
_source,
_self,
) {}
export const jsxs = jsx;
export const jsxDEV = jsx;
export const Fragment = Symbol("Fragment");
console.log("imported", import.meta.url);
`,
},
},
);
assert(files["file:///a.tsx.js"]);
assert(
files["file:///a.tsx.js"].startsWith(
`import { Fragment as _Fragment, jsx as _jsx } from "https://example.com/jsx/jsx-runtime";\n`,
),
);
},
});

View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "jsx"
}
}

6
cli/tests/testdata/jsx/deno-jsx.jsonc vendored Normal file
View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "http://localhost:4545/jsx"
}
}

View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"jsx": "react-jsxdev",
"jsxImportSource": "jsx"
}
}

View file

@ -0,0 +1,6 @@
{
"compilerOptions": {
"jsx": "react-jsxdev",
"jsxImportSource": "http://localhost:4545/jsx"
}
}

View file

@ -0,0 +1,6 @@
{
"imports": {
"jsx/jsx-runtime": "http://localhost:4545/jsx/jsx-runtime/index.ts",
"jsx/jsx-dev-runtime": "http://localhost:4545/jsx/jsx-dev-runtime/index.ts"
}
}

View file

@ -0,0 +1,12 @@
// deno-lint-ignore-file no-explicit-any
export function jsx(
_type: any,
_props: any,
_key: any,
_source: any,
_self: any,
) {}
export const jsxs = jsx;
export const jsxDEV = jsx;
export const Fragment = Symbol("Fragment");
console.log("imported", import.meta.url);

View file

@ -0,0 +1,12 @@
// deno-lint-ignore-file no-explicit-any
export function jsx(
_type: any,
_props: any,
_key: any,
_source: any,
_self: any,
) {}
export const jsxs = jsx;
export const jsxDEV = jsx;
export const Fragment = Symbol("Fragment");
console.log("imported", import.meta.url);

View file

@ -0,0 +1,2 @@
[WILDCARD]
imported http://localhost:4545/jsx/jsx-runtime

View file

@ -0,0 +1,2 @@
[WILDCARD]
imported http://localhost:4545/jsx/jsx-dev-runtime

View file

@ -0,0 +1,2 @@
[WILDCARD]
imported http://localhost:4545/jsx/jsx-runtime/index.ts

View file

@ -0,0 +1,2 @@
[WILDCARD]
imported http://localhost:4545/jsx/jsx-dev-runtime/index.ts

View file

@ -0,0 +1,7 @@
function A() {
return "hello";
}
export function B() {
return <A></A>;
}

View file

@ -0,0 +1,9 @@
/** @jsxImportSource http://localhost:4545/jsx */
function A() {
return "hello";
}
export function B() {
return <A></A>;
}

View file

@ -0,0 +1,9 @@
/** @jsxImportSource jsx */
function A() {
return "hello";
}
export function B() {
return <A></A>;
}

View file

@ -38,7 +38,7 @@ impl Loader for StubDocLoader {
#[derive(Debug)] #[derive(Debug)]
struct DocResolver { struct DocResolver {
import_map: Option<ImportMap>, import_map: Option<Arc<ImportMap>>,
} }
impl Resolver for DocResolver { impl Resolver for DocResolver {

View file

@ -668,8 +668,11 @@ impl ReplSession {
imports_not_used_as_values: ImportsNotUsedAsValues::Preserve, imports_not_used_as_values: ImportsNotUsedAsValues::Preserve,
// JSX is not supported in the REPL // JSX is not supported in the REPL
transform_jsx: false, transform_jsx: false,
jsx_automatic: false,
jsx_development: false,
jsx_factory: "React.createElement".into(), jsx_factory: "React.createElement".into(),
jsx_fragment_factory: "React.Fragment".into(), jsx_fragment_factory: "React.Fragment".into(),
jsx_import_source: None,
repl_imports: true, repl_imports: true,
}, },
)? )?

View file

@ -18,6 +18,7 @@ use crate::lockfile;
use crate::ops; use crate::ops;
use crate::proc_state::ProcState; use crate::proc_state::ProcState;
use crate::resolver::ImportMapResolver; use crate::resolver::ImportMapResolver;
use crate::resolver::JsxResolver;
use crate::tools::coverage::CoverageCollector; use crate::tools::coverage::CoverageCollector;
use deno_ast::swc::common::comments::CommentKind; use deno_ast::swc::common::comments::CommentKind;
@ -1053,14 +1054,21 @@ pub async fn run_tests_with_watch(
let paths_to_watch = paths_to_watch.clone(); let paths_to_watch = paths_to_watch.clone();
let paths_to_watch_clone = paths_to_watch.clone(); let paths_to_watch_clone = paths_to_watch.clone();
let maybe_resolver = let maybe_import_map_resolver =
ps.maybe_import_map.as_ref().map(ImportMapResolver::new); ps.maybe_import_map.clone().map(ImportMapResolver::new);
let maybe_jsx_resolver = ps
.maybe_config_file
.as_ref()
.map(|cf| {
cf.to_maybe_jsx_import_source_module()
.map(|im| JsxResolver::new(im, maybe_import_map_resolver.clone()))
})
.flatten();
let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone()); let maybe_locker = lockfile::as_maybe_locker(ps.lockfile.clone());
let maybe_imports = ps let maybe_imports = ps
.maybe_config_file .maybe_config_file
.as_ref() .as_ref()
.map(|cf| cf.to_maybe_imports()) .map(|cf| cf.to_maybe_imports());
.flatten();
let files_changed = changed.is_some(); let files_changed = changed.is_some();
let include = include.clone(); let include = include.clone();
let ignore = ignore.clone(); let ignore = ignore.clone();
@ -1081,13 +1089,24 @@ pub async fn run_tests_with_watch(
.filter_map(|url| deno_core::resolve_url(url.as_str()).ok()) .filter_map(|url| deno_core::resolve_url(url.as_str()).ok())
.collect() .collect()
}; };
let maybe_imports = if let Some(result) = maybe_imports {
result?
} else {
None
};
let maybe_resolver = if maybe_jsx_resolver.is_some() {
maybe_jsx_resolver.as_ref().map(|jr| jr.as_resolver())
} else {
maybe_import_map_resolver
.as_ref()
.map(|im| im.as_resolver())
};
let graph = deno_graph::create_graph( let graph = deno_graph::create_graph(
test_modules.clone(), test_modules.clone(),
false, false,
maybe_imports, maybe_imports,
cache.as_mut_loader(), cache.as_mut_loader(),
maybe_resolver.as_ref().map(|r| r.as_resolver()), maybe_resolver,
maybe_locker, maybe_locker,
None, None,
) )

View file

@ -414,6 +414,27 @@ pub struct ResolveArgs {
pub specifiers: Vec<String>, pub specifiers: Vec<String>,
} }
fn resolve_specifier(
state: &mut State,
specifier: &ModuleSpecifier,
) -> (String, String) {
let media_type = state
.graph
.get(specifier)
.map_or(&MediaType::Unknown, |m| &m.media_type);
let specifier_str = match specifier.scheme() {
"data" | "blob" => {
let specifier_str = hash_url(specifier, media_type);
state
.data_url_map
.insert(specifier_str.clone(), specifier.clone());
specifier_str
}
_ => specifier.to_string(),
};
(specifier_str, media_type.as_ts_extension().into())
}
fn op_resolve(state: &mut State, args: Value) -> Result<Value, AnyError> { fn op_resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
let v: ResolveArgs = serde_json::from_value(args) let v: ResolveArgs = serde_json::from_value(args)
.context("Invalid request from JavaScript for \"op_resolve\".")?; .context("Invalid request from JavaScript for \"op_resolve\".")?;
@ -434,30 +455,31 @@ fn op_resolve(state: &mut State, args: Value) -> Result<Value, AnyError> {
MediaType::from(specifier).as_ts_extension().to_string(), MediaType::from(specifier).as_ts_extension().to_string(),
)); ));
} else { } else {
let resolved_dependency = // here, we try to resolve the specifier via the referrer, but if we can't
match state.graph.resolve_dependency(specifier, &referrer, true) { // we will try to resolve the specifier via the configuration file, if
Some(resolved_specifier) => { // present, finally defaulting to a "placeholder" specifier. This handles
let media_type = state // situations like the jsxImportSource, which tsc tries to resolve the
.graph // import source from a JSX module, but the module graph only contains the
.get(resolved_specifier) // import as a dependency of the configuration file.
.map_or(&MediaType::Unknown, |m| &m.media_type); let resolved_dependency = if let Some(resolved_specifier) = state
let resolved_specifier_str = match resolved_specifier.scheme() { .graph
"data" | "blob" => { .resolve_dependency(specifier, &referrer, true)
let specifier_str = hash_url(resolved_specifier, media_type); .cloned()
state {
.data_url_map resolve_specifier(state, &resolved_specifier)
.insert(specifier_str.clone(), resolved_specifier.clone()); } else if let Some(resolved_specifier) = state
specifier_str .maybe_config_specifier
} .as_ref()
_ => resolved_specifier.to_string(), .map(|cf| state.graph.resolve_dependency(specifier, cf, true).cloned())
}; .flatten()
(resolved_specifier_str, media_type.as_ts_extension().into()) {
} resolve_specifier(state, &resolved_specifier)
None => ( } else {
"deno:///missing_dependency.d.ts".to_string(), (
".d.ts".to_string(), "deno:///missing_dependency.d.ts".to_string(),
), ".d.ts".to_string(),
}; )
};
resolved.push(resolved_dependency); resolved.push(resolved_dependency);
} }
} }

View file

@ -566,7 +566,9 @@ async fn absolute_redirect(
Ok(file_resp) Ok(file_resp)
} }
async fn main_server(req: Request<Body>) -> hyper::Result<Response<Body>> { async fn main_server(
req: Request<Body>,
) -> Result<Response<Body>, hyper::http::Error> {
return match (req.method(), req.uri().path()) { return match (req.method(), req.uri().path()) {
(&hyper::Method::POST, "/echo_server") => { (&hyper::Method::POST, "/echo_server") => {
let (parts, body) = req.into_parts(); let (parts, body) = req.into_parts();
@ -849,6 +851,27 @@ async fn main_server(req: Request<Body>) -> hyper::Result<Response<Body>> {
let version = format!("{:?}", req.version()); let version = format!("{:?}", req.version());
Ok(Response::new(version.into())) Ok(Response::new(version.into()))
} }
(_, "/jsx/jsx-runtime") | (_, "/jsx/jsx-dev-runtime") => {
let mut res = Response::new(Body::from(
r#"export function jsx(
_type,
_props,
_key,
_source,
_self,
) {}
export const jsxs = jsx;
export const jsxDEV = jsx;
export const Fragment = Symbol("Fragment");
console.log("imported", import.meta.url);
"#,
));
res.headers_mut().insert(
"Content-type",
HeaderValue::from_static("application/javascript"),
);
Ok(res)
}
_ => { _ => {
let mut file_path = testdata_path(); let mut file_path = testdata_path();
file_path.push(&req.uri().path()[1..]); file_path.push(&req.uri().path()[1..]);
@ -857,7 +880,9 @@ async fn main_server(req: Request<Body>) -> hyper::Result<Response<Body>> {
return Ok(file_resp); return Ok(file_resp);
} }
return Ok(Response::new(Body::empty())); Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::empty())
} }
}; };
} }