ElectronとVueを使用したデスクトップアプリの構築
公開: 2022-03-10JavaScriptは、特にReact、Vue、Angularなどのフレームワークの一部でWebサイトやWebアプリケーションを構築するための言語として知られていましたが、時間の経過とともに(早くも2009年)、JavaScriptをブラウザーの外部で実行できるようになりました。 Webブラウザの外部でJavaScriptコードを実行するオープンソースのクロスプラットフォームのJavaScriptランタイム環境であるNode.jsの登場。 これにより、JavaScriptをWebアプリケーションだけでなく、はるかに多く使用できるようになりました。その1つは、Electron.jsを使用してデスクトップアプリケーションを構築することです。
Electronを使用すると、豊富なネイティブ(オペレーティングシステム)APIを備えたランタイムを提供することにより、純粋なJavaScriptを使用してデスクトップアプリケーションを作成できます。 これは、Webサーバーではなくデスクトップアプリケーションに焦点を当てたNode.jsランタイムの変形として見ることができます。
このチュートリアルでは、Electronを使用してデスクトップアプリケーションを構築する方法を学習します。また、Vuejsを使用してElectronアプリケーションを構築する方法も学習します。
注:このチュートリアルに従うには、Vue.jsとVueCLIの基本的な知識が必要です。 このチュートリアルで使用されているすべてのコードは、私のGitHubにあります。 自由にクローンを作成して試してみてください。
デスクトップアプリケーションとは何ですか?
デスクトップアプリケーションは、デスクトップまたはラップトップコンピューターでスタンドアロンで実行されるアプリケーションです。 これらは特定のタスクを実行するアプリケーションであり、この目的のためだけにインストールされます。
デスクトップアプリケーションの例は、ドキュメントの作成と入力に使用されるMicrosoftWordです。 一般的なデスクトップアプリケーションの他の例は、Webブラウザー、Visual Studio Code、およびAdobePhotoshopです。 デスクトップアプリケーションは、アクセスして使用するためにデスクトップアプリケーションをインストールする必要があり、動作するためにインターネットアクセスを必要としない場合があるため、Webアプリケーションとは異なります。 一方、Webアプリには、そのようなアプリがホストされているURLにアクセスするだけでアクセスでき、アクセスするには常にインターネットアクセスが必要です。
デスクトップアプリの構築に使用されるフレームワークの例は次のとおりです。
- Java
Javaは、クラスベース、オブジェクト指向であり、実装の依存関係をできるだけ少なくするように設計された汎用プログラミング言語です。 これは、アプリケーション開発者が一度書けばどこでも実行できるようにすることを目的としています(WORA)。つまり、コンパイルされたJavaコードは、再コンパイルせずにJavaをサポートするすべてのプラットフォームで実行できます。 - Java FX
彼らの公式ドキュメントによると、これは、Java上に構築されたデスクトップ、モバイル、および組み込みシステム向けのオープンソースの次世代クライアントアプリケーションプラットフォームです。 - C#
C#は、強力な型指定、字句スコープ、必須、宣言型、機能的、汎用、オブジェクト指向、およびコンポーネント指向のプログラミング分野を網羅する汎用のマルチパラダイムプログラミング言語です。 - 。ネット
.NETは、さまざまな種類のアプリケーションを構築するための、無料のクロスプラットフォームのオープンソース開発者プラットフォームです。 .NETを使用すると、複数の言語、エディター、およびライブラリを使用して、Web、モバイル、デスクトップ、ゲーム、およびIoT向けに構築できます。
電子とは何ですか?
Electronは、デスクトップアプリケーションを構築するためのオープンソースフレームワークです。 以前は「Atomshell」として知られており、GitHubによって開発および保守されています。 HTML、CSS、JavaScriptを使用してクロスプラットフォームのデスクトップアプリケーションを作成できます。 これは、1つのコードベースを使用して、Windows、MacOS、およびその他のプラットフォーム用のデスクトップアプリケーションを構築できることを意味します。 これはNode.jsとChromiumに基づいています。 Electronで構築されたアプリケーションの例には、人気のあるAtomエディター、Visual Studio Code、デスクトップ用のWordpress、Slackなどがあります。
インストール
NPMを使用して、プロジェクトにElectronをインストールできます。
npm install electron --save-dev
次のコマンドを使用してElectronアプリを頻繁に使用する場合は、グローバルにインストールすることもできます。
npm install electron -g
Electronを使用したデスクトップ用のVuejsアプリの構築
Vuejsを使用したWebアプリケーションの構築に精通している場合は、Vuejsを使用してデスクトップアプリケーションを構築することができます。 これに必要なのは、VueCLIプラグインのElectronBuilderだけです。
VueCLIプラグインElectronBuilder
このツールを使用すると、Electronを使用してデスクトップ用のVueアプリを構築できます。つまり、VueアプリケーションをElectronアプリとして機能させることができます。 つまり、Webアプリケーションである可能性のあるVueアプリケーションを拡張して、別のフレームワークで別のデスクトップアプリケーションを構築しなくても、デスクトップ環境で動作するようにすることができます。 これにより、Vue開発者はWebを超えるオプションとパワーを得ることができます。 今後は、自分が持っているアイデアに取り組み、Windows、macOS、およびLinuxで実行できるデスクトップアプリケーションオプションをユーザーに提供できます。
これが実際に動作することを確認するために、NewsAPIを使用してNewsアプリを構築します。 このアプリケーションは最新ニュースのヘッドラインを提供し、APIを使用してWeb全体のニュースソースやブログから記事を検索できるようにします。 それらを使い始めるために必要なのは、ここから取得できる個人のAPIキーだけです。
以下を提供するシンプルなアプリを作成します。
-
/top-headlines
エンドポイントを使用して国を選択するオプションを備えた、選択した国のトップヘッドラインとブレイクヘッドラインを表示するページ。 News APIは、サポートしている国のリストからのニュースを提供します。リストはここにあります。 -
/everything
エンドポイントと、カテゴリを指定するクエリパラメータq
の組み合わせを使用した、選択したカテゴリからのニュース。
APIキーを取得したら、VueCLIを使用してアプリケーションを作成できます。 システムにVueCLIがインストールされていることを確認します。インストールされていない場合は、次のコマンドを使用してインストールします。
npm install -g @vue/cli # OR yarn global add @vue/cli
これが完了したら、CLIを使用してニュースアプリを作成します。
vue create news-app
このチュートリアルでは、Axiosを使用してNews APIからデータを取得しますが、より快適な代替手段を使用できます。 次のコマンドのいずれかを使用して、Axiosをインストールできます。
//NPM npm install axios // YARN yarn add axios
次のステップは、アプリケーションでグローバル構成用のAxiosインスタンスをセットアップすることです。 このaxios.jsファイルを作成するsrcフォルダーにpluginsフォルダーを作成します。 ファイルを作成したら、次のコード行を追加します。
import axios from "axios"; let baseURL = `https://newsapi.org/v2`; let apiKey = process.env.VUE_APP_APIKEY; const instance = axios.create({ baseURL: baseURL, timeout: 30000, headers: { "X-Api-Key": apiKey, }, }); export default instance;
ここでは、News APIから取得したbaseURL
とapiKey
を定義し、それをAxiosの新しいインスタンスに渡します。 このインスタンスは、 timeout
プロパティとともにbaseURL
とapiKey
を受け入れます。 News APIでは、APIにリクエストを送信するときにAPIキーを追加する必要があり、リクエストに添付する3つの方法がありますが、ここでは、ヘッダーのX-Api-Key
プロパティに追加してから、 instance
をエクスポートします。 これが完了すると、アプリ内のすべてのAxiosリクエストにこの構成を使用できるようになります。
これが完了したら、次のコマンドを使用して、CLIでプラグインElectronビルダーを追加できます。
vue add electron-builder
好みのElectronバージョンを選択するように求められます。これは、Electronの最新バージョン(執筆時点)であるため、バージョン9.0.0
を選択しました。
これが完了すると、次のコマンドを使用してアプリケーションを提供できるようになります。
Using Yarn(strongly recommended) yarn electron:serve OR NPM npm run electron:serve
これには、アプリのコンパイルと提供に時間がかかります。 これが完了すると、アプリケーションがシステム上でポップオープンします。これは次のようになります。
アプリのdevtoolsを閉じると、次のようになります。
この電子プラグインは、このアプリの開発のすべての部分がVueアプリと同じように機能するため、非常に便利で使いやすいです。 これは、Webアプリケーションとデスクトップアプリの両方に1つのコードベースを持つことができることを意味します。 私たちのアプリは3つの部分で構成されます。
- ランダムに選択された国のトップニュースを表示するランディングページ。
- ユーザーが選択した国のトップニュースを表示するためのページ。
- ユーザーが選択したカテゴリのトップニュースを表示するページ。
このために、すべてのナビゲーションリンクのヘッダーコンポーネントが必要になります。 それでは、 componentsフォルダーにファイルを作成し、 header.vueという名前を付けてから、次のコード行を追加します。
<template> <header class="header"> <div class="logo"> <div class="logo__container"> <img src="../assets/logo.png" alt="News app logo" class="logo__image" /> </div> <h1>News App</h1> </div> <nav class="nav"> <h4 class="nav__link"> <router-link to="/home">Home</router-link> </h4> <h4 class="nav__link"> <router-link to="/top-news">Top News</router-link> </h4> <h4 class="nav__link"> <router-link to="/categories">News By Category</router-link> </h4> </nav> </header> </template> <script> export default { name: "app-header", }; </script> <style> .header { display: flex; flex-wrap: wrap; justify-content: space-between; } .logo { display: flex; flex-wrap: nowrap; justify-content: space-between; align-items: center; height: 50px; } .logo__container { width: 50px; height: 50px; } .logo__image { max-width: 100%; max-height: 100%; } .nav { display: flex; flex-wrap: wrap; width: 350px; justify-content: space-between; } </style>
ここでは、アプリ名とロゴ(画像は私のGitHubにあります)と、アプリケーションの他の部分へのリンクを含むnavセクションを持つヘッダーコンポーネントを作成します。 次に、このページをレイアウトページ— App.vueにインポートして、すべてのページにヘッダーが表示されるようにします。
<template> <div> <app-header /> <router-view /> </div> </template> <script> import appHeader from "@/components/Header.vue"; export default { name: "layout", components: { appHeader, }, }; </script> <style> @import url("https://fonts.googleapis.com/css2?family=Abel&family=Staatliches&display=swap"); html, #app { min-height: 100vh; } #app { font-family: "Abel", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; background-color: #fff; } #app h1 { font-family: "Staatliches", cursive; } a { font-weight: bold; color: #2c3e50; text-decoration: none; } a:hover { text-decoration: underline; } a.router-link-exact-active { color: #42b983; } </style>
ここでは、スクリプトセクションでインポートして宣言した後、テンプレートセクションのデフォルトコンテンツを新しく作成したヘッダーコンポーネントに置き換えます。 最後に、スタイルセクションにアプリ全体のスタイルを追加します。
アプリを表示しようとすると、次のようになります。
次のステップは、 Home.vueファイルにコンテンツを追加することです。 このページは、アプリの最初のセクションをホストします。 ランダムに選ばれた国のトップニュース。 次のコード行でHome.vueファイルを更新します。
<template> <section class="home"> <h1>Welcome to News App</h1> <h4>Displaying Top News from {{ countryInfo.name }}</h4> <div class="articles__div" v-if="articles"> <news-card v-for="(article, index) in articles" :key="index" :article="article" ></news-card> </div> </section> </template> <script> import { mapActions, mapState } from "vuex"; import NewsCard from "../components/NewsCard"; export default { data() { return { articles: "", countryInfo: "", }; }, components: { NewsCard, }, mounted() { this.fetchTopNews(); }, computed: { ...mapState(["countries"]), }, methods: { ...mapActions(["getTopNews"]), async fetchTopNews() { let countriesLength = this.countries.length; let countryIndex = Math.floor( Math.random() * (countriesLength - 1) + 1 ); this.countryInfo = this.countries[countryIndex]; let { data } = await this.getTopNews( this.countries[countryIndex].value ); this.articles = data.articles; }, }, }; </script> <style> .articles__div { display: flex; flex-wrap: wrap; justify-content: center; } </style>
このファイルのスクリプトセクションでは、 mapActions
からmapState
とmapActionsをインポートします。これらは、このファイルの後半で使用します。 また、このページのすべてのニュースヘッドラインをレンダリングするNewsCard
コンポーネントをインポートします(これは次に作成します)。 次に、 fetchTopNews
メソッドを使用して、ストア内のcountries
の配列からランダムに選択された国から最新ニュースをフェッチします。 この国はgetTopNews
アクションに渡されます。これは、 baseURL/top-news?country=${randomCountry}
ような国のクエリとしてbaseURL
に追加されます。 これが完了したら、このデータをループして、テンプレートセクションのニュースカードコンポーネントのarticle
プロップにNewscard
ます。 トップニュースがどの国から来たのかを示す段落もあります。
次に、このニュースを表示するNewsCard
コンポーネントを設定します。 コンポーネントフォルダ内に新しいファイルを作成し、 NewsCard.vueという名前を付けて、次のコード行を追加します。
<template> <section class="news"> <div class="news__section"> <h1 class="news__title"> <a class="article__link" :href="article.url" target="_blank"> {{ article.title }} </a> </h1> <h3 class="news__author" v-if="article.author">{{ article.author }}</h3> <!-- <p class="article__paragraph">{{ article.description }}</p> --> <h5 class="article__published">{{ new Date(article.publishedAt) }}</h5> </div> <div class="image__container"> <img class="news__img" src="../assets/logo.png" :data-src="article.urlToImage" :alt="article.title" /> </div> </section> </template> <script> export default { name: "news-card", props: { article: Object, }, mounted() { this.lazyLoadImages(); }, methods: { lazyLoadImages() { const images = document.querySelectorAll(".news__img"); const options = { // If the image gets within 50px in the Y axis, start the download. root: null, // Page as root rootMargin: "0px", threshold: 0.1, }; const fetchImage = (url) => { return new Promise((resolve, reject) => { const image = new Image(); image.src = url; image.onload = resolve; image.onerror = reject; }); }; const loadImage = (image) => { const src = image.dataset.src; fetchImage(src).then(() => { image.src = src; }); }; const handleIntersection = (entries) => { entries.forEach((entry) => { if (entry.intersectionRatio > 0) { loadImage(entry.target); } }); }; // The observer for the images on the page const observer = new IntersectionObserver(handleIntersection, options); images.forEach((img) => { observer.observe(img); }); }, }, }; </script> <style> .news { width: 100%; display: flex; flex-direction: row; align-items: flex-start; max-width: 550px; box-shadow: 2px 1px 7px 1px #eee; padding: 20px 5px; box-sizing: border-box; margin: 15px 5px; border-radius: 4px; } .news__section { width: 100%; max-width: 350px; margin-right: 5px; } .news__title { font-size: 15px; text-align: left; margin-top: 0; } .news__author { font-size: 14px; text-align: left; font-weight: normal; } .article__published { text-align: left; } .image__container { width: 100%; max-width: 180px; max-height: 180px; } .news__img { transition: max-width 300ms cubic-bezier(0.4, 0, 1, 1), max-height 300ms cubic-bezier(0.4, 0, 1, 1); max-width: 150px; max-height: 150px; } .news__img:hover { max-width: 180px; max-height: 180px; } .article__link { text-decoration: none; color: inherit; } </style>
ここでは、 article
オブジェクトpropを使用してこのコンポーネントに渡されたデータを表示します。 各記事に添付されている画像を遅延読み込みする方法もあります。 このメソッドは、私たちが持っている記事の画像の数をループし、それらが表示されるようになると遅延読み込みします。 最後に、スタイルセクションにこのコンポーネントを対象としたスタイルがあります。
次は、最新のニュースを入手できるように店舗を開設することです。 次のコード行をindex.jsファイルに追加します。
import Vue from "vue"; import Vuex from "vuex"; import axios from "../plugins/axios"; Vue.use(Vuex); const store = new Vuex.Store({ state: { countries: [{ name: "United States of America", value: "us", }, { name: "Nigeria", value: "ng", }, { name: "Argentina", value: "ar", }, { name: "Canada", value: "ca", }, { name: "South Africa", value: "za", }, ], categories: [ "entertainment", "general", "health", "science", "business", "sports", "technology", ], }, mutations: {}, actions: { async getTopNews(context, country) { let res = await axios({ url: `/top-headlines?country=${country}`, method: "GET", }); return res; }, }, }); export default store;
ストアに2つのプロパティを追加しています。これらのプロパティの1つはcountries
です。 このプロパティには、国のオブジェクトの配列が含まれています。 categories
プロパティもあります。 これには、NewsAPIで利用可能なカテゴリの配列が含まれています。 読者は、特定の国やカテゴリからのトップニュースを自由に閲覧できることを望んでいます。 これはアプリの複数の部分でも必要になるため、ストアを利用しています。 ストアのアクションセクションには、ある国からトップニュースを取得するgetTopNews
メソッドがあります(この国は、このアクションを呼び出したコンポーネントから渡されました)。
この時点で、アプリを開くと、次のようなランディングページが表示されます。
background.jsファイル
このファイルは、Electronのアプリへのエントリポイントです。 このアプリのすべてのデスクトップアプリのような設定を制御します。 このファイルのデフォルトの状態は、私のGitHubにあります。
このファイルには、アプリのデフォルトのheight
やwidth
など、アプリに事前定義された構成がいくつか設定されています。 このファイルでできることのいくつかを見てみましょう。
Vuejsdevtoolsを有効にする
デフォルトでは、Electronの開発ツールにアクセスできますが、インストール後に有効になりません。 これは、Windows 10の既存のバグの結果であるため、 background.jsファイルを開くと、コメントアウトされた理由を示すコメント付きのコメントアウトされたコードが見つかります。
// Install Vue Devtools // Devtools extensions are broken in Electron 6.0.0 and greater // See https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/378 for more info // Electron will not launch with Devtools extensions installed on Windows 10 with dark mode // If you are not using Windows 10 dark mode, you may uncomment these lines // In addition, if the linked issue is closed, you can upgrade electron and uncomment these lines // try { // await installVueDevtools() // } catch (e) { // console.error('Vue Devtools failed to install:', e.toString()) // }
したがって、このバグの影響を受けていない場合は、 try/catch
ブロックのコメントを解除し、この同じファイル(5行目)でinstallVueDevtools
を検索して、コメントを解除することもできます。 これが完了すると、アプリが自動的に再起動し、開発ツールを確認すると、VuejsDevtoolsが表示されます。
アプリのカスタムアイコンを選択する
デフォルトでは、Electronアイコンがアプリのデフォルトアイコンとして設定されており、ほとんどの場合、独自のカスタムアイコンを設定することをお勧めします。 これを行うには、アイコンをパブリックフォルダーに移動し、名前をicon.pngに変更します。 次に行うことは、必要な依存関係であるelectron-icon-builder
を追加することです。
次のコマンドのいずれかを使用してインストールできます。
// With Yarn: yarn add --dev electron-icon-builder // or with NPM: npm install --save-dev electron-icon-builder
これが完了したら、この次のコマンドを実行できます。 これが完了すると、アイコンがElectron形式に変換され、コンソールに次のように印刷されます。
次に、 background.jsファイルでアイコンオプションを設定します。 このオプションは、 Electron
からインポートされたBrowserWindow
オプションの中にあります。 これを行うには、 BrowserWindow
を次のように更新します。
// Add this to the top of your file /* global __static */ // import path import path from 'path' // Replace win = new BrowserWindow({ width: 800, height: 600 }) // With win = new BrowserWindow({ width: 800, height: 600, icon: path.join(__static, 'icon.png') })
ここで、 yarn run electron:build
を実行してアプリを表示すると、更新されたアイコンがアプリアイコンとして使用されていることがわかりますが、開発中は変更されません。 この問題は、macOSでの手動修正に対処するのに役立ちます。
アプリのタイトルを設定する
アプリのタイトルがアプリ名(この場合はnews-app)に設定されていることに気付くでしょう。これを変更する必要があります。 これを行うには、次のように、 background.js
ファイルのBrowserWindow
メソッドにtitle
プロパティを追加する必要があります。
win = new BrowserWindow({ width: 600, height: 500, title: "News App", icon: path.join(__static, "icon.png"), webPreferences: { // Use pluginOptions.nodeIntegration, leave this alone // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION, }, });
ここでは、アプリのタイトルを「ニュースアプリ」に設定しています。 ただし、 index.htmlファイルでタイトルが選択されている場合、またはタイトルがこれに変更されない場合は、次のコードをファイルに追加してみてください。
win.on("page-title-updated", (event) => event.preventDefault());
BrowserWindow
からtitle
が更新されたときに発生するイベントをリッスンしています。 このイベントが発生すると、 index.htmlファイルにあるタイトルでタイトルを更新しないようにElectronに指示します。
変更する価値があるかもしれないもう1つのことは、 productName
。これは、アプリのアイコンにカーソルを合わせたときに表示される名前、またはコンピューターがアプリを認識する名前を制御します。 現在、私たちのアプリの名前はElectron
です。 本番環境でこの名前を変更するには、 vue.config.jsファイルを作成し、次のコード行を追加します。
module.exports = { pluginOptions: { electronBuilder: { builderOptions: { productName: "News App", }, }, }, };
ここでは、 productName
を「NewsApp」と定義して、アプリのビルドコマンドを実行すると、名前が「Electron」から「NewsApp」に変わるようにします。
マルチプラットフォームビルド
デフォルトでは、ビルドコマンドを実行すると、作成されるアプリは、実行されているプラットフォームによって異なります。 つまり、Linuxでbuildコマンドを実行すると、作成されるアプリはLinuxデスクトップアプリになります。 同じことが他のプラットフォーム(macOSとWindows)にも当てはまります。 ただし、Electronには、生成するプラットフォーム(または2つのプラットフォーム)を指定するオプションがあります。 使用可能なオプションは次のとおりです。
-
mac
-
win
-
linux
したがって、Windowsバージョンのアプリをビルドするには、次のコマンドを実行します。
// NPM npm electron:build -- --win nsis // YARN yarn electron:build --win nsis
結論
完成したアプリケーションは私のGitHubにあります。 Electronの公式ドキュメントには、デスクトップアプリを好きなようにカスタマイズするのに役立つ情報とガイドが記載されています。 私が試したが、このチュートリアルに含まれていないもののいくつかは次のとおりです。
- macOSでドックをカスタマイズする—https://www.electronjs.org/docs/tutorial/macos-dock。
- サイズ変更可能、最大化可能、その他多数の設定—https://github.com/electron/electron/blob/master/docs/api/browser-window.md#new-browserwindowoptions。
したがって、Electronアプリケーションでさらに多くのことを実行したい場合は、公式ドキュメントから始めることをお勧めします。
関連リソース
- Node.jshttps://en.wikipedia.org/wiki/Node.js
- Java(プログラミング言語)https://en.wikipedia.org/wiki/Java_(programming_language)
- Electron(ソフトウェアフレームワーク)
- JavaFX 14
- electronicjs
- Electronドキュメント
- VueCLIプラグインElectronBuilder
- ChrisNwambaによる交差点オブザーバーを使用したパフォーマンスのための遅延読み込み画像
- アクシオス
- NuxtでAxiosを使い始めるhttps://www.smashingmagazine.com/2020/05/getting-started-axios-nuxt/)by Timi Omoyeni