getalog

console.log geta6

こないだ社内の勉強会でwebpackのこととか話したのでまとめた

f:id:geta6:20160127192516p:plain

webpackとは

  • いろんなファイルをtranspileしてES5のJavaScriptに変換してくれるやつ
  • AMDかCommonJSの形式でファイルをロード(CommonJSならrequire)すると、transpileしたファイルをロードしてくれる
    • クライアント側のjsコードでもrequireを使用することができる
    • assetとしてビルドして配布するイメージ
  • コードが共用の場合、設定を変えることで素のrequireを利用するサーバー用コードと、webpackがpolyfillしたrequireを利用するクライアントコードとを別々に生成できる
  • 全てがJavaScriptになる、画像やCSS
    • 画像は「Base64かFilePath」に
    • CSSは「headにstyleを挿入するjsコード」に
  • 特定のファイルをどのようにtranspileするかはpathマッチングでプラグイン形式で設定する
    • webpackではこのプラグインのことを「ローダー」と呼ぶ
    • あるパターンに対して複数のローダーを積むこともできる
    • 例えば、/\.styl$/に対して['style', 'css', 'stylus']のように設定すると、「stylusをcssに変換して、cssを整形して、<style><head>へ挿入するjsコードに変換する」といったことができる

ビルドコードはだいたいこんなイメージ

require('./hoge.jsx');

↓

function require(moduleId) {
  return installedModules[moduleId];
}
const installedModules = [
  function() {
    eval(__TRANSPILED_CODE__);
  }
];
require(0);

csspngをrequireする時の使用例

import React, { Component } from 'react';
import logo from './logo.png';
import style from './MyComponent.styl';

class MyComponent extends Component {
  componentWillMount = () => style.use()
  componentWillUnmount = () => style.unuse()
  render = () => {
    return (
      <div id='MyComponent'>
        <img src={logo} />
        <span>Welcome to My Home Page </span>
      </div>
    );
  }
}

browserifyと何が違うのか

  • browserifyは「何が何でも一つのファイルに全部まとめるマン」
    • webpackは「時と場合に応じて適宜ファイルを分割するおじさん」
  • browserifyはシンプルな仕組みしか用意しない、だいたいタスクランナーが欲しくなるやつ
    • webpackはそのままでもある程度使えるようになってるのでconfig fileを準備する
// プロジェクトルートに置かれたwebpack.config.js
module.exports = {
  entry: {
    main: './src/main.js'
 },
  output: {
    path: './build',
    filename: '[name].js',
  }
};

webpackはgulpとかと同じようにjs-cli/js-interpretを採用しているのでwebpack.config.babel.jsなどとすればES6の記法でconfigが書ける

  • どっちがいいとかは無い、宗教みたいなもん
  • webpackを使えば「pngをロードした時に10kb以下ならbase64、10kb超えたらファイル分割する」とかいうことができる、そんくらい

MultipleEntry

  • 複数の設定・複数のファイルを同時にtranspileすることができる

一つの設定から複数のファイルを書き出すことができる

// webpack.config.js
module.exports = {
  entry: {
    main: './src/main.js'
    renderer: './src/renderer.js',
  },
  output: {
    path: './build',
    filename: '[name].js',
  }
};

複数の設定から複数のファイルを書き出すこともできる

// webpack.config.js
module.exports = [{
  entry: './src/main.js',
  output: {
    path: './build',
    filename: '[name].js',
  },
  target: 'node',
}, {
  entry: './src/renderer.js',
  output: {
    path: './build',
    filename: '[name].js',
  },
}];

DeadCodeElimination

  • webpackに搭載されているプラグインを使用すると、DEAD CODEが簡単に削除できる
    • UglifyJsPlugin、DefinePlugin

DefinePlugin

テンプレートのようなもの、コードの中からパターンを発見して置き換えてくれる

// webpack.config.js
new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
}),

コード中にprocess.env.NODE_ENVというパターンを見つけると、'development'などという文字列に置き換えてtranspileする

UglifyJsPlugin

通常通りのコード圧縮

UglifyにはConditional compilationという機能がある

簡単に言うと、常にfalseになるconditional blockはdropするよというモード

組み合わせる

if (process.env.NODE_ENV === 'development') {
  console.log('debug message here');
}if ('development' === 'development') {
  console.log('debug message here');
}

or

if ('production' === 'development') { // ここは常にfalseなのでuglifyするとdropされる
  console.log('debug message here');
}

便利なES6/7の書き方

arrow functionの即時実行

(() => {})()

jsxの文法中に書くのに使う、bindいらない

<div>
  {(() => {
    if (this.isSignedIn()) {
      return <div>Hello, {this.props.account.get('name')}!</div>;
    } else {
      return <a href='/signin'>Please sign in!</a>;
    }
  })()}
</div>

spread operator

DEBUGがtrueなら[1, 2, 3]、DEBUGがfalseなら[1, 2, 3, 4, 5]というarrayを作りたいとすると、下記のように書くことができる

[
  1, 2, 3,
  ...(DEBUG ? [] : [4, 5]),
]

spread演算子(...)によって続くカッコの中身が展開され、concatのような挙動になる

これはObjectにも適用することができる

{
  ...(DEBUG ? {a: 'a'} : {})
}

object assignment

こんな機能がある

const { a, b } = { a: 1, b: 2 };

実はキーワード引数的に利用出来る

const test = ({ key, val }) => {
  console.log(key); // hoge
}

test({ key: 'hoge' });

変数からobjectを作る時も楽

const key = 'hoge';
console.log({ key }); // { key: 'hoge' }

object return function

arrow functionからいきなりobjectを返す時に楽

(context) => ({
  hoge: context.getHoge(),
  fuga: context.getFuga(),
});

async/await

右辺にPromiseをとると同期的に待ってくれるようになる文法

functionの前にasyncと宣言すると、直下のスコープでawaitが使えるようになる

もう非同期も怖くない

const keys = (key) => new Promise((resolve, reject) => {
  redisClient.keys(key, (err, val) => err ? reject(err) : resolve(val));
});

async () => {
  const keyList = await keys('*');
  console.log(keyList);
};

waitも書ける

const wait = (time) => new Promise(resolve => setTimeout(resolve, time));

async () => {
  console.log('hoge');
  await wait(5000);
  console.log('fuga'); // 5秒後に表示される
};

asyncされた関数はPromiseを返すようになるので、async functionはawaitできる

async () => {
  await (async () => {
    await wait(1000);
  })();
};

右辺にとるのはthennableなら何でもよい、並列実行もできる

async () => {
  const [hoge, fuga, piyo] = await Promise.all([
    axios.get('/hoge.json'),
    axios.get('/fuga.json'),
    axios.get('/piyo.json'),
  ]);
  console.log(hoge, fuga, piyo);
};

babelのstage-0とruntimeが必要、ES5でも動くようにtranspileしてくれる

class context binding

あるES6 classについて、すべてのメソッドのcontextを自身に拘束したい場合がある(主にReact.Component)

通常ならばおそらくこう書かれる

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.bindedMethod = this.bindedMethod.bind(this);
  }

  bindedMethod() {
    console.log(this); // always MyComponent
  }
}

ところで、クラスには下記のような感じでメンバー変数を定義できる

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.bindedMethod = this.bindedMethod.bind(this);
  }

  hoge = 32 // コレ

  bindedMethod() {
    console.log(this.hoge); // always 32
  }
}
  • 「arrow functionがthisをバイパスすること・メンバー変数を定義できること」
  • この二つを利用して下記のように書くと、簡単にcontextを拘束することができる
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
  }

  hoge = 32

  bindedMethod = () => {
    console.log(this.hoge); // always 32
  }
}

よく使うjsライブラリ

strip-loader

  • webpackのloader
  • 'console.log'ってStringを渡せばtranspileした時にそのコードが消滅する
const strips = [
  'console.log',
  'console.info',
  'console.warn',
  'console.error',
  'console.assert',
].map(strip => `strip[]=${strip}`).join(',');

{
  loaders: [
    {
      test: /\.jsx?$/,
      exclude: /node_modules/,
      loaders: [...(DEBUG ? [] : [`strip?${strips}`]), 'babel'],
    },
  ]
};

classNames

  • HTMLのクラス名を生成する
this.setState({
  classNames: { hoge: true, fuga: true },
}, () => {
  className(this.state.classNames); // => 'hoge fuga'
});

domready

  • jQuery$.ready(function() {})相当
  • いざjQueryを破滅させて一番最初に困るのがdocument.readyの取り方だと思う
  • $.readyと互換性がある、既にreadyなら実行してくれる
domready(() => {
  console.log('ready!!');
  setTimeout(() => {
    domready(() => {
      console.log('ready!!'); // ちゃんと呼ばれる
    });
  }, 1000);
});

axios

  • Promiseを返すHTTP requester
    • つまりasync/await最適
  • nodeでもclientでも使える
    • nodeでもclientでも使える(重要)

mousetrap

  • 「Shift押しながらK」とか「Command押しながらG」とかのキーバインドを、Stringで指定できる
  • キーを順番に押した時の挙動なども簡単にbind可能
// single keys
Mousetrap.bind('4', () => console.log('4'));
Mousetrap.bind('esc', () => console.log('keyup escape'));


// combinations
Mousetrap.bind('command+shift+k', () => console.log('command shift k'));


// multiple
Mousetrap.bind('up up down down left right left right b a enter', () => {
  console.log('konami code');
});

上のコードで御察しの通り、インスタンス化することもできる

Velocity

  • きれいなアニメーション
  • jQueryありでもなしでも動く
  • tumblrのダッシュボードでも使用されてるらしい