Electronで動くweb-apiを作りたいので以下のサイトを参考に試してみます。
https://electron.atom.io/docs/tutorial/quick-start/
http://mherman.org/blog/2016/11/05/developing-a-restful-api-with-node-and-typescript/#.WRb91DekKV5
- LinuxMint v18.1
- Node.js v7.10.0 (nvm使用)
- Electron v1.6.7
- TypeScript v2.3.2
- Express.js v4.15.2
プロジェクトフォルダの作成
まずは今回のプロジェクトフォルダとpackage.jsonを作成します。
mkdir electron-api
cd electron-api
mkdir src
npm init -y
Electronのインストール
Electronのインストールと必須ファイルの編集を行います。
LinuxMint(Ubuntu)&nvm の環境だとElectronの npm -g インストールは今の所失敗するので、
プロジェクトへのローカルインストールを行います。
npm install electron --save-dev
touch main.js
touch index.html
各ファイルを以下のように編集します。
{
"name" : "electron-api",
"version" : "1.0.0",
"main" : "main.js"
}
const {app, BrowserWindow} = require('electron')
const path = require('path')
const url = require('url')
// 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 win
function createWindow () {
// Create the browser window.
win = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
win.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
// Open the DevTools.
win.webContents.openDevTools()
// Emitted when the window is closed.
win.on('closed', () => {
// 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.
win = null
})
}
// 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', () => {
// 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 (win === 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.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using node <script>document.write(process.versions.node)</script>,
Chrome <script>document.write(process.versions.chrome)</script>,
and Electron <script>document.write(process.versions.electron)</script>.
</body>
</html>
編集が完了したら以下のコマンドで実行してみます。
Hello World!が起動したら成功です。
./node_modules/.bin/electron .
TypeScriptのインストール
npm install typescript --save-dev
npm install gulp gulp-typescript --save-dev
npm install -g gulp
touch tsconfig.json
touch src/test.ts
tsconfig.json, test.ts を編集します。
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "dist"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}
console.log('Hello, TypeScript!');
TypeScriptでのコンパイルをテストします。
test.ts がコンパイルされて dist/test.js が作成されれば成功です。
node_modules/.bin/tsc
gulpの設定を作成します。
touch gulpfile.js
const gulp = require('gulp');
const ts = require('gulp-typescript');
const JSON_FILES = ['src/*.json', 'src/**/*.json'];
// pull in the project TypeScript config
const tsProject = ts.createProject('tsconfig.json');
gulp.task('scripts', () => {
const tsResult = tsProject.src()
.pipe(tsProject());
return tsResult.js.pipe(gulp.dest('dist'));
});
gulp.task('watch', ['scripts'], () => {
gulp.watch('src/**/*.ts', ['scripts']);
});
gulp.task('assets', function() {
return gulp.src(JSON_FILES)
.pipe(gulp.dest('dist'));
});
gulp.task('default', ['watch', 'assets']);
gulpでのコンパイルをテストします。
dist/test.js を削除した後、以下コマンドを実行します。
dist/test.js が再度作成されていれば成功です。
gulp
これで.tsファイルの編集が監視され、.jsファイルが自動で出力されるようになります。
Express.jsのインストール
Express.jsとデバッグ用ツールのdebugをインストールします。
npm install express debug --save
Node, Express関連のTypeScript用型定義情報をインストールします。
npm install @types/node @types/express @types/debug --save-dev
先ほど作成したtest.tsのファイル名をindex.tsに変更します。
mv src/test.ts src/index.ts
index.tsを以下の内容で編集します。この時点ではAppモジュールはまだありません。
import * as http from 'http';
import * as debug from 'debug';
import App from './App';
debug('ts-express:server');
const port = normalizePort(process.env.PORT || 3000);
App.set('port', port);
const server = http.createServer(App);
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
function normalizePort(val: number|string): number|string|boolean {
let port: number = (typeof val === 'string') ? parseInt(val, 10) : val;
if (isNaN(port)) return val;
else if (port >= 0) return port;
else return false;
}
function onError(error: NodeJS.ErrnoException): void {
if (error.syscall !== 'listen') throw error;
let bind = (typeof port === 'string') ? 'Pipe ' + port : 'Port ' + port;
switch(error.code) {
case 'EACCES':
console.error(`${bind} requires elevated privileges`);
process.exit(1);
break;
case 'EADDRINUSE':
console.error(`${bind} is already in use`);
process.exit(1);
break;
default:
throw error;
}
}
function onListening(): void {
let addr = server.address();
let bind = (typeof addr === 'string') ? `pipe ${addr}` : `port ${addr.port}`;
debug(`Listening on ${bind}`);
}
package.jsonのscripts欄にstartコマンドを追加します。
"scripts": {
"start": "node dist/index.js"
},
index.tsで読み込んでいるAppモジュールを作成します。
POSTパラメータをJSONで取得するためのbody-parserと、
Expressのログ出力パッケージであるmorganもインストールしておきます。
touch src/App.ts
npm install body-parser morgan --save
npm install @types/body-parser @types/morgan --save-dev
import * as path from 'path';
import * as express from 'express';
import * as logger from 'morgan';
import * as bodyParser from 'body-parser';
// Creates and configures an ExpressJS web server.
class App {
// ref to Express instance
public express: express.Application;
//Run configuration methods on the Express instance.
constructor() {
this.express = express();
this.middleware();
this.routes();
}
// Configure Express middleware.
private middleware(): void {
this.express.use(logger('dev'));
this.express.use(bodyParser.json());
this.express.use(bodyParser.urlencoded({ extended: false }));
}
// Configure API endpoints.
private routes(): void {
/* This is just to get up and running, and to make sure what we've got is
* working so far. This function will change when we start to add more
* API endpoints */
let router = express.Router();
// placeholder route handler
router.get('/', (req, res, next) => {
res.json({
message: 'Hello World!'
});
});
this.express.use('/', router);
}
}
export default new App().express;
ここまでで一通りWebアプリケーションサーバが作成できたので実行してみます。
gulpコマンドでコンパイルし、先ほど作成したstartコマンドで実行します。
gulp scripts
npm start
ブラウザで http://localhost:3000 にアクセスし、以下のように表示されれば成功です。
ElectronとExpressを統合する
これまでElectronとExpressを個別に作成してきましたが、Electron起動時にExpressが起動するように統合します。
また、Electronのmain.jsをTypeScriptの文法に従って編集したmain.tsをsrc配下に作成します。
基本的にはElectronのmain.jsでExpressのindex.jsの内容が実行されるように編集しました。
touch src/main.ts
import {app, BrowserWindow} from 'electron';
import * as http from 'http';
import * as debug from 'debug';
import App from './App';
export default class Main {
static application;
static BrowserWindow;
static mainWindow: BrowserWindow;
static port;
static server;
static main (app, browserWindow: typeof BrowserWindow) {
Main.BrowserWindow = browserWindow;
Main.application = app;
Main.application.on('window-all-closed', Main.onWindowAllClosed);
Main.application.on('ready', Main.onReady);
Main.application.on('activate', Main.onActivate);
Main.bootServer();
}
private static onReady() {
Main.mainWindow = new Main.BrowserWindow({width: 800, height: 600});
Main.mainWindow.loadURL('http://localhost:' + Main.port);
//Main.mainWindow.loadURL('file://' + __dirname + '/index.html');
Main.mainWindow.webContents.openDevTools();
Main.mainWindow.on('closed', Main.onClose);
}
private static onWindowAllClosed() {
if (process.platform !== 'darwin') {
Main.application.quit();
}
}
private static onActivate() {
if (Main.mainWindow === null) {
Main.onReady();
}
}
private static onClose() {
// Dereference the window object.
Main.mainWindow = null;
}
private static bootServer() {
debug('ts-express:server');
Main.port = Main.normalizePort(process.env.PORT || 3000);
App.set('port', Main.port);
Main.server = http.createServer(App);
Main.server.listen(Main.port);
Main.server.on('error', Main.onError);
Main.server.on('listening', Main.onListening);
}
private static normalizePort(val: number|string): number|string|boolean {
let port: number = (typeof val === 'string') ? parseInt(val, 10) : val;
if (isNaN(port)) return val;
else if (port >= 0) return port;
else return false;
}
private static onError(error: NodeJS.ErrnoException): void {
if (error.syscall !== 'listen') throw error;
let bind = (typeof Main.port === 'string') ? 'Pipe ' + Main.port : 'Port ' + Main.port;
switch(error.code) {
case 'EACCES':
console.error(`${bind} requires elevated privileges`);
process.exit(1);
break;
case 'EADDRINUSE':
console.error(`${bind} is already in use`);
process.exit(1);
break;
default:
throw error;
}
}
private static onListening(): void {
let addr = Main.server.address();
let bind = (typeof addr === 'string') ? `pipe ${addr}` : `port ${addr.port}`;
debug(`Listening on ${bind}`);
}
}
Main.main(app, BrowserWindow);
ここまで作成したら、gulpコマンドでコンパイルしてElectronで実行してみます。
gulp scripts
./node_modules/.bin/electron dist/main.js
Electronとの統合が成功したらルート直下のmain.jsとindex.htmlは不要と思います。
また、package.jsonのmainタグの内容を削除し、startコマンドも編集しておきます。
"scripts": {
"start": "./node_modules/.bin/electron dist/main.js"
},
これがベストプラクティスかはわかりませんが、とりあえずここまで試せたので記載しておきます。
以下、最終的なpackage.jsonです。
{
"name": "electron-api",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "./node_modules/.bin/electron dist/main.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/body-parser": "^1.16.3",
"@types/debug": "0.0.29",
"@types/express": "^4.0.35",
"@types/morgan": "^1.7.32",
"@types/node": "^7.0.18",
"electron": "^1.6.7",
"gulp": "^3.9.1",
"gulp-typescript": "^3.1.6",
"typescript": "^2.3.2"
},
"dependencies": {
"body-parser": "^1.17.1",
"debug": "^2.6.6",
"express": "^4.15.2",
"morgan": "^1.8.1"
}
}
以上になります。次はdbアクセス周りを調べようっと。