├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── embed_js ├── Cargo.toml └── src │ └── lib.rs ├── embed_js_build ├── Cargo.toml └── src │ └── lib.rs ├── embed_js_common ├── Cargo.toml └── src │ └── lib.rs ├── embed_js_derive ├── Cargo.toml └── src │ └── lib.rs └── examples ├── callbacks ├── Cargo.toml ├── Makefile.toml ├── build.rs ├── post_build.rs └── src │ └── lib.rs ├── simple ├── Cargo.toml ├── Makefile.toml ├── README.md ├── post_build.rs ├── src │ └── lib.rs └── subcrate │ ├── Cargo.toml │ ├── build.rs │ └── src │ └── lib.rs └── strings ├── Cargo.toml ├── Makefile.toml ├── build.rs ├── post_build.rs └── src └── lib.rs /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dylan Ede 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # embed_js 2 | 3 | Minimalist lightweight inline JavaScript for Rust applications targeting WebAssembly via the `wasm32-unknown-unknown` target. 4 | 5 | This project provides a low level interface designed for other crates to build higher level, 6 | more ergonomic APIs on top of. 7 | 8 | ## Getting started 9 | 10 | Try out the examples (in the examples subdirectory) to get a feel of how the pieces fit together. 11 | 12 | To build wasm applications, make sure you have both a recent Rust nightly and the 13 | wasm32-unknown-unknown target installed. 14 | 15 | #### Setting up the examples 16 | 17 | The examples require [cargo-make](https://github.com/sagiegurari/cargo-make), 18 | [cargo-script](https://github.com/DanielKeep/cargo-script) and [wasm-gc](https://github.com/alexcrichton/wasm-gc) to be installed. 19 | 20 | In an example's directory, make sure you have rustup set up to build with nightly Rust. 21 | 22 | Build the example using `cargo make`, which ensures that the post-build script is run. 23 | The resulting self-contained HTML file should be in 24 | "target/wasm32-unknown-unknown/release/" and can be ran in a browser (one that supports WebAssembly). 25 | 26 | Depending on the example you may need to check the console log in the browser (F12) to see its output. 27 | 28 | ## General usage 29 | 30 | There are two crates to use. `embed_js` is for crates using the `js` macro to embed JavaScript. 31 | `embed_js_build` should be used by those crates as a pre-processing stage in their build scripts. `embed_js_build` should 32 | also be used by application crates that build wasm binaries in their *post*-build scripts in order 33 | to gather the generated accompanying JavaScript to import when loading the wasm module. 34 | 35 | See the documentation of both crates for more detailed usage information, or check out the examples in this 36 | repository. 37 | 38 | ### [embed_js Documentation](https://docs.rs/embed_js) 39 | ### [embed_js_build Documentation](https://docs.rs/embed_js) 40 | 41 | ```toml 42 | [dependencies] 43 | embed_js = "^0.1.4" 44 | ``` 45 | 46 | ```toml 47 | [build-dependencies] 48 | embed_js_build = "^0.1.4" 49 | ``` 50 | 51 | ## Limitations 52 | 53 | Currently the `js` macro cannot be used inside other macros, with the exception of the `include!` macro. 54 | This is potentially fixable in the future. 55 | 56 | ## License 57 | 58 | Licensed under either of 59 | 60 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 61 | http://www.apache.org/licenses/LICENSE-2.0) 62 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 63 | http://opensource.org/licenses/MIT) 64 | 65 | at your option. 66 | 67 | ### Contribution 68 | 69 | Unless you explicitly state otherwise, any contribution intentionally submitted 70 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 71 | dual licensed as above, without any additional terms or conditions. 72 | -------------------------------------------------------------------------------- /embed_js/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embed_js" 3 | version = "0.1.4" 4 | authors = ["Dylan Ede "] 5 | description = """ 6 | Minimalist lightweight inline JavaScript for Rust applications targeting WebAssembly via the `wasm32-unknown-unknown` target. 7 | 8 | See also embed_js_build, the accompanying crate for use by build scripts. 9 | """ 10 | documentation = "https://docs.rs/embed_js" 11 | homepage = "https://github.com/dylanede/embed_js" 12 | repository = "https://github.com/dylanede/embed_js" 13 | readme = "../README.md" 14 | license = "MIT / Apache-2.0" 15 | keywords = ["javascript", "inline", "wasm", "js", "embed_js"] 16 | 17 | [dependencies] 18 | embed_js_derive = "^0.1.2" -------------------------------------------------------------------------------- /embed_js/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate allows you to embed inline javascript in your Rust code for use with the 2 | //! wasm32-unknown-unknown target. Crates also need to use the `embed_js_build` crate in their 3 | //! build script (see that crate's documentation for more details). Crates that generate binaries 4 | //! must also use `embed_js_build` in a post-build step to collect the generated accompanying 5 | //! javascript from itself and all dependencies. 6 | //! 7 | //! See the documentation pages of the macros in this crate for more details and examples. The 8 | //! embed_js repository also contains example projects. 9 | 10 | #[allow(unused_imports)] 11 | #[macro_use] 12 | extern crate embed_js_derive; 13 | 14 | #[doc(hidden)] 15 | pub use embed_js_derive::*; 16 | 17 | /// For every crate that uses the `js` macro directly, the `embed_js_preamble` macro must be called 18 | /// at least once somewhere in the crate, for example 19 | /// 20 | /// ```ignore 21 | /// #[macro_use] 22 | /// extern crate embed_js; 23 | /// 24 | /// embed_js_preamble!(); 25 | /// 26 | /// #[no_mangle] 27 | /// pub fn entry_point() { 28 | /// js!({console.log("Hello world!");}); 29 | /// } 30 | /// ``` 31 | /// 32 | /// You do not need to import `embed_js` at all in crates that only depend on crates that use the 33 | /// `js` macro without directly calling it themselves. 34 | #[macro_export] 35 | macro_rules! embed_js_preamble { 36 | () => { include!(concat!(env!("OUT_DIR"), "/embed_js_preamble.rs")); } 37 | } 38 | 39 | /// Call javascript, inline. 40 | /// 41 | /// This macro must not be called from within any other macro, otherwise it will fail to work. 42 | /// 43 | /// The javascript written inside calls to this macro must adhere to some additional rules: 44 | /// 45 | /// * Every statement must end in a semi-colon. 46 | /// * No single-quote multi-character strings are allowed. 47 | /// 48 | /// There are three forms for calling this macro: 49 | /// 50 | /// * `js!([arg1 as type1, arg2 as type2, &arg3, *arg3 as type3, &mut **arg3, ...] -> ret_type { /*javascript*/ })` 51 | /// 52 | /// In this form, you specify arguments and a return type. There are two categories of argument. 53 | /// 54 | /// Arguments preceded by a `&` are references. These arguments may be any number of reference 55 | /// operations (both immutable and mutable) on an identifier that has been dereferenced via `*` 56 | /// some number of times (including zero). These arguments are passed to the JavaScript as pointers, 57 | /// with type wasm type `i32`. These pointers can then be looked up in the wasm module memory buffer 58 | /// to access the contents of the value referenced. 59 | /// 60 | /// Other arguments take the form of a possibly dereferenced identifier followed by `as type`, 61 | /// for some type `type`. Values are cast using `as` to this type before passing to the JavaScript. 62 | /// 63 | /// Every type specified, including the return type, must be one of `i32`, `i64`, 64 | /// `f32` or `f64`. These are the only raw types supported by WebAssembly for interop at the 65 | /// moment. More complicated types are best passed by reference. 66 | /// 67 | /// Examples: 68 | /// 69 | /// ```ignore 70 | /// let x = 2; 71 | /// let y = js!([x as i32] -> i32 { 72 | /// return x + 1; 73 | /// }); 74 | /// // y is now 3 75 | /// ``` 76 | /// 77 | /// ```ignore 78 | /// let one_half = js!([] -> f64 { 79 | /// return 1.0 / 2.0; 80 | /// }); 81 | /// ``` 82 | /// 83 | /// * `js!([arg1 as type1, arg2 as type2, ...] { /*javascript*/ })` 84 | /// 85 | /// Like the previous form, but without a return type. Any value returned from javascript is 86 | /// discarded. 87 | /// 88 | /// * `js!({ /*javascript*/ })` 89 | /// 90 | /// No arguments or return type. 91 | #[macro_export] 92 | macro_rules! js { 93 | ([$($args:tt)*] $($tt:tt)*) => {{ 94 | #[derive(EmbedJsDetail)] 95 | #[allow(dead_code)] 96 | enum EmbedJsStruct { 97 | Input = (stringify!([$($args)*] $($tt)*), 0).1 98 | } 99 | EmbedJsStruct::call($($args)*) 100 | }}; 101 | ({$($tt:tt)*}) => {{ 102 | #[derive(EmbedJsDetail)] 103 | #[allow(dead_code)] 104 | enum EmbedJsStruct { 105 | Input = (stringify!({$($tt)*}), 0).1 106 | } 107 | EmbedJsStruct::call() 108 | }}; 109 | } 110 | 111 | /// Used to specify JavaScript that should be executed before the WebAssembly module is loaded. 112 | /// This is useful for specifying functions that can be shared between instances of inline JS, or 113 | /// set up other global state. This macro is used as a statement or at item level. 114 | /// 115 | /// The order of evaluation at runtime is the same as the order of `include_js` calls in the source, 116 | /// with all modules inlined. The order with respect to other crates is not specified. 117 | /// 118 | /// Example: 119 | /// 120 | /// ```ignore 121 | /// include_js! { 122 | /// window.my_global_function = function() { 123 | /// alert("Hello World!"); 124 | /// }; 125 | /// } 126 | /// ``` 127 | #[macro_export] 128 | macro_rules! include_js { 129 | ($($tt:tt)*) => {} 130 | } -------------------------------------------------------------------------------- /embed_js_build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embed_js_build" 3 | version = "0.1.4" 4 | authors = ["Dylan Ede "] 5 | description = """ 6 | Minimalist lightweight inline JavaScript for Rust applications targeting WebAssembly via the `wasm32-unknown-unknown` target. 7 | 8 | This is the crate for build scripts. For the js macro itself, see embed_js. 9 | """ 10 | documentation = "https://docs.rs/embed_js_build" 11 | homepage = "https://github.com/dylanede/embed_js" 12 | repository = "https://github.com/dylanede/embed_js" 13 | readme = "../README.md" 14 | license = "MIT / Apache-2.0" 15 | keywords = ["javascript", "inline", "wasm", "js", "embed_js"] 16 | 17 | [dependencies] 18 | embed_js_common = "^0.3" 19 | cpp_synmap = "^0.3.0" 20 | cpp_syn = { version = "^0.12.0", features = ["visit"] } 21 | serde_json = "^1.0" 22 | uuid = { version = "^0.5", features = ["v4"] } 23 | parity-wasm = "0.17.0" -------------------------------------------------------------------------------- /embed_js_build/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides functions to call from build and post-build scripts as part of 2 | //! wasm32-unknown-unknown builds that rely on crates using the `embed_js` crate to write inline 3 | //! javascript. 4 | //! 5 | //! See the `embed_js` repository for examples of how to use these crates together. 6 | 7 | extern crate embed_js_common; 8 | extern crate cpp_synmap; 9 | extern crate cpp_syn; 10 | extern crate serde_json; 11 | extern crate uuid; 12 | extern crate parity_wasm; 13 | 14 | use cpp_synmap::SourceMap; 15 | use cpp_syn::visit::Visitor; 16 | use cpp_syn::{Mac, TokenTree, Delimited}; 17 | 18 | use parity_wasm::elements::{Module, Section, ExportEntry, Internal}; 19 | 20 | use std::env; 21 | use std::path::{ PathBuf, Path }; 22 | use std::io::{ BufWriter, BufReader, Read }; 23 | use std::fs::File; 24 | use std::process::Command; 25 | use std::collections::hash_map::DefaultHasher; 26 | use std::hash::{Hash, Hasher}; 27 | use std::collections::HashMap; 28 | 29 | use embed_js_common::{ JsMac, JsMacArg }; 30 | 31 | struct JsVisitor<'a> { 32 | source_map: &'a mut SourceMap, 33 | instances: &'a mut Vec, 34 | included_js: &'a mut String 35 | } 36 | impl<'a> Visitor for JsVisitor<'a> { 37 | fn visit_mac(&mut self, mac: &Mac) { 38 | if mac.path.segments.len() != 1 { 39 | return; 40 | } 41 | let tts = match mac.tts[0] { 42 | TokenTree::Delimited(Delimited { ref tts, .. }, _) => &**tts, 43 | _ => return, 44 | }; 45 | match mac.path.segments[0].ident.as_ref() { 46 | "js" => { 47 | if let Ok(parsed) = embed_js_common::parse_js_mac_source_map(tts, self.source_map) { 48 | self.instances.push(parsed); 49 | } 50 | } 51 | "include_js" => { 52 | let js_source = if let (Some(first), Some(last)) = (tts.first(), tts.last()) { 53 | self.source_map.source_text(first.span().extend(last.span())).unwrap() 54 | } else { 55 | "" 56 | }; 57 | self.included_js.push_str(&js_source); 58 | self.included_js.push_str("\n"); 59 | } 60 | "include" => { 61 | use cpp_syn::{ Token, Lit, LitKind }; 62 | let mut iter = tts.iter().peekable(); 63 | match iter.next() { 64 | Some(&TokenTree::Token(Token::Literal(Lit { node: LitKind::Str(ref path, _), .. }), span)) => { 65 | if iter.next().is_some() { 66 | return; 67 | } 68 | let mut path = PathBuf::from(path); 69 | if !path.is_absolute() { 70 | let root = self.source_map.filename(span).unwrap(); 71 | path = root.join(path); 72 | } 73 | println!("cargo:warning=embed_js_build processing source in included file {}", path.display()); 74 | let krate = self.source_map.add_crate_root(path).unwrap(); 75 | self.visit_crate(&krate); 76 | } 77 | Some(&TokenTree::Token(Token::Ident(ref ident), span)) if ident.as_ref() == "concat" => { 78 | match iter.next() { 79 | Some(&TokenTree::Token(Token::Not, _)) => {} 80 | _ => return 81 | } 82 | let tts = match iter.next() { 83 | Some(&TokenTree::Delimited(Delimited { ref tts, .. }, _)) => { 84 | tts 85 | } 86 | _ => return 87 | }; 88 | let mut path = String::new(); 89 | let mut iter = tts.iter().peekable(); 90 | while let Some(t) = iter.next() { 91 | match *t { 92 | TokenTree::Token(Token::Literal(Lit { node: LitKind::Str(ref s, _), .. }), _) => { 93 | path.push_str(s); 94 | } 95 | TokenTree::Token(Token::Comma, _) => {} 96 | TokenTree::Token(Token::Ident(ref ident), _) if ident.as_ref() == "env" => { 97 | match iter.next() { 98 | Some(&TokenTree::Token(Token::Not, _)) => {} 99 | _ => return 100 | } 101 | let tts = match iter.next() { 102 | Some(&TokenTree::Delimited(Delimited { ref tts, .. }, _)) => { 103 | tts 104 | } 105 | _ => return 106 | }; 107 | if let Some(&TokenTree::Token(Token::Literal(Lit { node: LitKind::Str(ref s, _), .. }), _)) = tts.first() { 108 | if tts.len() != 1 { 109 | return 110 | } 111 | if let Ok(v) = std::env::var(s) { 112 | path.push_str(&v); 113 | } else { 114 | return 115 | } 116 | } else { 117 | return 118 | } 119 | } 120 | _ => return 121 | } 122 | } 123 | let mut path = PathBuf::from(path); 124 | if !path.is_absolute() { 125 | let root = self.source_map.filename(span).unwrap(); 126 | path = root.join(path); 127 | } 128 | println!("cargo:warning=embed_js_build processing source in included file {}", path.display()); 129 | let krate = self.source_map.add_crate_root(path).unwrap(); 130 | self.visit_crate(&krate); 131 | } 132 | _ => return 133 | } 134 | } 135 | _ => {} 136 | } 137 | } 138 | } 139 | 140 | /// Call this once from a build script for a crate that uses `embed_js` directly. 141 | /// 142 | /// Parameters: 143 | /// 144 | /// * `lib_root` The path to the crate root rust file, e.g. "src/lib.rs" 145 | /// 146 | /// Example: 147 | /// 148 | /// ```ignore 149 | /// extern crate embed_js_build; 150 | /// fn main() { 151 | /// use std::path::PathBuf; 152 | /// let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("src/lib.rs"); 153 | /// embed_js_build::preprocess_crate(&root); 154 | /// } 155 | /// ``` 156 | pub fn preprocess_crate(lib_root: &Path) { 157 | let mut source_map = SourceMap::new(); 158 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 159 | let mut instances = Vec::new(); 160 | let mut included_js = String::new(); 161 | let krate = source_map.add_crate_root(lib_root).unwrap(); 162 | JsVisitor { 163 | source_map: &mut source_map, 164 | instances: &mut instances, 165 | included_js: &mut included_js 166 | }.visit_crate(&krate); 167 | 168 | let js_path = out_dir.join("embed_js_data.json"); 169 | serde_json::to_writer(BufWriter::new(File::create(&js_path).unwrap()), &(instances, included_js)).unwrap(); 170 | let preamble_path = out_dir.join("embed_js_preamble.rs"); 171 | File::create(preamble_path).unwrap(); 172 | } 173 | 174 | /// Generated from `postprocess_crate`. 175 | pub struct PostProcessData { 176 | /// The path to the generated wasm binary. 177 | pub wasm_path: PathBuf, 178 | /// The contents of the wasm binary, provided for convenience. 179 | pub wasm: Vec, 180 | /// The javascript that should be put as the value of the `env` field in the `importObject` 181 | /// passed to `WebAssembly.instantiate`. 182 | pub imports: String, 183 | /// All javascript specified by the `include_js` macro in linked crates. This should be run 184 | /// before the WebAssembly module is loaded. 185 | pub included: String 186 | } 187 | /// Call this once **after** a wasm-unknown-unknown build has completed (i.e. from a post-build 188 | /// script) in order to generate the javascript imports that should accompany the wasm binary. 189 | /// 190 | /// See the `embed_js` repository for example projects using this function. 191 | /// 192 | /// Parameters: 193 | /// 194 | /// * `lib_name` The binary name to process, typically the name of the crate unless set otherwise 195 | /// in `Cargo.toml`. 196 | /// * `debug` Whether to look for the debug or release binary to process. Until wasm32-unkown-unknown 197 | /// supports debug builds, this should always be set to `false`. 198 | /// 199 | /// Example post-build script, taken from the "simple" example in the `embed_js` repository: 200 | /// 201 | /// ```ignore 202 | /// extern crate base64; 203 | /// extern crate embed_js_build; 204 | /// 205 | /// use std::fs::File; 206 | /// use std::io::Write; 207 | /// 208 | /// fn main() { 209 | /// let pp_data = embed_js_build::postprocess_crate("simple", false).unwrap(); 210 | /// let in_base_64 = base64::encode(&pp_data.wasm); 211 | /// let html_path = pp_data.wasm_path.with_extension("html"); 212 | /// let mut html_file = File::create(&html_path).unwrap(); 213 | /// write!(html_file, r#" 214 | /// 215 | /// 216 | /// 217 | /// wasm test 218 | /// 238 | /// 239 | /// 240 | /// "#, 241 | /// in_base_64, 242 | /// pp_data.imports 243 | /// ).unwrap(); 244 | /// } 245 | pub fn postprocess_crate(lib_name: &str, debug: bool) -> std::io::Result { 246 | let metadata_json = Command::new("cargo").args(&["metadata", "--format-version", "1"]).output().unwrap().stdout; 247 | let metadata_json: serde_json::Value = serde_json::from_slice(&metadata_json).unwrap(); 248 | let target_directory = Path::new(metadata_json.as_object().unwrap().get("target_directory").unwrap().as_str().unwrap()); 249 | let bin_prefix = target_directory.join(&format!("wasm32-unknown-unknown/{}/{}", if debug { "debug" } else { "release" }, lib_name)); 250 | 251 | // collect json data from all dependency crates 252 | let d_path = bin_prefix.with_extension("d"); 253 | let mut d_string = String::new(); 254 | File::open(&d_path)?.read_to_string(&mut d_string).unwrap(); 255 | let mut d_pieces: Vec = d_string.split_whitespace().map(String::from).collect::>(); 256 | { // stick escaped spaces back together 257 | let mut i = 0; 258 | while i < d_pieces.len() { 259 | while d_pieces[i].ends_with("\\") && i != d_pieces.len() - 1 { 260 | let removed = d_pieces.remove(i+1); 261 | d_pieces[i].push_str(&removed); 262 | } 263 | i += 1; 264 | } 265 | } 266 | d_pieces.remove(0); // remove lib path 267 | let mut js_macs: HashMap = HashMap::new(); 268 | let mut included_js = String::new(); 269 | for path in d_pieces { 270 | if path.ends_with("out/embed_js_preamble.rs") || path.ends_with("out\\embed_js_preamble.rs") { 271 | let data_path = PathBuf::from(path).with_file_name("embed_js_data.json"); 272 | let (mut crate_js_macs, crate_included_js): (Vec, String) = serde_json::from_reader(BufReader::new(File::open(data_path)?)).unwrap(); 273 | included_js.push_str(&crate_included_js); 274 | for js_mac in crate_js_macs.drain(..) { 275 | let mut hasher = DefaultHasher::new(); 276 | js_mac.hash(&mut hasher); 277 | let mac_hash = hasher.finish(); 278 | let key = format!("__embed_js__{:x}", mac_hash); 279 | if let Some(existing) = js_macs.get(&key) { 280 | if *existing != js_mac { 281 | panic!("A hash collision has occurred in the embed_js build process. Please raise a bug! Meanwhile, try making small changes to your embedded js to remove the collision.") 282 | } 283 | } 284 | js_macs.insert(key, js_mac); 285 | } 286 | } 287 | } 288 | 289 | let wasm_path = bin_prefix.with_extension("wasm"); 290 | match Command::new("wasm-gc").args(&[&wasm_path, &wasm_path]).output() { 291 | Ok(output) => { 292 | if !output.status.success() { 293 | panic!("wasm-gc encountered an error.\n\nstatus: {}\n\nstdout:\n\n{}\n\nstderr:\n\n{}", 294 | output.status, 295 | String::from_utf8(output.stdout).unwrap_or_else(|_| String::from("")), 296 | String::from_utf8(output.stderr).unwrap_or_else(|_| String::from(""))) 297 | } 298 | } 299 | Err(e) => panic!("Error attempting to run wasm-gc. Have you got it installed? Error message: {}", e) 300 | } 301 | let mut wasm = Vec::new(); 302 | BufReader::new(File::open(&wasm_path)?).read_to_end(&mut wasm)?; 303 | let mut module: Module = parity_wasm::deserialize_buffer(wasm.clone()).unwrap(); 304 | // modify the module to export the function table 305 | let has_table_export = module.export_section() 306 | .map(|exports| exports.entries() 307 | .iter() 308 | .any(|entry| entry.field() == "__table")) 309 | .unwrap_or(false); 310 | if !has_table_export && module.table_section().is_some() { 311 | let sections = module.sections_mut(); 312 | for section in sections { 313 | match *section { 314 | Section::Export(ref mut exports) => { 315 | exports.entries_mut().push(ExportEntry::new("__table".to_string(), Internal::Table(0))); 316 | break; 317 | } 318 | _ => {} 319 | } 320 | } 321 | } 322 | parity_wasm::serialize_to_file(&wasm_path, module.clone()).unwrap(); 323 | wasm.clear(); 324 | BufReader::new(File::open(&wasm_path)?).read_to_end(&mut wasm)?; 325 | let mut imports = String::new(); 326 | if let Some(import_section) = module.import_section() { 327 | for entry in import_section.entries() { 328 | if entry.module() == "env" { 329 | if let Some(mac) = js_macs.remove(entry.field()) { 330 | if !imports.is_empty() { 331 | imports.push_str(",\n"); 332 | } 333 | imports.push_str(&format!("{}:function(", entry.field())); 334 | let mut start = true; 335 | for arg in mac.args { 336 | if !start { 337 | imports.push_str(", "); 338 | } else { 339 | start = false; 340 | } 341 | match arg { 342 | JsMacArg::Ref(_, _, name) | 343 | JsMacArg::Primitive(_, name, _) => imports.push_str(&name) 344 | } 345 | } 346 | if let Some(body) = mac.body { 347 | imports.push_str(&format!("){{{}}}", body)); 348 | } else { 349 | imports.push_str("){}\n"); 350 | } 351 | } 352 | } 353 | } 354 | } 355 | 356 | // find 357 | Ok(PostProcessData { 358 | wasm_path, 359 | wasm, 360 | included: included_js, 361 | imports 362 | }) 363 | } -------------------------------------------------------------------------------- /embed_js_common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embed_js_common" 3 | version = "0.3.0" 4 | authors = ["Dylan Ede "] 5 | description = """ 6 | Implementation detail of embed_js and embed_js_build, do not use directly. 7 | """ 8 | homepage = "https://github.com/dylanede/embed_js" 9 | repository = "https://github.com/dylanede/embed_js" 10 | readme = "../README.md" 11 | license = "MIT / Apache-2.0" 12 | keywords = ["javascript", "inline", "wasm", "js", "embed_js"] 13 | 14 | [dependencies] 15 | cpp_synmap = "^0.3.0" 16 | cpp_syn = "^0.12.0" 17 | quote = "^0.3.15" 18 | serde = "^1.0" 19 | serde_derive = "^1.0" -------------------------------------------------------------------------------- /embed_js_common/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate cpp_synmap; 2 | extern crate cpp_syn; 3 | #[macro_use] 4 | extern crate quote; 5 | extern crate serde; 6 | #[macro_use] 7 | extern crate serde_derive; 8 | 9 | use cpp_synmap::SourceMap; 10 | use cpp_syn::{TokenTree, Delimited, DelimToken, Token, Span, BinOpToken}; 11 | 12 | use std::iter::Peekable; 13 | 14 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] 15 | pub enum WasmPrimitiveType { 16 | I32, 17 | I64, 18 | F32, 19 | F64, 20 | } 21 | 22 | fn parse_wasm_primitive_type<'a, I>(iter: &mut Peekable) -> Result 23 | where 24 | I: Iterator, 25 | { 26 | match iter.next() { 27 | Some(&TokenTree::Token(Token::Ident(ref ident), _)) => { 28 | match ident.as_ref() { 29 | "i32" => Ok(WasmPrimitiveType::I32), 30 | "i64" => Ok(WasmPrimitiveType::I64), 31 | "f32" => Ok(WasmPrimitiveType::F32), 32 | "f64" => Ok(WasmPrimitiveType::F64), 33 | _ => Err(()), 34 | } 35 | } 36 | _ => Err(()), 37 | } 38 | } 39 | 40 | #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 41 | pub enum JsMacArg { 42 | Ref(Vec, usize, String), 43 | Primitive(usize, String, WasmPrimitiveType) 44 | } 45 | 46 | #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 47 | pub struct JsMac { 48 | pub args: Vec, 49 | pub ret: Option, 50 | pub body: Option, 51 | } 52 | 53 | enum SpanJsMacArg { 54 | Ref(Vec, usize, Span), 55 | Primitive(usize, Span, WasmPrimitiveType) 56 | } 57 | 58 | struct SpanJsMac { 59 | args: Vec, 60 | ret: Option, 61 | body: Option, 62 | } 63 | 64 | fn parse_js_mac_span(tts: &[TokenTree]) -> Result { 65 | let mut iter = tts.iter().peekable(); 66 | let mut args; 67 | let ret; 68 | match iter.peek() { 69 | Some(&&TokenTree::Delimited(Delimited { delim, ref tts }, _)) => { 70 | match delim { 71 | DelimToken::Bracket => { 72 | iter.next(); // consume 73 | args = Vec::new(); 74 | { 75 | let mut iter = tts.iter().peekable(); 76 | let mut start = true; 77 | loop { 78 | if start { 79 | start = false; 80 | } else { 81 | match iter.next() { 82 | Some(&TokenTree::Token(Token::Comma, _)) => {}, 83 | None => break, 84 | _ => return Err(()), 85 | } 86 | } 87 | if iter.peek().is_none() { 88 | break; 89 | } 90 | let name; 91 | let mut derefs = 0; 92 | let mut refs = Vec::new(); 93 | loop { 94 | match iter.next() { 95 | Some(&TokenTree::Token(Token::BinOp(BinOpToken::And), _)) => { 96 | if let Some(&&TokenTree::Token(Token::Ident(ref ident), _)) = iter.peek() { 97 | if ident.as_ref() == "mut" { 98 | iter.next(); 99 | refs.push(true); 100 | } else { 101 | refs.push(false); 102 | } 103 | } else { 104 | refs.push(false); 105 | } 106 | } 107 | Some(&TokenTree::Token(Token::BinOp(BinOpToken::Star), _)) => { 108 | derefs += 1; 109 | } 110 | Some(&TokenTree::Token(Token::Ident(_), span)) => { 111 | name = span; 112 | break; 113 | }, 114 | _ => return Err(()) 115 | } 116 | } 117 | if refs.len() > 0 { 118 | args.push(SpanJsMacArg::Ref(refs, derefs, name)); 119 | } else { 120 | match iter.next() { 121 | Some(&TokenTree::Token(Token::Ident(ref ident), _)) if ident.as_ref() == "as" => {} 122 | _ => return Err(()), 123 | } 124 | args.push(SpanJsMacArg::Primitive(derefs, name, parse_wasm_primitive_type(&mut iter)?)); 125 | } 126 | } 127 | } 128 | ret = if let Some(&&TokenTree::Token(Token::RArrow, _)) = iter.peek() { 129 | iter.next(); 130 | Some(parse_wasm_primitive_type(&mut iter)?) 131 | } else { 132 | None 133 | }; 134 | } 135 | DelimToken::Brace => { // no params or return, 136 | args = vec![]; 137 | ret = None; 138 | }, 139 | _ => return Err(()), 140 | } 141 | } 142 | _ => return Err(()), 143 | } 144 | 145 | let result = match iter.next() { 146 | Some(&TokenTree::Delimited(Delimited { 147 | delim: DelimToken::Brace, 148 | ref tts, 149 | }, 150 | _)) => { 151 | SpanJsMac { 152 | args, 153 | ret, 154 | body: if tts.len() > 0 { Some(quote!(#(#tts)*).to_string()) } else { None }, 155 | } 156 | } 157 | _ => return Err(()), 158 | }; 159 | match iter.peek() { 160 | None => Ok(result), 161 | Some(_) => Err(()) 162 | } 163 | } 164 | 165 | pub fn parse_js_mac_source_map(tts: &[TokenTree], source_map: &SourceMap) -> Result { 166 | let spanned = parse_js_mac_span(tts)?; 167 | Ok(JsMac { 168 | args: spanned 169 | .args 170 | .into_iter() 171 | .map(|arg| { 172 | match arg { 173 | SpanJsMacArg::Ref(refs, derefs, span) => { 174 | JsMacArg::Ref(refs, derefs, source_map.source_text(span).unwrap().to_string()) 175 | } 176 | SpanJsMacArg::Primitive(derefs, span, t) => { 177 | JsMacArg::Primitive(derefs, source_map.source_text(span).unwrap().to_string(), t) 178 | } 179 | } 180 | }) 181 | .collect(), 182 | ret: spanned.ret, 183 | body: spanned.body, 184 | }) 185 | } 186 | 187 | pub fn parse_js_mac_string_source(tts: &[TokenTree], string_source: &str) -> Result { 188 | let spanned = parse_js_mac_span(tts)?; 189 | Ok(JsMac { 190 | args: spanned 191 | .args 192 | .into_iter() 193 | .map(|arg| { 194 | match arg { 195 | SpanJsMacArg::Ref(refs, derefs, span) => { 196 | JsMacArg::Ref(refs, derefs, string_source[span.lo..span.hi].to_string()) 197 | } 198 | SpanJsMacArg::Primitive(derefs, span, t) => { 199 | JsMacArg::Primitive(derefs, string_source[span.lo..span.hi].to_string(), t) 200 | } 201 | } 202 | }) 203 | .collect(), 204 | ret: spanned.ret, 205 | body: spanned.body, 206 | }) 207 | } -------------------------------------------------------------------------------- /embed_js_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "embed_js_derive" 3 | version = "0.1.2" 4 | authors = ["Dylan Ede "] 5 | description = """ 6 | Implementation detail of embed_js and embed_js_build, do not use directly. 7 | """ 8 | homepage = "https://github.com/dylanede/embed_js" 9 | repository = "https://github.com/dylanede/embed_js" 10 | readme = "../README.md" 11 | license = "MIT / Apache-2.0" 12 | keywords = ["javascript", "inline", "wasm", "js", "embed_js"] 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | embed_js_common = "^0.3" 19 | cpp_syn = { version = "^0.12.0", features = ["full", "parsing"] } 20 | quote = "^0.3.15" 21 | -------------------------------------------------------------------------------- /embed_js_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | extern crate cpp_syn; 4 | extern crate embed_js_common; 5 | #[macro_use] extern crate quote; 6 | use cpp_syn::{ TokenTree, Ident }; 7 | 8 | use proc_macro::TokenStream; 9 | use embed_js_common::{ WasmPrimitiveType, JsMacArg }; 10 | use std::collections::hash_map::DefaultHasher; 11 | use std::hash::{Hash, Hasher}; 12 | 13 | fn unwrap_delimited(t: &TokenTree) -> &[TokenTree] { 14 | match *t { 15 | TokenTree::Delimited(ref delimited, _) => &delimited.tts, 16 | _ => panic!("tried to delimit-unwrap a token that isn't a Delimited") 17 | } 18 | } 19 | 20 | fn prim_to_ty(ty: WasmPrimitiveType) -> Ident { 21 | match ty { 22 | WasmPrimitiveType::I32 => Ident::from("i32"), 23 | WasmPrimitiveType::I64 => Ident::from("i64"), 24 | WasmPrimitiveType::F32 => Ident::from("f32"), 25 | WasmPrimitiveType::F64 => Ident::from("f64"), 26 | } 27 | } 28 | 29 | #[proc_macro_derive(EmbedJsDetail)] 30 | pub fn embed_js(input: TokenStream) -> TokenStream { 31 | let s: String = input.to_string(); 32 | let tokens = cpp_syn::parse_token_trees(&s).unwrap(); 33 | let trimmed = unwrap_delimited(&unwrap_delimited(&unwrap_delimited(&tokens[4])[2])[2]); 34 | let js_mac = embed_js_common::parse_js_mac_string_source(trimmed, &s).expect("syntax error in js macro"); 35 | let mut hasher = DefaultHasher::new(); 36 | js_mac.hash(&mut hasher); 37 | let mac_hash = hasher.finish(); 38 | let mut type_params = Vec::new(); 39 | let mut arg_names = Vec::new(); 40 | let mut arg_types = Vec::new(); 41 | let mut extern_arg_types = Vec::new(); 42 | let mut cast_fragments = Vec::new(); 43 | let mut next_type_param = 0; 44 | for arg in js_mac.args { 45 | match arg { 46 | JsMacArg::Primitive(_, name, ty) => { 47 | arg_names.push(Ident::from(name)); 48 | let ty = prim_to_ty(ty); 49 | let ty_ = ty.clone(); 50 | extern_arg_types.push(quote!(#ty_)); 51 | cast_fragments.push(quote!()); 52 | arg_types.push(quote!(#ty)); 53 | } 54 | JsMacArg::Ref(refs, _, name) => { 55 | let mutable = refs[0]; 56 | let mutability = if mutable { 57 | quote!(mut) 58 | } else { 59 | quote!() 60 | }; 61 | arg_names.push(Ident::from(name)); 62 | let type_param = Ident::from(format!("T{}", next_type_param)); 63 | { 64 | let type_param_ = &type_param; 65 | next_type_param += 1; 66 | extern_arg_types.push(quote!(*mut u8)); 67 | arg_types.push(quote!(& #mutability #type_param_)); 68 | if mutable { 69 | cast_fragments.push(quote!(as *mut #type_param_ as *mut u8)); 70 | } else { 71 | cast_fragments.push(quote!(as *const #type_param_ as *const u8 as *mut u8)); 72 | } 73 | } 74 | type_params.push(type_param); 75 | } 76 | } 77 | } 78 | let type_params = if type_params.len() == 0 { 79 | quote!() 80 | } else { 81 | quote!(<#(#type_params),*>) 82 | }; 83 | let arg_names = &arg_names; 84 | let arg_types = &arg_types; 85 | let extern_name = Ident::from(format!("__embed_js__{:x}", mac_hash)); 86 | let result = match js_mac.ret { 87 | Some(ty) => { 88 | let ty = prim_to_ty(ty); 89 | quote! { 90 | impl EmbedJsStruct { 91 | fn call #type_params(#(#arg_names: #arg_types),*) -> #ty { 92 | extern { 93 | fn #extern_name(#(#arg_names: #extern_arg_types),*) -> #ty; 94 | } 95 | unsafe { #extern_name(#(#arg_names #cast_fragments),*) } 96 | } 97 | } 98 | } 99 | }, 100 | None => { 101 | quote! { 102 | impl EmbedJsStruct { 103 | fn call #type_params(#(#arg_names: #arg_types),*) { 104 | extern { 105 | fn #extern_name(#(#arg_names: #extern_arg_types),*); 106 | } 107 | unsafe { #extern_name(#(#arg_names #cast_fragments),*) } 108 | } 109 | } 110 | } 111 | } 112 | }; 113 | result.parse().unwrap() 114 | } -------------------------------------------------------------------------------- /examples/callbacks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "callbacks" 3 | version = "0.1.0" 4 | authors = ["Dylan Ede "] 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | embed_js = "^0.1.3" 11 | 12 | [build-dependencies] 13 | embed_js_build = "^0.1.3" 14 | -------------------------------------------------------------------------------- /examples/callbacks/Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.build] 2 | command = "cargo" 3 | args = ["build", "--target=wasm32-unknown-unknown", "--release"] 4 | 5 | [tasks.format] 6 | disabled = true 7 | 8 | [tasks.test] 9 | disabled = true 10 | 11 | [tasks.post-build] 12 | command = "cargo" 13 | args = ["script", "post_build.rs"] -------------------------------------------------------------------------------- /examples/callbacks/build.rs: -------------------------------------------------------------------------------- 1 | extern crate embed_js_build; 2 | 3 | fn main() { 4 | use std::path::PathBuf; 5 | let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("src/lib.rs"); 6 | embed_js_build::preprocess_crate(&root); 7 | } -------------------------------------------------------------------------------- /examples/callbacks/post_build.rs: -------------------------------------------------------------------------------- 1 | //! ```cargo 2 | //! [dependencies] 3 | //! base64 = "0.8.0" 4 | //! embed_js_build = "^0.1.3" 5 | //! ``` 6 | 7 | extern crate base64; 8 | extern crate embed_js_build; 9 | 10 | use std::fs::File; 11 | use std::io::Write; 12 | 13 | fn main() { 14 | let pp_data = embed_js_build::postprocess_crate("callbacks", false).unwrap(); 15 | let in_base_64 = base64::encode(&pp_data.wasm); 16 | let html_path = pp_data.wasm_path.with_extension("html"); 17 | let mut html_file = File::create(&html_path).unwrap(); 18 | write!(html_file, r#" 19 | 20 | 21 | 22 | wasm test 23 | 50 | 51 | 52 | "#, 53 | in_base_64, 54 | pp_data.included, 55 | pp_data.imports 56 | ).unwrap(); 57 | } -------------------------------------------------------------------------------- /examples/callbacks/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate embed_js; 3 | 4 | embed_js_preamble!(); 5 | 6 | // Some convenience JS for dealing with interop more easily. This sort of stuff should go in a 7 | // higher level crate. 8 | include_js! { 9 | window.refs = new Map(); 10 | window.next_ref_id = 0; 11 | window.new_ref = function(obj) { 12 | refs[next_ref_id] = obj; 13 | var id = next_ref_id; 14 | next_ref_id += 1; 15 | return id; 16 | }; 17 | window.drop_ref = function(id) { 18 | refs.delete(id); 19 | }; 20 | window.get_closure = function(ptr) { 21 | var array = new Int32Array(wasm_mem.buffer, ptr, 3); 22 | var f = wasm_table.get(array[0]); 23 | var data = array[1]; 24 | var drop = array[2]; 25 | var result = function() { f(data); }; 26 | result.drop = function() { drop(data); }; 27 | return result; 28 | } 29 | } 30 | 31 | // MarshalledClosure makes closure interop easier 32 | #[repr(C)] 33 | struct MarshalledClosure { 34 | call: extern fn(*mut ()), 35 | data: *mut (), 36 | drop: extern fn(*mut ()) 37 | } 38 | 39 | impl MarshalledClosure { 40 | unsafe fn drop(self) { 41 | (self.drop)(self.data); 42 | } 43 | } 44 | 45 | unsafe fn marshal_closure(f: F) -> MarshalledClosure 46 | where F : FnMut() 47 | { 48 | extern fn call(ptr: *mut ()) where F : FnMut() { 49 | let f = unsafe { &mut *(ptr as *mut F) }; 50 | f(); 51 | } 52 | extern fn drop_f(ptr: *mut ()) { 53 | let f = unsafe { Box::from_raw(ptr as *mut F) }; 54 | drop(f) 55 | } 56 | let mut bf = Box::new(f); 57 | let ptr = &mut *bf as *mut F as *mut (); 58 | std::mem::forget(bf); 59 | MarshalledClosure { 60 | call: call::, 61 | data: ptr, 62 | drop: drop_f:: 63 | } 64 | } 65 | 66 | #[no_mangle] 67 | pub fn entry_point() { 68 | // create a button 69 | let button_id = js!([] -> i32 { 70 | var button = document.createElement("button"); 71 | button.appendChild(document.createTextNode("Click me")); 72 | document.body.appendChild(button); 73 | return new_ref(button); 74 | }); 75 | 76 | // demonstration of a closure with internal state 77 | let my_callback = { 78 | let mut clicked_before = false; // this ends up owned by the closure 79 | move || { 80 | if !clicked_before { 81 | js!({ 82 | alert("You clicked the button!"); 83 | }); 84 | clicked_before = true; 85 | } else { 86 | js!({ 87 | alert("You clicked the button again!"); 88 | }) 89 | } 90 | } 91 | }; 92 | // Closure marshalling: 93 | // You must ensure that the lifetime bounds of the closure are respected 94 | // In this case the closure lives for the lifetime of the page (and its bounds allow that), 95 | // so we don't drop it - if we did we would call c.drop() 96 | let c = unsafe { marshal_closure(my_callback) }; 97 | // register the event handler 98 | js!([button_id as i32, &c] { 99 | var closure = get_closure(c); 100 | refs[button_id].addEventListener("click", function() { 101 | closure(); 102 | // if we wanted to drop the closure, we would call closure.drop() 103 | }); 104 | }); 105 | 106 | // drop the reference to the button - not that important in this example but good practice 107 | js!([button_id as i32] { 108 | drop_ref(button_id); 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /examples/simple/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple" 3 | version = "0.1.0" 4 | authors = ["Dylan Ede "] 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | subcrate = { path = "subcrate" } -------------------------------------------------------------------------------- /examples/simple/Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.build] 2 | command = "cargo" 3 | args = ["build", "--target=wasm32-unknown-unknown", "--release"] 4 | #args = ["rustc", "--target=wasm32-unknown-unknown", "--", "-C", "link-args=-Wl,-export-dynamic"] 5 | 6 | [tasks.format] 7 | disabled = true 8 | 9 | [tasks.test] 10 | disabled = true 11 | 12 | [tasks.post-build] 13 | command = "cargo" 14 | args = ["script", "post_build.rs"] -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | ## Simple example 2 | 3 | This simple example demonstrates depending on a crate (called "subcrate") containing inline javascript 4 | and building a self-contained HTML document that runs our compiled code. 5 | 6 | See the top-level readme for example build requirements. -------------------------------------------------------------------------------- /examples/simple/post_build.rs: -------------------------------------------------------------------------------- 1 | //! ```cargo 2 | //! [dependencies] 3 | //! base64 = "0.8.0" 4 | //! embed_js_build = "^0.1.4" 5 | //! ``` 6 | 7 | extern crate base64; 8 | extern crate embed_js_build; 9 | 10 | use std::fs::File; 11 | use std::io::Write; 12 | 13 | fn main() { 14 | let pp_data = embed_js_build::postprocess_crate("simple", false).unwrap(); 15 | let in_base_64 = base64::encode(&pp_data.wasm); 16 | let html_path = pp_data.wasm_path.with_extension("html"); 17 | let mut html_file = File::create(&html_path).unwrap(); 18 | write!(html_file, r#" 19 | 20 | 21 | 22 | wasm test 23 | 43 | 44 | 45 | "#, 46 | in_base_64, 47 | pp_data.imports 48 | ).unwrap(); 49 | } -------------------------------------------------------------------------------- /examples/simple/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate subcrate; 2 | 3 | pub use subcrate::*; 4 | 5 | #[no_mangle] 6 | pub fn add_two(x: i32) -> i32 { 7 | subcrate::add_one(subcrate::add_one(x)) 8 | } 9 | -------------------------------------------------------------------------------- /examples/simple/subcrate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subcrate" 3 | version = "0.1.0" 4 | authors = ["Dylan Ede "] 5 | 6 | [dependencies] 7 | embed_js = "^0.1.4" 8 | 9 | [build-dependencies] 10 | embed_js_build = "^0.1.4" 11 | -------------------------------------------------------------------------------- /examples/simple/subcrate/build.rs: -------------------------------------------------------------------------------- 1 | extern crate embed_js_build; 2 | 3 | fn main() { 4 | use std::path::PathBuf; 5 | use std::fs::File; 6 | use std::io::{Write, BufWriter}; 7 | 8 | // demonstration of support for js macro calls within files generated by a build script 9 | { 10 | let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 11 | let mut writer = BufWriter::new(File::create(out_dir.join("generated_source.rs")).unwrap()); 12 | write!(writer, r#" 13 | pub fn foo() {{ 14 | js!({{ 15 | alert("Hello from generated source!"); 16 | }}); 17 | }} 18 | "#).unwrap(); 19 | } 20 | 21 | // make sure to call embed_js_build::preprocess_crate *after* all generation is completed 22 | let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("src/lib.rs"); 23 | embed_js_build::preprocess_crate(&root); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /examples/simple/subcrate/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate embed_js; 3 | 4 | embed_js_preamble!(); 5 | 6 | // demonstration of using the js macro inside generated source - see the build script 7 | mod generated { 8 | include!(concat!(env!("OUT_DIR"), "/generated_source.rs")); 9 | } 10 | 11 | 12 | pub fn add_one(x: i32) -> i32 { 13 | generated::foo(); 14 | js!([x as i32] -> i32 { 15 | return x + 1; 16 | }) 17 | } -------------------------------------------------------------------------------- /examples/strings/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "strings" 3 | version = "0.1.0" 4 | authors = ["Dylan Ede "] 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | embed_js = "^0.1" 11 | 12 | [build-dependencies] 13 | embed_js_build = "^0.1" 14 | -------------------------------------------------------------------------------- /examples/strings/Makefile.toml: -------------------------------------------------------------------------------- 1 | [tasks.build] 2 | command = "cargo" 3 | args = ["build", "--target=wasm32-unknown-unknown", "--release"] 4 | 5 | [tasks.format] 6 | disabled = true 7 | 8 | [tasks.test] 9 | disabled = true 10 | 11 | [tasks.post-build] 12 | command = "cargo" 13 | args = ["script", "post_build.rs"] -------------------------------------------------------------------------------- /examples/strings/build.rs: -------------------------------------------------------------------------------- 1 | extern crate embed_js_build; 2 | 3 | fn main() { 4 | use std::path::PathBuf; 5 | let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("src/lib.rs"); 6 | embed_js_build::preprocess_crate(&root); 7 | } -------------------------------------------------------------------------------- /examples/strings/post_build.rs: -------------------------------------------------------------------------------- 1 | //! ```cargo 2 | //! [dependencies] 3 | //! base64 = "0.8.0" 4 | //! embed_js_build = "^0.1" 5 | //! ``` 6 | 7 | extern crate base64; 8 | extern crate embed_js_build; 9 | 10 | use std::fs::File; 11 | use std::io::Write; 12 | 13 | fn main() { 14 | let pp_data = embed_js_build::postprocess_crate("strings", false).unwrap(); 15 | let in_base_64 = base64::encode(&pp_data.wasm); 16 | let html_path = pp_data.wasm_path.with_extension("html"); 17 | let mut html_file = File::create(&html_path).unwrap(); 18 | write!(html_file, r#" 19 | 20 | 21 | 22 | wasm test 23 | 46 | 47 | 48 | "#, 49 | in_base_64, 50 | pp_data.imports 51 | ).unwrap(); 52 | } -------------------------------------------------------------------------------- /examples/strings/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate embed_js; 3 | 4 | embed_js_preamble!(); 5 | 6 | use std::mem; 7 | 8 | #[no_mangle] 9 | pub fn alloc(n: i32) -> *mut u8 { 10 | let mut v = Vec::with_capacity(n as usize); 11 | let ptr = v.as_mut_ptr(); 12 | mem::forget(v); 13 | ptr 14 | } 15 | 16 | #[no_mangle] 17 | pub fn free(ptr: *mut u8, len: i32) { 18 | drop(unsafe { Vec::from_raw_parts(ptr, 0, len as usize) }) 19 | } 20 | 21 | // not actually used in this example, but included for completeness 22 | #[no_mangle] 23 | pub fn realloc(ptr: *mut u8, old_len: i32, new_len: i32) -> *mut u8 { 24 | let mut v = unsafe{Vec::from_raw_parts(ptr, 0, old_len as usize)}; 25 | if new_len > old_len { 26 | v.reserve_exact((new_len - old_len) as usize); 27 | } else { 28 | unsafe { v.set_len(new_len as usize) } 29 | v.shrink_to_fit(); 30 | } 31 | let ptr = v.as_mut_ptr(); 32 | mem::forget(v); 33 | ptr 34 | } 35 | 36 | #[no_mangle] 37 | pub fn entry_point() { 38 | // Rust str to JS 39 | { 40 | let s = "Hello JS!"; 41 | let s = s.as_bytes(); 42 | let p = s.as_ptr(); 43 | let l = s.len(); 44 | js!([p as i32, l as i32] { 45 | var array = new Uint8Array(wasm_mem.buffer, p, l); 46 | var string = (new TextDecoder("utf-8")).decode(array); 47 | alert("str from Rust: " + string); 48 | }); 49 | } 50 | 51 | // JS String to Rust String 52 | let rust_string; 53 | { 54 | let mut l: u32 = 0; 55 | let l_ptr = &mut l as *mut _; 56 | let p = js!([l_ptr as i32] -> i32 { 57 | var js_string = "Hello Rust!"; 58 | // TextEncoder doesn't support encoding into an existing buffer yet :( 59 | var array = (new TextEncoder("utf-8")).encode(js_string); 60 | // allocate a Vec 61 | var ptr = wasm_exports.alloc(array.length); 62 | var rust_array = new Uint8Array(wasm_mem.buffer, ptr, array.length); 63 | // copy array 64 | rust_array.set(array); 65 | // writing into a *mut u32 66 | var rust_len = new Uint32Array(wasm_mem.buffer, l_ptr, 1); 67 | rust_len[0] = array.length; 68 | return ptr; 69 | }) as *mut u8; 70 | rust_string = unsafe { String::from_utf8_unchecked(Vec::from_raw_parts(p, l as usize, l as usize)) }; 71 | } 72 | 73 | // Rust String to JS 74 | { 75 | let s = rust_string.replace("Rust", "again JS"); 76 | let s = s.into_bytes(); 77 | let p = s.as_ptr(); 78 | let l = s.len(); 79 | let c = s.capacity(); 80 | mem::forget(s); 81 | js!([p as i32, l as i32, c as i32] { 82 | var array = new Uint8Array(wasm_mem.buffer, p, l); 83 | var string = (new TextDecoder("utf-8")).decode(array); 84 | alert("String from Rust: " + string); 85 | wasm_exports.free(p, c); 86 | }); 87 | } 88 | } 89 | --------------------------------------------------------------------------------