mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 04:51:22 +00:00
refactor(cli): runtime compiler APIs consolidated to Deno.emit() (#8799)
Closes: #4752
This commit is contained in:
parent
5f4e1767fe
commit
012f99bd9a
@ -44,11 +44,10 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[
|
||||
"UnixListenOptions",
|
||||
"WritePermissionDescriptor",
|
||||
"applySourceMap",
|
||||
"bundle",
|
||||
"compile",
|
||||
"connect",
|
||||
"consoleSize",
|
||||
"createHttpClient",
|
||||
"emit",
|
||||
"formatDiagnostics",
|
||||
"futime",
|
||||
"futimeSync",
|
||||
@ -77,7 +76,6 @@ const UNSTABLE_DENO_PROPS: &[&str] = &[
|
||||
"symlinkSync",
|
||||
"systemMemoryInfo",
|
||||
"systemCpuInfo",
|
||||
"transpileOnly",
|
||||
"umask",
|
||||
"utime",
|
||||
"utimeSync",
|
||||
|
188
cli/dts/lib.deno.unstable.d.ts
vendored
188
cli/dts/lib.deno.unstable.d.ts
vendored
@ -290,6 +290,8 @@ declare namespace Deno {
|
||||
/** Base directory to resolve non-relative module names. Defaults to
|
||||
* `undefined`. */
|
||||
baseUrl?: string;
|
||||
/** The character set of the input files. Defaults to `"utf8"`. */
|
||||
charset?: string;
|
||||
/** Report errors in `.js` files. Use in conjunction with `allowJs`. Defaults
|
||||
* to `false`. */
|
||||
checkJs?: boolean;
|
||||
@ -338,9 +340,6 @@ declare namespace Deno {
|
||||
/** Emit the source alongside the source maps within a single file; requires
|
||||
* `inlineSourceMap` or `sourceMap` to be set. Defaults to `false`. */
|
||||
inlineSources?: boolean;
|
||||
/** Perform additional checks to ensure that transpile only would be safe.
|
||||
* Defaults to `true`. */
|
||||
isolatedModules?: boolean;
|
||||
/** Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`.
|
||||
* Defaults to `"react"`. */
|
||||
jsx?: "react" | "preserve" | "react-native";
|
||||
@ -393,12 +392,17 @@ declare namespace Deno {
|
||||
/** Do not emit `"use strict"` directives in module output. Defaults to
|
||||
* `false`. */
|
||||
noImplicitUseStrict?: boolean;
|
||||
/** Do not include the default library file (`lib.d.ts`). Defaults to
|
||||
* `false`. */
|
||||
noLib?: boolean;
|
||||
/** Do not add triple-slash references or module import targets to the list of
|
||||
* compiled files. Defaults to `false`. */
|
||||
noResolve?: boolean;
|
||||
/** Disable strict checking of generic signatures in function types. Defaults
|
||||
* to `false`. */
|
||||
noStrictGenericChecks?: boolean;
|
||||
/** Include 'undefined' in index signature results. Defaults to `false`. */
|
||||
noUncheckedIndexedAccess?: boolean;
|
||||
/** Report errors on unused locals. Defaults to `false`. */
|
||||
noUnusedLocals?: boolean;
|
||||
/** Report errors on unused parameters. Defaults to `false`. */
|
||||
@ -487,122 +491,78 @@ declare namespace Deno {
|
||||
useDefineForClassFields?: boolean;
|
||||
}
|
||||
|
||||
/** **UNSTABLE**: new API, yet to be vetted.
|
||||
*
|
||||
* The results of a transpile only command, where the `source` contains the
|
||||
* emitted source, and `map` optionally contains the source map. */
|
||||
export interface TranspileOnlyResult {
|
||||
source: string;
|
||||
map?: string;
|
||||
interface ImportMap {
|
||||
imports: Record<string, string>;
|
||||
scopes?: Record<string, Record<string, string>>;
|
||||
}
|
||||
|
||||
/** **UNSTABLE**: new API, yet to be vetted.
|
||||
*
|
||||
* Takes a set of TypeScript sources and resolves to a map where the key was
|
||||
* the original file name provided in sources and the result contains the
|
||||
* `source` and optionally the `map` from the transpile operation. This does no
|
||||
* type checking and validation, it effectively "strips" the types from the
|
||||
* file.
|
||||
*
|
||||
* ```ts
|
||||
* const results = await Deno.transpileOnly({
|
||||
* "foo.ts": `const foo: string = "foo";`
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param sources A map where the key is the filename and the value is the text
|
||||
* to transpile. The filename is only used in the transpile and
|
||||
* not resolved, for example to fill in the source name in the
|
||||
* source map.
|
||||
* @param options An option object of options to send to the compiler. This is
|
||||
* a subset of ts.CompilerOptions which can be supported by Deno.
|
||||
* If unsupported option is passed then the API will throw an error.
|
||||
*/
|
||||
export function transpileOnly(
|
||||
sources: Record<string, string>,
|
||||
options?: CompilerOptions,
|
||||
): Promise<Record<string, TranspileOnlyResult>>;
|
||||
interface EmitOptions {
|
||||
/** Indicate that the source code should be emitted to a single file
|
||||
* JavaScript bundle that is an ES module (`"esm"`). */
|
||||
bundle?: "esm";
|
||||
/** If `true` then the sources will be typed checked, returning any
|
||||
* diagnostic errors in the result. If `false` type checking will be
|
||||
* skipped. Defaults to `true`.
|
||||
*
|
||||
* *Note* by default, only TypeScript will be type checked, just like on
|
||||
* the command line. Use the `compilerOptions` options of `checkJs` to
|
||||
* enable type checking of JavaScript. */
|
||||
check?: boolean;
|
||||
/** A set of options that are aligned to TypeScript compiler options that
|
||||
* are supported by Deno. */
|
||||
compilerOptions?: CompilerOptions;
|
||||
/** An [import-map](https://deno.land/manual/linking_to_external_code/import_maps#import-maps)
|
||||
* which will be applied to the imports. */
|
||||
importMap?: ImportMap;
|
||||
/** An absolute path to an [import-map](https://deno.land/manual/linking_to_external_code/import_maps#import-maps).
|
||||
* Required to be specified if an `importMap` is specified to be able to
|
||||
* determine resolution of relative paths. If a `importMap` is not
|
||||
* specified, then it will assumed the file path points to an import map on
|
||||
* disk and will be attempted to be loaded based on current runtime
|
||||
* permissions.
|
||||
*/
|
||||
importMapPath?: string;
|
||||
/** A record of sources to use when doing the emit. If provided, Deno will
|
||||
* use these sources instead of trying to resolve the modules externally. */
|
||||
sources?: Record<string, string>;
|
||||
}
|
||||
|
||||
/** **UNSTABLE**: new API, yet to be vetted.
|
||||
*
|
||||
* Takes a root module name, and optionally a record set of sources. Resolves
|
||||
* with a compiled set of modules and possibly diagnostics if the compiler
|
||||
* encountered any issues. If just a root name is provided, the modules
|
||||
* will be resolved as if the root module had been passed on the command line.
|
||||
*
|
||||
* If sources are passed, all modules will be resolved out of this object, where
|
||||
* the key is the module name and the value is the content. The extension of
|
||||
* the module name will be used to determine the media type of the module.
|
||||
*
|
||||
* ```ts
|
||||
* const [ maybeDiagnostics1, output1 ] = await Deno.compile("foo.ts");
|
||||
*
|
||||
* const [ maybeDiagnostics2, output2 ] = await Deno.compile("/foo.ts", {
|
||||
* "/foo.ts": `export * from "./bar.ts";`,
|
||||
* "/bar.ts": `export const bar = "bar";`
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param rootName The root name of the module which will be used as the
|
||||
* "starting point". If no `sources` is specified, Deno will
|
||||
* resolve the module externally as if the `rootName` had been
|
||||
* specified on the command line.
|
||||
* @param sources An optional key/value map of sources to be used when resolving
|
||||
* modules, where the key is the module name, and the value is
|
||||
* the source content. The extension of the key will determine
|
||||
* the media type of the file when processing. If supplied,
|
||||
* Deno will not attempt to resolve any modules externally.
|
||||
* @param options An optional object of options to send to the compiler. This is
|
||||
* a subset of ts.CompilerOptions which can be supported by Deno.
|
||||
*/
|
||||
export function compile(
|
||||
rootName: string,
|
||||
sources?: Record<string, string>,
|
||||
options?: CompilerOptions,
|
||||
): Promise<[Diagnostic[] | undefined, Record<string, string>]>;
|
||||
interface EmitResult {
|
||||
/** Diagnostic messages returned from the type checker (`tsc`). */
|
||||
diagnostics: Diagnostic[];
|
||||
/** Any emitted files. If bundled, then the JavaScript will have the
|
||||
* key of `deno:///bundle.js` with an optional map (based on
|
||||
* `compilerOptions`) in `deno:///bundle.js.map`. */
|
||||
files: Record<string, string>;
|
||||
/** An optional array of any compiler options that were ignored by Deno. */
|
||||
ignoredOptions?: string[];
|
||||
/** An array of internal statistics related to the emit, for diagnostic
|
||||
* purposes. */
|
||||
stats: Array<[string, number]>;
|
||||
}
|
||||
|
||||
/** **UNSTABLE**: new API, yet to be vetted.
|
||||
*
|
||||
* `bundle()` is part the compiler API. A full description of this functionality
|
||||
* can be found in the [manual](https://deno.land/manual/runtime/compiler_apis#denobundle).
|
||||
*
|
||||
* Takes a root module name, and optionally a record set of sources. Resolves
|
||||
* with a single JavaScript string (and bundle diagnostics if issues arise with
|
||||
* the bundling) that is like the output of a `deno bundle` command. If just
|
||||
* a root name is provided, the modules will be resolved as if the root module
|
||||
* had been passed on the command line.
|
||||
*
|
||||
* If sources are passed, all modules will be resolved out of this object, where
|
||||
* the key is the module name and the value is the content. The extension of the
|
||||
* module name will be used to determine the media type of the module.
|
||||
*
|
||||
* ```ts
|
||||
* // equivalent to "deno bundle foo.ts" from the command line
|
||||
* const [ maybeDiagnostics1, output1 ] = await Deno.bundle("foo.ts");
|
||||
*
|
||||
* const [ maybeDiagnostics2, output2 ] = await Deno.bundle("/foo.ts", {
|
||||
* "/foo.ts": `export * from "./bar.ts";`,
|
||||
* "/bar.ts": `export const bar = "bar";`
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @param rootName The root name of the module which will be used as the
|
||||
* "starting point". If no `sources` is specified, Deno will
|
||||
* resolve the module externally as if the `rootName` had been
|
||||
* specified on the command line.
|
||||
* @param sources An optional key/value map of sources to be used when resolving
|
||||
* modules, where the key is the module name, and the value is
|
||||
* the source content. The extension of the key will determine
|
||||
* the media type of the file when processing. If supplied,
|
||||
* Deno will not attempt to resolve any modules externally.
|
||||
* @param options An optional object of options to send to the compiler. This is
|
||||
* a subset of ts.CompilerOptions which can be supported by Deno.
|
||||
/**
|
||||
* **UNSTABLE**: new API, yet to be vetted.
|
||||
*
|
||||
* Similar to the command line functionality of `deno run` or `deno cache`,
|
||||
* `Deno.emit()` provides a way to provide Deno arbitrary JavaScript
|
||||
* or TypeScript and have it return JavaScript based on the options and
|
||||
* settings provided. The source code can either be provided or the modules
|
||||
* can be fetched and resolved in line with the behavior of the command line.
|
||||
*
|
||||
* Requires `allow-read` and/or `allow-net` if sources are not provided.
|
||||
*
|
||||
* @param rootSpecifier The specifier that will be used as the entry point.
|
||||
* If no sources are provided, then the specifier would
|
||||
* be the same as if you typed it on the command line for
|
||||
* `deno run`. If sources are provided, it should match
|
||||
* one of the names of the sources.
|
||||
* @param options A set of options to be used with the emit.
|
||||
*/
|
||||
export function bundle(
|
||||
rootName: string,
|
||||
sources?: Record<string, string>,
|
||||
options?: CompilerOptions,
|
||||
): Promise<[Diagnostic[] | undefined, string]>;
|
||||
export function emit(
|
||||
rootSpecifier: string | URL,
|
||||
options?: EmitOptions,
|
||||
): Promise<EmitResult>;
|
||||
|
||||
/** **UNSTABLE**: Should not have same name as `window.location` type. */
|
||||
interface Location {
|
||||
|
@ -497,18 +497,27 @@ impl Module {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Stats(pub Vec<(String, u128)>);
|
||||
pub struct Stats(pub Vec<(String, u32)>);
|
||||
|
||||
impl<'de> Deserialize<'de> for Stats {
|
||||
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let items: Vec<(String, u128)> = Deserialize::deserialize(deserializer)?;
|
||||
let items: Vec<(String, u32)> = Deserialize::deserialize(deserializer)?;
|
||||
Ok(Stats(items))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Stats {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
Serialize::serialize(&self.0, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Stats {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "Compilation statistics:")?;
|
||||
@ -620,6 +629,10 @@ impl Default for BundleType {
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EmitOptions {
|
||||
/// If true, then code will be type checked, otherwise type checking will be
|
||||
/// skipped. If false, then swc will be used for the emit, otherwise tsc will
|
||||
/// be used.
|
||||
pub check: bool,
|
||||
/// 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.
|
||||
@ -769,8 +782,8 @@ impl Graph {
|
||||
|
||||
let s = self.emit_bundle(&root_specifier, &ts_config.into())?;
|
||||
let stats = Stats(vec![
|
||||
("Files".to_string(), self.modules.len() as u128),
|
||||
("Total time".to_string(), start.elapsed().as_millis()),
|
||||
("Files".to_string(), self.modules.len() as u32),
|
||||
("Total time".to_string(), start.elapsed().as_millis() as u32),
|
||||
]);
|
||||
|
||||
Ok((s, stats, maybe_ignored_options))
|
||||
@ -918,18 +931,22 @@ impl Graph {
|
||||
/// emitting single modules as well as bundles, using Deno module resolution
|
||||
/// or supplied sources.
|
||||
pub fn emit(
|
||||
self,
|
||||
mut self,
|
||||
options: EmitOptions,
|
||||
) -> Result<(HashMap<String, String>, ResultInfo), AnyError> {
|
||||
let mut config = TsConfig::new(json!({
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
// TODO(@kitsonk) consider enabling this by default
|
||||
// see: https://github.com/denoland/deno/issues/7732
|
||||
"emitDecoratorMetadata": false,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"inlineSourceMap": false,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "React.createElement",
|
||||
"jsxFragmentFactory": "React.Fragment",
|
||||
"lib": TypeLib::DenoWindow,
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
@ -937,11 +954,7 @@ impl Graph {
|
||||
}));
|
||||
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://",
|
||||
@ -957,74 +970,138 @@ impl Graph {
|
||||
None
|
||||
};
|
||||
|
||||
let root_names = self.get_root_names(!config.get_check_js());
|
||||
let hash_data =
|
||||
vec![config.as_bytes(), version::deno().as_bytes().to_owned()];
|
||||
let graph = Arc::new(Mutex::new(self));
|
||||
|
||||
let response = tsc::exec(
|
||||
js::compiler_isolate_init(),
|
||||
tsc::Request {
|
||||
config: config.clone(),
|
||||
debug: options.debug,
|
||||
graph: graph.clone(),
|
||||
hash_data,
|
||||
maybe_tsbuildinfo: None,
|
||||
root_names,
|
||||
},
|
||||
)?;
|
||||
if !options.check && config.get_declaration() {
|
||||
return Err(anyhow!("The option of `check` is false, but the compiler option of `declaration` is true which is not currently supported."));
|
||||
}
|
||||
if options.bundle_type != BundleType::None && config.get_declaration() {
|
||||
return Err(anyhow!("The bundle option is set, but the compiler option of `declaration` is true which is not currently supported."));
|
||||
}
|
||||
|
||||
let mut emitted_files = HashMap::new();
|
||||
let graph = graph.lock().unwrap();
|
||||
match options.bundle_type {
|
||||
BundleType::Esm => {
|
||||
assert!(
|
||||
response.emitted_files.is_empty(),
|
||||
"No files should have been emitted from tsc."
|
||||
);
|
||||
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 {
|
||||
if options.check {
|
||||
let root_names = self.get_root_names(!config.get_check_js());
|
||||
let hash_data =
|
||||
vec![config.as_bytes(), version::deno().as_bytes().to_owned()];
|
||||
let graph = Arc::new(Mutex::new(self));
|
||||
let response = tsc::exec(
|
||||
js::compiler_isolate_init(),
|
||||
tsc::Request {
|
||||
config: config.clone(),
|
||||
debug: options.debug,
|
||||
graph: graph.clone(),
|
||||
hash_data,
|
||||
maybe_tsbuildinfo: None,
|
||||
root_names,
|
||||
},
|
||||
)?;
|
||||
|
||||
let graph = graph.lock().unwrap();
|
||||
match options.bundle_type {
|
||||
BundleType::Esm => {
|
||||
assert!(
|
||||
emitted_file.maybe_specifiers.is_some(),
|
||||
"Orphaned file emitted."
|
||||
response.emitted_files.is_empty(),
|
||||
"No files should have been emitted from tsc."
|
||||
);
|
||||
let specifiers = emitted_file.maybe_specifiers.clone().unwrap();
|
||||
assert_eq!(
|
||||
specifiers.len(),
|
||||
graph.roots.len(),
|
||||
1,
|
||||
"An unexpected number of specifiers associated with emitted file."
|
||||
"Only a single root module supported."
|
||||
);
|
||||
let specifier = specifiers[0].clone();
|
||||
let extension = match emitted_file.media_type {
|
||||
MediaType::JavaScript => ".js",
|
||||
MediaType::SourceMap => ".js.map",
|
||||
MediaType::Dts => ".d.ts",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let key = format!("{}{}", specifier, extension);
|
||||
emitted_files.insert(key, emitted_file.data.clone());
|
||||
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",
|
||||
MediaType::Dts => ".d.ts",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let key = format!("{}{}", specifier, extension);
|
||||
emitted_files.insert(key, emitted_file.data.clone());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok((
|
||||
emitted_files,
|
||||
ResultInfo {
|
||||
diagnostics: response.diagnostics,
|
||||
loadable_modules: graph.get_loadable_modules(),
|
||||
maybe_ignored_options,
|
||||
stats: response.stats,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
let start = Instant::now();
|
||||
let mut emit_count = 0_u32;
|
||||
match options.bundle_type {
|
||||
BundleType::Esm => {
|
||||
assert_eq!(
|
||||
self.roots.len(),
|
||||
1,
|
||||
"Only a single root module supported."
|
||||
);
|
||||
let specifier = &self.roots[0];
|
||||
let s = self.emit_bundle(specifier, &config.into())?;
|
||||
emit_count += 1;
|
||||
emitted_files.insert("deno:///bundle.js".to_string(), s);
|
||||
}
|
||||
BundleType::None => {
|
||||
let emit_options: ast::EmitOptions = config.into();
|
||||
for (_, module_slot) in self.modules.iter_mut() {
|
||||
if let ModuleSlot::Module(module) = module_slot {
|
||||
if !(emit_options.check_js
|
||||
|| module.media_type == MediaType::JSX
|
||||
|| module.media_type == MediaType::TSX
|
||||
|| module.media_type == MediaType::TypeScript)
|
||||
{
|
||||
emitted_files
|
||||
.insert(module.specifier.to_string(), module.source.clone());
|
||||
}
|
||||
let parsed_module = module.parse()?;
|
||||
let (code, maybe_map) = parsed_module.transpile(&emit_options)?;
|
||||
emit_count += 1;
|
||||
emitted_files.insert(format!("{}.js", module.specifier), code);
|
||||
if let Some(map) = maybe_map {
|
||||
emitted_files
|
||||
.insert(format!("{}.js.map", module.specifier), map);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.flush()?;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok((
|
||||
emitted_files,
|
||||
ResultInfo {
|
||||
diagnostics: response.diagnostics,
|
||||
loadable_modules: graph.get_loadable_modules(),
|
||||
maybe_ignored_options,
|
||||
stats: response.stats,
|
||||
},
|
||||
))
|
||||
let stats = Stats(vec![
|
||||
("Files".to_string(), self.modules.len() as u32),
|
||||
("Emitted".to_string(), emit_count),
|
||||
("Total time".to_string(), start.elapsed().as_millis() as u32),
|
||||
]);
|
||||
|
||||
Ok((
|
||||
emitted_files,
|
||||
ResultInfo {
|
||||
diagnostics: Default::default(),
|
||||
loadable_modules: self.get_loadable_modules(),
|
||||
maybe_ignored_options,
|
||||
stats,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared between `bundle()` and `emit()`.
|
||||
@ -1566,10 +1643,9 @@ impl Graph {
|
||||
let maybe_ignored_options =
|
||||
ts_config.merge_tsconfig(options.maybe_config_path)?;
|
||||
|
||||
let emit_options: ast::EmitOptions = ts_config.clone().into();
|
||||
|
||||
let mut emit_count: u128 = 0;
|
||||
let config = ts_config.as_bytes();
|
||||
let emit_options: ast::EmitOptions = ts_config.into();
|
||||
let mut emit_count = 0_u32;
|
||||
for (_, module_slot) in self.modules.iter_mut() {
|
||||
if let ModuleSlot::Module(module) = module_slot {
|
||||
// TODO(kitsonk) a lot of this logic should be refactored into `Module` as
|
||||
@ -1604,9 +1680,9 @@ impl Graph {
|
||||
self.flush()?;
|
||||
|
||||
let stats = Stats(vec![
|
||||
("Files".to_string(), self.modules.len() as u128),
|
||||
("Files".to_string(), self.modules.len() as u32),
|
||||
("Emitted".to_string(), emit_count),
|
||||
("Total time".to_string(), start.elapsed().as_millis()),
|
||||
("Total time".to_string(), start.elapsed().as_millis() as u32),
|
||||
]);
|
||||
|
||||
Ok(ResultInfo {
|
||||
@ -2270,6 +2346,7 @@ pub mod tests {
|
||||
.await;
|
||||
let (emitted_files, result_info) = graph
|
||||
.emit(EmitOptions {
|
||||
check: true,
|
||||
bundle_type: BundleType::None,
|
||||
debug: false,
|
||||
maybe_user_config: None,
|
||||
@ -2310,6 +2387,7 @@ pub mod tests {
|
||||
.await;
|
||||
let (emitted_files, result_info) = graph
|
||||
.emit(EmitOptions {
|
||||
check: true,
|
||||
bundle_type: BundleType::Esm,
|
||||
debug: false,
|
||||
maybe_user_config: None,
|
||||
@ -2347,6 +2425,7 @@ pub mod tests {
|
||||
user_config.insert("declaration".to_string(), json!(true));
|
||||
let (emitted_files, result_info) = graph
|
||||
.emit(EmitOptions {
|
||||
check: true,
|
||||
bundle_type: BundleType::None,
|
||||
debug: false,
|
||||
maybe_user_config: Some(user_config),
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::ast;
|
||||
use crate::colors;
|
||||
use crate::media_type::MediaType;
|
||||
use crate::import_map::ImportMap;
|
||||
use crate::module_graph::BundleType;
|
||||
use crate::module_graph::EmitOptions;
|
||||
use crate::module_graph::GraphBuilder;
|
||||
@ -10,11 +8,10 @@ use crate::program_state::ProgramState;
|
||||
use crate::specifier_handler::FetchHandler;
|
||||
use crate::specifier_handler::MemoryHandler;
|
||||
use crate::specifier_handler::SpecifierHandler;
|
||||
use crate::tsc_config;
|
||||
|
||||
use deno_core::error::generic_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::error::Context;
|
||||
use deno_core::serde::Serialize;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::serde_json::Value;
|
||||
@ -30,37 +27,46 @@ use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub fn init(rt: &mut deno_core::JsRuntime) {
|
||||
super::reg_json_async(rt, "op_compile", op_compile);
|
||||
super::reg_json_async(rt, "op_transpile", op_transpile);
|
||||
super::reg_json_async(rt, "op_emit", op_emit);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
enum RuntimeBundleType {
|
||||
#[serde(rename = "esm")]
|
||||
Esm,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CompileArgs {
|
||||
root_name: String,
|
||||
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, String>>,
|
||||
bundle: bool,
|
||||
options: Option<String>,
|
||||
}
|
||||
|
||||
async fn op_compile(
|
||||
async fn op_emit(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
args: Value,
|
||||
_data: BufVec,
|
||||
) -> Result<Value, AnyError> {
|
||||
let args: CompileArgs = serde_json::from_value(args)?;
|
||||
if args.bundle {
|
||||
deno_runtime::ops::check_unstable2(&state, "Deno.bundle");
|
||||
} else {
|
||||
deno_runtime::ops::check_unstable2(&state, "Deno.compile");
|
||||
}
|
||||
deno_runtime::ops::check_unstable2(&state, "Deno.emit");
|
||||
let args: EmitArgs = serde_json::from_value(args)?;
|
||||
let program_state = state.borrow().borrow::<Arc<ProgramState>>().clone();
|
||||
let runtime_permissions = {
|
||||
let state = state.borrow();
|
||||
state.borrow::<Permissions>().clone()
|
||||
};
|
||||
// when we are actually resolving modules without provided sources, we should
|
||||
// treat the root module as a dynamic import so that runtime permissions are
|
||||
// applied.
|
||||
let mut is_dynamic = false;
|
||||
let handler: Arc<Mutex<dyn SpecifierHandler>> =
|
||||
if let Some(sources) = args.sources {
|
||||
is_dynamic = true;
|
||||
Arc::new(Mutex::new(MemoryHandler::new(sources)))
|
||||
} else {
|
||||
Arc::new(Mutex::new(FetchHandler::new(
|
||||
@ -68,93 +74,44 @@ async fn op_compile(
|
||||
runtime_permissions,
|
||||
)?))
|
||||
};
|
||||
let mut builder = GraphBuilder::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 {
|
||||
BundleType::None
|
||||
};
|
||||
let debug = program_state.flags.log_level == Some(log::Level::Debug);
|
||||
let maybe_user_config: Option<HashMap<String, Value>> =
|
||||
if let Some(options) = args.options {
|
||||
Some(serde_json::from_str(&options)?)
|
||||
let maybe_import_map = if let Some(import_map_str) = args.import_map_path {
|
||||
let import_map_specifier =
|
||||
ModuleSpecifier::resolve_url_or_path(&import_map_str).context(
|
||||
format!("Bad file path (\"{}\") for import map.", import_map_str),
|
||||
)?;
|
||||
let import_map_url = import_map_specifier.as_url();
|
||||
let import_map = if let Some(value) = args.import_map {
|
||||
ImportMap::from_json(&import_map_url.to_string(), &value.to_string())?
|
||||
} else {
|
||||
None
|
||||
ImportMap::load(&import_map_str)?
|
||||
};
|
||||
let (emitted_files, result_info) = graph.emit(EmitOptions {
|
||||
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 mut builder = GraphBuilder::new(handler, maybe_import_map, None);
|
||||
let root_specifier =
|
||||
ModuleSpecifier::resolve_url_or_path(&args.root_specifier)?;
|
||||
builder.add(&root_specifier, is_dynamic).await?;
|
||||
let bundle_type = match args.bundle {
|
||||
Some(RuntimeBundleType::Esm) => BundleType::Esm,
|
||||
_ => BundleType::None,
|
||||
};
|
||||
let graph = builder.get_graph();
|
||||
let debug = program_state.flags.log_level == Some(log::Level::Debug);
|
||||
let (files, result_info) = graph.emit(EmitOptions {
|
||||
bundle_type,
|
||||
check: args.check.unwrap_or(true),
|
||||
debug,
|
||||
maybe_user_config,
|
||||
maybe_user_config: args.compiler_options,
|
||||
})?;
|
||||
|
||||
Ok(json!({
|
||||
"emittedFiles": emitted_files,
|
||||
"diagnostics": result_info.diagnostics,
|
||||
"files": files,
|
||||
"ignoredOptions": result_info.maybe_ignored_options,
|
||||
"stats": result_info.stats,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct TranspileArgs {
|
||||
sources: HashMap<String, String>,
|
||||
options: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RuntimeTranspileEmit {
|
||||
source: String,
|
||||
map: Option<String>,
|
||||
}
|
||||
|
||||
async fn op_transpile(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
args: Value,
|
||||
_data: BufVec,
|
||||
) -> Result<Value, AnyError> {
|
||||
deno_runtime::ops::check_unstable2(&state, "Deno.transpileOnly");
|
||||
let args: TranspileArgs = serde_json::from_value(args)?;
|
||||
|
||||
let mut compiler_options = tsc_config::TsConfig::new(json!({
|
||||
"checkJs": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "React.createElement",
|
||||
"jsxFragmentFactory": "React.Fragment",
|
||||
"inlineSourceMap": false,
|
||||
}));
|
||||
|
||||
let user_options: HashMap<String, Value> = if let Some(options) = args.options
|
||||
{
|
||||
serde_json::from_str(&options)?
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
let maybe_ignored_options =
|
||||
compiler_options.merge_user_config(&user_options)?;
|
||||
// TODO(@kitsonk) these really should just be passed back to the caller
|
||||
if let Some(ignored_options) = maybe_ignored_options {
|
||||
info!("{}: {}", colors::yellow("warning"), ignored_options);
|
||||
}
|
||||
|
||||
let emit_options: ast::EmitOptions = compiler_options.into();
|
||||
let mut emit_map = HashMap::new();
|
||||
|
||||
for (specifier, source) in args.sources {
|
||||
let media_type = MediaType::from(&specifier);
|
||||
let parsed_module = ast::parse(&specifier, &source, &media_type)?;
|
||||
let (source, maybe_source_map) = parsed_module.transpile(&emit_options)?;
|
||||
|
||||
emit_map.insert(
|
||||
specifier.to_string(),
|
||||
RuntimeTranspileEmit {
|
||||
source,
|
||||
map: maybe_source_map,
|
||||
},
|
||||
);
|
||||
}
|
||||
let result = serde_json::to_value(emit_map)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
@ -6,15 +6,21 @@ import {
|
||||
} from "../../std/testing/asserts.ts";
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.compile() - sources provided",
|
||||
name: "Deno.emit() - sources provided",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.compile("/foo.ts", {
|
||||
"/foo.ts": `import * as bar from "./bar.ts";\n\nconsole.log(bar);\n`,
|
||||
"/bar.ts": `export const bar = "bar";\n`,
|
||||
});
|
||||
assert(diagnostics == null);
|
||||
assert(actual);
|
||||
const keys = Object.keys(actual).sort();
|
||||
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
|
||||
"/foo.ts",
|
||||
{
|
||||
sources: {
|
||||
"/foo.ts": `import * as bar from "./bar.ts";\n\nconsole.log(bar);\n`,
|
||||
"/bar.ts": `export const bar = "bar";\n`,
|
||||
},
|
||||
},
|
||||
);
|
||||
assertEquals(diagnostics.length, 0);
|
||||
assert(!ignoredOptions);
|
||||
assertEquals(stats.length, 12);
|
||||
const keys = Object.keys(files).sort();
|
||||
assert(keys[0].endsWith("/bar.ts.js"));
|
||||
assert(keys[1].endsWith("/bar.ts.js.map"));
|
||||
assert(keys[2].endsWith("/foo.ts.js"));
|
||||
@ -23,12 +29,15 @@ Deno.test({
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.compile() - no sources provided",
|
||||
name: "Deno.emit() - no sources provided",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.compile("./subdir/mod1.ts");
|
||||
assert(diagnostics == null);
|
||||
assert(actual);
|
||||
const keys = Object.keys(actual).sort();
|
||||
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
|
||||
"./subdir/mod1.ts",
|
||||
);
|
||||
assertEquals(diagnostics.length, 0);
|
||||
assert(!ignoredOptions);
|
||||
assertEquals(stats.length, 12);
|
||||
const keys = Object.keys(files).sort();
|
||||
assertEquals(keys.length, 6);
|
||||
assert(keys[0].endsWith("cli/tests/subdir/mod1.ts.js"));
|
||||
assert(keys[1].endsWith("cli/tests/subdir/mod1.ts.js.map"));
|
||||
@ -36,183 +45,246 @@ Deno.test({
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.compile() - compiler options effects emit",
|
||||
name: "Deno.emit() - compiler options effects emit",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.compile(
|
||||
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
|
||||
"/foo.ts",
|
||||
{
|
||||
"/foo.ts": `export const foo = "foo";`,
|
||||
},
|
||||
{
|
||||
module: "amd",
|
||||
sourceMap: false,
|
||||
compilerOptions: {
|
||||
module: "amd",
|
||||
sourceMap: false,
|
||||
},
|
||||
sources: { "/foo.ts": `export const foo = "foo";` },
|
||||
},
|
||||
);
|
||||
assert(diagnostics == null);
|
||||
assert(actual);
|
||||
const keys = Object.keys(actual);
|
||||
assertEquals(diagnostics.length, 0);
|
||||
assert(!ignoredOptions);
|
||||
assertEquals(stats.length, 12);
|
||||
const keys = Object.keys(files);
|
||||
assertEquals(keys.length, 1);
|
||||
const key = keys[0];
|
||||
assert(key.endsWith("/foo.ts.js"));
|
||||
assert(actual[key].startsWith("define("));
|
||||
assert(files[key].startsWith("define("));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.compile() - pass lib in compiler options",
|
||||
name: "Deno.emit() - pass lib in compiler options",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.compile(
|
||||
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
|
||||
"file:///foo.ts",
|
||||
{
|
||||
"file:///foo.ts": `console.log(document.getElementById("foo"));
|
||||
console.log(Deno.args);`,
|
||||
},
|
||||
{
|
||||
lib: ["dom", "es2018", "deno.ns"],
|
||||
compilerOptions: {
|
||||
lib: ["dom", "es2018", "deno.ns"],
|
||||
},
|
||||
sources: {
|
||||
"file:///foo.ts": `console.log(document.getElementById("foo"));
|
||||
console.log(Deno.args);`,
|
||||
},
|
||||
},
|
||||
);
|
||||
assert(diagnostics == null);
|
||||
assert(actual);
|
||||
assertEquals(diagnostics.length, 0);
|
||||
assert(!ignoredOptions);
|
||||
assertEquals(stats.length, 12);
|
||||
const keys = Object.keys(files).sort();
|
||||
assertEquals(keys, ["file:///foo.ts.js", "file:///foo.ts.js.map"]);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.emit() - import maps",
|
||||
async fn() {
|
||||
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
|
||||
"file:///a.ts",
|
||||
{
|
||||
importMap: {
|
||||
imports: {
|
||||
"b": "./b.ts",
|
||||
},
|
||||
},
|
||||
importMapPath: "file:///import-map.json",
|
||||
sources: {
|
||||
"file:///a.ts": `import * as b from "b"
|
||||
console.log(b);`,
|
||||
"file:///b.ts": `export const b = "b";`,
|
||||
},
|
||||
},
|
||||
);
|
||||
assertEquals(diagnostics.length, 0);
|
||||
assert(!ignoredOptions);
|
||||
assertEquals(stats.length, 12);
|
||||
const keys = Object.keys(files).sort();
|
||||
assertEquals(
|
||||
Object.keys(actual).sort(),
|
||||
["file:///foo.ts.js", "file:///foo.ts.js.map"],
|
||||
keys,
|
||||
[
|
||||
"file:///a.ts.js",
|
||||
"file:///a.ts.js.map",
|
||||
"file:///b.ts.js",
|
||||
"file:///b.ts.js.map",
|
||||
],
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// TODO(@kitsonk) figure the "right way" to restore support for types
|
||||
// Deno.test({
|
||||
// name: "Deno.compile() - properly handles .d.ts files",
|
||||
// async fn() {
|
||||
// const [diagnostics, actual] = await Deno.compile(
|
||||
// "/foo.ts",
|
||||
// {
|
||||
// "/foo.ts": `console.log(Foo.bar);`,
|
||||
// "/foo_types.d.ts": `declare namespace Foo {
|
||||
// const bar: string;
|
||||
// }`,
|
||||
// },
|
||||
// {
|
||||
// types: ["/foo_types.d.ts"],
|
||||
// },
|
||||
// );
|
||||
// assert(diagnostics == null);
|
||||
// assert(actual);
|
||||
// assertEquals(
|
||||
// Object.keys(actual).sort(),
|
||||
// ["file:///foo.ts.js", "file:///file.ts.js.map"],
|
||||
// );
|
||||
// },
|
||||
// });
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.transpileOnly()",
|
||||
name: "Deno.emit() - no check",
|
||||
async fn() {
|
||||
const actual = await Deno.transpileOnly({
|
||||
"foo.ts": `export enum Foo { Foo, Bar, Baz };\n`,
|
||||
});
|
||||
assert(actual);
|
||||
assertEquals(Object.keys(actual), ["foo.ts"]);
|
||||
assert(actual["foo.ts"].source.startsWith("export var Foo;"));
|
||||
assert(actual["foo.ts"].map);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.transpileOnly() - config effects commit",
|
||||
async fn() {
|
||||
const actual = await Deno.transpileOnly(
|
||||
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
|
||||
"/foo.ts",
|
||||
{
|
||||
"foo.ts": `/** This is JSDoc */\nexport enum Foo { Foo, Bar, Baz };\n`,
|
||||
},
|
||||
{
|
||||
removeComments: true,
|
||||
check: false,
|
||||
sources: {
|
||||
"/foo.ts": `export enum Foo { Foo, Bar, Baz };\n`,
|
||||
},
|
||||
},
|
||||
);
|
||||
assert(actual);
|
||||
assertEquals(Object.keys(actual), ["foo.ts"]);
|
||||
assert(!actual["foo.ts"].source.includes("This is JSDoc"));
|
||||
assert(actual["foo.ts"].map);
|
||||
assertEquals(diagnostics.length, 0);
|
||||
assert(!ignoredOptions);
|
||||
assertEquals(stats.length, 3);
|
||||
const keys = Object.keys(files).sort();
|
||||
assert(keys[0].endsWith("/foo.ts.js"));
|
||||
assert(keys[1].endsWith("/foo.ts.js.map"));
|
||||
assert(files[keys[0]].startsWith("export var Foo;"));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.bundle() - sources passed",
|
||||
name: "Deno.emit() - no check - config effects emit",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.bundle("/foo.ts", {
|
||||
"/foo.ts": `export * from "./bar.ts";\n`,
|
||||
"/bar.ts": `export const bar = "bar";\n`,
|
||||
});
|
||||
assert(diagnostics == null);
|
||||
assert(actual.includes(`const bar = "bar"`));
|
||||
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
|
||||
"/foo.ts",
|
||||
{
|
||||
check: false,
|
||||
compilerOptions: { removeComments: true },
|
||||
sources: {
|
||||
"/foo.ts":
|
||||
`/** This is JSDoc */\nexport enum Foo { Foo, Bar, Baz };\n`,
|
||||
},
|
||||
},
|
||||
);
|
||||
assertEquals(diagnostics.length, 0);
|
||||
assert(!ignoredOptions);
|
||||
assertEquals(stats.length, 3);
|
||||
const keys = Object.keys(files).sort();
|
||||
assert(keys[0].endsWith("/foo.ts.js"));
|
||||
assert(keys[1].endsWith("/foo.ts.js.map"));
|
||||
assert(!files[keys[0]].includes("This is JSDoc"));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.bundle() - no sources passed",
|
||||
name: "Deno.emit() - bundle esm - with sources",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.bundle("./subdir/mod1.ts");
|
||||
assert(diagnostics == null);
|
||||
assert(actual.length);
|
||||
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
|
||||
"/foo.ts",
|
||||
{
|
||||
bundle: "esm",
|
||||
sources: {
|
||||
"/foo.ts": `export * from "./bar.ts";\n`,
|
||||
"/bar.ts": `export const bar = "bar";\n`,
|
||||
},
|
||||
},
|
||||
);
|
||||
assertEquals(diagnostics.length, 0);
|
||||
assert(!ignoredOptions);
|
||||
assertEquals(stats.length, 12);
|
||||
assertEquals(Object.keys(files), ["deno:///bundle.js"]);
|
||||
assert(files["deno:///bundle.js"].includes(`const bar = "bar"`));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.bundle() - JS Modules included",
|
||||
name: "Deno.emit() - bundle esm - no sources",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.bundle("/foo.js", {
|
||||
"/foo.js": `export * from "./bar.js";\n`,
|
||||
"/bar.js": `export const bar = "bar";\n`,
|
||||
});
|
||||
assert(diagnostics == null);
|
||||
assert(actual.includes(`const bar = "bar"`));
|
||||
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
|
||||
"./subdir/mod1.ts",
|
||||
{
|
||||
bundle: "esm",
|
||||
},
|
||||
);
|
||||
assertEquals(diagnostics.length, 0);
|
||||
assert(!ignoredOptions);
|
||||
assertEquals(stats.length, 12);
|
||||
assertEquals(Object.keys(files), ["deno:///bundle.js"]);
|
||||
assert(files["deno:///bundle.js"].length);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "runtime compiler APIs diagnostics",
|
||||
name: "Deno.emit() - bundle esm - include js modules",
|
||||
async fn() {
|
||||
const [diagnostics] = await Deno.compile("/foo.ts", {
|
||||
"/foo.ts": `document.getElementById("foo");`,
|
||||
});
|
||||
assert(Array.isArray(diagnostics));
|
||||
assert(diagnostics.length === 1);
|
||||
const { diagnostics, files, ignoredOptions, stats } = await Deno.emit(
|
||||
"/foo.js",
|
||||
{
|
||||
bundle: "esm",
|
||||
sources: {
|
||||
"/foo.js": `export * from "./bar.js";\n`,
|
||||
"/bar.js": `export const bar = "bar";\n`,
|
||||
},
|
||||
},
|
||||
);
|
||||
assertEquals(diagnostics.length, 0);
|
||||
assert(!ignoredOptions);
|
||||
assertEquals(stats.length, 12);
|
||||
assertEquals(Object.keys(files), ["deno:///bundle.js"]);
|
||||
assert(files["deno:///bundle.js"].includes(`const bar = "bar"`));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.emit() - generates diagnostics",
|
||||
async fn() {
|
||||
const { diagnostics, files } = await Deno.emit(
|
||||
"/foo.ts",
|
||||
{
|
||||
sources: {
|
||||
"/foo.ts": `document.getElementById("foo");`,
|
||||
},
|
||||
},
|
||||
);
|
||||
assertEquals(diagnostics.length, 1);
|
||||
const keys = Object.keys(files).sort();
|
||||
assert(keys[0].endsWith("/foo.ts.js"));
|
||||
assert(keys[1].endsWith("/foo.ts.js.map"));
|
||||
},
|
||||
});
|
||||
|
||||
// See https://github.com/denoland/deno/issues/6908
|
||||
Deno.test({
|
||||
name: "Deno.compile() - SWC diagnostics",
|
||||
name: "Deno.emit() - invalid syntax does not panic",
|
||||
async fn() {
|
||||
await assertThrowsAsync(async () => {
|
||||
await Deno.compile("/main.js", {
|
||||
"/main.js": `
|
||||
export class Foo {
|
||||
constructor() {
|
||||
console.log("foo");
|
||||
}
|
||||
export get() {
|
||||
console.log("bar");
|
||||
}
|
||||
}`,
|
||||
await Deno.emit("/main.js", {
|
||||
sources: {
|
||||
"/main.js": `
|
||||
export class Foo {
|
||||
constructor() {
|
||||
console.log("foo");
|
||||
}
|
||||
export get() {
|
||||
console.log("bar");
|
||||
}
|
||||
}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: `Deno.compile() - Allows setting of "importsNotUsedAsValues"`,
|
||||
name: 'Deno.emit() - allows setting of "importsNotUsedAsValues"',
|
||||
async fn() {
|
||||
const [diagnostics] = await Deno.compile("/a.ts", {
|
||||
"/a.ts": `import { B } from "./b.ts";
|
||||
const b: B = { b: "b" };
|
||||
`,
|
||||
"/b.ts": `export interface B {
|
||||
b: string;
|
||||
};
|
||||
`,
|
||||
}, {
|
||||
importsNotUsedAsValues: "error",
|
||||
const { diagnostics } = await Deno.emit("/a.ts", {
|
||||
sources: {
|
||||
"/a.ts": `import { B } from "./b.ts";
|
||||
const b: B = { b: "b" };`,
|
||||
"/b.ts": `export interface B {
|
||||
b:string;
|
||||
};`,
|
||||
},
|
||||
compilerOptions: {
|
||||
importsNotUsedAsValues: "error",
|
||||
},
|
||||
});
|
||||
assert(diagnostics);
|
||||
assertEquals(diagnostics.length, 1);
|
||||
|
@ -1,14 +1,16 @@
|
||||
const [errors, program] = await Deno.compile(
|
||||
const { diagnostics, files } = await Deno.emit(
|
||||
"/main.ts",
|
||||
{
|
||||
"/main.ts":
|
||||
`/// <reference lib="dom" />\n\ndocument.getElementById("foo");\nDeno.args;`,
|
||||
},
|
||||
{
|
||||
target: "es2018",
|
||||
lib: ["es2018", "deno.ns"],
|
||||
sources: {
|
||||
"/main.ts":
|
||||
`/// <reference lib="dom" />\n\ndocument.getElementById("foo");\nDeno.args;`,
|
||||
},
|
||||
compilerOptions: {
|
||||
target: "es2018",
|
||||
lib: ["es2018", "deno.ns"],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
console.log(errors);
|
||||
console.log(Object.keys(program).sort());
|
||||
console.log(diagnostics);
|
||||
console.log(Object.keys(files).sort());
|
||||
|
@ -1,2 +1,2 @@
|
||||
undefined
|
||||
[]
|
||||
[ "file:///[WILDCARD]main.ts.js", "file:///[WILDCARD]main.ts.js.map" ]
|
||||
|
@ -1,12 +1,14 @@
|
||||
const [errors, program] = await Deno.compile(
|
||||
const { diagnostics, files } = await Deno.emit(
|
||||
"/main.ts",
|
||||
{
|
||||
"/main.ts": `document.getElementById("foo");`,
|
||||
},
|
||||
{
|
||||
lib: ["dom", "esnext"],
|
||||
sources: {
|
||||
"/main.ts": `document.getElementById("foo");`,
|
||||
},
|
||||
compilerOptions: {
|
||||
lib: ["dom", "esnext"],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
console.log(errors);
|
||||
console.log(Object.keys(program).sort());
|
||||
console.log(diagnostics);
|
||||
console.log(Object.keys(files).sort());
|
||||
|
@ -1,2 +1,2 @@
|
||||
undefined
|
||||
[]
|
||||
[ "file:///[WILDCARD]main.ts.js", "file:///[WILDCARD]main.ts.js.map" ]
|
||||
|
@ -1,5 +1,5 @@
|
||||
console.log(Deno.permissions.query);
|
||||
console.log(Deno.compile);
|
||||
console.log(Deno.emit);
|
||||
self.onmessage = () => {
|
||||
self.close();
|
||||
};
|
||||
|
@ -1,2 +1,2 @@
|
||||
[Function: query]
|
||||
[AsyncFunction: compile]
|
||||
[Function: emit]
|
||||
|
@ -49,6 +49,15 @@ impl fmt::Display for IgnoredCompilerOptions {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for IgnoredCompilerOptions {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
Serialize::serialize(&self.items, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// in Deno.
|
||||
@ -64,7 +73,6 @@ pub const IGNORED_COMPILER_OPTIONS: &[&str] = &[
|
||||
"importHelpers",
|
||||
"inlineSourceMap",
|
||||
"inlineSources",
|
||||
"isolatedModules",
|
||||
"module",
|
||||
"noEmitHelpers",
|
||||
"noLib",
|
||||
@ -97,6 +105,7 @@ pub const IGNORED_RUNTIME_COMPILER_OPTIONS: &[&str] = &[
|
||||
"help",
|
||||
"incremental",
|
||||
"init",
|
||||
"isolatedModules",
|
||||
"listEmittedFiles",
|
||||
"listFiles",
|
||||
"mapRoot",
|
||||
@ -246,6 +255,14 @@ impl TsConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_declaration(&self) -> bool {
|
||||
if let Some(declaration) = self.0.get("declaration") {
|
||||
declaration.as_bool().unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge a serde_json value into the configuration.
|
||||
pub fn merge(&mut self, value: &Value) {
|
||||
json_merge(&mut self.0, value);
|
||||
|
@ -1,97 +1,88 @@
|
||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
// @ts-check
|
||||
|
||||
// This file contains the runtime APIs which will dispatch work to the internal
|
||||
// compiler within Deno.
|
||||
((window) => {
|
||||
const core = window.Deno.core;
|
||||
const util = window.__bootstrap.util;
|
||||
|
||||
function opCompile(request) {
|
||||
return core.jsonOpAsync("op_compile", request);
|
||||
}
|
||||
|
||||
function opTranspile(
|
||||
request,
|
||||
) {
|
||||
return core.jsonOpAsync("op_transpile", request);
|
||||
/**
|
||||
* @typedef {object} ImportMap
|
||||
* @property {Record<string, string>} imports
|
||||
* @property {Record<string, Record<string, string>>=} scopes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} OpEmitRequest
|
||||
* @property {"esm"=} bundle
|
||||
* @property {boolean=} check
|
||||
* @property {Record<string, any>=} compilerOptions
|
||||
* @property {ImportMap=} importMap
|
||||
* @property {string=} importMapPath
|
||||
* @property {string} rootSpecifier
|
||||
* @property {Record<string, string>=} sources
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef OpEmitResponse
|
||||
* @property {any[]} diagnostics
|
||||
* @property {Record<string, string>} files
|
||||
* @property {string[]=} ignoredOptions
|
||||
* @property {Array<[string, number]>} stats
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {OpEmitRequest} request
|
||||
* @returns {Promise<OpEmitResponse>}
|
||||
*/
|
||||
function opEmit(request) {
|
||||
return core.jsonOpAsync("op_emit", request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} specifier
|
||||
* @returns {string}
|
||||
*/
|
||||
function checkRelative(specifier) {
|
||||
return specifier.match(/^([\.\/\\]|https?:\/{2}|file:\/{2})/)
|
||||
? specifier
|
||||
: `./${specifier}`;
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): change return type to interface?
|
||||
function transpileOnly(
|
||||
sources,
|
||||
options = {},
|
||||
) {
|
||||
util.log("Deno.transpileOnly", { sources: Object.keys(sources), options });
|
||||
const payload = {
|
||||
sources,
|
||||
options: JSON.stringify(options),
|
||||
};
|
||||
return opTranspile(payload);
|
||||
}
|
||||
/**
|
||||
* @typedef {object} EmitOptions
|
||||
* @property {"esm"=} bundle
|
||||
* @property {boolean=} check
|
||||
* @property {Record<string, any>=} compilerOptions
|
||||
* @property {ImportMap=} importMap
|
||||
* @property {string=} importMapPath
|
||||
* @property {Record<string, string>=} sources
|
||||
*/
|
||||
|
||||
// TODO(bartlomieju): change return type to interface?
|
||||
async function compile(
|
||||
rootName,
|
||||
sources,
|
||||
options = {},
|
||||
) {
|
||||
const payload = {
|
||||
rootName: sources ? rootName : checkRelative(rootName),
|
||||
sources,
|
||||
options: JSON.stringify(options),
|
||||
bundle: false,
|
||||
};
|
||||
util.log("Deno.compile", {
|
||||
rootName: payload.rootName,
|
||||
sources: !!sources,
|
||||
options,
|
||||
});
|
||||
/** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
|
||||
const result = await opCompile(payload);
|
||||
util.assert(result.emittedFiles);
|
||||
const maybeDiagnostics = result.diagnostics.length === 0
|
||||
? undefined
|
||||
: result.diagnostics;
|
||||
|
||||
return [maybeDiagnostics, result.emittedFiles];
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): change return type to interface?
|
||||
async function bundle(
|
||||
rootName,
|
||||
sources,
|
||||
options = {},
|
||||
) {
|
||||
const payload = {
|
||||
rootName: sources ? rootName : checkRelative(rootName),
|
||||
sources,
|
||||
options: JSON.stringify(options),
|
||||
bundle: true,
|
||||
};
|
||||
util.log("Deno.bundle", {
|
||||
rootName: payload.rootName,
|
||||
sources: !!sources,
|
||||
options,
|
||||
});
|
||||
/** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
|
||||
const result = await opCompile(payload);
|
||||
const output = result.emittedFiles["deno:///bundle.js"];
|
||||
util.assert(output);
|
||||
const maybeDiagnostics = result.diagnostics.length === 0
|
||||
? undefined
|
||||
: result.diagnostics;
|
||||
return [maybeDiagnostics, output];
|
||||
/**
|
||||
* @param {string | URL} rootSpecifier
|
||||
* @param {EmitOptions=} options
|
||||
* @returns {Promise<OpEmitResponse>}
|
||||
*/
|
||||
function emit(rootSpecifier, options = {}) {
|
||||
util.log(`Deno.emit`, { rootSpecifier });
|
||||
if (!rootSpecifier) {
|
||||
return Promise.reject(
|
||||
new TypeError("A root specifier must be supplied."),
|
||||
);
|
||||
}
|
||||
if (!(typeof rootSpecifier === "string")) {
|
||||
rootSpecifier = rootSpecifier.toString();
|
||||
}
|
||||
if (!options.sources) {
|
||||
rootSpecifier = checkRelative(rootSpecifier);
|
||||
}
|
||||
return opEmit({ rootSpecifier, ...options });
|
||||
}
|
||||
|
||||
window.__bootstrap.compilerApi = {
|
||||
bundle,
|
||||
compile,
|
||||
transpileOnly,
|
||||
emit,
|
||||
};
|
||||
})(this);
|
||||
|
@ -94,9 +94,7 @@
|
||||
signals: __bootstrap.signals.signals,
|
||||
Signal: __bootstrap.signals.Signal,
|
||||
SignalStream: __bootstrap.signals.SignalStream,
|
||||
transpileOnly: __bootstrap.compilerApi.transpileOnly,
|
||||
compile: __bootstrap.compilerApi.compile,
|
||||
bundle: __bootstrap.compilerApi.bundle,
|
||||
emit: __bootstrap.compilerApi.emit,
|
||||
permissions: __bootstrap.permissions.permissions,
|
||||
Permissions: __bootstrap.permissions.Permissions,
|
||||
PermissionStatus: __bootstrap.permissions.PermissionStatus,
|
||||
|
Loading…
Reference in New Issue
Block a user