webpackを利用するkintoneプラグイン開発の流れ


kintoneプラグインの開発手法・ツールの利用については、最近色々とツール拡充がなされ、2015年の3月にサードパーティ開発が可能になって以来のちょっとした転換期がきているように思います。あくまでも開発手法・ツールについての話ですが・・・。
話が逸れましたが、例えばプラグインのパッケージング方法が package.sh から @kintone/create-plugin を用いた方法が今後メーカー推奨になるとの噂も出てきていますし、いわゆるモダンな開発方法というのも必要に応じて取り込んでいきたいものです。

kintoneのカスタマイズやプラグイン開発で webpack を利用されている方も多いと思いますが、プラグイン開発ツールとして webpack を適用するものがwebpackプラグインとして登場してきたので、今回はそれらを使った開発の流れを見ていきたいと思います。

大まかな流れ

  1. @kintone/create-plugin でプラグインの開発に必要な雛形を展開する
  2. @kintone/webpack-plugin-kintone-plugin等webpack経由でのプラグインパッケージングに必要なモジュールをインストールする
  3. webpack.config.js を準備する
  4. manifest.json を修正する
  5. プラグインをパッケージングする

詳しく見ていきたいと思います。

@kintone/create-plugin で雛形を展開する

まずは、プラグイン開発に必要なフォルダ構成・ファイルの雛形を @kintone/create-plugin で展開します。後に利用する @kintone/webpack-plugin-kintone-plugin にはない部分(ファイル群の雛形が生成されるのも便利ですが鍵の生成が大きい)を補うために最初に実行します。

npx @kintone/create-plugin hello-webpack-plugin

設定のための対話を進め終えると、ここでは hello-webpack-plugin というフォルダが展開されますが、この時点での構成は次の通りです。

├── node_modules/
├── package-lock.json
├── package.json
├── private.ppk
├── scripts/
└── src/

@kintone/create-plugin によるwebpackは用いない従来のプラグイン開発が可能という状態です。

webpack関連のモジュールの追加

次にwebpackを使ったパッケージングを行うために、@kintone/webpack-plugin-kintone-plugin とwebpackのバンドルに必要な webpackwebpack-cli をインストールします。@kintone/webpack-plugin-kintone-pluginはwebpackのpluginとして提供されています。

npm install --save-dev webpack webpack-cli @kintone/webpack-plugin-kintone-plugin

この時点で、package.json は次のようになります。

package.json
{
  "name": "hello-webpack-plugin",
  "version": "0.1.0",
  "scripts": {
    "start": "node scripts/npm-start.js",
    "upload": "kintone-plugin-uploader dist/plugin.zip --watch --waiting-dialog-ms 3000",
    "develop": "npm run build -- --watch",
    "build": "kintone-plugin-packer --ppk private.ppk --out dist/plugin.zip src",
    "lint": "eslint src"
  },
  "devDependencies": {
    "@kintone/plugin-packer": "^1.0.1",
    "@kintone/plugin-uploader": "^2.2.0",
    "@kintone/webpack-plugin-kintone-plugin": "^1.0.7",
    "eslint": "^4.19.1",
    "eslint-config-kintone": "^1.3.0",
    "npm-run-all": "^4.1.3",
    "webpack": "^4.28.2",
    "webpack-cli": "^3.1.2"
  }
}

webpack.config.js を準備する

次に、webpackの設定ファイルであるwebpack.config.js を準備します。

  • entry@kintone/create-pluginで生成されたファイルを利用することにします。
  • output./src/js配下にdistフォルダを設けて、そこに出力するようにします(これがプラグインのパッケージング対象になります)。
  • plugins では@kintone/webpack-plugin-kintone-pluginに必要なパラメータをセットしますが、manifestJSONPathprivateKeyPath@kintone/create-pluginで生成されたファイルパスを指定します。
webpack.config.js
const path = require("path");
const KintonePlugin = require("@kintone/webpack-plugin-kintone-plugin");

module.exports = {
    mode: 'development',
    entry: {
        desktop: './src/js/desktop.js',
        config: './src/js/config.js'
    },
    output: {
        path: path.resolve(__dirname, 'src', 'js', 'dist'),
        filename: '[name].js'
    },
    plugins: [
        new KintonePlugin({
            manifestJSONPath: './src/manifest.json',
            privateKeyPath: './private.ppk',
            pluginZipPath: './dist/plugin.zip'
        })
    ]
};

manifest.jsonを修正する

最後のステップです。webpackでバンドルされたJSファイルをプラグインのパッケージングの設定ファイルであるmanifest.jsonに指定します。今回だとjs/dist/desktop.jsといったパスになります。

manifest.json
{
  "manifest_version": 1,
  "version": 1,
  "type": "APP",
  "desktop": {
    "js": [
      "https://js.cybozu.com/jquery/3.3.1/jquery.min.js",
      "js/dist/desktop.js"
    ],
    "css": [
      "css/51-modern-default.css",
      "css/desktop.css"
    ]
  },
  "icon": "image/icon.png",
  "config": {
    "html": "html/config.html",
    "js": [
      "https://js.cybozu.com/jquery/3.3.1/jquery.min.js",
      "js/dist/config.js"
    ],
    "css": [
      "css/51-modern-default.css",
      "css/config.css"
    ],
    "required_params": [
      "message"
    ]
  },
  "name": {
    "en": "hello-webpack-plugin"
  },
  "description": {
    "en": "hello-webpack-plugin"
  }
}

パッケージング

ここまでの準備が大変なのですが、これでやっとwebpackのバンドル経由でプラグインのパッケージングが可能になります。webpack.config.jsを配置した階層でwebpackを実行すれば、webpack.config.jsで指定したpluginZipPathの階層にプラグインのZIPファイルが生成されます。production用のコマンドは次の通りです。

$(npm bin)/webpack --mode production

package.jsonscripts@kintone/create-pluginに適した形のままですので、次のようにwebpack利用が簡単になるよう自分で修正するといいと思います。

package.json
{
  "name": "hello-webpack-plugin",
  "version": "0.1.0",
  "scripts": {
    "upload": "kintone-plugin-uploader dist/plugin.zip --watch --waiting-dialog-ms 3000",
    "watch": "webpack --mode development --watch",
    "build:dev": "webpack --mode development",
    "build:prod": "webpack --mode production",
    "lint": "eslint src"
  },
  "devDependencies": {
    "@kintone/plugin-packer": "^1.0.1",
    "@kintone/plugin-uploader": "^2.2.0",
    "@kintone/webpack-plugin-kintone-plugin": "^1.0.7",
    "eslint": "^4.19.1",
    "eslint-config-kintone": "^1.3.0",
    "npm-run-all": "^4.1.3",
    "webpack": "^4.28.2",
    "webpack-cli": "^3.1.2"
  }
}

これで、例えば$(npm bin)/webpack --mode productionnpm run build:prodで実行できるようになります。

ちなみに、プラグインファイルは、webpack.config.jsで指定した通り./dist/plugin.zipとして生成されています。

おまけ - ES6やReactを使いたい時

kintoneは業務システムとして利用されることが多い訳で、ES6やReactを利用したい場合にはwebpack時にBabelによるトランスパイルも実行されるようにして、IE11等モダンでないブラウザでも利用できるようにしておくのがいいでしょう。

ES6

まずは、ES6への対応を見てみましょう。まずはbabel-loaderGitHubページを参考に、トランスパイルに必要なモジュールをインストールしましょう。webpackもそうですが、Babelも結構アップデート頻度ありそうなので、バージョンへの意識が必要そうです。執筆時点では、webpack 4.x | babel-loader 8.x | babel 7.xです。

npm install --save-dev babel-loader @babel/core @babel/preset-env

これに伴い、webpack.config.jsmodule.rulesに次のようにルールの追加を行います。

また、resolve.extensionsも追加しておきます。これによって、別ファイルをimportする際に拡張子を省略できます。

webpack.config.js
const path = require("path");
const KintonePlugin = require("@kintone/webpack-plugin-kintone-plugin");

module.exports = {
    mode: 'development',
    entry: {
        desktop: './src/js/desktop.js',
        config: './src/js/config.js'
    },
    output: {
        path: path.resolve(__dirname, 'src', 'js', 'dist'),
        filename: '[name].js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    },
    resolve: {
        extensions: ['.js']
    },
    plugins: [
        new KintonePlugin({
            manifestJSONPath: './src/manifest.json',
            privateKeyPath: './private.ppk',
            pluginZipPath: './dist/plugin.zip'
        })
    ]
};

React

まず、Reactの利用に必要なモジュールをインストールします。

npm install --save react react-dom

また、ES6の時と同じようにBabelはじめトランスパイルに必要なモジュールをインストールします。React用の@babel/preset-reactも今回必要になります。

npm install --save-dev babel-loader @babel/core @babel/preset-react

webpack.config.jsmodule.rulesに次のようにルールの追加を行います。

webpack.config.js
const path = require("path");
const KintonePlugin = require("@kintone/webpack-plugin-kintone-plugin");

module.exports = {
    mode: 'development',
    entry: {
        desktop: './src/js/desktop.js',
        config: './src/js/config.js'
    },
    output: {
        path: path.resolve(__dirname, 'src', 'js', 'dist'),
        filename: '[name].js'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-react']
                    }
                }
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.jsx']
    },
    plugins: [
        new KintonePlugin({
            manifestJSONPath: './src/manifest.json',
            privateKeyPath: './private.ppk',
            pluginZipPath: './dist/plugin.zip'
        })
    ]
};

ただし、npm run build:devはファイルサイズが512kBを超えてエラーを吐きました。
[追記] 2019年6月のアップデートで20MBまで拡張され、React等のライブラリも導入しやすくなりました(512kB制限で出ていた下記のエラーメッセージはそのまま残しておきます)。

Error: ".desktop.js[1]" should match format "https-url", ".desktop.js[1]" file size should be <= 512KB, ".desktop.js[1]" should match some schema in anyOf
    at validateManifest (/home/vagrant/workspace/hello-webpack-plugin/node_modules/@kintone/plugin-packer/src/zip.js:142:15)
    at preprocessToRezip.then (/home/vagrant/workspace/hello-webpack-plugin/node_modules/@kintone/plugin-packer/src/zip.js:36:7)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)

npm run build:prodであれば収まりましたが、Reactまでを入れて1ファイルにバンドルするのはちょっと無理があるようです("まとめ"で考察します)。

ES6とReactを併用する場合

ここまでの合わせ技になりますが、慣れてないとwebpack.config.jsの書き方に迷う可能性があるので、一通り流していきましょう。

まずはモジュールのインストールです。

npm install --save react react-dom
npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-react

webpack.config.jsmodule.rulesへの追記は次のようになります。

webpack.config.js
const path = require("path");
const KintonePlugin = require("@kintone/webpack-plugin-kintone-plugin");

module.exports = {
    mode: 'development',
    entry: {
        desktop: './src/js/desktop.js',
        config: './src/js/config.js'
    },
    output: {
        path: path.resolve(__dirname, 'src', 'js', 'dist'),
        filename: '[name].js'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env', '@babel/preset-react']
                    }
                }
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.jsx']
    },
    plugins: [
        new KintonePlugin({
            manifestJSONPath: './src/manifest.json',
            privateKeyPath: './private.ppk',
            pluginZipPath: './dist/plugin.zip'
        })
    ]
};

以上がES6とReactの併用時の準備になります。

まとめ

webpackプラグインである@kintone/webpack-plugin-kintone-pluginを用いて、webpackを利用したkintoneプラグインの開発の流れについて見てきました。

恐らくplugin-packerの機能だと思いますが、プラグインのパッケージング時にmanifest.jsonに記述がないファイルはプラグインファイルから除外されるようです(package.shは関係ないファイルもフォルダ中のファイルが内包されていた)。
プラグイン構成ファイルの上限である 512kB 20MB制限を満たしているかもこの時チェックしているようです。
Reactのセクションでバンドルファイルが512kB超える可能性が高いお話をしましたが、ファイル容量の超過に対する方策は主に3つかと思います。
[追記] 2019年6月のアップデートで20MBまで拡張され、React等のライブラリも導入しやすくなりましたので、バシバシ活用すると良いかと思います。

  • プラグインファイルに内包するのではなくてCDNから配信する
  • webpackのファイル分割(optimization.splitChunks)を行う(試してません)
  • externalsを利用して依存モジュールを外だし分割する

いずれの方法もこの開発の流れの延長で取れる方策だと思いますので、必要に応じて検討することになりそうです。

いずれにせよ、全体的な結論としてwebpackを利用するkintoneプラグインの開発は今回の流れが現状最適と言えそうです。

参考にしたサイト等