Laravel プロジェクトに Angular を導入してみた

はじめに

Laravel のプロジェクトに Angular を導入してみたのでご紹介。 Angular (TypeScript) を Webpack でトランスパイルして、blade で表示する流れ。

環境

  • Laravel 5.2
  • Angular 4.0
  • TypeScript 2.1
  • Webpack 1

すでに Laravel をインストールして、new project した前提で話を進める。

Laravel からいらないライブラリやファイルを削除する

Laravel の JS や CSS のビルドは、gulp で行われているのでそれに関係するライブラリやファイル群を削除。

 gulpfile.js
 gulp
 gulp-environments
 gulp-if
 gulp-load-plugins
 gulp-notify
 gulp-plumber
 gulp-postcss
 gulp-rev
 gulp-sass
 gulp-sourcemaps
 gulp-typescript
 gulp-uglify
 gulp-watch
 gulp-webpack
 lite-server

Angular / TypeScript などなどインストー

Angular を TypeScript で書くのでインストール。他、ビルド等で必要なものも。

@angular/common
@angular/compiler
@angular/core
@angular/forms
@angular/http
@angular/platform-browser
@angular/platform-browser-dynamic
@types/core-js
@types/jasmine
@types/karma
@types/lodash
@types/node
@types/webpack
@types/webpack-env
core-js
es6-promise
es6-shim
hammerjs
jasmine-core
karma-typescript
rxjs
systemjs
zone.js
tslint
tslint-loader
css-loader
karma
karma-jasmine
karma-phantomjs-launcher
karma-sourcemap-loader
karma-webpack
phantomjs-prebuilt
style-loader
ts-helpers
ts-loader
typescript
webpack

Angular 用のディレクトリを掘ってコンポーネント配置

public/ の中に入れてしまうか、resources/ の下に入れてしまうか迷った結果、ルートに angular というディレクトリを掘ってそこに集約。これはどこでも良いと思うが、わかりやすさを重視した。 コンポーネントの配置は、基本的に Angular の Style Guide に乗っ取った。components/ の中はどのコンポーネントが何を内包しているかわかりやすくするため、階層型にしている。

├── components
│   ├── footer
│   │   ├── footer.component.html
│   │   ├── footer.component.scss
│   │   ├── footer.component.spec.ts
│   │   └── footer.component.ts
│   ├── header
│   │   ├── header.component.html
│   │   ├── header.component.scss
│   │   ├── header.component.spec.ts
│   │   └── header.component.ts
│   ├── main-area
│   │   ├── xxx/ (子コンポーネント)
│   │   ├── main-area.component.html
│   │   ├── main-area.component.scss
│   │   ├── main-area.component.spec.ts
│   │   └── main-area.component.ts
├── config
│   ├── config.ts
│   ├── development.ts
│   ├── production.ts
│   └── staging.ts
├── environments
│   ├── environment.prod.ts
│   └── environment.ts
├── main.ts
├── polyfills.ts
└── services

Webpack や TypeScript の設定

tsconfig.json はいろんな設定をパクった結果、今のものに行き着いた。tsconfig.json の設定如何では、コンパイル通らなくなったりコード書きづらくなる(any 周りなど)のでドキュメント見ながら設定することをおすすめする。 また、webpack.config.js は、development と production で分岐。 development では sourcemap を出してデバッグしやすく、production ではなるべくバンドルを圧縮させるプラグインを使用。 webpack-dev-server を使ってみたのですが、ルートディレクトリに index.html がないと起動しないらしく、webpack –watch で代用。

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "allowJs": true,
    "moduleResolution": "node",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": false,
    "removeComments": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "compileOnSave": false,
    "types": [
      "node",
      "jasmine"
    ],
    "lib": [
      "es2015", "dom"
    ]
  },
  "typeRoots": [
    "node_modules/@types"
  ],
  "exclude": [
    "node_modules",
    "vendor"
  ]

webpack.config.js

'use strict';

var webpack = require('webpack');
var path = require('path');
var isProd = process.env.NODE_ENV === 'production';

var config = {
  devtool: isProd ? 'eval' : 'inline-source-map',
  entry: {
    'app': './angular/main.ts'
  },
  output: {
    path: path.resolve(__dirname, 'public/js'),
    filename: 'app.js',
    chunkFilename: (+new Date) + '.app.js'
  },
  resolve: {
    modules: [
      path.join(__dirname + 'angular/'),
      "node_modules"
    ],
    extensions: ['.ts', '.js']
  },
  module: {
    rules: [
      {
        test: /\.ts?$/,
        use: ['light-ts-loader', 'angular2-template-loader'],
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: ['to-string-loader', 'style-loader', 'css-loader']
      },
      {
        test: /\.scss$/,
        use: ['to-string-loader', 'css-loader', 'sass-loader']
      },
      {
        test: /\.html$/,
        use: ['html-loader?-minimize']
      },
      {
        test: /\.(png|woff|woff2|eot|ttf|svg)$/,
        use: ['url-loader']
      }
    ]
  },
  plugins: isProd ? [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: true,
        screw_ie8: true
      }
    }),
    new webpack.optimize.AggressiveMergingPlugin()
  ] : [],
  watchOptions: {
    poll: true
  }
};

module.exports = config;

ユニットテスト設定

こちらもいろんなライブラリを使ってみた結果、Angular のドキュメントでも紹介されていた karma + jasmine に行き着いた。 karma の設定は簡単だったが、disconnected になったときのエラーを探るときが大変。(karma start しないとでてこない)

karma.conf.js

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine'],
    files: [
      {pattern: './karma-shims.js', watched: false}
    ],
    exclude: [
    ],
    preprocessors: {
      './karma-shims.js': ['webpack', 'sourcemap'],
      'angular/**/*.spec.ts': ['coverage']
    },
    webpack: require('./webpack.config.js'),
    webpackMiddleware: {
      watchOptions: {
        poll: true
      }
    },
    reporters: ['progress', 'coverage'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS'],
    singleRun: false,
    concurrency: Infinity,

    coverageReporter: {
      dir: 'public/coverage',
      reporters: [
        {
          type: 'json',
          subdir: './',
          file: 'result.json',
        },
        {
          type: 'lcov',
        }
      ]
    }
  })
}

Angular のバージョン上げてみた

Angular 2 -> 4

これ以上早くならないと思っていたビルド時間が早くなり、バンドルのサイズも1〜2割ほどちっちゃくなったので感動。笑

比較結果

Angular 2
<dev>
Time: 15.06s
bundle size: 10.9MB
<prod>
Time: 14.05s
bundle size: 4.72MB
Angular 4
<dev>
Time: 20.40s
bundle size: 9.39MB
<prod>
Time: 11.96s
bundle size: 3.8MB

まとめ

Angular はドキュメントがかなり充実してるので、ドキュメントを見よう!
https://angular.io

※ もう少し細かいところで詰まった気がしているが、だいたい tsconfig.json か webpack.config.js の内容や設定を理解してなかったのが原因だったので設定ファイルのドキュメントを見るのが大事