Pages

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.


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


<html>

  <head>

    <title>Electron from scratch</title>

  </head>

  <body style="width:100%;height:100%;margin:0">

    <div id="app" style="width:100%;height:100%">

      Hello, world!

    </div>

  </body>

</html>



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:



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


<html>

  <head>

    <title>Electron from scratch</title>

  </head>

  <body style="width:100%;height:100%;margin:0">

    <div id="app" style="width:100%;height:100%">

      Loading...

    </div>

    <script type="text/javascript" src="index.js" />

  </body>

</html>



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

if (app) {

  app.innerHTML = 'Hello, world!';

}



With the end of this code, we have a minimal page skeleton in HTML. Now we can start writing typescript to make it a real interactive application. To make use of the compiled javascript in the out/ folder, our copied html file makes reference to index.js rather than the index.ts we've created.


Building a website with React

React is an extremely powerful framework that allows us to write pure functions, but to build this application, we will not go too deep in React. So lets start with installing npn.


> npm install --save-dev @types/react @types/react-dom


We need to make tweaks to our build process to get React up and running, such as changing the jsx setting in tsconfig.json to 'react' instead of 'preserve'. We also need to invoke React to update the DOM with the ReactDOM.render(element, contents) method. Finally, we need to grab type definitions for react and react-dom to provide type-checking and code completion features.


Now, we will pull in the React libraries over the CDN and make a few changes in the code. Here’s how it all looks:


<html>

  <head>

    <title>Electron from scratch</title>

  </head>

  <body style="width:100%;height:100%;margin:0">

    <div id="app" style="width:100%;height:100%">

      Loading...

    </div>

    <!-- 

      For the moment we've just added references to hosted copies of React.

      These will get replaced in a moment.

    -->

    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>

    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

    <script type="text/javascript" src="index.js"></script>

  </body>

</html>



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 => <em>{props.children}</em>;


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

const App = () => (

  <div>

    Hello, <Emphasis>world</Emphasis>

  </div>

);


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

ReactDOM.render(<App />, 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 actually 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



No comments:

Post a Comment

Make new Model/Controller/Migration in Laravel

  In this article, we have included steps to create a model and controller or resource controller with the help of command line(CLI). Here w...