-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
2,629 additions
and
58 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
[workspace] | ||
members = [ | ||
"cli", | ||
"compiler", | ||
"core", | ||
"test_plugin", | ||
"test_util", | ||
|
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
[package] | ||
name = "deno_compiler" | ||
version = "0.49.0" | ||
license = "MIT" | ||
description = "Compiles TypeScript and bundles JavaScript with the ability to create v8 snapshots" | ||
repository = "https://github.com/denoland/deno" | ||
authors = ["the Deno authors"] | ||
edition = "2018" | ||
|
||
exclude = [ | ||
"typescript/tests/*", | ||
"typescript/src/*", | ||
"typescript/scripts/*", | ||
"typescript/doc/*", | ||
"typescript/lib/*/*.json", | ||
] | ||
|
||
[lib] | ||
path = "lib.rs" | ||
|
||
[dependencies] | ||
base64 = "0.12.3" | ||
bytecount = "0.6.0" | ||
deno_core = { path = "../core", version = "0.50.0" } | ||
jsonc-parser = "0.12.2" | ||
log = "0.4.11" | ||
ring = "0.16.15" | ||
regex = "1.3.9" | ||
serde_json = "1.0.56" | ||
serde = { version = "1.0.114", features = ["derive"] } | ||
sourcemap = "6.0.0" | ||
|
||
[dev-dependencies] | ||
tempfile = "3.1.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# deno_compiler |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. | ||
|
||
use crate::source_map_bundler::SourceMapBundler; | ||
use crate::Result; | ||
|
||
use deno_core::ErrBox; | ||
use std::error::Error; | ||
use std::fmt; | ||
use std::path::PathBuf; | ||
|
||
static SYSTEM_LOADER_CODE: &str = include_str!("system_loader.js"); | ||
static SYSTEM_LOADER_ES5_CODE: &str = include_str!("system_loader_es5.js"); | ||
|
||
fn count_newlines(s: &str) -> usize { | ||
bytecount::count(s.as_bytes(), b'\n') | ||
} | ||
|
||
struct BundleError { | ||
message: String, | ||
} | ||
|
||
impl fmt::Display for BundleError { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
write!(f, "Bundle Error: {}", self.message) | ||
} | ||
} | ||
|
||
impl fmt::Debug for BundleError { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
write!(f, "BundleError {{ message: {} }}", self.message) | ||
} | ||
} | ||
|
||
impl Error for BundleError {} | ||
|
||
#[derive(Debug)] | ||
pub struct BundleEmit { | ||
code: String, | ||
maybe_map: Option<String>, | ||
maybe_map_file_name: Option<PathBuf>, | ||
} | ||
|
||
pub type BundleResult = Result<BundleEmit>; | ||
|
||
pub struct BundleFile<'a> { | ||
code: &'a str, | ||
map: &'a str, | ||
} | ||
|
||
pub struct BundleOptions<'a> { | ||
/// The file name of the output file of the bundle. This is used when | ||
/// configuring the source map and determining the name of the outputted | ||
/// source map. | ||
file_name: PathBuf, | ||
/// If `true` the source map will be inlined into the bundled JavaScript file. | ||
/// Otherwise it will be returned in the result. | ||
inline_source_map: bool, | ||
/// The main module specifier to initialize to bootstrap the bundle. | ||
main_specifier: &'a str, | ||
/// Any named exports that the main module specifier might have that should be | ||
/// re-exported in the bundle. | ||
maybe_named_exports: Option<Vec<&'a str>>, | ||
/// If `false` then a bundle loader that is compatible with ES2017 or later | ||
/// will be used to bootstrap the module. If `true` then a bundle loader that | ||
/// is compatible with ES5 or later will be used. *Note* when targeting ES5 | ||
/// the `bundle()` function will error if the bundle requires top level await | ||
/// for one of the modules. | ||
target_es5: bool, | ||
} | ||
|
||
/// Take a vector of files and a structure of options and output a single file | ||
/// JavaScript file bundle. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `files` - A vector of `BundleFile`s, which are preprocessed files where | ||
/// the code is a SystemJS module with a module specifier and the source map | ||
/// is a map between the original source code and the supplied code. | ||
/// * `options` - A structure of options which affect the behavior of the | ||
/// function. | ||
/// | ||
/// # Errors | ||
/// | ||
/// The function will error if there are issues processing the source map files | ||
/// or if there are incompatible option settings. | ||
/// | ||
pub fn bundle(files: Vec<BundleFile>, options: BundleOptions) -> BundleResult { | ||
let mut code = String::new(); | ||
let preamble = if options.target_es5 { | ||
SYSTEM_LOADER_ES5_CODE | ||
} else { | ||
SYSTEM_LOADER_CODE | ||
}; | ||
code.push_str(preamble); | ||
let mut source_map_bundle = | ||
SourceMapBundler::new(options.file_name.file_name().unwrap().to_str()); | ||
for file in files.iter() { | ||
let line_offset = count_newlines(&code); | ||
code.push_str(file.code); | ||
source_map_bundle.append_from_str(file.map, line_offset)?; | ||
} | ||
let has_exports = options.maybe_named_exports.is_some(); | ||
let top_level_await = code.contains("execute: async function"); | ||
if top_level_await && options.target_es5 { | ||
let message = "The bundle target does not support top level await, but top level await is required.".to_string(); | ||
return Err(ErrBox::from(BundleError { message })); | ||
} | ||
let init_code = if has_exports { | ||
if top_level_await { | ||
format!( | ||
"\nvar __exp = await __instantiate(\"{}\", true);\n", | ||
options.main_specifier | ||
) | ||
} else { | ||
format!( | ||
"\nvar __exp = __instantiate(\"{}\", false);\n", | ||
options.main_specifier | ||
) | ||
} | ||
} else if top_level_await { | ||
format!( | ||
"\nawait __instantiate(\"{}\", true);\n", | ||
options.main_specifier | ||
) | ||
} else { | ||
format!("\n__instantiate(\"{}\", false);\n", options.main_specifier) | ||
}; | ||
code.push_str(&init_code); | ||
if let Some(named_exports) = options.maybe_named_exports { | ||
for named_export in named_exports.iter() { | ||
let export_code = match *named_export { | ||
"default" => "export default __exp[\"default\"];\n".to_string(), | ||
_ => { | ||
format!("export var {} = __[\"{}\"];\n", named_export, named_export) | ||
} | ||
}; | ||
code.push_str(&export_code); | ||
} | ||
}; | ||
let mut map_bytes: Vec<u8> = vec![]; | ||
source_map_bundle | ||
.into_sourcemap() | ||
.to_writer(&mut map_bytes)?; | ||
|
||
if options.inline_source_map { | ||
let map_base64 = base64::encode(map_bytes); | ||
let map_pragma = format!( | ||
"\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,{}\n", | ||
map_base64 | ||
); | ||
code.push_str(&map_pragma); | ||
|
||
Ok(BundleEmit { | ||
code, | ||
maybe_map: None, | ||
maybe_map_file_name: None, | ||
}) | ||
} else { | ||
let maybe_map = Some(String::from_utf8(map_bytes)?); | ||
let mut map_file_name = options.file_name; | ||
map_file_name.set_extension("js.map"); | ||
|
||
Ok(BundleEmit { | ||
code, | ||
maybe_map, | ||
maybe_map_file_name: Some(map_file_name), | ||
}) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_bundle() { | ||
let emit = bundle( | ||
vec![BundleFile { | ||
code: r#"System.register("https://deno.land/x/a.ts", [], function (exports_1, context_1) { | ||
"use strict"; | ||
var __moduleName = context_1 && context_1.id; | ||
return { | ||
setters: [], | ||
execute: function () { | ||
console.log("hello deno"); | ||
} | ||
}; | ||
}); | ||
"#, | ||
map: r#"{ | ||
"version": 3, | ||
"sources": ["coolstuff.js"], | ||
"names": ["x", "alert"], | ||
"mappings": "AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM" | ||
}"#, | ||
}], | ||
BundleOptions { | ||
file_name: PathBuf::from( | ||
std::env::var_os("CARGO_MANIFEST_DIR").unwrap(), | ||
) | ||
.join("test.js"), | ||
inline_source_map: true, | ||
main_specifier: "https://deno.land/x/a.ts", | ||
maybe_named_exports: None, | ||
target_es5: false, | ||
}, | ||
) | ||
.unwrap(); | ||
println!("{}", emit.code); | ||
assert!(emit.code.starts_with("// Copyright 2018")); | ||
assert!(emit.maybe_map.is_none()); | ||
assert!(emit.maybe_map_file_name.is_none()); | ||
} | ||
} |
Oops, something went wrong.