Skip to content
This repository has been archived by the owner on Sep 25, 2018. It is now read-only.

Commit

Permalink
Build XDE with Webpack (#17)
Browse files Browse the repository at this point in the history
* [xde] Build XDE with Webpack

Build XDE with Webpack.

Benefits:
 - Adds HMR capabilities
 - Tree shaking with web pack 2
 - Potential other optimizations in the future that I haven't thought of yet.

Key points:
 - Keeps node_modules in `app` unbundled -- at the moment, there's no benefit in bundling them.
 - Uses source maps to keep debug-ability  (for some reason source-map support was turned off in Chrome Developer Tools in Electron for me...make sure to turn it on)
 - You can run with hot module reloading turned off or on -- run `npm run start[-local | -staging]-hot` to use it, omit the `-hot` to not.
 - Modified npm scripts so that you don't have to run any watcher script separately. Just run the correct `npm run start-blahblahblha` command and go.
 - No need to `yarn` in both directories -- `yarn` in `dev/xde` will use a postinstall script to `yarn` in the `dev/xde/app` directory and also rebuild any node_modules using electron rebuild.
 - When bundling with Webpack, don't transpile commonJS modules -- Webpack understands these and uses them to do tree shaking.
 - Temporarily, we need to use the `es2015` preset with babel when using HMR -- there is a bug: gaearon/react-hot-loader#391

cc @jesseruder @ide

* [xde] Install React Developer Tools

* [xde] Update yarn.lock.

* [XDE] Fix native dependency installation, update Electron

* [xde] Rebuild when app starts, not on install

* [xde] Fix dirname issue in renderer

* [xde] Clean up gulp + webpack config

* Fix yarn

* [xde] Fixup babel/HMR config

* re yarn

* s/index/renderer

* separate entry point for HMR

fbshipit-source-id: f4e811b
  • Loading branch information
skevy authored and expbot committed Nov 15, 2016
1 parent f38f732 commit c625b57
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 89 deletions.
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"presets": ["es2015-node6/object-rest", "es2017", "react"],
"presets": ["es2015-node6/object-rest", "es2017", "stage-1", "react"],
"plugins": [
"transform-class-properties",
"transform-decorators-legacy",
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ nvm use v6
```

- Install `gulp-cli`: `npm i -g gulp-cli`.
- Go into the `xde/` directory where you cloned the Git repo and run `npm install`.
- Go into the `xde/app` directory and run `npm install`.
- Go into the `xde/` directory where you cloned the Git repo and run `yarn` or `npm install`.
- Once that completes, run `npm start` from `xde/` to start the GUI.
- If you get a watchman error, you may need to increase your "max_queued_events" limit. On linux you can find this at /proc/sys/fs/inotify/max_queued_events.
- If you get `ENOENT: no such file or directory, open '.../node_modules/electron-prebuilt/path.txt'`, run `cd node_modules/electron-prebuilt && node install.js` from `xde/`. See the issue here: https://github.com/electron-userland/electron-prebuilt/issues/76.
- If you get `ENOENT: no such file or directory, open '.../node_modules/electron/path.txt'`, run `cd node_modules/electron && node install.js` from `xde/`. See the issue here: https://github.com/electron-userland/electron-prebuilt/issues/76.
22 changes: 9 additions & 13 deletions app/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,16 @@
document.getElementById('app-loading').addEventListener('click', () => {
remote.getCurrentWindow().openDevTools();
}, false);

const React = require('react');
const ReactDOM = require('react-dom');
const App = require('../build/ui/App');
let rootElement = React.createElement(App, {
segment: analytics,
});
let rootNode = document.getElementById('app');
ReactDOM.render(rootElement, rootNode);

window.addEventListener('beforeunload', () => {
ReactDOM.unmountComponentAtNode(rootNode);
});
})();
</script>
<script>
{
window.HMR = process.env.HOT ? true : false;
const script = document.createElement('script');
const port = process.env.DEVSERVER_PORT || 8282;
script.src = (process.env.HOT) ? `http://localhost:${port}/renderer.js` : '../build/renderer.js';
document.write(script.outerHTML);
}
</script>
</body>
</html>
57 changes: 21 additions & 36 deletions gulp/build-tasks.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import 'instapromise';

import electronPrebuilt from 'electron-prebuilt';
import electron from 'electron';
import {
installNodeHeaders,
rebuildNativeModules,
shouldRebuildNativeModules,
} from 'electron-rebuild';
import fs from 'fs';
import gulp from 'gulp';
import babel from 'gulp-babel';
import changed from 'gulp-changed';
import rename from 'gulp-rename';
import sourcemaps from 'gulp-sourcemaps';
import logger from 'gulplog';
import path from 'path';
import rimraf from 'rimraf';
Expand All @@ -27,44 +24,32 @@ const paths = {
};

let tasks = {
async buildNativeModules() {
let shouldRebuild = await shouldRebuildNativeModules(electronPrebuilt);
if (!shouldRebuild) {
return;
}

let versionResult = await spawnAsync(electronPrebuilt, ['--version']);
let electronVersion = /v(\d+\.\d+\.\d+)/.exec(versionResult.stdout)[1];

// When Node and Electron share the same ABI version again (discussion here:
// https://github.com/electron/electron/issues/5851) we can remove this
// check and rely solely on shouldRebuildNativeModules again
let hasHeaders = await hasNodeHeadersAsync(electronVersion);
if (hasHeaders) {
return;
}
buildNativeModules(force = false) {
return async function() {
let shouldRebuild = await shouldRebuildNativeModules(electron);
if (!shouldRebuild && !force) {
return;
}

logger.info(`Rebuilding native Node modules for Electron ${electronVersion}...`);
await installNodeHeaders(electronVersion);
await rebuildNativeModules(electronVersion, paths.nodeModules);
},
let versionResult = await spawnAsync(electron, ['--version']);
let electronVersion = /v(\d+\.\d+\.\d+)/.exec(versionResult.stdout)[1];

babel() {
return gulp.src(paths.source.js)
.pipe(changed(paths.build))
.pipe(sourcemaps.init())
.pipe(babel())
.pipe(sourcemaps.write('__sourcemaps__'))
.pipe(gulp.dest(paths.build));
},
// When Node and Electron share the same ABI version again (discussion here:
// https://github.com/electron/electron/issues/5851) we can remove this
// check and rely solely on shouldRebuildNativeModules again
let hasHeaders = await hasNodeHeadersAsync(electronVersion);
if (hasHeaders && !force) {
return;
}

watchBabel(done) {
gulp.watch(paths.source.js, tasks.babel);
done();
logger.info(`Rebuilding native Node modules for Electron ${electronVersion}...`);
await installNodeHeaders(electronVersion);
await rebuildNativeModules(electronVersion, paths.nodeModules);
};
},

icon() {
let contentsPath = path.dirname(path.dirname(electronPrebuilt));
let contentsPath = path.dirname(path.dirname(electron));
let resourcesPath = path.join(contentsPath, 'Resources');
return gulp.src(paths.macIcon)
.pipe(rename('electron.icns'))
Expand Down
27 changes: 6 additions & 21 deletions gulpfile.babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,22 @@ function getReleaseTask(platforms) {
};
}

gulp.task('build:deploy', tasks.babel);
gulp.task('build', gulp.parallel(
tasks.buildNativeModules,
tasks.babel,
gulp.task('rebuild', gulp.parallel(
tasks.buildNativeModules(),
tasks.icon,
));
gulp.task('watch', gulp.parallel(
tasks.buildNativeModules,
gulp.series(tasks.babel, tasks.watchBabel),
gulp.task('rebuild:force', gulp.parallel(
tasks.buildNativeModules(true),
tasks.icon,
));
gulp.task('release', gulp.series(
tasks.clean,
tasks.babel,
getReleaseTask(['mac', 'win', 'linux']),
tasks.verifyMacApp,
));
gulp.task('release:mac', gulp.series(
tasks.clean,
tasks.babel,
getReleaseTask(['mac']),
tasks.verifyMacApp,
));
gulp.task('release:windows', gulp.series(
tasks.clean,
tasks.babel,
getReleaseTask(['win']),
));
gulp.task('release:linux', gulp.series(
tasks.clean,
tasks.babel,
getReleaseTask(['linux']),
));
gulp.task('release:windows', getReleaseTask(['win']));
gulp.task('release:linux', getReleaseTask(['linux']));
gulp.task('clean', tasks.clean);
58 changes: 44 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
{
"private": true,
"scripts": {
"start": "gulp build && cross-env XDE_NPM_START=1 electron ./app",
"staging": "gulp build && cross-env XDE_NPM_START=1 EXPONENT_STAGING=1 electron ./app",
"start": "cross-env XDE_NPM_START=1 npm run _start",
"start-hot": "cross-env HOT=1 npm run start",
"start-staging": "cross-env XDE_NPM_START=1 EXPONENT_STAGING=1 npm run _start",
"start-staging-hot": "cross-env HOT=1 npm run start-staging",
"start-local": "cross-env XDE_NPM_START=1 EXPONENT_LOCAL=1 npm run _start",
"start-local-hot": "cross-env HOT=1 npm run start-local",
"_start": "concurrently --kill-others --raw \"npm run webpack\" \"npm run app\"",
"app": "gulp rebuild && electron ./app",
"webpack": "if [ -n \"$HOT\" ]; then npm run webpack-hot; else npm run webpack-dev; fi",
"webpack-dev": "webpack -w --env.dev",
"webpack-hot": "webpack --env.dev && cross-env BABEL_ENV=hot webpack-dev-server -w --env.hmr --env.dev",
"build": "cross-env NODE_ENV=production webpack --env.prod",
"postinstall": "cd ./app && yarn && cd ../ && npm run build",
"lint": "eslint src",
"local": "gulp build && cross-env XDE_NPM_START=1 EXPONENT_LOCAL=1 electron ./app",
"dist": "gulp release",
"mac": "gulp release:mac",
"win": "gulp release:windows",
"linux": "gulp release:linux"
"dist": "gulp clean && npm run build && gulp release",
"mac": "gulp clean && npm run build && gulp release:mac",
"win": "gulp clean && npm run build && gulp release:windows",
"linux": "gulp clean && npm run build && gulp release:linux"
},
"build": {
"asar": false,
Expand Down Expand Up @@ -46,30 +56,50 @@
"@ccheever/crayon": "^5.0.0",
"@exponent/json-file": "^5.0.1",
"@exponent/spawn-async": "^1.2.5",
"babel-eslint": "^7.0.0",
"babel-plugin-transform-class-properties": "^6.16.0",
"babel-core": "^6.18.0",
"babel-eslint": "^7.1.0",
"babel-loader": "^6.2.5",
"babel-plugin-dev-expression": "^0.2.1",
"babel-plugin-flow-react-proptypes": "^0.15.0",
"babel-plugin-transform-class-properties": "^6.18.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-runtime": "^6.9.0",
"babel-preset-babili": "^0.0.5",
"babel-preset-es2015": "^6.18.0",
"babel-preset-es2015-node6": "^0.3.0",
"babel-preset-es2017": "^6.16.0",
"babel-preset-react": "^6.5.0",
"babel-preset-react-optimize": "^1.0.1",
"babel-preset-stage-1": "^6.16.0",
"concurrently": "^3.1.0",
"cross-env": "^3.0.0",
"electron-builder": "^7.10.2",
"electron-prebuilt": "^1.3.1",
"electron-rebuild": "^1.1.5",
"electron": "1.4.6",
"electron-builder": "^7.14.2",
"electron-devtools-installer": "^2.0.1",
"electron-rebuild": "^1.3.0",
"eslint": "^3.1.1",
"eslint-config-exponent": "^4.0.0",
"eslint-plugin-babel": "^3.3.0",
"eslint-plugin-react": "^6.3.0",
"gulp": "gulpjs/gulp#4.0",
"getenv": "^0.7.0",
"gulp": "git+https://github.com/gulpjs/gulp#4.0",
"gulp-babel": "^6.1.2",
"gulp-changed": "^1.3.0",
"gulp-plumber": "^1.1.0",
"gulp-rename": "^1.2.2",
"gulp-sourcemaps": "^1.6.0",
"gulp-watch": "^4.3.6",
"gulplog": "^1.0.0",
"instapromise": "2.0.7-rc.1",
"rimraf": "^2.5.2"
"json-loader": "^0.5.4",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"react-hot-loader": "3.0.0-beta.6",
"rimraf": "^2.5.2",
"source-map-support": "^0.4.5",
"webpack": "2.1.0-beta.25",
"webpack-dev-server": "2.1.0-beta.9",
"webpack-node-externals": "^1.5.4"
}
}
8 changes: 8 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ if (!require('electron-squirrel-startup')) {
});

app.on('ready', () => {
if (process.env.NODE_ENV === 'development') {
const devToolsInstaller = require('electron-devtools-installer');
const { default: installExtension, REACT_DEVELOPER_TOOLS } = devToolsInstaller;

installExtension(REACT_DEVELOPER_TOOLS)
.then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log('An error occurred: ', err));
}
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1200,
Expand Down
51 changes: 51 additions & 0 deletions src/renderer-hot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @flow
*/

import React from 'react';
import ReactDOM from 'react-dom';
import Redbox from 'redbox-react';

import { AppContainer } from 'react-hot-loader';

import App from './ui/App';

const rootNode = document.getElementById('app');

const render = () => {
if (window.HMR) {
ReactDOM.render(
<AppContainer errorReporter={Redbox}>
<App segment={window.analytics} />
</AppContainer>,
rootNode
);
} else {
ReactDOM.render(
<App segment={window.analytics} />,
rootNode
);
}
};

window.addEventListener('beforeunload', () => {
ReactDOM.unmountComponentAtNode(rootNode);
});

if (window.HMR) {
// Hot Module Replacement API
if (module.hot) {
try {
// host re-render
// $FlowFixMe
module.hot.accept('./ui/App', render);
}
catch (error) {
// hot re-render failed. display a nice error page like inwebpack-hot-middleware
const RedBox = require('redbox-react');
ReactDOM.render(<RedBox error={error} className="redbox" />, rootNode);
}
}
}

render();
19 changes: 19 additions & 0 deletions src/renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @flow
*/

import React from 'react';
import ReactDOM from 'react-dom';

import App from './ui/App';

const rootNode = document.getElementById('app');

ReactDOM.render(
<App segment={window.analytics} />,
rootNode
);

window.addEventListener('beforeunload', () => {
ReactDOM.unmountComponentAtNode(rootNode);
});
2 changes: 1 addition & 1 deletion src/ui/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ class App extends React.Component {
};

async _versionStringAsync() {
let pkgJsonFile = new JsonFile(path.join(__dirname, '../../package.json'));
let pkgJsonFile = new JsonFile(path.join(__dirname, '../../app/package.json'));
let versionString = await pkgJsonFile.getAsync('version');
return versionString;
}
Expand Down
2 changes: 2 additions & 0 deletions src/ui/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

export { default as App } from './App';
Loading

0 comments on commit c625b57

Please sign in to comment.