Question:
Building an Electron app from scratch

In the previous blog, >Building an Electron App from Scratch (Part 1), we learned about Electron, starting with npm, creating a browser window, and adding Typescript. Now its time to move on to building a real desktop application. In this blog, we will learn to create an app website, add React, and bundle it with Webpack.


Read our blog on >how to integrate SASS into the build and consume a UI toolkit.


We will start with a simple HTML code and make it more complicated as we go.


  

    Electron from scratch

  

  

    

      Hello, world!

    

  



The electron code must also be modified so that the file is loaded rather than example.com.


window.loadURL(`file://${__dirname}/../website/index.html`);



Since electron is running out/electron/index.js, we load index.html to that path. We need to transfer our website from src/website/index.html to out/website/index.html


Now, add a quick copy command to our build script in package.json:


"build": "tsc && cp ./src/website/index.html

                    ./out/website/index.html",



And a quick npm run start shows off the new content:


It's looking very simple. Let’s add some more code to make it more interactive.


  

    Electron from scratch

  

  

    

      Loading...

    

    

    

    

  



import * as React from 'react';

import * as ReactDOM from 'react-dom';


// We find our app DOM element as before

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


// Here's an example of a couple of simple React components

const Emphasis: React.FunctionComponent = props => {props.children};


// You can see how we can mix html and nested components together

const App = () => (

  

    Hello, world

  

);


// Finally, we render our top-level component to the actual DOM.

ReactDOM.render(, app);



// Here's what the above code actually gets compiled to.

// I've added some comments to explain each bit.


// This line turns on "strict mode" for javascript.

// It's an older feature which will cause some problematic patterns to fail explicitly.

"use strict";


// We have some boilerplate here where typescript is attempting to define a module system.

// We'll look at this in more detail in the next section, since this doesn't quite work just yet.

var __importStar = (this && this.__importStar) || function (mod) {

    if (mod && mod.__esModule) return mod;

    var result = {};

    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];

    result["default"] = mod;

    return result;

};

Object.defineProperty(exports, "__esModule", { value: true });


// Now our import statements are translated into usages of the module system

const React = __importStar(require("react"));

const ReactDOM = __importStar(require("react-dom"));


// The start of our actual code: this line is copied verbatim

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


// We can see here how our JSX is translated into calls to React.createElement

// Also, the type definition on Emphasis is stripped out.

const Emphasis = (props) => React.createElement("em", null, props.children);

const App = () => (React.createElement("div", null,

    "Hello, ",

    React.createElement(Emphasis, null, "world")));


// Finally, the JSX inside our ReactDom call is expanded out and we start the render.

ReactDOM.render(React.createElement(App, null), app);



Bundling javascript files together

We must figure out how to ship React alongside our app. We're currently referencing React from an online CDN, but we don't want our desktop application to depend on an internet resource.


We'll also need to find out how to break our app into numerous files that reference each other as it gets more involved.


Webpack will solve these issues. We'll use it for simple tasks for now, but webpack setting is notoriously difficult.


Webpack's main function is to bundle our module references and package our code with React. That module boilerplate typescript converted our import * as React from "react"; calls into? That calls a genuine module system.


Here is how we can add npm modules to get started:


> npm install --save-dev webpack webpack-cli ts-loader

> npm install --save react react-dom


Here in the abode written codes, webpack and webpack-cli provide the build tool. Additionally, we require ts-loader so that it can be integrated with the typescript compiler and added to the build process. Instead of using a hosted CDN, we are now pulling react and react-dom from npm.


Webpack's configuration is done all in one place, unlike some other build tools where we specify a series of steps that source code must go through. This is how our initial webpack configuration looks:



'use strict';


// pull in the 'path' module from node

const path = require('path');


// export the configuration as an object

module.exports = {

  // development mode will set some useful defaults in webpack

  mode: 'development',

  // the entry point is the top of the tree of modules.

  // webpack will bundle this file and everything it references.

  entry: './src/website/index.tsx',

  // we specify we want to put the bundled result in the matching out/ folder

  output: {

    filename: 'index.js',

    path: path.resolve(__dirname, 'out/website'),

  },

  module: {

    // rules tell webpack how to handle certain types of files

    rules: [

      // at the moment the only custom handling we have is for typescript files

      // .ts and .tsx files get passed to ts-loader

      {

        test: /\.tsx?$/,

        loader: 'ts-loader',

      },

    ],

  },

  resolve: {

    // specify certain file extensions to get automatically appended to imports

    // ie we can write `import 'index'` instead of `import 'index.ts'`

    extensions: ['.ts', '.tsx', '.js'],

  },

};



Now, we have to replace the call to tsc in the package.json build script with webpack --config webpack.website.config.js and run it. This gist shows our index.js's enormous size increase. The overall structure and how each library we depend on has been inlined into a module system can be seen without understanding every line. The file contains our compiled index.tsx at the bottom.


The above Webpack setup produces the website side of the application. Because the electron process runs in a node and the website process runs in a browser environment, several features, like the module system, will be different, so we require unique configurations for each. Electron configuration:

'use strict';


const path = require('path');


module.exports = {

  mode: 'development',

  entry: './src/electron/index.ts',

  output: {

    filename: 'index.js',

    path: path.resolve(__dirname, 'out/electron')

  },

  module: {

    rules: [{ test: /\.tsx?$/, loader: 'ts-loader' }]

  },

  resolve: {

    extensions: ['.ts', '.tsx', '.js']

  },

  // tell webpack that we're building for electron

  target: 'electron-main',

  node: {

    // tell webpack that we actually want a working __dirname value

    // (ref: https://webpack.js.org/configuration/node/#node-__dirname)

    __dirname: false

  }

};



Most of the content is above code, just like the previous one. However, the main difference strikes in the target : 'electron-main' and node: { __dirname: false } lines


The former affects some module loading settings; for instance, our electron import in src/electron/index.ts is converted into a regular node require("electron") call rather than being handled directly by webpack, and electron itself is not inlined into the bundle. The latter is necessary to make loading our local index.html work.


We can also ask webpack to copy the index.html file for us since it is currently only handling our typescript code. We could just use a straightforward file copy plugin. However the html-webpack-plugin can make webpack perform a little bit more intelligently. We add the following to our config after using npm to install the plugin:

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {

  // [...]

  plugins: [

    new HtmlWebpackPlugin({

      template: 'src/website/index.html',

    }),

  ],

}


Now that webpack has been installed, it will actually read our index.html file, add a script tag that calls the webpack entry point, and then write it to the output folder. As a result, when writing the HTML, we don't need to be aware of the entry point's name, nor do we need to add a copy step to our npm build script.


Additionally, webpack supports the --watch parameter, so our build:watch script continues to function as normal.


This is now our screen will look like after all the above coding

In the previous blog, Building an Electron App from Scratch (Part 1), we learned about Electron, starting with npm, creating a browser window, and adding Typescript. Now its time to move on to building a real desktop application. In this blog, we will learn to create an app website, add React, and bundle it with Webpack.


We will start with a simple HTML code and make it more complicated as we go.


  

    Electron from scratch

  

  

    

      Hello, world!

    

  



The electron code must also be modified so that the file is loaded rather than example.com.


window.loadURL(`file://${__dirname}/../website/index.html`);



Since the electron is running out/electron/index.js, we load index.html to that path. We need to transfer our website from src/website/index.html to out/website/index.html


Now, add a quick copy command to our build script in package.json:


"build": "tsc && cp ./src/website/index.html

                    ./out/website/index.html",



And a quick npm run start shows off the new content:



It looks very simple. Let’s add some more code to make it more interactive.


  

    Electron from scratch

  

  

    

      Loading...

    

    

    

    

  



import * as React from 'react';

import * as ReactDOM from 'react-dom';


// We find our app DOM element as before

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


// Here's an example of a couple of simple React components

const Emphasis: React.FunctionComponent = props => {props.children};


// You can see how we can mix html and nested components together

const App = () => (

  

    Hello, world

  

);


// Finally, we render our top-level component to the actual DOM.

ReactDOM.render(, app);


// Here's what the above code actually gets compiled to.

// I've added some comments to explain each bit.


// This line turns on "strict mode" for javascript.

// It's an older feature which will cause some problematic patterns to fail explicitly.

"use strict";


// We have some boilerplate here where typescript is attempting to define a module system.

// We'll look at this in more detail in the next section, since this doesn't quite work just yet.

var __importStar = (this && this.__importStar) || function (mod) {

    if (mod && mod.__esModule) return mod;

    var result = {};

    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];

    result["default"] = mod;

    return result;

};

Object.defineProperty(exports, "__esModule", { value: true });


// Now our import statements are translated into usages of the module system

const React = __importStar(require("react"));

const ReactDOM = __importStar(require("react-dom"));


// The start of our actual code: this line is copied verbatim

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


// We can see here how our JSX is translated into calls to React.createElement

// Also, the type definition on Emphasis is stripped out.

const Emphasis = (props) => React.createElement("em", null, props.children);

const App = () => (React.createElement("div", null,

    "Hello, ",

    React.createElement(Emphasis, null, "world")));


// Finally, the JSX inside our ReactDom call is expanded out and we start the render.

ReactDOM.render(React.createElement(App, null), app);



Bundling javascript files together

We must figure out how to ship React alongside our app. We're currently referencing React from an online CDN, but we don't want our desktop application to depend on an internet resource.


We'll also need to find out how to break our app into numerous files that reference each other as it gets more involved.


Webpack will solve these issues. We'll use it for simple tasks for now, but webpack setting is notoriously difficult.


Webpack's main function is to bundle our module references and package our code with React. That module boilerplate typescript converted our import * as React from "react"; calls into? That is called a genuine module system.


Here is how we can add npm modules to get started:


> npm install --save-dev webpack webpack-cli ts-loader

> npm install --save react react-dom


Here in the abode written codes, webpack and webpack-cli provide the build tool. 


Additionally, we require a ts-loader so that it can be integrated with the typescript compiler and added to the build process. Instead of using a hosted CDN, we are now pulling react and react-dom from npm.


Webpack's configuration is done all in one place, unlike some other build tools where we specify a series of steps that the source code must go through. This is how our initial webpack configuration looks:



'use strict';


// pull in the 'path' module from node

const path = require('path');


// export the configuration as an object

module.exports = {

  // development mode will set some useful defaults in webpack

  mode: 'development',

  // the entry point is the top of the tree of modules.

  // webpack will bundle this file and everything it references.

  entry: './src/website/index.tsx',

  // we specify we want to put the bundled result in the matching out/ folder

  output: {

    filename: 'index.js',

    path: path.resolve(__dirname, 'out/website'),

  },

  module: {

    // rules tell webpack how to handle certain types of files

    rules: [

      // at the moment the only custom handling we have is for typescript files

      // .ts and .tsx files get passed to ts-loader

      {

        test: /\.tsx?$/,

        loader: 'ts-loader',

      },

    ],

  },

  resolve: {

    // specify certain file extensions to get automatically appended to imports

    // ie we can write `import 'index'` instead of `import 'index.ts'`

    extensions: ['.ts', '.tsx', '.js'],

  },

};



Now, we have to replace the call to tsc in the package.json build script with webpack --config webpack.website.config.js and run it. This gist shows our index.js's enormous size increase. The overall structure and how each library we depend on has been inlined into a module system can be seen without understanding every line. The file contains our compiled index.tsx at the bottom.


The above Webpack setup produces the website side of the application. Because the electron process runs in a node and the website process runs in a browser environment, several features, like the module system, will be different, so we require unique configurations for each. Electron configuration:


'use strict';


const path = require('path');


module.exports = {

  mode: 'development',

  entry: './src/electron/index.ts',

  output: {

    filename: 'index.js',

    path: path.resolve(__dirname, 'out/electron')

  },

  module: {

    rules: [{ test: /\.tsx?$/, loader: 'ts-loader' }]

  },

  resolve: {

    extensions: ['.ts', '.tsx', '.js']

  },

  // tell webpack that we're building for electron

  target: 'electron-main',

  node: {

    // tell webpack that we actually want a working __dirname value

    // (ref: https://webpack.js.org/configuration/node/#node-__dirname)

    __dirname: false

  }

};



Most of the content is above code, just like the previous one. However, the main difference strikes in the target : 'electron-main' and node: { __dirname: false } lines


The former affects some module loading settings; for instance, our electron import in src/electron/index.ts is converted into a regular node require("electron") call rather than being handled directly by webpack, and electron itself is not inlined into the bundle. The latter is necessary to make loading our local index.html work.


We can also ask webpack to copy the index.html file for us since it is currently only handling our typescript code. We could just use a straightforward file copy plugin. However, the html-webpack-plugin can make webpack perform a little bit more intelligently. We add the following to our config after using npm to install the plugin:


const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {

  // [...]

  plugins: [

    new HtmlWebpackPlugin({

      template: 'src/website/index.html',

    }),

  ],

}


Now that webpack has been installed, it will actually read our index.html file, add a script tag that calls the webpack entry point, and then write it to the output folder. As a result, when writing the HTML, we don't need to be aware of the entry point's name, nor do we need to add a copy step to our npm build script.


Additionally, webpack supports the --watch parameter, so our build:watch script continues to function as normal.


This is now our screen will look like after all the above coding



Adequate Infosoft

Adequate Infosoft

Submit
0 Answers