@wakamshaさんの AltJS, AltCSS 課題を webpack, SCSS, TypeScript, React でやってみた

JavaScript

はじめに

こんにちは、すわ@秋葉原オフィスです。先日 Naoki YAMADA (@wakamsha) さんがSpeakerdeckで公開したAltJS/AltCSSに関するスライドを大変興味深く読ませてもらいました。

このスライドは社内の新卒社員向け研修の内容とのことで、最後に「課題」があったため、今回私なりに回答してみました。

…といったような、植木が執筆したブログに触発され、私が最近業務で使っている技術を織り交ぜた別解を作ってみました。

お題

  • JavaScript, CSS いずれも何かしらのプリプロセッサを使用すること(※HTMLは任意)
  • ローカルサーバーの起動
  • ファイルを変更したら自動的にビルド処理が実行
  • コンテンツは「Hello, world」でOK
  • 自信がある人は 何かしらのフレームワークも導入してみよう

回答

今回は以下の回答をしてみました。

  • webpack でビルドする
  • AltJSとして TypeScript を使う
  • AltCSSとして Sass (SCSS) を使う
  • webpack-dev-server で自動ビルド&ローカルサーバー起動を行う
    • ブラウザのホットリロードも行う
  • フレームワークとして React を使う

検証環境

  • macOS Sierra 10.12.4
  • Homebrew 1.2.2
  • Node.js v6.10.3
  • yarn v0.24.6

環境構築

今回は npm だけだと味気ないので、yarn を使ってみました。コマンドは、npmyarn に変えるだけでほぼ済みます。

$ brew install yarn

yarn init は使わず、直接 package.json を作成します。結局あとで編集する場合はこっちの方が楽です。

{
  "name": "webpack-react-sample",
  "version": "1.0.0",
  "description": "webpack & React sample",
  "main": "app.js",
  "scripts": {
    "start": "$(yarn bin)/webpack-dev-server",
    "build": "$(yarn bin)/webpack --progress --colors",
    "postinstall": "yarn run build"
  },
  "keywords": [
    "webpack",
    "react",
    "typescript",
    "scss"
  ],
  "author": "suwa.yuki <suwa.yuki@classmethod.jp>",
  "license": "MIT",
  "dependencies": {
    "react": "^15.5.4",
    "react-dom": "^15.5.4"
  },
  "devDependencies": {
    "@types/react": "^15.0.27",
    "@types/react-dom": "^15.5.0",
    "css-loader": "^0.28.4",
    "extract-text-webpack-plugin": "^2.1.0",
    "file-loader": "^0.11.2",
    "html-webpack-plugin": "^2.28.0",
    "node-sass": "^4.5.3",
    "sass-loader": "^6.0.5",
    "style-loader": "^0.18.2",
    "ts-loader": "^2.1.0",
    "typescript": "^2.3.4",
    "url-loader": "^0.5.8",
    "webpack": "^2.6.1",
    "webpack-dev-server": "^2.4.5"
  }
}

scriptsstart を書くことで、yarn start で実行されます。また build はカスタムコマンドです。こちらは yarn run build で走ります。

なお postinstall を書くことで、yarn install 実行時にビルドを走らせることができます。優しさです。

コード

ファイルがたくさんあるので GitHub にアップしました。

今回は React と TypeScript を組み合わせて使っています。

index.ts では ReactDOM を使って div 要素にコンポーネントをレンダリングしています。

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from "js/App";
import 'scss/main';

ReactDOM.render(
	<App content="Hello World!"/>,
	document.getElementById('app')
);

index.ts の中で呼んでいる App クラスは以下のような感じです。TypeScript で JSX がサポートされているので、このような書き方ができます。

import * as React from 'react';

export interface Props {
    content: string;
}

export default class MyComponent extends React.Component<Props, {}> {
    render() {
        return <div><h1>{this.props.content}</h1></div>
    }
}

また、Webpack の設定は下記の通りです。ポイントは HtmlWebpackPlugin を使っているところです。これを使うと HTML ファイルをテンプレートを元に生成できます。ビルド対象のファイルを含めるタグ (scriptlink) を自動で付与してくれます。便利です。

'use strict';

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

module.exports = [
  {
    name: 'app',
    context: path.join(__dirname,  'src'),
    entry: {
      index: './js/index.tsx',
    },
    output: {
      path: path.join(__dirname, 'dist'),
      filename: 'assets/[name].js',
      publicPath: ''
    },
    devServer: {
      contentBase: 'dist',
      port: 8000
    },
    devtool: 'source-map',
    module: {
      loaders: [
        {
          test: /\.ts$|\.tsx$/,
          exclude: /node_modules/,
          loader: 'ts-loader'
        },
        { 
          test: /\.scss$/,
          loader: ExtractTextPlugin.extract({ 
            fallback: 'style-loader',
            use: [
              {
                loader: 'css-loader?modules'
              },
              {
                loader: 'sass-loader'
              }
            ]
          })
        }
      ]
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.js', '.jsx', '.css', '.scss'],
      modules: [
        'src', 'node_modules'
      ]
    },
    plugins: [
      new HtmlWebpackPlugin({
        title: 'Example App',
        template: 'template/index.ejs'
      }),
      new ExtractTextPlugin('assets/[name].css')
    ]
  }
];

実行してみる

yarn run build を実行すると、ビルドした後にローカルサーバーを起動し、待機モードに入ります。

$ yarn run build
yarn run v0.24.6
$ $(yarn bin)/webpack --progress --colors
 10% [0] building modules 0/1 modules 1 active ...webpack-react-sample/src/js/index.tsxts-loader: Using typescript@2.3.4 and /Users/Yuki/work/webpack-react-sample/tsconfig.json
[0] Hash: 163cc2fa966552a21ffb
Version: webpack 2.6.1
Child app:
    Hash: 163cc2fa966552a21ffb
    Time: 3576ms
                   Asset       Size  Chunks                    Chunk Names
         assets/index.js     740 kB       0  [emitted]  [big]  index
        assets/index.css   95 bytes       0  [emitted]         index
     assets/index.js.map     882 kB       0  [emitted]         index
    assets/index.css.map   93 bytes       0  [emitted]         index
              index.html  232 bytes          [emitted]
       [0] ../~/process/browser.js 5.42 kB {0} [built]
      [10] ../~/react-dom/lib/ReactUpdates.js 9.53 kB {0} [built]
      [19] ../~/react/lib/React.js 3.32 kB {0} [built]
      [50] ../~/react/react.js 56 bytes {0} [built]
      [81] ./scss/main.scss 41 bytes {0} [built]
      [82] ../~/react-dom/index.js 59 bytes {0} [built]
      [83] ./js/App.tsx 951 bytes {0} [built]
     [113] ../~/react-dom/lib/ReactDOM.js 5.14 kB {0} [built]
     [177] ../~/react/lib/ReactPureComponent.js 1.32 kB {0} [built]
     [178] ../~/react/lib/ReactVersion.js 350 bytes {0} [built]
     [181] ../~/react/lib/onlyChild.js 1.34 kB {0} [built]
     [183] ./js/index.tsx 225 bytes {0} [built]
     [184] ../~/css-loader?modules!../~/sass-loader/lib/loader.js!./scss/main.scss 229 bytes [built]
     [186] ../~/style-loader/lib/addStyles.js 8.7 kB [built]
     [187] ../~/style-loader/lib/urls.js 3.01 kB [built]
        + 173 hidden modules
    Child html-webpack-plugin for "index.html":
           [0] ../~/lodash/lodash.js 540 kB {0} [built]
           [1] ../~/html-webpack-plugin/lib/loader.js!./template/index.ejs 551 bytes {0} [built]
           [2] ../~/webpack/buildin/global.js 509 bytes {0} [built]
           [3] ../~/webpack/buildin/module.js 517 bytes {0} [built]
    Child extract-text-webpack-plugin:
           [0] ../~/css-loader/lib/css-base.js 2.26 kB {0} [built]
           [1] ../~/css-loader?modules!../~/sass-loader/lib/loader.js!./scss/main.scss 229 bytes {0} [built]
✨  Done in 4.58s.

ローカルの 8000 ポートで Web サーバーが起動するので、ブラウザで http://localhost:8000/ にアクセスするとページが表示されます。

ソースコード (JavaScript ファイル または SCSS ファイル) が修正されると再ビルドが走り、自動的にブラウザの再読み込みが行われます。

demo

まとめ

Naoki YAMADA (@wakamsha) さんの課題は、最近の JavaScript 界隈のほんの端っこを知る機会としてとても良い内容だと思いました。ぜひ皆さんも実際にコードを書いて動かしてみてください。

参考資料

AWS Cloud Roadshow 2017 福岡