Skip to content

Commit 20c25ca

Browse files
authored
Merge pull request #1295 from alexcrichton/js-snippets
Implement the local JS snippets RFC
2 parents f161717 + d6e3770 commit 20c25ca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+1221
-475
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ members = [
8181
"examples/webaudio",
8282
"examples/webgl",
8383
"examples/without-a-bundler",
84+
"examples/without-a-bundler-no-modules",
8485
"tests/no-std",
8586
]
8687
exclude = ['crates/typescript']

azure-pipelines.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,15 @@ jobs:
4040
- template: ci/azure-install-sccache.yml
4141
- script: cargo test --target wasm32-unknown-unknown
4242
displayName: "wasm-bindgen test suite"
43+
env:
44+
RUST_LOG: wasm_bindgen_test_runner
45+
GECKODRIVER_ARGS: --log trace
4346
- script: cargo test --target wasm32-unknown-unknown -p js-sys
4447
displayName: "js-sys test suite"
4548
- script: cargo test --target wasm32-unknown-unknown -p webidl-tests
4649
displayName: "webidl-tests test suite"
50+
env:
51+
WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1
4752
- script: cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features "Node Window Document"
4853
displayName: "web-sys build"
4954

@@ -94,6 +99,8 @@ jobs:
9499
- template: ci/azure-install-sccache.yml
95100
- script: cargo test -p wasm-bindgen-webidl
96101
- script: cargo test -p webidl-tests --target wasm32-unknown-unknown
102+
env:
103+
WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE: 1
97104

98105
- job: test_ui
99106
displayName: "Run UI tests"

ci/azure-install-geckodriver.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,14 @@ steps:
1212
Write-Host "##vso[task.setvariable variable=GECKODRIVER;]$pwd\geckodriver.exe"
1313
displayName: "Download Geckodriver (Windows)"
1414
condition: eq( variables['Agent.OS'], 'Windows_NT' )
15+
16+
# It turns out that geckodriver.exe will fail if firefox takes too long to
17+
# start, and for whatever reason the first execution of `firefox.exe` can
18+
# take upwards of a mimute. It seems that subsequent executions are much
19+
# faster, so have a dedicated step to run Firefox once which should I
20+
# guess warm some cache somewhere so the headless tests later on all
21+
# finish successfully
22+
- script: |
23+
"C:\Program Files\Mozilla Firefox\firefox.exe" --version
24+
displayName: "Load firefox.exe into cache (presumably?)"
25+
condition: eq( variables['Agent.OS'], 'Windows_NT' )

crates/backend/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ spans = []
1515
extra-traits = ["syn/extra-traits"]
1616

1717
[dependencies]
18+
bumpalo = "2.1"
1819
lazy_static = "1.0.0"
1920
log = "0.4"
2021
proc-macro2 = "0.4.8"

crates/backend/src/ast.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use proc_macro2::{Ident, Span};
22
use shared;
33
use syn;
44
use Diagnostic;
5+
use std::hash::{Hash, Hasher};
56

67
/// An abstract syntax tree representing a rust program. Contains
78
/// extra information for joining up this rust code with javascript.
@@ -24,6 +25,8 @@ pub struct Program {
2425
pub dictionaries: Vec<Dictionary>,
2526
/// custom typescript sections to be included in the definition file
2627
pub typescript_custom_sections: Vec<String>,
28+
/// Inline JS snippets
29+
pub inline_js: Vec<String>,
2730
}
2831

2932
/// A rust to js interface. Allows interaction with rust objects/functions
@@ -66,11 +69,37 @@ pub enum MethodSelf {
6669
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
6770
#[derive(Clone)]
6871
pub struct Import {
69-
pub module: Option<String>,
72+
pub module: ImportModule,
7073
pub js_namespace: Option<Ident>,
7174
pub kind: ImportKind,
7275
}
7376

77+
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
78+
#[derive(Clone)]
79+
pub enum ImportModule {
80+
None,
81+
Named(String, Span),
82+
Inline(usize, Span),
83+
}
84+
85+
impl Hash for ImportModule {
86+
fn hash<H: Hasher>(&self, h: &mut H) {
87+
match self {
88+
ImportModule::None => {
89+
0u8.hash(h);
90+
}
91+
ImportModule::Named(name, _) => {
92+
1u8.hash(h);
93+
name.hash(h);
94+
}
95+
ImportModule::Inline(idx, _) => {
96+
2u8.hash(h);
97+
idx.hash(h);
98+
}
99+
}
100+
}
101+
}
102+
74103
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
75104
#[derive(Clone)]
76105
pub enum ImportKind {

crates/backend/src/codegen.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,25 +94,45 @@ impl TryToTokens for ast::Program {
9494
shared::SCHEMA_VERSION,
9595
shared::version()
9696
);
97+
let encoded = encode::encode(self)?;
9798
let mut bytes = Vec::new();
9899
bytes.push((prefix_json.len() >> 0) as u8);
99100
bytes.push((prefix_json.len() >> 8) as u8);
100101
bytes.push((prefix_json.len() >> 16) as u8);
101102
bytes.push((prefix_json.len() >> 24) as u8);
102103
bytes.extend_from_slice(prefix_json.as_bytes());
103-
bytes.extend_from_slice(&encode::encode(self)?);
104+
bytes.extend_from_slice(&encoded.custom_section);
104105

105106
let generated_static_length = bytes.len();
106107
let generated_static_value = syn::LitByteStr::new(&bytes, Span::call_site());
107108

109+
// We already consumed the contents of included files when generating
110+
// the custom section, but we want to make sure that updates to the
111+
// generated files will cause this macro to rerun incrementally. To do
112+
// that we use `include_str!` to force rustc to think it has a
113+
// dependency on these files. That way when the file changes Cargo will
114+
// automatically rerun rustc which will rerun this macro. Other than
115+
// this we don't actually need the results of the `include_str!`, so
116+
// it's just shoved into an anonymous static.
117+
let file_dependencies = encoded.included_files
118+
.iter()
119+
.map(|file| {
120+
let file = file.to_str().unwrap();
121+
quote! { include_str!(#file) }
122+
});
123+
108124
(quote! {
109125
#[allow(non_upper_case_globals)]
110126
#[cfg(target_arch = "wasm32")]
111127
#[link_section = "__wasm_bindgen_unstable"]
112128
#[doc(hidden)]
113129
#[allow(clippy::all)]
114-
pub static #generated_static_name: [u8; #generated_static_length] =
115-
*#generated_static_value;
130+
pub static #generated_static_name: [u8; #generated_static_length] = {
131+
static _INCLUDED_FILES: &[&str] = &[#(#file_dependencies),*];
132+
133+
*#generated_static_value
134+
};
135+
116136
})
117137
.to_tokens(tokens);
118138

crates/backend/src/encode.rs

Lines changed: 100 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,96 @@
1+
use proc_macro2::{Ident, Span};
12
use std::cell::RefCell;
23
use std::collections::HashMap;
3-
4-
use proc_macro2::{Ident, Span};
4+
use std::env;
5+
use std::fs;
6+
use std::path::PathBuf;
7+
use util::ShortHash;
58

69
use ast;
710
use Diagnostic;
811

9-
pub fn encode(program: &ast::Program) -> Result<Vec<u8>, Diagnostic> {
12+
pub struct EncodeResult {
13+
pub custom_section: Vec<u8>,
14+
pub included_files: Vec<PathBuf>,
15+
}
16+
17+
pub fn encode(program: &ast::Program) -> Result<EncodeResult, Diagnostic> {
1018
let mut e = Encoder::new();
1119
let i = Interner::new();
1220
shared_program(program, &i)?.encode(&mut e);
13-
Ok(e.finish())
21+
let custom_section = e.finish();
22+
let included_files = i.files.borrow().values().map(|p| &p.path).cloned().collect();
23+
Ok(EncodeResult { custom_section, included_files })
1424
}
1525

1626
struct Interner {
17-
map: RefCell<HashMap<Ident, String>>,
27+
bump: bumpalo::Bump,
28+
files: RefCell<HashMap<String, LocalFile>>,
29+
root: PathBuf,
30+
crate_name: String,
31+
}
32+
33+
struct LocalFile {
34+
path: PathBuf,
35+
definition: Span,
36+
new_identifier: String,
1837
}
1938

2039
impl Interner {
2140
fn new() -> Interner {
2241
Interner {
23-
map: RefCell::new(HashMap::new()),
42+
bump: bumpalo::Bump::new(),
43+
files: RefCell::new(HashMap::new()),
44+
root: env::var_os("CARGO_MANIFEST_DIR").unwrap().into(),
45+
crate_name: env::var("CARGO_PKG_NAME").unwrap(),
2446
}
2547
}
2648

2749
fn intern(&self, s: &Ident) -> &str {
28-
let mut map = self.map.borrow_mut();
29-
if let Some(s) = map.get(s) {
30-
return unsafe { &*(&**s as *const str) };
31-
}
32-
map.insert(s.clone(), s.to_string());
33-
unsafe { &*(&*map[s] as *const str) }
50+
self.intern_str(&s.to_string())
3451
}
3552

3653
fn intern_str(&self, s: &str) -> &str {
37-
self.intern(&Ident::new(s, Span::call_site()))
54+
// NB: eventually this could be used to intern `s` to only allocate one
55+
// copy, but for now let's just "transmute" `s` to have the same
56+
// lifetmie as this struct itself (which is our main goal here)
57+
bumpalo::collections::String::from_str_in(s, &self.bump).into_bump_str()
58+
}
59+
60+
/// Given an import to a local module `id` this generates a unique module id
61+
/// to assign to the contents of `id`.
62+
///
63+
/// Note that repeated invocations of this function will be memoized, so the
64+
/// same `id` will always return the same resulting unique `id`.
65+
fn resolve_import_module(&self, id: &str, span: Span) -> Result<&str, Diagnostic> {
66+
let mut files = self.files.borrow_mut();
67+
if let Some(file) = files.get(id) {
68+
return Ok(self.intern_str(&file.new_identifier))
69+
}
70+
let path = if id.starts_with("/") {
71+
self.root.join(&id[1..])
72+
} else if id.starts_with("./") || id.starts_with("../") {
73+
let msg = "relative module paths aren't supported yet";
74+
return Err(Diagnostic::span_error(span, msg))
75+
} else {
76+
return Ok(self.intern_str(&id))
77+
};
78+
79+
// Generate a unique ID which is somewhat readable as well, so mix in
80+
// the crate name, hash to make it unique, and then the original path.
81+
let new_identifier = format!("{}{}", self.unique_crate_identifier(), id);
82+
let file = LocalFile {
83+
path,
84+
definition: span,
85+
new_identifier,
86+
};
87+
files.insert(id.to_string(), file);
88+
drop(files);
89+
self.resolve_import_module(id, span)
90+
}
91+
92+
fn unique_crate_identifier(&self) -> String {
93+
format!("{}-{}", self.crate_name, ShortHash(0))
3894
}
3995
}
4096

@@ -64,8 +120,30 @@ fn shared_program<'a>(
64120
.iter()
65121
.map(|x| -> &'a str { &x })
66122
.collect(),
67-
// version: shared::version(),
68-
// schema_version: shared::SCHEMA_VERSION.to_string(),
123+
local_modules: intern
124+
.files
125+
.borrow()
126+
.values()
127+
.map(|file| {
128+
fs::read_to_string(&file.path)
129+
.map(|s| {
130+
LocalModule {
131+
identifier: intern.intern_str(&file.new_identifier),
132+
contents: intern.intern_str(&s),
133+
}
134+
})
135+
.map_err(|e| {
136+
let msg = format!("failed to read file `{}`: {}", file.path.display(), e);
137+
Diagnostic::span_error(file.definition, msg)
138+
})
139+
})
140+
.collect::<Result<Vec<_>, _>>()?,
141+
inline_js: prog
142+
.inline_js
143+
.iter()
144+
.map(|js| intern.intern_str(js))
145+
.collect(),
146+
unique_crate_identifier: intern.intern_str(&intern.unique_crate_identifier()),
69147
})
70148
}
71149

@@ -111,7 +189,13 @@ fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant<
111189

112190
fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result<Import<'a>, Diagnostic> {
113191
Ok(Import {
114-
module: i.module.as_ref().map(|s| &**s),
192+
module: match &i.module {
193+
ast::ImportModule::Named(m, span) => {
194+
ImportModule::Named(intern.resolve_import_module(m, *span)?)
195+
}
196+
ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32),
197+
ast::ImportModule::None => ImportModule::None,
198+
},
115199
js_namespace: i.js_namespace.as_ref().map(|s| intern.intern(s)),
116200
kind: shared_import_kind(&i.kind, intern)?,
117201
})

crates/backend/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#![cfg_attr(feature = "extra-traits", deny(missing_debug_implementations))]
33
#![doc(html_root_url = "https://docs.rs/wasm-bindgen-backend/0.2")]
44

5+
extern crate bumpalo;
56
#[macro_use]
67
extern crate log;
78
extern crate proc_macro2;

crates/backend/src/util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ pub fn ident_ty(ident: Ident) -> syn::Type {
9494

9595
pub fn wrap_import_function(function: ast::ImportFunction) -> ast::Import {
9696
ast::Import {
97-
module: None,
97+
module: ast::ImportModule::None,
9898
js_namespace: None,
9999
kind: ast::ImportKind::Function(function),
100100
}

0 commit comments

Comments
 (0)