mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-26 03:24:13 +03:00
Implement a polyfill
attribute for imports
Allow using imported APIs under alternative names, such as prefixed names, for web APIs when the exact API differs across browsers.
This commit is contained in:
parent
11bcaf42d5
commit
3c14f7a6eb
@ -144,6 +144,7 @@ pub struct ImportType {
|
||||
pub doc_comment: Option<String>,
|
||||
pub instanceof_shim: String,
|
||||
pub extends: Vec<Ident>,
|
||||
pub polyfills: Vec<Ident>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
||||
@ -478,6 +479,7 @@ impl ImportType {
|
||||
shared::ImportType {
|
||||
name: self.js_name.clone(),
|
||||
instanceof_shim: self.instanceof_shim.clone(),
|
||||
polyfills: self.polyfills.iter().map(|s| s.to_string()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ mod closures;
|
||||
pub struct Context<'a> {
|
||||
pub globals: String,
|
||||
pub imports: String,
|
||||
pub imports_post: String,
|
||||
pub footer: String,
|
||||
pub typescript: String,
|
||||
pub exposed_globals: HashSet<&'static str>,
|
||||
@ -68,6 +69,7 @@ struct ClassField {
|
||||
pub struct SubContext<'a, 'b: 'a> {
|
||||
pub program: &'a shared::Program,
|
||||
pub cx: &'a mut Context<'b>,
|
||||
pub polyfills: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
const INITIAL_SLAB_VALUES: &[&str] = &["undefined", "null", "true", "false"];
|
||||
@ -469,12 +471,14 @@ impl<'a> Context<'a> {
|
||||
/* tslint:disable */\n\
|
||||
{import_wasm}\n\
|
||||
{imports}\n\
|
||||
{imports_post}\n\
|
||||
|
||||
{globals}\n\
|
||||
{footer}",
|
||||
import_wasm = import_wasm,
|
||||
globals = self.globals,
|
||||
imports = self.imports,
|
||||
imports_post = self.imports_post,
|
||||
footer = self.footer,
|
||||
)
|
||||
};
|
||||
@ -1765,6 +1769,11 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
)
|
||||
})?;
|
||||
}
|
||||
for f in self.program.imports.iter() {
|
||||
if let shared::ImportKind::Type(ty) = &f.kind {
|
||||
self.register_polyfills(ty);
|
||||
}
|
||||
}
|
||||
for f in self.program.imports.iter() {
|
||||
self.generate_import(f)?;
|
||||
}
|
||||
@ -2140,6 +2149,19 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
self.cx.typescript.push_str("}\n");
|
||||
}
|
||||
|
||||
fn register_polyfills(
|
||||
&mut self,
|
||||
info: &shared::ImportType,
|
||||
) {
|
||||
if info.polyfills.len() == 0 {
|
||||
return
|
||||
}
|
||||
self.polyfills
|
||||
.entry(info.name.to_string())
|
||||
.or_insert(Vec::new())
|
||||
.extend(info.polyfills.iter().cloned());
|
||||
}
|
||||
|
||||
fn import_name(&mut self, import: &shared::Import, item: &str) -> Result<String, Error> {
|
||||
// First up, imports don't work at all in `--no-modules` mode as we're
|
||||
// not sure how to import them.
|
||||
@ -2153,6 +2175,30 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
// Similar to `--no-modules`, only allow polyfills basically for web
|
||||
// apis, shouldn't be necessary for things like npm packages or other
|
||||
// imported items.
|
||||
let polyfills = self.polyfills.get(item);
|
||||
if let Some(polyfills) = polyfills {
|
||||
assert!(polyfills.len() > 0);
|
||||
|
||||
if let Some(module) = &import.module {
|
||||
bail!(
|
||||
"import of `{}` from `{}` has a polyfill of `{}` listed, but
|
||||
polyfills aren't supported when importing from modules",
|
||||
item,
|
||||
module,
|
||||
&polyfills[0],
|
||||
);
|
||||
}
|
||||
if let Some(ns) = &import.js_namespace {
|
||||
bail!("import of `{}` through js namespace `{}` isn't supported \
|
||||
right now when it lists a polyfill",
|
||||
item,
|
||||
ns);
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out what identifier we're importing from the module. If we've
|
||||
// got a namespace we use that, otherwise it's the name specified above.
|
||||
let name_to_import = import.js_namespace.as_ref().map(|s| &**s).unwrap_or(item);
|
||||
@ -2171,6 +2217,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
let use_node_require = self.cx.use_node_require();
|
||||
let imported_identifiers = &mut self.cx.imported_identifiers;
|
||||
let imports = &mut self.cx.imports;
|
||||
let imports_post = &mut self.cx.imports_post;
|
||||
let identifier = self
|
||||
.cx
|
||||
.imported_names
|
||||
@ -2193,8 +2240,33 @@ impl<'a, 'b> SubContext<'a, 'b> {
|
||||
name_to_import, name, module
|
||||
));
|
||||
}
|
||||
name
|
||||
} else if let Some(polyfills) = polyfills {
|
||||
imports_post.push_str("const l");
|
||||
imports_post.push_str(&name);
|
||||
imports_post.push_str(" = ");
|
||||
switch(imports_post, &name, polyfills);
|
||||
imports_post.push_str(";\n");
|
||||
|
||||
fn switch(dst: &mut String, name: &str, left: &[String]) {
|
||||
if left.len() == 0 {
|
||||
return dst.push_str(name);
|
||||
}
|
||||
dst.push_str("(typeof ");
|
||||
dst.push_str(name);
|
||||
dst.push_str(" == 'undefined' ? ");
|
||||
match left.len() {
|
||||
1 => dst.push_str(&left[0]),
|
||||
_ => switch(dst, &left[0], &left[1..]),
|
||||
}
|
||||
dst.push_str(" : ");
|
||||
dst.push_str(name);
|
||||
dst.push_str(")");
|
||||
}
|
||||
format!("l{}", name)
|
||||
} else {
|
||||
name
|
||||
}
|
||||
name
|
||||
});
|
||||
|
||||
// If there's a namespace we didn't actually import `item` but rather
|
||||
|
@ -189,6 +189,7 @@ impl Bindgen {
|
||||
let mut cx = js::Context {
|
||||
globals: String::new(),
|
||||
imports: String::new(),
|
||||
imports_post: String::new(),
|
||||
footer: String::new(),
|
||||
typescript: format!("/* tslint:disable */\n"),
|
||||
exposed_globals: Default::default(),
|
||||
@ -208,6 +209,7 @@ impl Bindgen {
|
||||
js::SubContext {
|
||||
program,
|
||||
cx: &mut cx,
|
||||
polyfills: Default::default(),
|
||||
}.generate()?;
|
||||
}
|
||||
cx.finalize(stem)?
|
||||
|
@ -185,6 +185,13 @@ impl BindgenAttrs {
|
||||
})
|
||||
}
|
||||
|
||||
fn polyfills(&self) -> impl Iterator<Item = &Ident> {
|
||||
self.attrs.iter().filter_map(|a| match a {
|
||||
BindgenAttr::Polyfill(s) => Some(s),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the variadic attributes is present
|
||||
fn variadic(&self) -> bool {
|
||||
self.attrs.iter().any(|a| match *a {
|
||||
@ -226,6 +233,7 @@ pub enum BindgenAttr {
|
||||
JsName(String, Span),
|
||||
JsClass(String),
|
||||
Extends(Ident),
|
||||
Polyfill(Ident),
|
||||
Variadic,
|
||||
}
|
||||
|
||||
@ -286,6 +294,10 @@ impl Parse for BindgenAttr {
|
||||
input.parse::<Token![=]>()?;
|
||||
return Ok(BindgenAttr::Extends(input.parse::<AnyIdent>()?.0));
|
||||
}
|
||||
if attr == "polyfill" {
|
||||
input.parse::<Token![=]>()?;
|
||||
return Ok(BindgenAttr::Polyfill(input.parse::<AnyIdent>()?.0));
|
||||
}
|
||||
if attr == "module" {
|
||||
input.parse::<Token![=]>()?;
|
||||
return Ok(BindgenAttr::Module(input.parse::<syn::LitStr>()?.value()));
|
||||
@ -556,6 +568,7 @@ impl ConvertToAst<BindgenAttrs> for syn::ForeignItemType {
|
||||
rust_name: self.ident,
|
||||
js_name,
|
||||
extends: attrs.extends().cloned().collect(),
|
||||
polyfills: attrs.polyfills().cloned().collect(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +87,7 @@ pub struct ImportStatic {
|
||||
pub struct ImportType {
|
||||
pub name: String,
|
||||
pub instanceof_shim: String,
|
||||
pub polyfills: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
|
@ -503,7 +503,16 @@ impl<'src> FirstPassRecord<'src> {
|
||||
doc_comment: None,
|
||||
instanceof_shim: format!("__widl_instanceof_{}", name),
|
||||
extends: Vec::new(),
|
||||
polyfills: Vec::new(),
|
||||
};
|
||||
|
||||
// whitelist a few names that have known polyfills
|
||||
match name {
|
||||
"AudioContext" => {
|
||||
import_type.polyfills.push(Ident::new("webkitAudioContext", Span::call_site()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let extra = camel_case_ident(name);
|
||||
let extra = &[&extra[..]];
|
||||
self.append_required_features_doc(&import_type, &mut doc_comment, extra);
|
||||
|
@ -66,7 +66,8 @@
|
||||
- [`module = "blah"`](./reference/attributes/on-js-imports/module.md)
|
||||
- [`static_method_of = Blah`](./reference/attributes/on-js-imports/static_method_of.md)
|
||||
- [`structural`](./reference/attributes/on-js-imports/structural.md)
|
||||
- [variadic](./reference/attributes/on-js-imports/variadic.md)
|
||||
- [`variadic`](./reference/attributes/on-js-imports/variadic.md)
|
||||
- [`polyfill`](./reference/attributes/on-js-imports/polyfill.md)
|
||||
- [On Rust Exports](./reference/attributes/on-rust-exports/index.md)
|
||||
- [`constructor`](./reference/attributes/on-rust-exports/constructor.md)
|
||||
- [`js_name = Blah`](./reference/attributes/on-rust-exports/js_name.md)
|
||||
|
24
guide/src/reference/attributes/on-js-imports/polyfill.md
Normal file
24
guide/src/reference/attributes/on-js-imports/polyfill.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Polyfilling APIs
|
||||
|
||||
In JS new APIs often have polyfills via different names in various contexts. For
|
||||
example the `AudioContext` API is known as `webkitAudioContext` in Safari at the
|
||||
time of this writing. The `polyfill` attribute indicates these alternative
|
||||
names.
|
||||
|
||||
For example to use `AudioContext` you might do:
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
#[wasm_bindgen(polyfill = webkitAudioContext)]
|
||||
type AudioContext;
|
||||
|
||||
// methods on `AudioContext` ...
|
||||
}
|
||||
```
|
||||
|
||||
Whenever `AudioContext` is used it'll use `AudioContext` if the global namespace
|
||||
defines it or alternatively it'll fall back to `webkitAudioContext`.
|
||||
|
||||
Note that `polyfill` cannot be used with `module = "..."` or `js_namespace =
|
||||
...`, so it's basically limited to web-platform APIs today.
|
@ -26,6 +26,7 @@ pub mod math;
|
||||
pub mod node;
|
||||
pub mod option;
|
||||
pub mod optional_primitives;
|
||||
pub mod polyfill;
|
||||
pub mod rethrow;
|
||||
pub mod simple;
|
||||
pub mod slice;
|
||||
|
5
tests/wasm/polyfill.js
Normal file
5
tests/wasm/polyfill.js
Normal file
@ -0,0 +1,5 @@
|
||||
exports.import_me = function() {};
|
||||
|
||||
global.PolyfillBar = class {
|
||||
foo() { return 123; }
|
||||
};
|
40
tests/wasm/polyfill.rs
Normal file
40
tests/wasm/polyfill.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen(module = "tests/wasm/polyfill.js")]
|
||||
extern "C" {
|
||||
fn import_me();
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(polyfill = PolyfillBar)]
|
||||
type PolyfillFoo;
|
||||
#[wasm_bindgen(constructor)]
|
||||
fn new() -> PolyfillFoo;
|
||||
#[wasm_bindgen(method)]
|
||||
fn foo(this: &PolyfillFoo) -> u32;
|
||||
|
||||
#[wasm_bindgen(polyfill = PolyfillBaz1, polyfill = PolyfillBar)]
|
||||
type PolyfillFoo2;
|
||||
#[wasm_bindgen(constructor)]
|
||||
fn new() -> PolyfillFoo2;
|
||||
#[wasm_bindgen(method)]
|
||||
fn foo(this: &PolyfillFoo2) -> u32;
|
||||
|
||||
#[wasm_bindgen(polyfill = PolyfillBaz1, polyfill = PolyfillBar, polyfill = PolyfillBaz2)]
|
||||
type PolyfillFoo3;
|
||||
#[wasm_bindgen(constructor)]
|
||||
fn new() -> PolyfillFoo3;
|
||||
#[wasm_bindgen(method)]
|
||||
fn foo(this: &PolyfillFoo3) -> u32;
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
pub fn polyfill_works() {
|
||||
import_me();
|
||||
|
||||
assert_eq!(PolyfillFoo::new().foo(), 123);
|
||||
assert_eq!(PolyfillFoo2::new().foo(), 123);
|
||||
assert_eq!(PolyfillFoo3::new().foo(), 123);
|
||||
}
|
Loading…
Reference in New Issue
Block a user