-->
ads here

[ElectronJS] Xây dựng Desktop Application với ElectronJS, Webpack và React - Part 1

advertise here

Nếu bạn biết về lập trình web với ReactJS, HTML,CSS nhưng lại muốn làm một desktop app thì phải làm như thế nào? Trong bài viết hôm nay, mình sẽ hướng dẫn các bạn làm desktop app bằng ElectronJS kết hợp với ReactJS, HTML và CSS

Bài viết này là phần thứ nhất trong chuỗi bài về ElectronJS

1. Giới thiệu sơ lược về ElectronJS

ElectronJS là gì?

ElectronJS là một framework dùng để tạo ra Desktop native application. Đây là một open-source và cross - platform framework. Nhờ có ElectronJS bạn hoàn toàn có thể build một native app chạy trên được cả Window, MacOS và Linux chỉ cần sử dụng HTML, CSS và Javascript.

Trong bài viết này, mình sẽ hướng dẫn các bạn khởi tạo một Desktop app với ElectronJS, ReactJS và Webpack

2. Khởi tạo dự án

Bước đầu tiên là khởi tạo project bằng lệnh npm hoặc yarn. Bạn có thể dùng flag -y để cho gọn. Khi đó 1 file package.json sẽ được tạo ra.

$ mkdir <folder-name> && cd <folder-name> $ yarn -y # You can use npm instead $ npm -y

Sau khi file package.json được tạo ra, bạn có thể thay đổi các trường thông tin theo ý mình, tuy nhiên phải cần có các field sau:

  • productName: Tên của app, sẽ cần dùng bởi package electron-packager
  • version: Version cho app mỗi lần release (1.0.0)
  • homePage: URL to project home page, thường có giá trị là (./)
  • main: entry point của app (main.js)

3. Tạo react app

Trong phần này, chúng ta sẽ khởi tạo React App. Có 2 cách để tạo React app trong trường hợp này:

  1. Sử dụng create-react-app generator
  2. Tự tạo starter template và tự cấu hình từ đầu.

Chúng ta sẽ chọn hướng tiếp cận thứ 2 vì:

  • Khi tạo react app từ ban đầu, ta sẽ tự do cấu hình webpack và customize hơn so với create react app
  • Với create-react-app khi ta kết hợp với Electron phải chạy 2 process (1 là npm script và 1 là process của Electron) đồng thời. Trong khi nếu cách thứ 2, chỉ cần chạy 1 process đã có thể handle cả Electron và React

Đầu tiên chúng ta sẽ cài 2 package căn bản của React là reactreact-dom

$ yarn add react react-dom

Tiếp theo chúng ta sẽ tạo 2 component đầu tiên của ứng dụng.
Tạo file src/index.jsx với nội dung như sau để render component và gắn vào DOM

// src/index.jsx import React from 'react' import { render } from 'react-dom' // Import main App component import App from './components/app' // Since we are using HtmlWebpackPlugin WITHOUT a template, we should create our own root node in the body element before rendering into it let root = document.createElement('div') // Append root div to body root.id = 'root' document.body.appendChild(root) // Render the app into the root div render(<App />, document.getElementById('root'))

Tiếp theo ta sẽ tạo App component. Trong thư mục src tạo thư mục components và file App.jsx như sau

// src/components/App.jsx import React from 'react' // Create main App component const App = () => ( <div> <h1>Hello, electron!</h1> <p>Let's start building your awesome desktop app with electron and React!</p> </div> ) // Export the App component export default App

4. Thêm electronJS và config main process

Sau khi đã xử lý xong với React, bước tiếp theo ta sẽ “take care” đến Electron. Cài đặt electronelectron-packager.

  • electron thì khỏi phải bàn, package này giúp chúng ta chạy được electron
  • electron-packager cho phép chúng ta build ứng dụng electron về các file bundle tương ứng với từng hệ điều hành như .exe (Windows) hoặc .dmg (MacOS)
$ yarn add electron electron-packager

Ngoài ra chúng ta có thể cài thêm electron-devtools-installer để có thể cài đặt devtool vào electron app, nhăm hỗ trợ debug tốt hơn trong quá trình code

$ yarn add electron-devtools-installer

Sau khi đã cài đặt xong, chúng ta tiến hành config main process của electron. Trong thư mục root của project, tạo file main.js (có thể đổi tên khác nhưng lưu ý là phải đúng với đường dẫn ở field main trong package.json đã nói ở trên)

// main.js 'use strict' const { app, BrowserWindow, remote } = require('electron'); const path = require('path'); const url = require('url'); const { default: installExtension, REACT_DEVELOPER_TOOLS } = require('electron-devtools-installer'); let mainWindow; console.log(`Running in ${process.env.NODE_ENV} mode`); let dev = process.env.NODE_ENV === 'development'; if (process.platform === 'win32') { app.commandLine.appendSwitch('high-dpi-support', 'true') app.commandLine.appendSwitch('force-device-scale-factor', '1') } function createWindow() { mainWindow = new BrowserWindow({ width: 1024, height:768, show: false, webPreferences: { nodeIntegration: true } }); let indexPath; if (dev && process.argv.indexOf('--noDevServer') === -1) { indexPath = url.format({ protocol: 'http:', host: 'localhost:8080', pathname: 'index.html', slashes: true }) } else { indexPath = url.format({ protocol: 'file:', pathname: path.join(__dirname, 'dist', 'index.html'), slashes: true }) } mainWindow.loadURL(indexPath); mainWindow.once('ready-to-show', () => { mainWindow.show(); if (dev) { installExtension(REACT_DEVELOPER_TOOLS) .catch(err => console.log('Error loading React DevTools: ', err)) mainWindow.webContents.openDevTools() } }); mainWindow.once('closed', () => { mainWindow = null; }) } app.on('ready', createWindow); app.on('window-all-closed', () => { // 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', () => { // 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() } })

5. Cấu hình webpack

Sau khi React và Electron đã sẵn sàng, công việc tiếp theo của chúng ta là cấu hình webpack. Ở đây chúng ta sẽ tạo ra 2 file webpack config cho 2 môi trường là development và production.
Nội dung của 2 file này phần lớn là như nhau, chỉ khác nhau một số điểm như sau:

  • webpack.dev.config.js: sử dụng devServer và sourcemap
  • webpack.build.config.js: sẽ sử dụng mini-css-extract-plugin để optimize css style và dùng babili-webpack-plugin như babel minifier

Cụ thể như sau:
Trong thư mục của project, tạo file webpack.dev.config.js

const webpack = require('webpack'); const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') const { spawn } = require('child_process'); const defaultInclude = path.resolve(__dirname, 'src'); module.exports = { mode: 'developement', module: { rules: [ { test: /\.css$/, use: [ { loader: 'style-loader' }, { loader: 'css-loader' } ], include: defaultInclude, }, { test: /\.s[ac]ss$/i, use: ['style-loader', 'css-loader', 'sass-loader'], include: defaultInclude, }, { test: /\.jsx?$/, use: ['babel-loader'], include: defaultInclude, }, { test: /\.(jpe?g|png|gif)$/, use: [{ loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]' }], include: defaultInclude, }, { test: /\.(eot|svg|ttf|woff|woff2)$/, use: [{ loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]' }], include: defaultInclude } ] }, target: 'electron-renderer', plugins: [ new HtmlWebpackPlugin(), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('development') }) ], devtool: 'cheap-source-map', devServer: { contentBase: path.resolve(__dirname, 'dist'), stats: { colors: true, chunks: false, children: false }, before() { spawn( 'electron', ['.'], { shell: true, env: process.env, stdio: 'inherit' } ) .on('close', code => process.exit(0)) .on('error', spawnError => console.error(spawnError)) } }, resolve: { extensions: ['.js', '.json', '.jsx'] } }

Và tương tự tạo file webpack.build.config.js cho production build

const webpack = require('webpack'); const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const BabiliPlugin = require('babili-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin') const defaultInclude = path.resolve(__dirname, 'src') module.exports = { mode: 'production', module: { rules: [ { test: /\.css$/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader' } ], include: defaultInclude, }, { test: /\.s[ac]ss$/i, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], include: defaultInclude, }, { test: /\.jsx?$/, use: ['babel-loader'], include: defaultInclude, }, { test: /\.(jpe?g|png|gif)$/, use: [{ loader: 'file-loader?name=img/[name]__[hash:base64:5].[ext]' }], include: defaultInclude, }, { test: /\.(eot|svg|ttf|woff|woff2)$/, use: [{ loader: 'file-loader?name=font/[name]__[hash:base64:5].[ext]' }], include: defaultInclude } ] }, target: 'electron-renderer', plugins: [ new HtmlWebpackPlugin(), new MiniCssExtractPlugin({ // Options similar to the same options in webpackOptions.output // both options are optional filename: 'bundle.css', chunkFilename: '[id].css' }), new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }), new BabiliPlugin() ], stats: { colors: true, children: false, chunks: false, modules: false }, resolve: { extensions: ['.js', '.json', '.jsx'] } }

  • Lưu ý: trong project này mình sẽ dùng SCSS để style thay vì dùng CSS, nên sẽ cấu hình thêm việc đọc file .scss

Sau khi config webpack xong, chúng ta sẽ cái các package cần thiết, bao gồm webpack, các loader, và các plugin

$ yarn add -D webpack webpack-cli webpack-dev-server @babel/core @babel/preset-react babili-webpack-plugin babel-loader css-loader sass-loader file-loader style-loader html-webpack-plugin mini-css-extract-plugin

Chúng ta biết rằng, webpack sử dụng babel và @babel/preset-react để build code React, nhưng webpack không tự nhận diện ra plugin này 1 cách tự động. Vì vậy chúng ta phải dùng file .babelrc để đảm bảo webpack sử dụng plugin mà chúng ta đã cài. Trong thư mục gốc của project, tạo file .babelrc với nội dung như sau:

{ "presets": [ "@babel/preset-react" ] }

6. Thêm các script để hoàn chỉnh package.json

Bước cuối cùng, sau khi đã chuẩn bị sẵn sàng từ React, Electron cho đến Webpack configuration, chúng ta sẽ thêm các npm script trong package.json để sử dụng trong quá trình code cũng như build production.
Chúng ta sẽ có 5 script như sau được để trong mục scripts của package.json

{ "scripts:" { "start": "NODE_ENV=development webpack-dev-server --hot --host 0.0.0.0 --config=./webpack.dev.config.js --mode=development", "prod": "webpack --mode=production --config webpack.build.config.js && NODE_ENV=production electron --noDevServer .", "build": "NODE_ENV=production webpack --config webpack.build.config.js --mode production", "package": "npm run build", "postpackage": "NODE_ENV=production electron-packager ./ --out=./builds" } }

Để chạy app ở development mode thì chỉ cần yarn start

Kết

Như vậy là mình và các bạn đã vừa hoàn thành xong 1 boilerplate để phát triển 1 ứng dụng desktop sử dụng ElectronJS, ReactJS và Webpack. Trong phần 2 của bài viết này, mình sẽ thêm Redux và React-router vào boilerplate này. Cùng chờ đón phần 2 nhé. Thanks

Reference:

Advertisement
COMMENTS ()