Permalink
Checking mergeability…
Don’t worry, you can still create the pull request.
Comparing changes
Open a pull request
3
contributors
Unified
Split
Showing
with
159 additions
and 12 deletions.
- +12 −4 README.md
- +19 −2 packages/react-scripts/README.md
- +15 −0 packages/react-scripts/config/webpack.config.js
- +9 −5 packages/react-scripts/package.json
- +3 −0 packages/react-scripts/scripts/build.js
- +16 −0 packages/react-scripts/scripts/init.js
- +45 −0 packages/react-scripts/scripts/utils/rustUtils.js
- +3 −0 packages/react-scripts/template/gitignore
- +1 −0 packages/react-scripts/template/rust-toolchain
- +10 −1 packages/react-scripts/template/src/App.js
- +9 −0 packages/react-scripts/template/src/App.rs
- +17 −0 packages/react-scripts/template/src/wasmLoader.js
| @@ -1,17 +1,24 @@ | |||
| # Create React App [](https://travis-ci.org/facebook/create-react-app) [](https://github.com/facebook/create-react-app/pulls) | |||
| # Create React App (Rust) [](https://travis-ci.org/facebook/create-react-app) [](https://github.com/facebook/create-react-app/pulls) | |||
|
|
|||
| Create React apps with no build configuration. | |||
| Create React apps with support for WebAssembly/Rust with no build configuration. | |||
|
|
|||
| - [Creating an App](#creating-an-app) – How to create a new app. | |||
| - [User Guide](https://facebook.github.io/create-react-app/) – How to develop apps bootstrapped with Create React App. | |||
|
|
|||
| Create React App works on macOS, Windows, and Linux.<br> | |||
| Create React App works on macOS, Windows, and Linux. However Rust tools may behave diferently on Windows in certain cercumstances.<br> | |||
| If something doesn’t work, please [file an issue](https://github.com/facebook/create-react-app/issues/new). | |||
|
|
|||
| ## Quick Overview | |||
|
|
|||
| If you haven't already you'll need to install Rust and source it into your current context | |||
| ```sh | |||
| npx create-react-app my-app | |||
| 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 --scripts-version react-scripts-rust | |||
| cd my-app | |||
| npm start | |||
| ``` | |||
| @@ -79,6 +86,7 @@ my-app | |||
| ├── App.css | |||
| ├── App.js | |||
| ├── App.test.js | |||
| ├── App.rs | |||
| ├── index.css | |||
| ├── index.js | |||
| ├── logo.svg | |||
| @@ -1,7 +1,24 @@ | |||
| # react-scripts | |||
| # react-scripts-rust | |||
|
|
|||
| This package includes scripts and configuration used by [Create React App](https://github.com/facebook/create-react-app).<br> | |||
| This package includes scripts and configuration used by [Create React App (Rust)](https://github.com/thomashorrobin/create-react-app-rust).<br> | |||
| Please refer to its documentation: | |||
|
|
|||
| - [Getting Started](https://facebook.github.io/create-react-app/docs/getting-started) – How to create a new app. | |||
| - [User Guide](https://facebook.github.io/create-react-app/) – How to develop apps bootstrapped with Create React App. | |||
|
|
|||
|
|
|||
| ## Quick Overview | |||
|
|
|||
| First install Rust if you haven't already | |||
| ```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 --scripts-version react-scripts-rust | |||
| cd my-app | |||
| npm start | |||
| ``` | |||
|
|
|||
| After the project has loaded in Chrome any changes in `src/lib.rs` should result in an instant update in the browser. | |||
| @@ -325,6 +325,21 @@ module.exports = function(webpackEnv) { | |||
| ], | |||
| include: paths.appSrc, | |||
| }, | |||
| { | |||
| test: /\.rs$/, | |||
| use: [ | |||
| { | |||
| loader: 'wasm-loader', | |||
| }, | |||
| { | |||
| loader: 'rust-native-wasm-loader', | |||
| options: { | |||
| release: true, | |||
| gc: true, | |||
| }, | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| // "oneOf" will traverse all following loaders until one will | |||
| // match the requirements. When no loader matches it will fall | |||
| @@ -1,14 +1,14 @@ | |||
| { | |||
| "name": "react-scripts", | |||
| "version": "2.1.1", | |||
| "description": "Configuration and scripts for Create React App.", | |||
| "repository": "facebook/create-react-app", | |||
| "name": "react-scripts-rust", | |||
| "version": "2.0.17", | |||
| "description": "Configuration and scripts for using Rust with Create React App", | |||
| "repository": "thomashorrobin/create-react-app-rust", | |||
| "license": "MIT", | |||
| "engines": { | |||
| "node": ">=6" | |||
| }, | |||
| "bugs": { | |||
| "url": "https://github.com/facebook/create-react-app/issues" | |||
| "url": "https://github.com/thomashorrobin/create-react-app-rust/issues" | |||
| }, | |||
| "files": [ | |||
| "bin", | |||
| @@ -35,6 +35,7 @@ | |||
| "bfj": "6.1.1", | |||
| "case-sensitive-paths-webpack-plugin": "2.1.2", | |||
| "chalk": "2.4.1", | |||
| "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.1.3", | |||
| "react-dev-utils": "^6.1.1", | |||
| "resolve": "1.8.1", | |||
| "rust-native-wasm-loader": "^0.8.1", | |||
| "sass-loader": "7.1.0", | |||
| "style-loader": "0.23.0", | |||
| "terser-webpack-plugin": "1.1.0", | |||
| "toml-js": "0.0.8", | |||
| "url-loader": "1.1.1", | |||
| "wasm-loader": "^1.3.0", | |||
| "webpack": "4.19.1", | |||
| "webpack-dev-server": "3.1.9", | |||
| "webpack-manifest-plugin": "2.0.4", | |||
| @@ -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; | |||
| @@ -82,6 +83,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); | |||
| }) | |||
| @@ -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. | |||
| @@ -0,0 +1,45 @@ | |||
| const execSync = require('child_process').execSync; | |||
| const chalk = require('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' } | |||
| ); | |||
| }, | |||
| }; | |||
| @@ -21,3 +21,6 @@ | |||
| npm-debug.log* | |||
| yarn-debug.log* | |||
| yarn-error.log* | |||
|
|
|||
| /target | |||
| **/*.rs.bk | |||
| @@ -0,0 +1 @@ | |||
| nightly | |||
| @@ -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 ( | |||
| <div className="App"> | |||
| <header className="App-header"> | |||
| <img src={logo} className="App-logo" alt="logo" /> | |||
| <p> | |||
| Edit <code>src/App.js</code> and save to reload. | |||
| Edit <code>src/App.js</code> or <code>src/App.rs</code> and save to reload. | |||
| </p> | |||
| <a | |||
| className="App-link" | |||
| @@ -0,0 +1,9 @@ | |||
| #[no_mangle] | |||
| pub extern "C" fn fact(mut n: u32) -> u32 { | |||
| let mut result = 1; | |||
| while n > 0 { | |||
| result = result * n; | |||
| n = n - 1; | |||
| } | |||
| result | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||