` with id `react-root` that React can attach to.
121 |
122 | ```
123 |
124 |
125 |
126 |
Electron App
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | ```
135 |
136 | Run `npm run start` to see it all spinning. The script `./node_modules/babel-core/browser.js` transpiles our JSX with Babel on the client.
137 |
138 |
139 | ## Bundling it up
140 |
141 | Having the JSX transpile at runtime is fine for testing it out and joshin' around, but it isn't particularly ideal in any real setting. Let's fix it and use Webpack to bundle up our JavaScript.
142 |
143 | Edit `index.html` and replace the two script tags with one single tag pointing at `./build/bundle.js`. In a moment we'll configure Webpack to output our JavaScript into this file.
144 |
145 | ```
146 |
147 |
148 |
149 |
Electron App
150 |
151 |
152 |
153 |
154 |
155 |
156 | ```
157 |
158 | Before installing Webpack from `npm`, we're going to create its configuration file. Below is `webpack.config.js` in its entirety.
159 |
160 | ```
161 | var webpack = require('webpack');
162 |
163 | module.exports = {
164 | context: __dirname + '/src',
165 | entry: './entry.js',
166 |
167 | output: {
168 | filename: 'bundle.js',
169 | path: __dirname + '/build'
170 | },
171 |
172 | module: {
173 | loaders: [
174 | { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }
175 | ]
176 | }
177 | };
178 | ```
179 |
180 | Let's go through the file, it's just regular JavaScript (which means you can do all sorts of fancy JavaScripting if you feel like it).
181 |
182 | ```
183 | // webpack.config.js
184 | module.exports = { /* ... */ };
185 | ```
186 |
187 | This exports the configuration object.
188 |
189 | ```
190 | context: __dirname + '/src',
191 | entry: './entry.js',
192 | ```
193 |
194 | The `entry` property is the entry point for Webpack when it starts bundling everything together. Everything that's required directly in this file, or in subsequently required files, will be processed by Webpack. This includes non-JavaScript as well, which we'll get to later when we include Sass styles.
195 |
196 | The `context` property is an absolute path. It's used when resolving the location of `entry`, and since our entry file is `./src/entry.js` we'll put `__dirname + '/src'` in the `context` property and `entry.js` in the `entry` property.
197 |
198 |
199 | ```
200 | output: {
201 | filename: 'bundle.js',
202 | path: __dirname + '/build'
203 | },
204 | ```
205 |
206 | The above instructs Webpack to output the file `bundle.js` in the path `__dirname + '/build`, which is what we wrote earlier in `index.html`.
207 |
208 | ```
209 | module: {
210 | loaders: [
211 | { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }
212 | ]
213 | }
214 | ```
215 |
216 | Webpack supports a number of loaders for different file types. These are specified as an array of objects in `module.loaders`. The file type is matched by a regular expression, and when there's a match the file is processed by a loader.
217 |
218 | We can also ignore files with the `exclude` property. We don't want to bundle anything in the `node_modules` directory, so we exclude all JavaScript files from that folder by matching a regular expression.
219 |
220 | With `webpack.config.js` created, install Webpack (`npm install --save-dev webpack`) and run `./node_modules/.bin/webpack` in the project directory. This generates `./build/bundle.js`. To make running Webpack less tedious let's also add the command as an `npm` script.
221 |
222 | ```
223 | ...
224 | "scripts": {
225 | "start": "./node_modules/.bin/electron .",
226 | "build": "./node_modules/.bin/webpack"
227 | }
228 | ...
229 | ```
230 |
231 | Now run `npm run build && npm run start` and you should see the same `Hello from React!` page as in the previous section. However, now it's transpiled, bundled, and cooked so there's no real-time transpiling going on.
232 |
233 |
234 | ## Hot Reload Development Environment
235 |
236 | Webpack has a development server that detects updates to any files that are part of the bundle and automatically reloads those files. In fact, it's so fancy that it can replace only the modules that have been updated. Our example is not that fancy (but hey, feel free to go down that rabbit hole).
237 |
238 | First off we need to install the Webpack dev server with `npm install --save-dev webpack-dev-server`. With it installed you can run the live reloading dev server with the following command.
239 |
240 | ```
241 | ./node_modules/.bin/webpack-dev-server --hot --inline
242 | ```
243 |
244 | The dev server now continually builds the source files and serves them at `http://localhost:8080/`. In the name of consistency, let's change the `webpack.config.js` so that we serve the bundled files from `http://localhost:8080/build/` by adding a `publicPath` property to the `output` object.
245 |
246 | ```
247 | ...
248 | output: {
249 | filename: 'bundle.js',
250 | path: __dirname + '/build',
251 | publicPath: 'http://localhost:8080/build/'
252 | },
253 | ...
254 | ```
255 |
256 | We'll have to modify our project a tiny bit for this to work both development and production environments. In `index.html` we're still referring to the build output (`./build/bundle.js`) and not the dev server (`http://localhost:8080/build/bundle.js`). This is exactly what we want when packaging up the Electron app, but for development purposes we want it to look at the dev server. We'll make this happen by setting an environment variable as part of our `start` script in `package.json`.
257 |
258 | ```
259 | ...
260 | "start": "ENVIRONMENT=DEV ./node_modules/.bin/electron .",
261 | ...
262 | ```
263 |
264 | The environment variable `ENVIRONMENT` is set to `DEV` for the duration of the script command, which in this case is while the app is running. Since this is an Electron app and not a regular website, we can query for this variable in our `index.html`. If `process.env.ENVIRONMENT === 'DEV'` we point to the dev server's `bundle.js`.
265 |
266 | ```
267 | ...
268 |
269 |
270 |
283 |
284 | ...
285 | ```
286 |
287 | As in previous sections, let's add an `npm` script for running the dev server.
288 |
289 | ```
290 | ...
291 | "scripts": {
292 | "start": "ENVIRONMENT=DEV ./node_modules/.bin/electron .",
293 | "build": "./node_modules/.bin/webpack",
294 | "watch": "./node_modules/.bin/webpack-dev-server --hot --inline"
295 | }
296 | ...
297 | ```
298 |
299 | Now open two terminal windows and run `npm run watch` in one and `npm run start` in the other. As you make edits to `entry.js` or the files referenced from it, you'll see the Electron app update with the changes.
300 |
301 | _Note: Any changes to `main.js` or `index.html` will not automatically cause Webpack to live-reload. You will need to restart Webpack and the Electron app to see changes made in those files._
302 |
303 |
304 | ## Adding Sass
305 |
306 | Adding Sass styles is pretty simple using Webpack loaders. In `webpack.config.js`'s `module.loaders` we'll add a loader for pre-processing our Sass and then loading and applying it to our page (yes, we're letting Webpack add it to the document). First, install the loaders we need with `npm install --save-dev style-loader css-loader sass-loader`, and then use them in `webpack.config.js`.
307 |
308 | ```
309 | ...
310 | module: {
311 | loaders: [
312 | { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
313 | { test: /\.scss$/, loader: 'style-loader!css-loader!sass-loader' }
314 | ]
315 | }
316 | ...
317 | ```
318 |
319 | The syntax `style-loader!css-loader!sass-loader` will apply the `sass-loader`, `css-loader` and `style-loader` in right-to-left order to any `.scss` file that has been included in our JavaScript with `require()`. The `sass-loader` compiles the Sass markup to CSS, `css-loader` interprets and resolves `@import` and `url(...)` paths, and `style-loader` applies the CSS to the document. One nice thing to note is that Webpack will resolve any relative paths it encounters in the Sass markup.
320 |
321 | Now that we're prepped to load Sass, create a file `./static/styles/main.scss` in the project directory and `require()` it at the top of `./src/entry.js`.
322 |
323 | ```
324 | require('../static/sass/main.scss');
325 |
326 | var React = require('react');
327 | ...
328 | ```
329 |
330 | Run the project and you will see the styles from `main.scss` applied to the document. There are many other loaders out there, and if you for example prefer Less over Sass you'll easily replace `sass-loader` with `less-loader`.
331 |
332 |
333 | ## Extra: Package your Mac app
334 |
335 | Finally, you may wish to distribute your app. There's a command line tool called `electron-packager` that makes this process super simple. Install it (`npm install --save-dev electron-packager`) and add a `osx-package` script to your `package.json` scripts.
336 |
337 | ```
338 | ...
339 | "osx-package": "./node_modules/.bin/webpack -p && ./node_modules/electron-packager/cli.js ./ ElectronReactSass --out ./bin --platform=darwin --arch=x64 --version=0.34.0 --overwrite --ignore=\"ignore|bin|node_modules\""
340 | ...
341 | ```
342 |
343 | Unfortunately Apple does not allow Electron apps in the App Store, but you can still distribute it elsewhere. However, unless you code sign your app it will cause security warnings. Code signing is pretty simple process, but you will need a [Developer ID](https://developer.apple.com/developer-id/). In this app skeleton I've added the `npm` scripts `osx-sign` and `osx-verify` ([more on signing Electron apps](http://www.pracucci.com/atom-electron-signing-mac-app.html)).
344 |
345 | ```
346 | ...
347 | "osx-sign": "codesign --deep --force --verbose --sign \"
\" ./bin/ElectronReactSass-darwin-x64/ElectronReactSass.app",
348 |
349 | "osx-verify": "codesign --verify -vvvv ./bin/ElectronReactSass-darwin-x64/ElectronReactSass.app && spctl -a -vvvv ./bin/ElectronReactSass-darwin-x64/ElectronReactSass.app",
350 | ...
351 | ```
352 |
353 | Replace `` with your Developer ID and you should be ready to go. In other words, with these npm scripts in your `package.json`-file the following command should package your app, code sign it, and finally verify that your code signing went well.
354 |
355 | ```
356 | npm run osx-package && npm run osx-sign && npm run osx-verify
357 | ```
358 |
359 | All that's left now is have people download and use your Electron-powered app. Good luck!
360 |
361 | _--- Marcus Stenbeck / [@marcusstenbeck](http://twitter.com/marcusstenbeck) / [juxt.com](http://juxt.com/)_
362 |
363 | ## Links
364 |
365 | [This skeleton app on GitHub.](https://github.com/juxtinteractive/electron-react-sass)
366 |
367 | [Electron - Signing a Mac Application](http://www.pracucci.com/atom-electron-signing-mac-app.html)
368 |
369 | [electron-packager on GitHub](https://github.com/maxogden/electron-packager)
370 |
--------------------------------------------------------------------------------