こないだ社内の勉強会でwebpackのこととか話したのでまとめた
webpackとは
- いろんなファイルをtranspileしてES5のJavaScriptに変換してくれるやつ
- AMDかCommonJSの形式でファイルをロード(CommonJSならrequire)すると、transpileしたファイルをロードしてくれる
- クライアント側のjsコードでもrequireを使用することができる
- assetとしてビルドして配布するイメージ
- コードが共用の場合、設定を変えることで素のrequireを利用するサーバー用コードと、webpackがpolyfillしたrequireを利用するクライアントコードとを別々に生成できる
- 全てがJavaScriptになる、画像やCSSも
- 特定のファイルをどのようにtranspileするかはpathマッチングでプラグイン形式で設定する
ビルドコードはだいたいこんなイメージ
require('./hoge.jsx'); ↓ function require(moduleId) { return installedModules[moduleId]; } const installedModules = [ function() { eval(__TRANSPILED_CODE__); } ]; require(0);
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が書ける
MultipleEntry
一つの設定から複数のファイルを書き出すことができる
// 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'); });
上のコードで御察しの通り、インスタンス化することもできる