electron + webpack + react + sass

以前、electron + webpack + sassという記事を書きました。今回はその続編で、electron + webpack + react + sassの開発環境を整えてみた、という話です。

electronアプリを作る際に、

  • rendererプロセス
  • browserプロセス(Mainプロセス)

を意識することになると思います。

electron + reactと謳う場合は、rendererプロセス側でreactを使う、という意味になると思っていて、この記事でもその文脈に則ってます。

electronアプリケーションを作る方法として正しいのかそうでないかはわからないのですが、やってみたかったこととしては、

  • webpackを使う
  • webpackで、browserプロセスとrendererプロセスそれぞれのjsファイルを作る
  • 画面(renderer側のhtml)で利用するstylesheetをsassで書いて、webpackでcssにする

があって、rendererプロセスのjsは、画面のhtmlへ引き込むjsとなるため、Reactを使う、という構成としています。

上記の環境ができたので、纏めておきます。


ディレクトリ構成

次のディレクトリ構成で構築した。 webpackによる諸々のファイルの出力先をappとして、考えた。


.
├── README.md
├── index.js
├── node_modules
├── package.json
├── src
│   ├── browser
│   │   └── index.js
│   └── renderer
│       ├── components
│       │   └── Root.jsx
│       ├── index.html
│       ├── index.jsx
│       └── sass
│           └── main.scss
└── webpack.config.js

各ファイルの内容

index.js

require('./app/browser/index.js')

package.json(抜粋)

{
  "main": "index.js",
  "scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "devDependencies": {
    "babel-core": "^6.18.2",
    "babel-loader": "^6.2.7",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0",
    "css-loader": "^0.25.0",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.9.0",
    "node-sass": "^3.11.2",
    "sass-loader": "^4.0.2",
    "style-loader": "^0.13.1",
    "webpack": "^1.13.3"
  },
  "dependencies": {
    "electron": "^1.4.4",
    "react": "^15.3.2",
    "react-dom": "^15.3.2"
  }
}

src/browser/index.js


import {app, BrowserWindow} from "electron";
import path from "path";

const ROOT_PATH = "file://" + path.resolve("");

app.on("ready", e => {
  const mainWindow = new BrowserWindow({width: 800, height: 600});
  mainWindow.loadURL(`${ROOT_PATH}/app/renderer/index.html`);
});

app.on("window-all-closed", e => {
  app.quit();
});

src/renderer/components/Root.jsx


import React from 'react';

export default class Root extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>Hello</div>
    );
  }
}

src/renderer/index.html


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Awesome App</title>
    <link rel="stylesheet" href="./css/style.css">
  </head>
  <body>
    <div id="root"></div>
    <script src="index.js" type="text/javascript"></script>
  </body>
</html>

src/renderer/index.jsx


import * as React    from 'react';
import * as ReactDOM from 'react-dom';

import Root from './components/Root.jsx';

ReactDOM.render(
  <Root />,
  document.getElementById('root')
);

src/renderer/sass/main.scss


body {
  background-color: blue;
}

webpack.config.js


const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')

module.exports = [
  {
    entry: {
      main: './src/browser/index.js',
    },
    output: {
      path: path.join(__dirname, 'app/browser'),
      filename: 'index.js'
    },
    module: {
      loaders: [
        {
          test: /\.jsx?$/,
          loader: "babel-loader",
          exclude: /node_modules/,
          query: {
            presets: ['es2015', 'react']
          }
        }
      ]
    },
    target: "electron"
  },
  {
    entry: {
      index: './src/renderer/index.html',
      style: './src/renderer/sass/main.scss'
    },
    output: {
      path: path.join(__dirname, 'app/renderer'),
      filename: 'index.js'
    },
    module: {
      loaders: [
        {
          test: /\.html$/,
          loader: "file?name=[name].[ext]"
        },
        {
          test: /\.css$/,
          loader: ExtractTextPlugin.extract("style-loader", "css-loader")
        },
        {
          test: /\.scss$/,
          loader: ExtractTextPlugin.extract("style-loader", "css-loader!sass-loader")
        }
      ]
    },
    plugins: [
      new ExtractTextPlugin("css/[name].css")
    ]
  },
  {
    entry: {
      renderer: './src/renderer/index.jsx',
    },
    output: {
      path: path.join(__dirname, 'app/renderer'),
      filename: 'index.js'
    },
    module: {
      loaders: [
        {
          test: /\.jsx?$/,
          loader: "babel-loader",
          exclude: /node_modules/,
          query: {
            presets: ['es2015', 'react']
          }
        }
      ]
    }
  }
];

ビルド

webpackを使ったビルドは、次のコマンドで行う。

./node_modules/webpack/bin/webpack.js

実行

electronアプリの実行は、次のコマンドで行う。

npm start

課題

  • src配下を修正したら、毎度webpackを実行しなきゃいけない。
  • bootstrapのようなフォントを提供するライブラリに追従できない(と思う)。
  • reduxを導入したらどうなるか。
  • sass/cssをjsとして扱ったらどうなるのか。
  • src配下を修正したら、毎度webpackを実行しなきゃいけない。

など、たくさんある。


課題はたくさんありますが、少し前進した気がします。