Разработка сайтов в Краматорске, ДНР. Создайте безопасное настольное приложение с помощью Electron Forge и React

 
 

В этой статье мы создадим простое настольное приложение, используя Electron и React. Это будет небольшой текстовый редактор под названием «блокнот», который автоматически сохраняет изменения по мере ввода, подобно FromScratch. Мы уделим внимание обеспечению безопасности приложения с помощью Electron Forge, современного инструмента сборки, предоставленного командой Electron.

Electron Forge — это «полный инструмент для создания, публикации и установки современных приложений Electron». Он предоставляет удобную среду разработки, а также настраивает все необходимое для сборки приложения для нескольких платформ (хотя в этой статье мы этого касаться не будем).

Мы предполагаем, что вы знаете, что такое Electron и React, хотя вам не нужно знать их, чтобы следовать статье.

Код готового приложения вы можете найти на GitHub.

Настраивать

В этом руководстве предполагается, что на вашем компьютере установлен Node. Если это не так, перейдите на официальную страницу загрузки и загрузите правильные двоичные файлы для вашей системы или используйте диспетчер версий, например nvm. Мы также предполагаем рабочую установку Git.

Ниже я буду использовать два важных термина: «основной» и «рендерер». Приложения Electron «управляются» файлом JavaScript Node.js. Этот файл называется «основным» процессом и отвечает за все, что связано с операционной системой, а также за создание окон браузера. В этих окнах браузера работает Chromium, и они называются «рендеринговой» частью Electron, потому что именно эта часть отображает что-то на экране.

Теперь давайте начнем с создания нового проекта. Поскольку мы хотим использовать Electron Forge и React, мы перейдем на веб-сайт Forge и посмотрим на руководство по интеграции React.

Во-первых, нам нужно настроить Electron Forge с помощью шаблона webpack. Вот как мы можем сделать это в одной команде терминала:

$ npx create-electron-app scratchpad —template=webpack

Выполнение этой команды займет некоторое время, поскольку она устанавливает и настраивает все, от Git до веб-пакета и package.jsonфайла. Когда это будет сделано и мы перейдем cdв этот каталог, мы увидим следующее:

➜ scratchpad git: (master) ls

node_modules

package.json

src

webpack.main.config.js

webpack.renderer.config.js

webpack.rules.js

Мы пропустим node_modulesи и package.json, прежде чем заглянуть в srcпапку, давайте пройдемся по файлам веб-пакета, поскольку их три. Это связано с тем, что Electron на самом деле запускает два файла JavaScript: один для части Node.js, называемый «основным», где он создает окна браузера и взаимодействует с остальной частью операционной системы, и часть Chromium, называемую «рендерером», которая часть, которая фактически отображается на вашем экране.

Третий файл веб-пакета webpack.rules.js— это место, где устанавливается любая общая конфигурация между Node.js и Chromium, чтобы избежать дублирования.

Хорошо, теперь пришло время заглянуть в srcпапку:

➜ src git: (master) ls

index.css

index.html

main.js

renderer.js

Не слишком громоздкий: файл HTML и CSS, а также файл JavaScript для основного и рендерера. Это выглядит хорошо. Мы откроем их позже в статье.

Добавление реакции

Настройка веб-пакета может быть довольно сложной, поэтому, к счастью, мы можем в основном следовать руководству по интеграции React в Electron. Мы начнем с установки всех необходимых нам зависимостей.

Во-первых devDependencies:

npm install —save-dev @babel/core @babel/preset-reactbabel-loader

За ними следуют React и React-dom в качестве обычных зависимостей:

npm install —save react-dom

Со всеми установленными зависимостями нам нужно научить веб-пакет поддерживать JSX. Мы можем сделать это либо в, либо в webpack.renderer.js, webpack.rules.jsно мы будем следовать руководству и добавим следующий загрузчик в webpack.rules.js:

module.exports = [

...

{

test: /\.jsx? $/,

use: {

loader: 'babel-loader’,

options: {

exclude: /node_modules/,

presets: ['@babel/preset-react’]

}

}

},

];

Хорошо, это должно сработать. Давайте быстро проверим его, открыв src/renderer.jsи заменив его содержимое следующим:

import '. /app.jsx’;

import '. /index.css’;

Затем создайте новый файл src/app.jsxи добавьте следующее:

import React from 'react’;

import ReactDOM from 'react-dom’;

ReactDOM.render (

Hello from React in Electron!

, document.body) ;

Мы можем проверить, работает ли это, запустив npm startв консоли. Если откроется окно с надписью «Привет из React in Electron!», все в порядке.

Возможно, вы заметили, что инструменты разработчика открыты, когда отображается окно. Это из-за этой строки в main.jsфайле:

mainWindow.webContents.openDevTools () ;

Лучше пока оставить это, так как оно пригодится нам во время работы. Мы вернемся к main.jsэтому позже в этой статье, когда настроим его безопасность и другие параметры.

Что касается ошибки и предупреждений в консоли, то их можно смело игнорировать. Установка компонента React document.bodyдействительно может быть проблематичной из-за вмешательства стороннего кода, но мы не являемся веб-сайтом и не запускаем чужой код. Электрон также дает нам предупреждение, но мы займемся этим позже.

Создание нашей функциональности

Напоминаем, что мы собираемся создать небольшой блокнот: небольшое приложение, которое сохраняет все, что мы печатаем, по мере того, как мы это печатаем.

Для начала мы добавим CodeMirror и react-codemirror, чтобы получить простой в использовании редактор:

npm install —save react-codemirror

Настроим CodeMirror. Во- первых, нам нужно открыть src/renderer.jsи импортировать и потребовать немного CSS. CodeMirror поставляется с несколькими разными темами, поэтому выберите ту, которая вам нравится, но в этой статье мы будем использовать тему Material. Теперь ваш renderer.js должен выглядеть так:

import 'codemirror/lib/codemirror.css’;

import 'codemirror/theme/material.css’;

import '. /app.jsx’;

import '. /index.css’;

Обратите внимание, как мы импортируем наши собственные файлы после CodeMirror CSS. Мы делаем это, чтобы потом было легче переопределить стиль по умолчанию.

Затем в наш app.jsxфайл мы собираемся импортировать наш CodeMirrorкомпонент следующим образом:

import CodeMirror from 'react-codemirror’;

Создайте новый компонент React app.jsx, который добавляет CodeMirror:

const ScratchPad = () => {

const options = {

theme: «material»

};

const updateScratchpad = newValue => {

console.log (newValue)

}

return <CodeMirror

value="Hello from CodeMirror in React in Electron"

onChange={updateScratchpad}

options={options} />;

}

Также замените функцию рендеринга, чтобы загрузить наш компонент ScratchPad:

ReactDOM.render (, document.body) ;

Когда мы сейчас запустим приложение, мы должны увидеть текстовый редактор с текстом «Привет от CodeMirror в React в Electron». Когда мы введем его, обновления будут отображаться в нашей консоли.

Мы также видим, что есть белая рамка, и что наш редактор на самом деле не заполняет все окно, так что давайте что-нибудь с этим сделаем. Пока мы это делаем, мы займемся домашним хозяйством в наших файлах index.htmlи.index.css

Во- первых, в index.html, давайте удалим все внутри элемента body, так как оно нам все равно не нужно. Затем мы изменим заголовок на «Блокнот», чтобы в строке заголовка не было «Hello World!». по мере загрузки приложения.

Мы также добавим файл Content-Security-Policy. Что это означает, слишком много для рассмотрения в этой статье (у MDN есть хорошее введение), но по сути это способ предотвратить действия стороннего кода, которые мы не хотим делать. Здесь мы указываем ему разрешать только сценарии из нашего источника (файла) и ничего больше.

В общем, наш index.htmlбудет очень пустым и будет выглядеть так:

<! DOCTYPE html>

http-equiv="Content-Security-Policy" content="script-src 'self';">

Теперь перейдем к index.css. Теперь мы можем удалить все, что там есть, и заменить это:

html, body {

position: relative;

width:100vw;

height:100vh;

margin:0;

background: #263238;

}

.ReactCodeMirror,

.CodeMirror {

position: absolute;

height: 100vh;

inset: 0;

}

Это делает несколько вещей:

Он удаляет поле, которое элемент body имеет по умолчанию.

Он делает элемент CodeMirror той же высоты и ширины, что и само окно.

Он добавляет тот же цвет фона к элементу body, чтобы он хорошо сочетался.

Обратите внимание, как мы используем inset — сокращенное свойство CSS для верхнего, правого, нижнего и левого значений. Поскольку мы знаем, что наше приложение всегда будет работать в Chromium версии 89, мы можем использовать современный CSS, не беспокоясь о поддержке!

Так что это очень хорошо: у нас есть приложение, которое мы можем запустить и которое позволяет нам печатать в нем. Сладкий!

Кроме того, когда мы закрываем приложение и перезапускаем его снова, все снова пропало. Мы хотим записать в файловую систему, чтобы наш текст был сохранен, и мы хотим сделать это максимально безопасно. Для этого мы теперь переключим наше внимание на main.jsфайл.

Возможно, вы также заметили, что, несмотря на то, что мы добавили цвет фона к элементам htmlи body, окно по-прежнему остается белым, пока мы загружаем приложение. Это потому, что загрузка нашего index.cssфайла занимает несколько миллисекунд. Чтобы улучшить внешний вид, мы можем настроить окно браузера на определенный цвет фона при его создании. Итак, давайте перейдем к нашему main.jsфайлу и добавим цвет фона. Измените свой mainWindow, чтобы он выглядел так:

const mainWindow = new BrowserWindow ({

width: 800,

height: 600,

backgroundColor: «#263238»,

}) ;

И теперь, когда вы начинаете, белая вспышка должна исчезнуть!

Сохраняем наш блокнот на диск

Когда я объяснял Electron ранее в этой статье, я сделал его немного проще, чем он есть. В то время как у Electron есть основной процесс и процесс рендеринга, в последние годы появился третий контекст — скрипт предварительной загрузки.

Идея сценария предварительной загрузки заключается в том, что он действует как мост между основным (который может получить доступ ко всем API-интерфейсам Node.js) и средством визуализации (которого определенно не должно быть!). В наш сценарий предварительной загрузки мы можем добавить функции, которые могут взаимодействовать с основным процессом, а затем предоставить их процессу визуализации таким образом, чтобы это не влияло на безопасность нашего приложения.

Итак, давайте посмотрим, что мы хотим сделать:

Когда пользователь вносит изменения, мы хотим сохранить их на диск.

Когда приложение запускается, мы хотим загрузить обратно этот сохраненный контент с диска и убедиться, что он отображается в нашем редакторе CodeMirror.

Во-первых, мы напишем код, который позволит нам загружать и сохранять содержимое на диск в нашем main.jsфайле. Этот файл уже импортирует pathмодуль Node, но нам также нужно импортировать fs, чтобы работать с файловой системой. Добавьте это в начало файла:

const fs = require ('fs’) ;

Затем нам нужно выбрать место для нашего сохраненного текстового файла. Здесь мы собираемся использовать appDataпапку, которая автоматически создается вашим приложением для хранения информации. Вы можете получить его с помощью app.getPathфункции, поэтому давайте добавим filenameпеременную в main.jsфайл прямо перед createWindowфункцией:

const filename = `${app.getPath ('userData’) }/content.txt`;

После этого нам понадобятся две функции: одна для чтения файла и одна для его сохранения. Мы назовем их loadContentи saveContent, и вот как они выглядят:

const loadContent = async () => {

return fs.existsSync (filename)? fs.readFileSync (filename, 'utf8'): '';

}

const saveContent = async (content) => {

fs.writeFileSync (filename, content, 'utf8') ;

}

Они оба однострочники, использующие встроенные fsметоды. Для loadContent, нам сначала нужно проверить, существует ли файл уже (поскольку его не будет там при первом запуске!), и если это не так, мы можем вернуть пустую строку.

saveContentеще проще: когда он вызывается, мы вызываем writeFileего с именем файла, содержимым и убеждаемся, что он сохранен как UTF8.

Теперь, когда у нас есть эти функции, нам нужно их подключить. И способ сообщить об этом через IPC, Inter Process Communication. Давайте настроим это дальше.

Настройка IP-камеры

Во-первых, нам нужно импортировать ipcMainиз Electron, поэтому убедитесь, что ваша require ('Electron’) строка main.jsвыглядит так:

const { app, BrowserWindow, ipcMain } = require ('electron’) ;

IPC позволяет отправлять сообщения от рендерера к основному (и наоборот). Прямо под saveContentфункцией добавьте следующее:

ipcMain.on («saveContent», (e, content) =>{

saveContent (content) ;

}) ;

Когда мы получаем saveContentсообщение от рендерера, мы вызываем saveContentфункцию с полученным содержимым. Довольно просто. Но как мы назовем эту функцию? Вот где все немного усложняется.

Мы не хотим, чтобы файл рендерера имел доступ ко всему этому, потому что это было бы очень небезопасно. Нам нужно добавить посредника, который может взаимодействовать с main.jsфайлом и файлом рендерера. Это то, что может сделать скрипт предварительной загрузки.

Давайте создадим этот preload.jsфайл в srcкаталоге и свяжем его в нашем mainWindowвот так:

const mainWindow = new BrowserWindow ({

width: 800,

height: 600,

backgroundColor: «#263238»,

webPreferences: {

preload: path.join (__dirname, 'preload.js’),

}

}) ;

Затем в наш скрипт предварительной загрузки мы добавим следующий код:

const { ipcRenderer, contextBridge } = require («electron») ;

contextBridge.exposeInMainWorld (

'scratchpad’,

{

saveContent: (content) => ipcRenderer.send ('saveContent’, content)

}

)

contextBridge.exposeInMainWorldпозволяет нам добавить функцию saveContentв наш renderer.jsфайл, не делая доступными весь Electron и Node. Таким образом, рендерер знает только о saveContentтом, как и где сохраняется содержимое, но не знает. Первый аргумент, «блокнот», — это глобальная переменная, которая saveContentбудет доступна в. Чтобы вызвать ее в нашем приложении React, мы делаем window.scratchpad.saveContent (content) ;.

Давайте сделаем это сейчас. Открываем наш app.jsxфайл и обновляем updateScratchpadфункцию вот так:

const updateScratchpad = newValue => {

window.scratchpad.saveContent (newValue) ;

};

Вот и все. Теперь каждое изменение, которое мы делаем, записывается на диск. Но когда мы закрываем и снова открываем приложение, оно снова пустое. Нам также нужно загрузить содержимое при первом запуске.

Загружайте контент, когда мы открываем приложение

Мы уже написали loadContentфункцию в main.js, поэтому давайте подключим ее к нашему пользовательскому интерфейсу. Мы использовали IPC sendи onдля сохранения контента, так как нам не нужно было получать ответ, а теперь нужно получить файл с диска и отправить его рендереру. Для этого мы будем использовать IPC invokeи handleфункции. invokeвозвращает обещание, которое разрешается с тем, что handleвозвращает функция.

Мы начнем с написания обработчика в нашем main.jsфайле, прямо под saveContentобработчиком:

ipcMain.handle («loadContent», (e) => {

return loadContent () ;

}) ;

В нашем preload.jsфайле мы вызовем эту функцию и предоставим ее нашему коду React. К нашему exporeInMainWorldсписку свойств мы добавляем второе, называемое content:

contextBridge.exposeInMainWorld (

'scratchpad’,

{

saveContent: (content) => ipcRenderer.send ('saveContent’, content),

content: ipcRenderer.invoke («loadContent»),

}

) ;

В нашем app.jsxмы можем получить это с помощью window.scratchpad.content, но это обещание, поэтому нам нужно awaitэто сделать перед загрузкой. Для этого мы оборачиваем средство визуализации ReactDOM в асинхронный IFFE следующим образом:

(async () => {

const content = await window.scratchpad.content;

ReactDOM.render (, document.body) ;

}) () ;

Мы также обновляем наш ScratchPadкомпонент, чтобы использовать text prop в качестве начального значения:

const ScratchPad = ({text}) => {

const options = {

theme: «material»

};

const updateScratchpad = newValue => {

window.scratchpad.saveContent (newValue) ;

};

return (

<CodeMirror

value={text}

onChange={updateScratchpad}

options={options}

/>

) ;

};

Вот и все: мы успешно интегрировали Electron и React и создали небольшое приложение, которое пользователи могут вводить и которое автоматически сохраняется, не предоставляя нашему блокноту доступ к файловой системе, который мы не хотим ему предоставлять.

Мы закончили, верно? Ну, есть несколько вещей, которые мы можем сделать, чтобы он выглядел немного более похожим на «приложение».

«Быстрая» загрузка

Вы могли заметить, что когда вы открываете приложение, проходит несколько секунд, прежде чем текст становится видимым. Выглядит не очень, поэтому лучше дождаться загрузки приложения и только потом его показывать. Это ускорит работу всего приложения, поскольку вы не будете смотреть на неактивное окно.

Во- первых, мы добавляем show: falseк нашему new BrowserWindowвызову и добавляем прослушиватель к ready-to-showсобытию. Там мы показываем и фокусируем наше созданное окно:

const mainWindow = new BrowserWindow ({

width: 800,

height: 600,

backgroundColor: «#263238»,

show: false,

webPreferences: {

preload: path.join (__dirname, 'preload.js’),

}

}) ;

mainWindow.once ('ready-to-show’, () => {

mainWindow.show () ;

mainWindow.focus () ;

}) ;

Пока мы в main.jsфайле, мы также удалим openDevToolsвызов, так как мы не хотим показывать это пользователям:

mainWindow.webContents.openDevTools () ;

Теперь, когда мы запускаем приложение, отображается окно приложения с уже существующим содержимым. Намного лучше!

Сборка и установка приложения

Теперь, когда приложение готово, мы можем его создать. Electron Forge уже создал для этого команду. Run npm run makeand Forge создаст приложение и установщик для вашей текущей операционной системы и поместит их в папку «out», готовые для установки, будь.exeто.dmgфайлы.deb.

Если вы работаете в Linux и получаете сообщение об ошибке rpmbuild, установите пакет «rpm», например, sudo apt install rpmв Ubuntu. Если вы не хотите создавать установщик rpm, вы также можете удалить блок «@electron-forge/maker-rpm» из создателей в вашем файле package.json.

При этом будут упущены некоторые важные вещи, такие как подпись кода, нотариальное заверение и автоматические обновления, но мы оставим их для следующей статьи.

Это действительно минимальный пример интеграции Electron и React. С самим приложением мы можем сделать гораздо больше. Вот несколько идей для изучения:

Добавьте крутую иконку на рабочий стол.

Создайте поддержку темного и светлого режимов на основе настроек операционной системы либо с помощью медиа-запросов, либо с помощью API-интерфейса nativeTheme, предоставленного Electron.

Добавьте ярлыки с помощью чего-то вроде mousetrap.js или ускорителей меню Electron и globalShortcuts.

Сохранение и восстановление размера и положения окна.

Синхронизация с сервером вместо файла на диске.

3D-печать5GABC-анализAndroidAppleAppStoreAsusCall-центрChatGPTCRMDellDNSDrupalExcelFacebookFMCGGoogleHuaweiInstagramiPhoneLinkedInLinuxMagentoMicrosoftNvidiaOpenCartPlayStationPOS материалPPC-специалистRuTubeSamsungSEO-услугиSMMSnapchatSonyStarlinkTikTokTwitterUbuntuUp-saleViasatVPNWhatsAppWindowsWordPressXiaomiYouTubeZoomАвдеевкаАктивные продажиАкцияАлександровск ЛНРАлмазнаяАлчевскАмвросиевкаАнализ конкурентовАнализ продажАнтимерчандайзингАнтрацитАртемовскАртемовск ЛНРАссортиментная политикаБелгородБелицкоеБелозерскоеБердянскБизнес-идеи (стартапы)БрендБрянкаБукингВахрушевоВендорВидеоВикипедияВирусная рекламаВирусный маркетингВладивостокВнутренние продажиВнутренний маркетингВолгоградВолновахаВоронежГорловкаГорнякГорскоеДебальцевоДебиторкаДебиторская задолженностьДезинтермедитацияДзержинскДивизионная система управленияДизайнДимитровДирект-маркетингДисконтДистрибьюторДистрибьюцияДобропольеДокучаевскДоменДружковкаЕкатеринбургЕнакиевоЖдановкаЗапорожьеЗимогорьеЗолотоеЗоринскЗугрэсИжевскИловайскИрминоКазаньКалининградКировскКировскоеКомсомольскоеКонстантиновкаКонтент-маркетингКонтент-планКопирайтингКраматорскКрасноармейскКрасногоровкаКраснодарКраснодонКраснопартизанскКрасный ЛиманКрасный ЛучКременнаяКураховоКурскЛисичанскЛуганскЛутугиноМакеевкаМариупольМаркетингМаркетинговая информацияМаркетинговые исследованияМаркетинговый каналМаркетинг услугМаркетологМарьинкаМедиаМелекиноМелитопольМенеджментМерчандайзерМерчандайзингМиусинскМолодогвардейскМоскваМоспиноНижний НовгородНиколаевНиколаевкаНишевой маркетингНовоазовскНовогродовкаНоводружескНовосибирскНумерическая дистрибьюцияОдессаОмскОтдел маркетингаПартизанский маркетингПервомайскПеревальскПетровскоеПлата за кликПоисковая оптимизацияПопаснаяПравило ПаретоПривольеПрогнозирование продажПродвижение сайтов в ДонецкеПроизводство видеоПромоПромоушнПрямой маркетингРабота для маркетологаРабота для студентаРазработка приложенийРаспродажаРегиональные продажиРекламаРеклама на асфальтеРемаркетингРетро-бонусРибейтРитейлРовенькиРодинскоеРостов-на-ДонуРубежноеСамараСанкт-ПетербургСаратовСватовоСвердловскСветлодарскСвятогорскСевастопольСеверодонецкСеверскСедовоСейлз промоушнСелидовоСимферопольСинергияСколковоСлавянскСнежноеСоздание сайтов в ДонецкеСоледарСоциальные сетиСочиСтаробельскСтаробешевоСтахановСтимулирование сбытаСуходольскСчастьеТелемаркетингТельмановоТираспольТорговый представительТорезТрейд маркетингТрейд промоушнТюменьУглегорскУгледарУкраинскХабаровскХарцызскХерсонХостингЦелевая аудиторияЦифровой маркетингЧасов ЯрЧелябинскШахтерскЮжно-СахалинскЮнокоммунаровскЯндексЯсиноватая