| 1 | 1 | new file mode 100644 | 
                    
                | ... | ... | @@ -0,0 +1,105 @@ | 
                    
                |  | 0 | +--- | 
                    
                |  | 1 | +layout: post | 
                    
                |  | 2 | +title: "Integrate native Node.js modules into an Electron app (2/2)" | 
                    
                |  | 3 | +date: 2018-02-28 21:48 | 
                    
                |  | 4 | +comments: true | 
                    
                |  | 5 | +categories: [javascript] | 
                    
                |  | 6 | +cover: /images/cover/avatar.png | 
                    
                |  | 7 | +keywords:  | 
                    
                |  | 8 | +description:  | 
                    
                |  | 9 | +--- | 
                    
                |  | 10 | + | 
                    
                |  | 11 | +### tl;dr | 
                    
                |  | 12 | + | 
                    
                |  | 13 | +- package an Electron app into OS-specific bundle | 
                    
                |  | 14 | +- save space by keeping only a few needed `node_modules` directories—tips & tricks | 
                    
                |  | 15 | + | 
                    
                |  | 16 | +# Current state | 
                    
                |  | 17 | + | 
                    
                |  | 18 | +[So we've got super simple app which uses node.js features thanks to Electron](/2018/02/22/integrate-native-node-dot-js-modules-into-an-electron-app-1-slash-2/). Let's say it evolves into 100k LOC large app with dozens of dependencies (both browser-friendly and native node.js). How to produce a space-efficient  bundle (in the context of Electron)? | 
                    
                |  | 19 | + | 
                    
                |  | 20 | +# Overview and planning | 
                    
                |  | 21 | + | 
                    
                |  | 22 | +We'll be using [electron-packager](https://github.com/electron-userland/electron-packager) to create a OS-specific distributable bundle (*Electron bundle*). After we build javascript (*javascript bundle*) we keep an eye on native modules location and `node_modules` content inside Electron bundle.  | 
                    
                |  | 23 | + | 
                    
                |  | 24 | +Build production quality javascript bundle is webpack-specific (and probably also babel-specific). I won't cover this part as it has nothing to do with to Electron. If you use newest Webpack [4.0](https://github.com/webpack/webpack/releases/tag/v4.0.0) you can use nice new features related to development/production mode. | 
                    
                |  | 25 | + | 
                    
                |  | 26 | +# Organize package.json | 
                    
                |  | 27 | + | 
                    
                |  | 28 | +Electron-packager copies `node_modules` into the final Electron bundle (which is slow and isn't space-efficient at all). The good news is it ignores all packages in `devDependencies` group in `package.json`. We'll use that.  | 
                    
                |  | 29 | + | 
                    
                |  | 30 | +We need `bindings` dependency to keep in *Electron bundle* `node_modules`. The dependency is responsible for lazy loading of native node.js modules and cannot be part of *javascript bundle*. As it is a dependency of your project dependencies, it is not listed in `package.json`. Simply do `npm i --save bindings`. This can be tricky and can break things but yolo.  | 
                    
                |  | 31 | + | 
                    
                |  | 32 | +Notice deps groups: | 
                    
                |  | 33 | + | 
                    
                |  | 34 | +{% codeblock package.json lang:json %} | 
                    
                |  | 35 | +{ | 
                    
                |  | 36 | +  "name": "electron-tutorial", | 
                    
                |  | 37 | +  "main": "index.electron.js", | 
                    
                |  | 38 | +  "scripts": { | 
                    
                |  | 39 | +    "build": "webpack", | 
                    
                |  | 40 | +    "electron": "electron .", | 
                    
                |  | 41 | +    "test": "jest" | 
                    
                |  | 42 | +  }, | 
                    
                |  | 43 | +  "dependencies": { | 
                    
                |  | 44 | +    "bindings": "^1.3.0" | 
                    
                |  | 45 | +  }, | 
                    
                |  | 46 | +  "devDependencies": { | 
                    
                |  | 47 | +    "electron": "^1.8.2", | 
                    
                |  | 48 | +    "electron-packager": "^11.0.1", | 
                    
                |  | 49 | +    "electron-rebuild": "^1.7.3", | 
                    
                |  | 50 | +    "jest": "^22.4.0", | 
                    
                |  | 51 | +    "serialport": "^6.0.5", | 
                    
                |  | 52 | +    "webpack": "^3.11.0" | 
                    
                |  | 53 | +  } | 
                    
                |  | 54 | +} | 
                    
                |  | 55 | +{% endcodeblock %} | 
                    
                |  | 56 | + | 
                    
                |  | 57 | +In projects I develop there's usually a few non-Electron dependencies in the main Electron file (as seen in example below). Keep all non-Electron dependencies inside `dependencies` group (unless you plan to bundle the main file with Webpack's `target: 'electron-main'` option). | 
                    
                |  | 58 | + | 
                    
                |  | 59 | + | 
                    
                |  | 60 | +{% codeblock index.electron.js lang:javascript %} | 
                    
                |  | 61 | +const { app, BrowserWindow } = require('electron'); | 
                    
                |  | 62 | +const Raven = require('raven'); | 
                    
                |  | 63 | +const os = require('os'); | 
                    
                |  | 64 | +const isDev = require('electron-is-dev'); | 
                    
                |  | 65 | + | 
                    
                |  | 66 | +const isBundled = !isDev; | 
                    
                |  | 67 | + | 
                    
                |  | 68 | +if (process.env.NODE_ENV === 'production') { | 
                    
                |  | 69 | +  Raven.config('XXX', { | 
                    
                |  | 70 | +    captureUnhandledRejections: true, | 
                    
                |  | 71 | +    tags: { | 
                    
                |  | 72 | +      process: process.type, | 
                    
                |  | 73 | +      electron: process.versions.electron, | 
                    
                |  | 74 | +      chrome: process.versions.chrome, | 
                    
                |  | 75 | +      platform: os.platform(), | 
                    
                |  | 76 | +      platform_release: os.release() | 
                    
                |  | 77 | +    } | 
                    
                |  | 78 | +  }).install(); | 
                    
                |  | 79 | +} | 
                    
                |  | 80 | + | 
                    
                |  | 81 | +// ...rest of electron main file | 
                    
                |  | 82 | +{% endcodeblock %} | 
                    
                |  | 83 | + | 
                    
                |  | 84 | +I would keep `raven` and `electron-is-dev` in `dependencies` group. | 
                    
                |  | 85 | + | 
                    
                |  | 86 | +# Make sure there are native modules | 
                    
                |  | 87 | + | 
                    
                |  | 88 | +Simply copy all native modules (`*.node`) to `build` directory (they should be built in production quality by default). I wrote a few words about them in the [previous article](http://localhost:4000/2018/02/22/integrate-native-node-dot-js-modules-into-an-electron-app-1-slash-2/).  | 
                    
                |  | 89 | + | 
                    
                |  | 90 | +There's a tiny change in `relectron-rebuild` command. By default it won't rebuild modules in `devDependencies` group. Run the command with `t` option: `./node_modules/.bin/electron-rebuild -e node_modules/electron -t prod,dev`. | 
                    
                |  | 91 | + | 
                    
                |  | 92 | +*Note 1: I've run into this error while running Electron app: `Uncaught Error: Could not find module root given file: "file:///Users/cinan/Coding/js/electron-tutorial/electron-tutorial-darwin-x64/electron-tutorial.app/Contents/Resources/app/build/app.js". Do you have a package.json file?` This is a [known bug](https://github.com/TooTallNate/node-bindings/issues/29). There is a pull request (not yet merged), you can install fixed version with `npm i --save "bindings@https://github.com/ArnsboMedia/node-bindings.git#fix-getFileName-method-for-electron-use"`* | 
                    
                |  | 93 | + | 
                    
                |  | 94 | +# Can I build the package finally? | 
                    
                |  | 95 | + | 
                    
                |  | 96 | +First run `PLATFORM=electron npm run build` to create a *javacript bundle*. Build native modules with `./node_modules/.bin/electron-rebuild -e node_modules/electron -t prod,dev` and copy them into `build` directory: `cp node_modules/serialport/build/Release/serialport.node build`.  | 
                    
                |  | 97 | + | 
                    
                |  | 98 | +Now run `./node_modules/.bin/electron-packager . --overwrite` and wait a minute. New *Electron bundle* will be created inside directory `electron-tutorial-darwin-x64` (differs on Linux and Windows).  | 
                    
                |  | 99 | + | 
                    
                |  | 100 | +Check out `node_modules` in *Electron bundle* (in macOS it is `electron-tutorial-darwin-x64/electron-tutorial.app/Contents/Resources/app/node_modules`). There should be a single `bindings` directory. On macOS you can run the product with `open electron-tutorial-darwin-x64/electron-tutorial.app`. | 
                    
                |  | 101 | + | 
                    
                |  | 102 | +*Note 2: if you find out your `node_modules` directory is empty (although there are `dependencies` defined in `package.json`) then upgrade to npm@next `npm i -g npm@next` ([related bug](https://github.com/npm/npm/issues/19356)).* | 
                    
                |  | 103 | + | 
                    
                |  | 104 | + |