diff --git a/pkg/interface/CONTRIBUTING.md b/pkg/interface/CONTRIBUTING.md index 7cd8fb6ff..07d4cce1d 100644 --- a/pkg/interface/CONTRIBUTING.md +++ b/pkg/interface/CONTRIBUTING.md @@ -10,19 +10,98 @@ applications. Landscape applications will usually make good use of Gall, but it's not strictly required if a Landscape application is not interacting with ships directly. -Create a development ship, then once your ship is running, mount to Unix with -`|mount %`. This will create a folder named 'home' in your pier in Unix. The -'home' desk contains the working state of your ship -- like a Git repository, -when you want to make a change to it, `|commit %home`. - ## Contributing to Landscape applications +To begin developing on Landscape, find the `urbitrc-sample` file found +at `urbit/pkg/interface/config/urbitrc-sample`. Copy it as `urbitrc`. +Open it using your preferred code editor and you should see the following: + +``` +module.exports = { + URBIT_PIERS: [ + "/Users/user/ships/zod/home", + ], + herb: false, + URL: 'http://localhost:80' +}; +``` + +This file is the configuration file for your front-end development environment. +Let's walk through it. + +The first line, listing piers, specifies which piers to copy the JS files into. +By default, the development environment won't copy files into any pier, even if +you've set the pier in `urbitrc`. + +If you want to copy the JS files into your ship, as it would run in a regular +user environment, uncomment these lines in +`pkg/interface/config/webpack.dev.js`: + +```javascript +// uncomment to copy into all piers +// +// return Promise.all(this.piers.map(pier => { +// const dst = path.resolve(pier, 'app/landscape/js/index.js'); +// copyFile(src, dst).then(() => { +// if(!this.herb) { +// return; +// } +// pier = pier.split('/'); +// const desk = pier.pop(); +// return exec(`herb -p hood -d '+hood/commit %${desk}' ${pier.join('/')}`); +// }); +// })); +``` + +And then set your pier in `urbitrc` (ensure it ends in `/home`). The `herb` +option in your `urbitrc` will automatically commit the changes to your ship if +you have herb installed (see `pkg/herb`). + +For most developers, if you are making changes to Landscape without any back-end +changes on the Urbit ship itself, and you have an Urbit ship running already, +you don't have to boot a development ship. You can simply set up the dev server +for the development environment and point it at your running ship. + +To do this, set the `URL` property in your urbitrc and replace it with the URL +of the urbit that you are testing on. For example, a development ship by default +lives at `localhost:80` so our `urbitrc` would have: + +```javascript +module.exports = { + URL: 'http://localhost:80' +} +``` + +Then get everything installed: + +``` +## go to urbit's interface directory and install the required tooling +cd urbit/pkg/interface +npm install + +## Start your development server +npm run start +``` + +You can then access a hot reloaded version of the interface at +`http://localhost:9000`. + +If you set the URL to your running ship, like +`http://sampel-palnet.arvo.network`, then you can use your actual ship while +running local-only development changes. + +As previously stated, if your changes require back-end development (front-end +and Gall changes, for example), or you just want an empty development +environment, you'll want to create a development ship. + +### Creating a development ship + [nix](https://github.com/NixOS/nix) and `git-lfs` should be installed at this point, and have been used to `make build` the project. -Designing interfaces within urbit/urbit additionally requires that the +First follow the [instructions](https://urbit.org/using/develop/#creating-a-development-ship) for -fake `~zod` initialization have been followed. +fake `~zod` initialization. Once your fake ship is running and you see ``` @@ -32,43 +111,27 @@ in your console, be sure to 'mount' your ship's working state (what we call 'desks') to your local machine via the `|mount %` command. This will ensure that code you modify locally can be committed to your ship and initialized. -To begin developing Urbit's frontend, you'll need to sync your currently-running -fake ship with the urbit/urbit repo's code. Find the `urbitrc-sample` file found -at `urbit/pkg/interface/config/urbitrc-sample`. Open it using your preferred -code editor and you should see the following: - -``` -module.exports = { - URBIT_PIERS: [ - "/Users/user/ships/zod/home", - ] -}; -``` - -Edit the path between quotes `/Users/user/ships/zod/home` with wherever your -fake ship is located on your machine. This zod location path *must* end in -`../home` to correctly intitalize any code you write. Save the file as `urbitrc` -inside that same folder. Any code edited within the `urbit/urbit` will now be -able to be synced to your running ship, and previewed in the browser. - To set up urbit's Javascript environment, you'll need node (ideally installed -via [nvm](https://github.com/nvm-sh/nvm)) and webpack, which will be installed via -node. +via [nvm](https://github.com/nvm-sh/nvm)) and webpack, which will be installed +via node. -Perform the following steps to get the above set up for urbit's apps: +If you want to copy the code into your ship, perform the following steps: ``` ## go to urbit's interface directory and install the required tooling cd urbit/pkg/interface npm install -## Start watching the entire directory for changes +## Build the JS code npm run build:dev ``` -Any changes made to any files within the `/pkg/interface` directory will now trigger -a gulp rebuild when saved. To sync these changes to your running ship, enter -dojo and input the following: +If you want to run the JavaScript code in a dev server, you can simply set the +URL in your `urbitrc` to `localhost:80` and `npm run start` instead. + +If you set your pier in `urbitrc`, and uncommented the code in the webpack +config, then once the build process is running, commit on your ship to copy the +changed JS code in: ``` |commit %home @@ -78,19 +141,21 @@ Your urbit should take a moment to process the changes, and will emit a `>=`. Refreshing your browser will display the newly-rendered interface. Once you are done editing code, and wish to commit changes to git, stop your -`build:dev` process. Do not commit compiled code, but submit the source code +process. Do not commit compiled code, but submit the source code for review. Please also ensure your pull request fits our standards for [Git hygiene][contributing]. -[contributing]: /CONTRIBUTING.md#git-practice [arvo]: /pkg/arvo +[contributing]: /CONTRIBUTING.md#git-practice +[arvo]: /pkg/arvo [interface]:/pkg/interface ## Linting The Urbit interface uses Eslint to lint the JavaScript code. To install the linter and for usage through the command, do the following: + ```bash $ cd ./pkg/interface $ npm install @@ -98,6 +163,7 @@ $ npm run lint ``` To use the linter, run npm scripts + ```bash $ npm run lint # lints all files in `interface` $ npm run lint-file ./src/apps/chat/**/*.js # lints all .js files in `interface/chat` @@ -119,7 +185,8 @@ documentation for its everyday use -- just create a repo [using its template][template], install and then start it, and you'll soon be up and running. -[cla]: https://github.com/urbit/create-landscape-app [template]: -https://github.com/urbit/create-landscape-app/generate [gall]: -https://urbit.org/docs/learn/arvo/gall/ [chat]: /pkg/arvo/app/chat-view.hoon +[cla]: https://github.com/urbit/create-landscape-app +[template]: https://github.com/urbit/create-landscape-app/generate +[gall]:https://urbit.org/docs/learn/arvo/gall/ +[chat]: /pkg/arvo/app/chat-view.hoon [publish]: /pkg/arvo/app/publish.hoon \ No newline at end of file diff --git a/pkg/interface/config/urbitrc-sample b/pkg/interface/config/urbitrc-sample index 1315a1d06..c0b2be933 100644 --- a/pkg/interface/config/urbitrc-sample +++ b/pkg/interface/config/urbitrc-sample @@ -2,5 +2,6 @@ module.exports = { URBIT_PIERS: [ "/Users/user/ships/zod/home", ], - herb: false + herb: false, + URL: 'http://localhost:80' }; diff --git a/pkg/interface/config/webpack.dev.js b/pkg/interface/config/webpack.dev.js index 75e68ac1e..620100526 100644 --- a/pkg/interface/config/webpack.dev.js +++ b/pkg/interface/config/webpack.dev.js @@ -22,22 +22,49 @@ class UrbitShipPlugin { 'UrbitShipPlugin', async (compilation) => { const src = path.resolve(compiler.options.output.path, 'index.js'); - return Promise.all(this.piers.map(pier => { - const dst = path.resolve(pier, 'app/landscape/js/index.js'); - copyFile(src, dst).then(() => { - if(!this.herb) { - return; - } - pier = pier.split('/'); - const desk = pier.pop(); - return exec(`herb -p hood -d '+hood/commit %${desk}' ${pier.join('/')}`); - }); - })); + // uncomment to copy into all piers + // + // return Promise.all(this.piers.map(pier => { + // const dst = path.resolve(pier, 'app/landscape/js/index.js'); + // copyFile(src, dst).then(() => { + // if(!this.herb) { + // return; + // } + // pier = pier.split('/'); + // const desk = pier.pop(); + // return exec(`herb -p hood -d '+hood/commit %${desk}' ${pier.join('/')}`); + // }); + // })); } - ) + ); } } +let devServer = { + contentBase: path.join(__dirname, '../dist'), + hot: true, + port: 9000, + historyApiFallback: true +}; + +if(urbitrc.URL) { + devServer = { + ...devServer, + index: '', + proxy: { + '/~landscape/js/index.js': { + target: 'http://localhost:9000', + pathRewrite: (req, path) => '/index.js' + }, + '**': { + target: urbitrc.URL, + // ensure proxy doesn't timeout channels + proxyTimeout: 0 + } + } + }; +} + module.exports = { mode: 'development', entry: { @@ -54,7 +81,8 @@ module.exports = { plugins: [ '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-optional-chaining', - '@babel/plugin-proposal-class-properties' + '@babel/plugin-proposal-class-properties', + 'react-hot-loader/babel' ] } }, @@ -77,12 +105,7 @@ module.exports = { extensions: ['.js', '.ts', '.tsx'] }, devtool: 'inline-source-map', - // devServer: { - // contentBase: path.join(__dirname, './'), - // hot: true, - // port: 9000, - // historyApiFallback: true - // }, + devServer: devServer, plugins: [ new UrbitShipPlugin(urbitrc) // new CleanWebpackPlugin(), diff --git a/pkg/interface/package-lock.json b/pkg/interface/package-lock.json index 0fa9a6661..df9bb156f 100644 --- a/pkg/interface/package-lock.json +++ b/pkg/interface/package-lock.json @@ -3510,6 +3510,12 @@ "entities": "^2.0.0" } }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "dev": true + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -4761,6 +4767,16 @@ "is-glob": "^4.0.1" } }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dev": true, + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -6113,6 +6129,15 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "dev": true, + "requires": { + "dom-walk": "^0.1.0" + } + }, "mini-create-react-context": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", @@ -7327,11 +7352,41 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, + "react-hot-loader": { + "version": "4.12.21", + "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.21.tgz", + "integrity": "sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA==", + "dev": true, + "requires": { + "fast-levenshtein": "^2.0.6", + "global": "^4.3.0", + "hoist-non-react-statics": "^3.3.0", + "loader-utils": "^1.1.0", + "prop-types": "^15.6.1", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "dev": true + }, "react-markdown": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-4.3.1.tgz", @@ -9452,8 +9507,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -9474,14 +9528,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9496,20 +9548,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -9626,8 +9675,7 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -9639,7 +9687,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -9654,7 +9701,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -9662,14 +9708,12 @@ "minimist": { "version": "1.2.5", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -9688,7 +9732,6 @@ "version": "0.5.3", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "^1.2.5" } @@ -9750,8 +9793,7 @@ "npm-normalize-package-bin": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "npm-packlist": { "version": "1.4.8", @@ -9779,8 +9821,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -9792,7 +9833,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -9870,8 +9910,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -9907,7 +9946,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9927,7 +9965,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9971,14 +10008,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -10459,8 +10494,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -10481,14 +10515,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10503,20 +10535,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -10633,8 +10662,7 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -10646,7 +10674,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -10661,7 +10688,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -10669,14 +10695,12 @@ "minimist": { "version": "1.2.5", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -10695,7 +10719,6 @@ "version": "0.5.3", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "^1.2.5" } @@ -10757,8 +10780,7 @@ "npm-normalize-package-bin": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "npm-packlist": { "version": "1.4.8", @@ -10786,8 +10808,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -10799,7 +10820,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -10877,8 +10897,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -10914,7 +10933,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -10934,7 +10952,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -10978,14 +10995,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/pkg/interface/package.json b/pkg/interface/package.json index b2be946bd..4f85e8c21 100644 --- a/pkg/interface/package.json +++ b/pkg/interface/package.json @@ -46,6 +46,7 @@ "eslint-plugin-react": "^7.19.0", "file-loader": "^6.0.0", "html-webpack-plugin": "^4.2.0", + "react-hot-loader": "^4.12.21", "sass": "^1.26.5", "sass-loader": "^8.0.2", "webpack": "^4.43.0", @@ -59,7 +60,7 @@ "tsc:watch": "tsc --watch", "build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.dev.js", "build:prod": "cross-env NODE_ENV=production webpack --config config/webpack.prod.js", - "start": "webpack-dev-server", + "start": "webpack-dev-server --config config/webpack.dev.js", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", diff --git a/pkg/interface/src/App.js b/pkg/interface/src/App.js index 7e2e62afb..a34223d28 100644 --- a/pkg/interface/src/App.js +++ b/pkg/interface/src/App.js @@ -1,3 +1,5 @@ +import { hot } from 'react-hot-loader/root'; +import 'react-hot-loader'; import * as React from 'react'; import { BrowserRouter as Router, Route, withRouter, Switch } from 'react-router-dom'; import styled, { ThemeProvider, createGlobalStyle } from 'styled-components'; @@ -45,7 +47,7 @@ const Content = styled.div` const StatusBarWithRouter = withRouter(StatusBar); -export default class App extends React.Component { +class App extends React.Component { constructor(props) { super(props); this.ship = window.ship; @@ -152,3 +154,5 @@ export default class App extends React.Component { } } +export default process.env.NODE_ENV === 'production' ? App : hot(App); +