diff --git a/README.md b/README.md index 75aed91b989..935306c3a56 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,13 @@ If something doesn’t work, please [file an issue](https://github.com/facebook/ ## Quick Overview +If you haven't already you'll need to install Rust and source it into your current context +```sh +curl https://sh.rustup.rs -sSf | sh +source $HOME/.cargo/env +``` + +Then you can run the tool with `npx` ```sh npx create-react-app my-app cd my-app diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 93e4d4d2d18..87c6e13f3cd 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -1,6 +1,6 @@ { - "name": "react-scripts", - "version": "2.1.5", + "name": "@bowdo/react-scripts", + "version": "next", "description": "Configuration and scripts for Create React App.", "repository": "facebook/create-react-app", "license": "MIT", @@ -34,6 +34,7 @@ "babel-preset-react-app": "^7.0.1", "bfj": "6.1.1", "case-sensitive-paths-webpack-plugin": "2.2.0", + "command-exists": "^1.2.8", "css-loader": "1.0.0", "dotenv": "6.0.0", "dotenv-expand": "4.2.0", @@ -63,10 +64,13 @@ "react-app-polyfill": "^0.2.1", "react-dev-utils": "^7.0.3", "resolve": "1.10.0", + "rust-native-wasm-loader": "^0.8.1", "sass-loader": "7.1.0", "style-loader": "0.23.1", "terser-webpack-plugin": "1.2.2", + "toml-js": "0.0.8", "url-loader": "1.1.2", + "wasm-loader": "^1.3.0", "webpack": "4.28.3", "webpack-dev-server": "3.1.14", "webpack-manifest-plugin": "2.0.4", diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js index 8cd1ad77ee5..d689fdfc261 100644 --- a/packages/react-scripts/scripts/build.js +++ b/packages/react-scripts/scripts/build.js @@ -43,6 +43,7 @@ const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); const printBuildError = require('react-dev-utils/printBuildError'); +const rustUtils = require('./utils/rustUtils'); const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild; @@ -64,7 +65,6 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { const argv = process.argv.slice(2); const writeStatsJson = argv.indexOf('--stats') !== -1; -// Generate configuration const config = configFactory('production'); // We require that you explicitly set browsers and do not fall back to @@ -82,6 +82,8 @@ checkBrowsers(paths.appPath, isInteractive) fs.emptyDirSync(paths.appBuild); // Merge with the public folder copyPublicFolder(); + // compile Rust to wasm + rustUtils.build(); // Start the webpack build return build(previousFileSizes); }) diff --git a/packages/react-scripts/scripts/init.js b/packages/react-scripts/scripts/init.js index 9b473ab3d95..64fa15c156c 100644 --- a/packages/react-scripts/scripts/init.js +++ b/packages/react-scripts/scripts/init.js @@ -204,6 +204,22 @@ module.exports = function( console.log('Initialized a git repository.'); } + const rustUtils = require('./utils/rustUtils'); + + if (rustUtils.isRustInstalled()) { + console.log('rust installed 👍'); + } else { + console.log( + chalk.bold.red('rust not installed') + ); + console.log( + 'install rust from here https://www.rust-lang.org/en-US/install.html' + ); + return; + } + + rustUtils.installRustWebAssemblyTools(); + // Display the most elegant way to cd. // This needs to handle an undefined originalDirectory for // backward compatibility with old global-cli's. diff --git a/packages/react-scripts/scripts/utils/rustUtils.js b/packages/react-scripts/scripts/utils/rustUtils.js new file mode 100644 index 00000000000..3e31c522c37 --- /dev/null +++ b/packages/react-scripts/scripts/utils/rustUtils.js @@ -0,0 +1,45 @@ +const execSync = require('child_process').execSync; +const chalk = require('react-dev-utils/chalk'); +const commandExistsSync = require('command-exists').sync; +const fs = require('fs'); +const toml = require('toml-js'); + +module.exports = { + build: () => { + try { + // first build app.wasm and then optimize with wasm-gc + execSync( + 'rustc +nightly --target wasm32-unknown-unknown -O --crate-type=cdylib src/App.rs -o build/app.wasm && wasm-gc build/app.wasm', + { stdio: 'inherit' } + ); + } catch (error) { + console.log(chalk.bold.red('error compiling rust')); + } + }, + isRustInstalled: () => commandExistsSync('rustup'), + installRustWebAssemblyTools: () => { + if (!commandExistsSync('wasm-gc')) { + try { + execSync('cargo install wasm-gc', { stdio: 'inherit' }); + } catch (e) { + console.log(e); + console.log(`${chalk.bold.red('error installing wasm-gc')} please install manually ${chalk.red('cargo install wasm-gc')}')`); + } + } + execSync( + 'cargo init --lib', + { stdio: 'inherit' } + ); + fs.readFile('Cargo.toml', function (err, data) { + var cargo = toml.parse(data); + cargo.lib = {}; + cargo.lib['crate-type'] = ['cdylib']; + cargo.lib.path = 'src/App.rs'; + fs.writeFile('Cargo.toml', toml.dump(cargo), err => console.log(err)); + }); + execSync( + 'rustup target add wasm32-unknown-unknown --toolchain nightly', + { stdio: 'inherit' } + ); + }, +}; diff --git a/packages/react-scripts/template/gitignore b/packages/react-scripts/template/gitignore index 4d29575de80..404580abbd1 100644 --- a/packages/react-scripts/template/gitignore +++ b/packages/react-scripts/template/gitignore @@ -21,3 +21,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +/target +**/*.rs.bk diff --git a/packages/react-scripts/template/rust-toolchain b/packages/react-scripts/template/rust-toolchain new file mode 100644 index 00000000000..07ade694b1a --- /dev/null +++ b/packages/react-scripts/template/rust-toolchain @@ -0,0 +1 @@ +nightly \ No newline at end of file diff --git a/packages/react-scripts/template/src/App.js b/packages/react-scripts/template/src/App.js index 7e261ca47e6..25c43654d1b 100644 --- a/packages/react-scripts/template/src/App.js +++ b/packages/react-scripts/template/src/App.js @@ -1,15 +1,24 @@ import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; +import { loadWasm } from "./wasmLoader"; class App extends Component { + constructor() { + super(); + loadWasm(result => { + const calculateFactorial = result.instance.exports['fact']; + const x = 5; + alert(`the factorial of ${ x } is ${ calculateFactorial(x) }`); + }); + } render() { return (
logo

- Edit src/App.js and save to reload. + Edit src/App.js or src/App.rs and save to reload.

u32 { + let mut result = 1; + while n > 0 { + result = result * n; + n = n - 1; + } + result +} diff --git a/packages/react-scripts/template/src/wasmLoader.js b/packages/react-scripts/template/src/wasmLoader.js new file mode 100644 index 00000000000..9b65ced3e5c --- /dev/null +++ b/packages/react-scripts/template/src/wasmLoader.js @@ -0,0 +1,17 @@ +import rustFile from './App.rs'; + +export function loadWasm(handleResultObjectPromise) { + switch (process.env.NODE_ENV) { + case 'production': + fetch('app.wasm') + .then(response => response.arrayBuffer()) + .then(bytes => WebAssembly.instantiate(bytes, {})) + .then(handleResultObjectPromise); + break; + case 'development': + rustFile().then(handleResultObjectPromise); + break; + default: + break; + } +} \ No newline at end of file