Use React, Electron, Dva, Webpack, Node.js, Websocket to quickly build cross-platform applications

Time:2019-9-11

Use React, Electron, Dva, Webpack, Node.js, Websocket to quickly build cross-platform applications

at presentElectronstaygithubAbovestarQuantity is about to followReact-nativeJust as much.

Here, under the Tucao,webpackI feel like I’m secretly updating every week. It’s terrible. AndAngularUpdate to8,VueA new version is coming out soon.5GThis year will be commercial and Huawei’s system will come out.RNIt hasn’t been updated to official yet.1Version, and technology that claims to make front-end developers unemployedflutterAlso in the crazy update, the front-end is really endless to learn

Use React, Electron, Dva, Webpack, Node.js, Websocket to quickly build cross-platform applications
Use React, Electron, Dva, Webpack, Node.js, Websocket to quickly build cross-platform applications

Back to the point, we can’t deny that the big front-end is really awesome.PCThe end can be developed across three platforms, and the mobile end can be written at one time to generate various small programs as well asReact-nativeApply, then run iniosIn Android and the webpage, I have to say here – — Jingdong’sTaroFramework these people have putNode.jsandwebpackSpent the day

YeswebpackUnfamiliar. Look at my previous articles. I don’t focus on them today.webpack

Welcome to my column “Advance to the Front” are all highly praised articles by hundreds of stars.

  • Handwritten React Optimized Scaffold
  • Incomplete manual for front-end performance optimization
  • Handwritten Vue scaffolding
  • This article source git warehouse address

Let’s talk about it first.ElectronOfficial website introduction:

UseJavaScript, HTML and CSSBuild cross-platform desktop applications. If you can build a website, you can build a desktop application.ElectronIs a useJavaScript, HTML and CSSTechnology creates the framework of native programs, which are responsible for the more difficult parts. You just need to focus on the core of your application.

  • What does that mean?
  • Electron = Node. JS + Google Browser + Common JS Code Generation ApplicationsUltimately packaged into installation packages is a complete application
  • ElectronThere are two processes, the main process is responsible for the more difficult part, rendering the process (usual).JSCode) part, responsible forUIInterface display
  • Between two processes can be throughremoteModules, andIPCRenderandIPCMainThe former is similar to mounting communication on global attributes (much like the earliest namespace modularization scheme), while the latter is based on publishing and subscribing mechanism, customizing event monitoring and triggering to achieve communication between two processes.
  • ElectronEquivalent toReactThe generated single page application has a shell. If it involves complex functions such as file manipulation, it depends on it.ElectronThe main process, because the main process can be called directlyNode.jsOfAPIIt can also be usedC++Plug-in, hereNode.jsThe degree of force is highlighted, which can be written in the background.CRUDIt can also be used as middleware, and now it can write the front end.

Talking about Technical Selection

  • UseReactGo to the bottomUIDrawing is the first choice for large projectsReact+TS
  • Best practices in state management are certainly notReduxAt present, it is preferreddvaOrredux-saga
  • Construction Tool SelectionwebpackIf notwebpackIt’s a real loss, it will severely limit your front-end development, so I suggest you study hard.Node.jsandwebpack
  • Choose the ordinary one.RestfulArchitecture, notGraphQLMaybe I am rightGraphQLUnderstanding is not deep, not understanding the essence
  • In the field of communication protocol, we choosewebsoketAnd ordinaryhttpcommunication mode
  • the reason being thatdemoThere are many areas that are not refined and will be targeted later.electronTo create an open source project of Netease Cloud Music, this must be done.

Start with a formal environment

Use React, Electron, Dva, Webpack, Node.js, Websocket to quickly build cross-platform applications

  • configFile placementwebpackconfiguration file
  • serverFolder placementNode.jsBack-end server code
  • srcDecomposition of source code
  • main.jsyesElectronThe entry file
  • jsonFiles are script entry files and package management files~

Development Mode Project Start-up Ideas:

  • Start firstwebpackPackaging code into memory for hot updates
  • RestartElectronRead the correspondingurlAddress file content, also hot update

Set upwebpackEntrance

        app: ['babel-polyfill', './src/index.js', './index.html'],
        vendor: ['react']
        }
    

ignoreElectronIn the code, do not usewebpackPacking (because)ElectronIf you have background module code, the package will report an error.


externals: [
        (function () {
            var IGNORES = [
                'electron'
            ];
            return function (context, request, callback) {
                if (IGNORES.indexOf(request) >= 0) {
                    return callback(null, "require('" + request + "')");
                }
                return callback();
            };
        })()
    ]
    

Add code splitting

optimization: {
        runtimeChunk: true,
        splitChunks: {
            chunks: 'all'
        }
    },

Setting up hot updates, etc.

 
    plugins: [
        new HtmlWebpackPlugin({
            template: './index.html'
        }),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.NamedModulesPlugin(),
    ],
    mode: 'development',
    devServer: {
        contentBase: '../build',
        open: true,
        port: 5000,
        hot: true
    },

Join inbabel

{
    loader: 'babel-loader',
    Options: {//jsx grammar
        presets: ["@babel/preset-react",
            // tree shaking loads babel-polifill presets on demand from back to front 
            ["@babel/preset-env", {
                "modules": false,
                "useBuiltIns": "false", "corejs": 2,
            }],
        ],

    plugins: [
        // Support import lazy loading plugins from front to back
        "@babel/plugin-syntax-dynamic-import",
        // andt-mobile loads true on demand is less, and if you don't use the value of less style, you can write'css' 
        ["import", { libraryName: "antd-mobile", style: true }],
        // Identifying class components
        ["@babel/plugin-proposal-class-properties", { "loose": true }],
        //
    ],
    cacheDirectory: true
},
}

Look at the configuration file for the main processmain.js

// Modules to control application life and create native browser window
const { app, BrowserWindow, ipcMain, Tray, Menu } = require('electron')
const path = require('path')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
app.disableHardwareAcceleration()
// ipcMain.on('sync-message', (event, arg) => {
//   console.log("sync - message")
//   // event.returnValue('message', 'tanjinjie hello')
// })
function createWindow() {
  // Create the browser window.
  tray = new Tray(path.join(__dirname, './src/assets/bg.jpg'));
  tray.setToolTip('wechart');
  tray.on('click', () => {
    mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show()
  });
  const contextMenu = Menu.buildFromTemplate([
    {label:'exit', click: () => mainWindow. quit ()},
  ]);
  tray.setContextMenu(contextMenu);
  mainWindow = new BrowserWindow({
    width: 805,
    height: 500,
    webPreferences: {
      nodeIntegration: true
    },
    // titleBarStyle: 'hidden'
    frame: false
  })

  // Custom Enlargement and Reduction Pallet Function
  ipcMain.on('changeWindow', (event, arg) => {
    if (arg === 'min') {
      console.log('min')
      mainWindow.minimize()
    } else if (arg === 'max') {
      console.log('max')
      if (mainWindow.isMaximized()) {
        mainWindow.unmaximize()
      } else {
        mainWindow.maximize()
      }
    } else if (arg === "hide") {
      console.log('hide')
      mainWindow.hide()
    }
  })
  // and load the index.html of the app.
  // mainWindow.loadFile('index.html')
  mainWindow.loadURL('http://localhost:5000');
  BrowserWindow.addDevToolsExtension(
    path.join(__dirname, './src/extensions/react-dev-tool'),
  );


  // Open the DevTools.
  // mainWindow.webContents.openDevTools()

  // Emitted when the window is closed.
  mainWindow.on('closed', function () {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    mainWindow = null
    BrowserWindow.removeDevToolsExtension(
      path.join(__dirname, './src/extensions/react-dev-tool'),
    );
  })
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', function () {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') app.quit()
})

app.on('activate', function () {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (mainWindow === null) createWindow()
})


// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

Today, I only talk about configuration in development mode, because there are too many, two articles have been written to the rest of the configuration.gitWarehouse view

Start the project in development mode:

  • Use "dev": "webpack-dev-server --config ./config/webpack.dev.js",Packing code into memory
  • Use "start": "electron ."openelectronRead the resources in the corresponding memory address to achieve hot update

After the project is started, at the entranceindex.jsIn the file, injectdva

import React from 'react'
import App from './App'
import dva from 'dva'
import Homes from './model/Homes'
import main from './model/main'
const app = dva()
app.router(({ history, app: store }) => (
  <App
    history={history}
    getState={store._store.getState}
    dispatch={store._store.dispatch}
  />
));
app.model(Homes)
app.model(main)
app.start('#root')

I have to say hereredux,redux-sage,dvaDirectly look at the picture

First isRedux

  • ReactOnly responsible for page rendering, not page logic, page logic can be extracted separately from it, become storeState and page logic from<App/>Take it out and become independent.store,
  • Page logic isReducr, <TodoList/> and <AddTodoBtn/>All arePure ComponentThroughconnectIt’s easy to add a layer to both of them.wrapperIn this way, we can build up and constructstoreLinks: Throughdispatchtowardsstore injectionactionPromptedstoreThe status changes and subscriptions are made at the same time. store When the state changes, the state is changed by ____________.connect The components are refreshed and used.dispatchtostoreSend outactionThis process can be intercepted and naturally added here.MiddlewareTo achieve various custom functions, eg: logging, so that each part of the responsibility, lower coupling, higher reuse, better scalability

Use React, Electron, Dva, Webpack, Node.js, Websocket to quickly build cross-platform applications

Then it’s injection.Redux-sage

  • As mentioned above, we can use Middleware to intercept action, so asynchronous network operation is very convenient, just make a Middleware. Here we use redux-saga class library, give a chestnut:
  • Click CreateTodoThe button to initiate an action of type == addTodo
  • sagaIntercept thisactionLaunchhttpIf the request succeeds, continue to reducer Send onetype == addTodoSuccOf actionTo prompt the creation to succeed, and vice versa type == addTodoFailOfactionthat will do

Finally: Dva

  • With the three steps ahead, DvaThe emergence of such a phenomenon is just like that. Dva According to the official website, Dva Based on React + Redux + Saga Best practices precipitate, doing three important things, greatly improving the coding experience:
  • holdstoreand sagaUnify into one modelThe concept, written in onejsInside file
  • Added oneSubscriptionsFor collecting other sourcesaction,Eg: Keyboard operation
  • modelIt’s very concise, similar to DSLperhaps RoR, codingQuick to fly _______________
  • Agreement is better than configuration. It’s always good.

Use React, Electron, Dva, Webpack, Node.js, Websocket to quickly build cross-platform applications

Use React, Electron, Dva, Webpack, Node.js, Websocket to quickly build cross-platform applications

At the entranceAPPIn components, injectionpropsTo realize the management of state tree

import React from 'react'
import { HashRouter, Route, Redirect, Switch } from 'dva/router';
import Home from './pages/home'
const Router = (props) => {
    return (
        <HashRouter>
            <Switch>
                <Route path="/home" component={Home}></Route>
                <Redirect to="/home"></Redirect>
            </Switch>
        </HashRouter>
    )
}
export default Router

In componentsconnectConnect the state tree

import React from 'react'
import { ipcRenderer } from 'electron'
import { NavLink, Switch, Route, Redirect } from 'dva/router'
import Title from '../../components/title'
import Main from '../main'
import Friend from '../firend'
import More from '../more'
import { connect } from 'dva'
import './index.less'
class App extends React.Component {
    componentDidMount() {
        ipcRenderer.send('message', 'hello electron')
        ipcRenderer.on('message', (event, arg) => {
            console.log(arg, new Date(Date.now()))
        })
        const ws = new WebSocket('ws://localhost:8080');
        ws.onopen = function () {
            ws.send('123')
            console.log('open')
        }
        ws.onmessage = function () {
            console.log('onmessage')
        }
        ws.onerror = function () {
            console.log('onerror')
        }
        ws.onclose = function () {
            console.log('onclose')
        }
    }
    componentWillUnmount() {
        ipcRenderer.removeAllListeners()
    }
    render() {
        console.log(this.props)
        return (
            <div className="wrap">
                <div className="nav">
                    <NavLink to="/home/main">Home</NavLink>
                    <NavLink to="/home/firend">Friend</NavLink>
                    <NavLink to="/home/more">More</NavLink>
                </div>
                <div className="content">
                    <Title></Title>
                    <Switch>
                        <Route path="/home/main" component={Main}></Route>
                        <Route path="/home/firend" component={Friend}></Route>
                        <Route path="/home/more" component={More}></Route>
                        <Redirect to="/home/main"></Redirect>
                    </Switch>
                </div>
            </div>
        )
    }
}
export default connect(
    ({ main }) => ({
        test: main.main
    })
)(App)
// ipcRenderer.sendSync('sync-message','sync-message')

What did you do with the components above?

  • Up in the component mounted life cycle function, startedwebsocketConnect, and mount event listeners that respond, send messages to the main thread, and trigger the main thread’smessageEvent.
  • When the component is about to be uninstalled, remove all event listeners for cross-process communication
  • UseddvaRouting jumps
  • Connect the state tree, read the state treemainModularmainState data

Subcomponents that go into the previous component

import React from 'react'
import { connect } from 'dva'
class App extends React.Component {
    handleAdd = () => {
        this.props.dispatch({
            type: 'home/add',
            val: 5,
            res: 1
        })
    }
    handleDel = () => {
    }
    render() {
        const { homes } = this.props
        console.log(this.props)
        return (
            <div>
                <button onClick={this.handleAdd}>add</button>
                <button onClick={this.handleDel}>{homes}</button>
            </div>
        )
    }
}
export default connect(
    ({ home, main }) => ({
        homes: home.num,
        mains: main.main
    })
)(App)

Let’s also see what this component does.

  • Connect state tree, readhome,mainThe state data of the module is converted intoprops
  • Binding events, if you click the button,dispatchCorrespondingeffectsUpdate the status tree data, and then update the page

Finally, let’s see how to control the window display of the main process through the rendering process.

import React from 'react'
import { ipcRenderer } from 'electron'
import './index.less'
export default class App extends React.Component {
    handle = (type) => {
        return () => {
            if (type === 'min') {
                console.log('min')
                ipcRenderer.send('changeWindow', 'min')
            } else if (type === 'max') {
                console.log('max')
                ipcRenderer.send('changeWindow', 'max')
            } else {
                console.log('hide')
                ipcRenderer.send('changeWindow', 'hide')
            }
        }
    }
    render() {
        return (
            <div className="title-container">
                <div className= "title" style= {"Webkit App Region": "drag"}> drag-and-drop area </div>
                <button onClick={this.handle ('min')}> Minimize </button>
                <button onClick={this.handle ('max')}> Maximize </button>
                <button onClick={this.handle ('hide')}> tray </button>
            </div>
        )
    }
}
  • adoptIPCRenderCommunication with the main process, control window display and hiding

Let’s go togetherdvaMedium managementmodelHave a look

  • homeModular
export default {
    namespace: 'home',
    state: {
        homes: [1, 2, 3],
        num: 0
    },
    reducers: {
        adds(state, { newNum }) {
            return {
                ...state,
                num: newNum
            }
        }
    },
    effects: {
        * add({ res, val }, { put, select, call }) {
            const { home } = yield select()
            console.log(home.num)
            yield console.log(res, val)
            const newNum = home.num + 1
            yield put({ type: 'adds', newNum })
        }
    },
}

dvaIt really saves us a lot of code, and it’s better maintained and easier to read.

  • Its approximate flow chart

Use React, Electron, Dva, Webpack, Node.js, Websocket to quickly build cross-platform applications

  • If not, it is recommended to go to the official website to see examples. Generally speaking, it is not like this.RXJSThe learning route is so steep.

Node.jsMiddle code

const express = require('express')
const { Server } = require("ws");
const app = express()
const wsServer = new Server({ port: 8080 })
wsServer.on('connection', (ws) => {
    ws.onopen = function () {
        console.log('open')
    }
    ws.onmessage = function (data) {
        console.log(data)
        ws.send('234')
        console.log('onmessage' + data)
    }
    ws.onerror = function () {
        console.log('onerror')
    }
    ws.onclose = function () {
        console.log('onclose')
    }
});

app.listen(8000, (err) => {
    If (! Err) {console. log ('listening on OK')} else{
        Console. log ('failed listening')
    }
})

Come up and give one first.websocket 8080Port listening, binding events, and usingexpressListen on native ports8000

  • In this way, an application does not necessarily need real-time communication, depending on the needs to decide when to do real-time communication.
  • RestfulThe architecture still exists.Node.jsAs middleware orIOMore output from the underlying serverCRUDFine

Today I’m going to write this first. The routines are the same. The basic shelf has been built. You can put the code in.cloneGo down and play slowly, add function. Give me one if you can.starHezan, thank you.

This article git warehouse source address, welcome star

Recommended Today

Resolving Time Zone Errors with Docker

1. The correct time zone in China is set to CST, i.e.China Standard TimeIn usedockerstart-upJenkinsWhen mirroring, the settings for mirroring are usuallyCoordinated Universal Time。 So add parameters at startup -v /etc/localtime:/etc/localtimeMount the local time zone to the mirror, so that the mirror gets the correct time zone. Then go to Jenkins and fill in the […]