npm-scriptsで脱Gulpを目指す

脱create-react-appをするとcssコンパイル導入もしやすくなります。

しかし自動コンパイルをするには自分で設定する必要があるので、 タスクランナーの設定をしていきます。

Gulpを使ってもいいのですが、せっかくなので脱Gulpもしてしまいましょう。

依存というのはいつの時代も悪だ。

npm-srciptsをタスクランナーにする

早速使いたいという方はこの項目を飛ばして2から読み始めてください。

最初にnpm-scriptsについて少し説明すると、package.jsonに記述している「scripts」の部分です。

ファイルの保存がされたらそのスクリプトを実行させることでGulpのような タスクランナーとして扱う感じですね。

Reactを使わない場合はpug / postcss / es6 の変更を監視して 変更があったらそれぞれを再コンパイルしてリロードするといった形をとっていました。

今回はcreate-react-appの続きの話になっているのでコンパイル対象は postcssとes6(React含む)となっています。

npm-scriptsのデメリットを言っておくと

  • Gulpと比べて可読性が落ちる
  • クロスプラットフォームではない(windowsで動かすようだとMacでうまく動かない)

Gulpの習得コストとGulp用プラグインに依存しなくなってnode_modulesをそのまま利用できるので 長い目で見ると良い感じかもしれません。

問題は家ではWindowsを使って外ではMacBookを使うとなった場合、 クロスプラットフォームではないのでMac用の記述も用意しないといけないのがちょっと面倒ですね。

それ以外は特に困らないというかそこまでGulpをがっつり使いこなせてたわけじゃないので 正直使っている分にはあまり差を感じないと体感できたので私はGulpからnpm-scriptsに乗り換えました。

npm-scriptsを並列で呼び出すには

npm-scriptsの本番に入りましょう。

npm-scriptsは基本的には1つずつしか動かせません。

npm start やら yarn start やらですね。

並列で動かすにはnpm-run-allというモジュールを使います。

yarn add -D npm-run-all

package.jsonのscriptsに以下のを追記してください。

"start": "npm-run-all watch-* --parallel"

このnpm-run-allはscriptsを複数起動することができる優れものです。

Gulpからnpm-scriptsに移り変わるときはこれがあるとすごく便利です。

npm-run-all 「実行タスク」 「オプション」といった並びです。 一応逆にしても動きます。

scriptsに 「watch-*」 というのは watch-って頭についている名前のスクリプトに該当するものを動かします。

次にオプションとして「 --parallel」 とありますがこれを付けると並列的にscriptsを動かせるようになります。

webpack-devからbrowser-sycnに乗り換える

これは余談になりますが私はこの環境でやってるので記載しておきます。

仮想サーバーが動いている状態で該当ファイルを保存したら自動的にコンパイルして ホットリロードしてくれるのが理想形ですね。

今後はwebpack-devより便利なBrowserSyncを使っていきます。

正直どっちでもいいのですが、後者の方が便利感があるので乗り換えましょう。

yarn add -D browser-sync

package.jsonのscriptsに以下のを追記してください。

"watch-server": "browser-sync start --config bs-config.js",

このスクリプトはwatch-~となってるのでnpm-run-allの監視対象となります。

次にbrowser-sync用の設定ファイル「bs-config.js」を作りましょう。

ここで思ったのですがnode.jsで使うやつは本当に設定ファイル多くて面倒くさく感じますね……

まぁ言ってても始まらないので作りましょう。

以下はReactではなくPugでhtmlを作ってた時の私が使ってたものになります。

基本はコピペでOKだと思いますが、必要に応じて変更してください。

Browsersync options http://www.browsersync.io/docs/options/

上記のサイトで設定項目の意味は分かると思います。

module.exports = {
    "ui": {
        "port": 3001
    },
    "files": ['dist/**/*.html', 'dist/css/**/*.css', 'dist/*.js'],
    "watchEvents": ["change"],
    "watch": false,
    "ignore": [],
    "single": false,
    "watchOptions": {
        "ignoreInitial": true
    },
    "server": {
        baseDir: 'dist',
        directory: true
    },
    "proxy": false,
    "port": 3000,
    "middleware": false,
    "serveStatic": [],
    "ghostMode": {
        "clicks": true,
        "scroll": true,
        "location": true,
        "forms": {
            "submit": true,
            "inputs": true,
            "toggles": true
        }
    },
    "logLevel": "info",
    "logPrefix": "Browsersync",
    "logConnections": false,
    "logFileChanges": true,
    "logSnippet": true,
    "rewriteRules": [],
    "open": "local",
    "browser": "default",
    "cors": false,
    "xip": false,
    "hostnameSuffix": false,
    "reloadOnRestart": false,
    "notify": true,
    "scrollProportionally": true,
    "scrollThrottle": 0,
    "scrollRestoreTechnique": "window.name",
    "scrollElements": [],
    "scrollElementMapping": [],
    "reloadDelay": 0,
    "reloadDebounce": 500,
    "reloadThrottle": 0,
    "plugins": [],
    "injectChanges": true,
    "startPath": null,
    "minify": true,
    "host": null,
    "localOnly": false,
    "codeSync": true,
    "timestamps": true,
    "clientEvents": [
        "scroll",
        "scroll:element",
        "input:text",
        "input:toggles",
        "form:submit",
        "form:reset",
        "click"
    ],
    "socket": {
        "socketIoOptions": {
            "log": false
        },
        "socketIoClientConfig": {
            "reconnectionAttempts": 50
        },
        "path": "/browser-sync/socket.io",
        "clientPath": "/browser-sync",
        "namespace": "/browser-sync",
        "clients": {
            "heartbeatTimeout": 5000
        }
    },
    "tagNames": {
        "less": "link",
        "scss": "link",
        "css": "link",
        "jpg": "img",
        "jpeg": "img",
        "png": "img",
        "svg": "img",
        "gif": "img",
        "js": "script"
    },
    "injectNotification": false
};

自動コンパイルするために変更監視をするchokidarを導入

node.jsで手軽にファイル監視をできる「chokidar」を導入します。

create-react-appとかだと最初からホットリロード機能がついてるのであまり気にしませんでしたが、 自力で組む場合はこれも自分で実装する必要があります。

もしかするともっと便利なものがあるかもしれませんがとりあえずchokidarを使っていきましょう。

chokidarの読み方は「チョキダー」であってるのでしょうか……?

チョキダー!

面白い名前ですね。恐らくちゃんとした意味があるとは思うのですが、 ググっても意外と出てこないし誰も疑問に思ってないのでしょうか(笑)

chokidarインストールをしましょう。

ついでに便利なモジュールも一緒に入れておきます。

yarn add -D chokidar chokidar-cli

それではchokidarを使う為の設定ファイルを作りましょう。

また設定ファイルかよ……ってなると思いますが1回組んだらほとんど触らなくなるので頑張りましょう。

コピペで必要なところを変更すればOKです。

// モジュール読み込み
const cpexec = require("child_process").exec; // コマンド実行
const path = require("path"); // パス文字列操作

// ディレクトリ固定文字列
const srcDir = "src/";
const distDir = "dist/";

// 拡張子ごとに入力元の基準ディレクトリを設定
const baseDir = {
  ".tsx": srcDir,
  ".css": srcDir + "pcss"
};

// 引数からパスを取得
const srcPath = path.normalize(process.argv[2]);

// パスから拡張子を取得
const extStr = path.extname(srcPath);

// コンパイル・コマンドを生成
let cmdStr = "";
switch (extStr) {
  // 拡張子.typescript用コンパイルコマンド
  case ".ts":
  case ".tsx":
    cmdStr = "webpack";
    break;
  // 拡張子.pcss用コンパイルコマンド
  case ".css":
    cmdStr = "postcss src/pcss/style.css --map --output dist/css/style.min.css";
    break;
  //登録外拡張子用メッセージ
  default:
    cmdStr = `echo Command for for "${extStr}" is not registered.`;
}

// コマンドの実行
cpexec(cmdStr, (error, stdout) => {
  // エラー処理
  if (error) {
    // コンソールにエラー・ログを表示
    console.log(error);
  } else {
    // コンソールに結果ログを表示
    console.log(stdout);
  }
});

内容はできる限りコメント化しておいたのでよく読んでください。

pugの設定がありますが、流用してるだけなので一旦コメントアウトしています。 必要であればご利用ください。

pugの時はReactを使ってなかったのでReact用に少し書き換えています。

これによりpackage.jsonに記載されている項目を少し変えます。

{
  "name": "pure_react",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "compile-pcss": "postcss src/pcss/style.css --map --output dist/css/style.min.css",
    "compile-js": "webpack",
    "watch-compile": "chokidar src/**/*.ts* src/pcss/**/*.css --ignore src/**/_*.* --command \"node compile.js {path}\"",
    "watch-server": "browser-sync start --config bs-config.js",
    "start": "npm-run-all --parallel watch-*"
  },
  "dependencies": {
    "react": "^16.12.0",
    "react-dom": "^16.12.0"
  },
  "devDependencies": {
    "@types/node": "^13.1.4",
    "@types/react": "^16.9.17",
    "@types/react-dom": "^16.9.4",
    "browser-sync": "^2.26.7",
    "chokidar": "^3.3.1",
    "chokidar-cli": "^2.1.0",
    "npm-run-all": "^4.1.5",
    "postcss-calc": "^7.0.1",
    "postcss-cli": "^7.0.0",
    "postcss-color-function": "^4.1.0",
    "postcss-cssnext": "^3.1.0",
    "postcss-import": "^12.0.1",
    "postcss-mixins": "^6.2.3",
    "postcss-nested": "^4.2.1",
    "postcss-simple-vars": "^5.0.2",
    "postcss-url": "^8.0.0",
    "react-hot-loader": "^4.12.18",
    "ts-loader": "^6.2.1",
    "typescript": "^3.7.4",
    "webpack": "^4.41.5",
    "webpack-cli": "^3.3.10",
  }
}

基本はwebpackとpostcssどちらかのコマンドをファイル変更後にコンパイルしますが、 いちいち仮想サーバーを立ち上げるのも面倒な時はそれぞれ単体でコンパイルできるようにコマンドを残しています。

後はjsを極力使わないようにjsは監視対象外にしました。

共存という道もあったのですが、jsが混じったらtsでやる意味がなくなるので排除してます。

npm-scriptsの不満点

npm-scriptsでいろんなもの動かして混乱してますが、結局Gulpでもやることは同じなんですよね。

これぐらいであればGulpを使わないほうが見通しが良いと思います。

不満点としては検索しても解決方が見つかりづらい(脱Gulpが流行ってきたおかげでだいぶマシ)ところですかね。

大規模じゃなければ見通しが良く快適

しかしこれで

  • React
  • TypeScript
  • postcss
  • browser-sync
  • hot-Reload

の環境が出来上がりました。

Reactで使えるCSS用のライブラリを導入しつつ、かゆいところに手が届かないときは 独自でCSSを構築するということができるようになっています。

Material-UIは一番人気ですが、Bootstrap並みにマテリアル臭がかもしだしてきてるので 天邪鬼の私は中国発のCSSライブラリを使おうと思います。

Ant Designというライブラリです。

中国と言えば一昔前では微妙なグラフィックだったのですが、 気付いたら日本を余裕で追い越す見た目を完成させていました。

オリジナリティは無いが技術力は確か!

フラットで洗礼されたデザインでマテリアル臭もしないため、 Material-UIに飽きてきた人には良いかもしれません。

私もあまり使ったことはないですが、ReactでTypescriptにも対応しているため使いやすそうです。

それでは!