Aurelia CLI は一体何をしているのだろう(au run 編)

# はじめに
Aurelia CLIを`au run`コマンドを題材に読み砕いて見ていく、という記事です。

私がサーバーサイドエンジニアでフロントエンドの知識が浅いため、
知り得た情報をなるべく細かく書いています。

# Aurelia CLI

https://aurelia.io/docs/cli/basics

**Aurelia CLI**はAureliaを使用する際に便利なコマンド群を提供します。

#　環境
　　package | version
--- | ---
aurelia-bootstrapper | 2.3.0
aurelia-cli | 1.0.0-beta.6

# テストプロジェクト作成


まずAureliaのプロジェクトを作成してみます

`au new`でプロジェクトを作成できます。`here`オプションでカレントディレクトリ内に作成してます。

設定は以下の通りです。

```
tanakeinoMac-puro:aurelia-test tanakei$ au new --here
                      _ _          ____ _     ___
  __ _ _   _ _ __ ___| (_) __ _   / ___| |   |_ _|
 / _` | | | | '__/ _ \ | |/ _` | | |   | |    | |
| (_| | |_| | | |  __/ | | (_| | | |___| |___ | |
 \__,_|\__,_|_|  \___|_|_|\__,_|  \____|_____|___|


Which module loader / bundler would you like to use?

1. Webpack (Default)
   A powerful and popular bundler for JavaScript
2. CLI's built-in bundler with RequireJS
   RequireJS is a mature and stable module loader for JavaScript.
3. CLI's built-in bundler with SystemJS
   SystemJS is Dynamic ES module loader, the most versatile module loader for JavaScript

[Webpack]>

What platform are you targeting?

1. Web (Default)
   The default web platform setup.
2. ASP.NET Core
   A powerful, patterns-based way to build dynamic websites with .NET.

[Web]>

What transpiler would you like to use?

1. Babel (Default)
   An open source, standards-compliant ES2015 and ESNext transpiler.
2. TypeScript
   An open source, ESNext superset that adds optional strong typing.

[Babel]>

How would you like to setup your template?

1. Default (Default)
   No markup processing.
2. Minimum Minification
   Removes comments and whitespace between block level elements such as div, blockquote, p, header, footer ...etc
3. Maximum Minification
   Removes comments, script & link element [type] attributes and all whitespace between all elements. Also remove attribute quotes where possible.
   Collapses boolean attributes.

[Default]>

What css processor would you like to use?

1. None (Default)
   Use standard CSS with no pre-processor.
2. Less
   Extends the CSS language, adding features that allow variables, mixins, functions and many other techniques.
3. Sass
   A mature, stable, and powerful professional grade CSS extension.
4. Stylus
   Expressive, dynamic and robust CSS.
5. PostCSS
   A tool for transforming CSS with JavaScript.

[None]> 3

Which unit test runners would you like to use?

1. None
   Skip testing. My code is always perfect anyway.
2. Karma
   Configure your app with Karma and Jasmine
3. Jest
   Configure your app with Jest and Jasmine

Select one or more options separated by spaces
> 1

Would you like to configure integration testing?

1. No (Default)
   Skip testing. My code is always perfect anyway.
2. Protractor
   Configure your app with Protractor

[No]> 1

What is your default code editor?

1. Visual Studio Code (Default)
   Code editing. Redefined. Free. Open source. Runs everywhere.
2. Atom
   A hackable text editor for the 21st Century.
3. Sublime
   A sophisticated text editor for code, markup and prose.
4. WebStorm
   A lightweight yet powerful IDE, perfectly equipped for complex client-side development.
5. None of the Above
   Do not configure any editor-specific options.

[Visual Studio Code]> 3

Project Configuration

    Name: aurelia-test
    Platform: Web
    Bundler: Webpack
    Loader: None
    Transpiler: Babel
    Markup Processor: None
    CSS Processor: Sass
    Unit Test Runner: None
    Integration Test Runner: None
    Editor: Sublime


WARNING: The current directory is not empty. Would you like to create the project in this folder?

1. Yes (Default)
   Creates the project structure based on your selections even though the current directory is not empty
2. Restart
   Restarts the wizard, allowing you to make different selections.
3. Abort
   Aborts the new project wizard.

[Yes]> 1
Project structure created and configured.

Note: lock files are not cross compatible between package managers. Choose Yarn here only if you intend to use Yarn for future package installs. Alternatively, remove either yarn.lock or package-lock.json from the project directory before installing new packages.

Would you like to install the project dependencies?

1. Yes, use Yarn (Default)
   Installs all server, client and tooling dependencies needed to build the project using Yarn.
2. Yes, use NPM
   Installs all server, client and tooling dependencies needed to build the project using NPM.
3. No
   Completes the new project wizard without installing dependencies.

[Yes, use Yarn]> 2
```

作成後、`au run`を押すと...


![スクリーンショット 2018-12-06 2.10.55.png](https://qiita-image-store.s3.amazonaws.com/0/118247/cc122f5b-3e43-bd65-5589-ab9826edc73e.png)


動いた！


## `au run`で一体何が起こったのか

ここで本題に入ります。

`au run`コマンドを使えば簡単にプロジェクトを実行できるのはわかりました。
Webpackもサーバーサイドも何もいじっていないのに、すごいですね。

だが待ってほしい。

そういうものだ、で形付けるにはあまりにも強引だ。私、気になります。

### コマンド詳細

まず、ヘルプを見てみます


```
run --analyze --env value --hmr

    Builds the application and serves up the assets via a local web server, watching files for changes as you work.

    --analyze - Enable Webpack Bundle Analyzer. Typically paired with --env prod

    --env - Sets the build environment.

    --hmr - Enable Hot Module Reload
```

詳細に見ていきましょう

#### --analyze
これは`Webpack Bundle Analyzer`というものを有効にしています。

Webpackでバンドルしたファイルの内訳を視覚化してくれるツールのようです。

記事あったので貼っておきます

[webpackを可視化するツールを紹介#バンドルファイル内の各パッケージがどのくらいの容量を占めているか知りたい](https://qiita.com/kurosame/items/9e7092cdf08ff2ba7500#%E3%83%90%E3%83%B3%E3%83%89%E3%83%AB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%86%85%E3%81%AE%E5%90%84%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%8C%E3%81%A9%E3%81%AE%E3%81%8F%E3%82%89%E3%81%84%E3%81%AE%E5%AE%B9%E9%87%8F%E3%82%92%E5%8D%A0%E3%82%81%E3%81%A6%E3%81%84%E3%82%8B%E3%81%8B%E7%9F%A5%E3%82%8A%E3%81%9F%E3%81%84)

#### --env

ビルド環境の設定です。

#### --hmr

これは`Hot Module Replacement`というものを有効にしています。

これはページをリロードすすることなく更新したコンポーネントを読み込み、描画するという機能です。

通常dev環境では`Hot Reload`となり、こちらはファイル変更時にページをリロードするので、コンポーネントのステートが初期化されます。

これも記事あります

[Hot Module Replacementの設定と仕組みを理解する](https://qiita.com/haradakunihiko/items/40486ec2b6b9aea119bb)

### コードを追ってみる

`au run`で実行されるコマンドは`au new`した際に作成された`aurelia_project/tasks`に含まれています。

```bash
tanakeinoMac-puro:aurelia-test tanakei$ cd aurelia_project/tasks/
tanakeinoMac-puro:tasks tanakei$ ls -la
total 72
drwxr-xr-x  11 tanakei  staff   352 12  7 00:07 .
drwxr-xr-x   7 tanakei  staff   224 12  6 02:34 ..
-rw-r--r--   1 tanakei  staff  1512 12  6 02:25 build.js
-rw-r--r--   1 tanakei  staff   371 12  5 22:57 build.json
-rw-r--r--   1 tanakei  staff   834 12  6 02:46 environment.js
-rw-r--r--   1 tanakei  staff   616 11 30 23:26 jest.js
-rw-r--r--   1 tanakei  staff   250 11 30 23:26 jest.json
-rw-r--r--   1 tanakei  staff   449 11 30 23:26 karma.js
-rw-r--r--   1 tanakei  staff   247 11 30 23:26 karma.json
-rw-r--r--   1 tanakei  staff  1322 12  6 03:16 run.js
-rw-r--r--   1 tanakei  staff   537 12  7 00:07 run.json
```

`run`はrun.js/run.jsonを読んでいるわけです。


##### run.json

最初に`run.json`を見てみます

```json:aurelia_project/tasks/run.json
{
  "name": "test",
  "description": "Builds the application and serves up the assets via a local web server, watching files for changes as you work.",
  "flags": [
    {
      "name": "analyze",
      "description": "Enable Webpack Bundle Analyzer. Typically paired with --env prod",
      "type": "boolean"
    },
    {
      "name": "env",
      "description": "Sets the build environment.",
      "type": "string"
    },
    {
      "name": "hmr",
      "description": "Enable Hot Module Reload",
      "type": "boolean"
    }
  ]
}
```

なるほど、これはヘルプの内容ですね。つまりこれを編集すると...

```
"description": "僕の考えた最強呪文",
```

```bash
tanakeinoMac-puro:tasks tanakei$ au

run --analyze --env value --hmr

    僕の考えた最強呪文

    --analyze - Enable Webpack Bundle Analyzer. Typically paired with --env prod

    --env - Sets the build environment.

    --hmr - Enable Hot Module Reload
```

おｋ。次は`run.js`を見ます。

##### run.js

```js:aurelia_project/tasks/run.js
import {config} from './build';
import configureEnvironment from './environment';
import webpack from 'webpack';
import Server from 'webpack-dev-server';
import project from '../aurelia.json';
import {CLIOptions, reportWebpackReadiness} from 'aurelia-cli';
import gulp from 'gulp';

function runWebpack(done) {
  // https://webpack.github.io/docs/webpack-dev-server.html
  let opts = {
    host: 'localhost',
    publicPath: config.output.publicPath,
    filename: config.output.filename,
    hot: project.platform.hmr || CLIOptions.hasFlag('hmr'),
    inline: true,
    port: project.platform.port,
    contentBase: config.output.path,
    historyApiFallback: true,
    open: project.platform.open,
    stats: {
      colors: require('supports-color')
    }
  };

  if (project.platform.hmr || CLIOptions.hasFlag('hmr')) {
    config.plugins.push(new webpack.HotModuleReplacementPlugin());
    config.entry.app.unshift(`webpack-dev-server/client?http://${opts.host}:${opts.port}/`, 'webpack/hot/dev-server');
  }

  const compiler = webpack(config);
  let server = new Server(compiler, opts);

  server.listen(opts.port, opts.host, function(err) {
    if (err) throw err;

    reportWebpackReadiness(opts);
    done();
  });
}

const run = gulp.series(
  configureEnvironment,
  runWebpack
);

export { run as default };
```

コマンドで最初に呼ばれる箇所は

```js
const run = gulp.series(
  configureEnvironment,
  runWebpack
);

export { run as default };
```

これですね。`run`をdefaultとしています。

`gulp.series`でfunctionを順番に処理していきます。


最初の`configureEnvironment`は

```js
import configureEnvironment from './environment';
```

`environment.js`を読んでいるようです。見てみましょう。

```js:aurelia_project/tasks/environment.js
import project from '../aurelia.json';
import rename from 'gulp-rename';
import {CLIOptions} from 'aurelia-cli';
import gulp from 'gulp';
import fs from 'fs';
import path from 'path';
import through from 'through2';

function configureEnvironment() {
  let env = CLIOptions.getEnvironment();

  return gulp.src(`aurelia_project/environments/${env}${project.transpiler.fileExtension}`)
    .pipe(rename(`environment${project.transpiler.fileExtension}`))
    .pipe(gulp.dest(project.paths.root))
    .pipe(through.obj(function (file, enc,  cb) {
      // https://github.com/webpack/watchpack/issues/25#issuecomment-287789288
      var now = Date.now() / 1000;
      var then = now - 10;
      fs.utimes(file.path, then, then, function (err) { if (err) throw err });
      cb(null, file);
    }));
}

export default configureEnvironment;
```

`aurelia_project/environments/<env>.js`のファイルを`environment.js`にリネームして、`project.paths.root`に出力しています。

`project.paths.root`は`aurelia_project/aurelia.json`に記載されています

```js:aurelia_project/aurelia.json

...
  "paths": {
    "root": "src",
...
```
つまり、初期設定ですと`src/environment.js`に出力されるわけですね。

`run.js`に戻って次は`runWebpack`の処理ですね。
これは`run.js`内部に実装されています。

順番に見てみます。

```js:aurelia_project/tasks/run.js
  let opts = {
    host: 'localhost',
    publicPath: config.output.publicPath,
    filename: config.output.filename,
    hot: project.platform.hmr || CLIOptions.hasFlag('hmr'),
    inline: true,
    port: project.platform.port,
    contentBase: config.output.path,
    historyApiFallback: true,
    open: project.platform.open,
    stats: {
      colors: require('supports-color')
    }
  };
```

サーバーの設定のようですね。

```js
hot: project.platform.hmr || CLIOptions.hasFlag('hmr'),
```

`CLIOptions.hasFlag('hmr')`で`hmr`オプションをチェックして、HMRを使用するか設定しているわけですね。
ただその前に、`project.platform.hmr`があります。
これは先程説明した`aurelia.json`から設定を先に取得しています。

次行きます

```js:aurelia_project/tasks/run.js
  if (project.platform.hmr || CLIOptions.hasFlag('hmr')) {
    config.plugins.push(new webpack.HotModuleReplacementPlugin());
    config.entry.app.unshift(`webpack-dev-server/client?http://${opts.host}:${opts.port}/`, 'webpack/hot/dev-server');
  }
```

`hmr`オプションが有効のときに、webpackに`HotModuleReplacementPlugin`を追加しています。


次行きます

```js:aurelia_project/tasks/run.js
  const compiler = webpack(config);
  let server = new Server(compiler, opts);

  server.listen(opts.port, opts.host, function(err) {
    if (err) throw err;

    reportWebpackReadiness(opts);
    done();
  });
```

webpackコンパイラを作成しています。引数の`config`は

```js
import {config} from './build';
```

build.jsに設定されているようですね。見てみましょう。

```js:aurelia_project/tasks/build.js
import webpackConfig from '../../webpack.config';

...
const config = webpackConfig({
  production, server, extractCss, coverage, analyze
});
...
```

`webpack.config.js`の設定を持ってきているとうわけですね。


戻ります。

webpackコンパイラと、先程のオプションと共に`Server`に渡しています。

`Server`とは

```js
import Server from 'webpack-dev-server';
```

なるほど、`au run`で`webpack-dev-server`がwebサーバーとして起動していたわけですね。

その後、`server.listen`でサーバーを起動して、run.jsの処理は終了です。

`reportWebpackReadiness`は`webpack-reporter`という起動ログを表示する関数を叩いています。

https://github.com/aurelia/cli/blob/00ed99754233660f29eb3471f9aad0db9cfe3141/lib/index.js#L12
https://github.com/aurelia/cli/blob/00ed99754233660f29eb3471f9aad0db9cfe3141/lib/build/webpack-reporter.js

# おまけ

`Aurelia CLI`の挙動について、理解が深まったのではないでしょうか。

他のコマンドも同様に見ていくと面白いと思います。

私も暇ができたら見てみます。

**よいAureliaライフを！！！**

そして

12/24は🎂**古河渚**🎂の誕生日です。

皆さん、CLANNADを見ましょう。

![なぎさ](https://pbs.twimg.com/media/Dr5H0DbUcAMb9yK?format=jpg&name=small)



さようなら