Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk committed Jul 29, 2020
1 parent 1b60840 commit 97252ec
Show file tree
Hide file tree
Showing 17 changed files with 2,629 additions and 58 deletions.
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"cli",
"compiler",
"core",
"test_plugin",
"test_util",
Expand Down
157 changes: 99 additions & 58 deletions cli/tsc/00_typescript.js

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions compiler/Cargo.toml
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"
1 change: 1 addition & 0 deletions compiler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# deno_compiler
214 changes: 214 additions & 0 deletions compiler/bundler.rs
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());
}
}
Loading

0 comments on commit 97252ec

Please sign in to comment.