こないだ社内の勉強会で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'); });
上のコードで御察しの通り、インスタンス化することもできる
Velocity
jest-cliでcollectCoverageが動かない時の直し方メモ
備忘録
- babel*@5 (勘弁して
- jest-cli@0.8.2
TL;DR
preprocessor.js
のbabel.transform
にretainLines
オプションを渡して、キャッシュをクリアする
くわしいめも
jestの設定はこんな感じ
{ "name": "test", "rootDir": "./src", "cacheDirectory": "<rootDir>/../tmp", "collectCoverage": true, "scriptPreprocessor": "<rootDir>/../preprocessor.js", "preprocessorIgnorePatterns": [ "node_modules" ], "unmockedModulePathPatterns": [...] }
最初に書いたpreprocessor.jsがこれ
var babel = require('babel-core'); module.exports = { process: function(src, filename) { if (!babel.canCompile(filename)) { /* ごにょごにょする */ } else { return babel.transform(src, { filename: filename }).code; } } };
出るエラーがこちら
Failed with unexpected error. /path/to/project/node_modules/jest-cli/src/jest.js:230 throw error; ^ TypeError: Cannot read property 'text' of undefined at /path/to/project/node_modules/jest-cli/node_modules/istanbul/lib/report/html.js:236:45 at Array.forEach (native) at annotateFunctions (/path/to/project/node_modules/jest-cli/node_modules/istanbul/lib/report/html.js:219:26) at HtmlReport.Report.mix.writeDetailPage (/path/to/project/node_modules/jest-cli/node_modules/istanbul/lib/report/html.js:422:9) at /path/to/project/node_modules/jest-cli/node_modules/istanbul/lib/report/html.js:484:26 at SyncFileWriter.extend.writeFile (/path/to/project/node_modules/jest-cli/node_modules/istanbul/lib/util/file-writer.js:57:9) at FileWriter.extend.writeFile (/path/to/project/node_modules/jest-cli/node_modules/istanbul/lib/util/file-writer.js:147:23) at /path/to/project/node_modules/jest-cli/node_modules/istanbul/lib/report/html.js:483:24 at Array.forEach (native) at HtmlReport.Report.mix.writeFiles (/path/to/project/node_modules/jest-cli/node_modules/istanbul/lib/report/html.js:477:23) npm ERR! Test failed. See above for more details.
どうやらjestじゃなくてistanbulのエラーっぽい
このエラー、なんか見たことあると思ったらbabelでretainLinesをつけずにtranspileしたコードをistanbulにかけるとlinenoが取れなくなって出るやつでした
なので、preprocessorでretainLinesを渡すようにした、ついでにcoverageちゃんととれるようにignore lineつけるようにする
var babel = require('babel-core'); module.exports = { process: function(src, filename) { if (!babel.canCompile(filename)) { /* ごにょごにょする */ } else { return babel.transform(src, { filename: filename, retainLines: true, auxiliaryCommentBefore: 'istanbul ignore next' }).code; } } };
キャッシュディレクトリにpreprocess-cache
ってのができてると思うので、削除する
で、動かすとなおる
electron@0.35のコードをwebpackしたら"electron"がないって言われた
electron
をcommonjsモジュールとして追加してあげる必要がある。
最初、何を思ったのかexternalとかIgnorePluginとかで無視しようとして時間を浪費したのでメモ。
NG
export default { plugins: [ new webpack.IgnorePlugin(/^(?:electron)$/), ] }
NG
export default { externals: ['electron'], }
OK
export default { plugins: [ new webpack.ExternalsPlugin('commonjs', ['electron']), ] }
apolloで買った楽曲をまとめてダウンロードするためのchrome拡張を作った
ネット同人音楽即売会APOLLOが開催の運びとなりました。
早速楽曲を買ったんですが、購入した曲をダウンロードする時に一個一個ばらけてて非常にめんどくさい。
なので、これをまとめてダウンロードできるようにするボタンを購入後ページに表示するchrome拡張を作りました。
Apollo Booster - Chrome Web Store
使い方は簡単、インストールしてから下記いずれかの注文後商品ダウンロードページを開くだけです。
- 購入後のチェックアウト画面
- 購入商品一覧
はかどる。
babel6のバグを引き当てた
gulpfileの中でasync/await使ってgazeでwatchしてたんだけど、watcherの中でconstを使うとSyntaxErrorが出る問題に遭遇した。
const copy = (source, dest) => new Promise((resolve, reject) => { ncp(source, dest, err => err ? reject(err) : resolve()); }); const watch = pattern => new Promise((resolve, reject) => { gaze(pattern, (err, watcher) => err ? reject(err) : resolve(watcher)); }); gulp.task('copy', async () => { const watcher = await watch(`${src}/**/*`); watcher.on('changed', async file => { util.log('[changed]', file); const rel = file.substr(path.join(__dirname, `..${src}`).length); // ^^^^^ SyntaxError: iter.js: "rel" is read-only (This is an error on an internal node. Probably an internal error. Location has been estimated.) await copy(`${src}/${rel}`, `${dst}/${rel}`); }); });
どうやらbabelのバグらしいという情報を得た、issue見る限り同じ症状っぽい。
タグがpending triage
になってたから直るまで時間かかりそうで、ちょっと作りたいものがあるので直るまで5系使って待つことにした。
@geta6 https://t.co/BmUxCe7Si5 Babel 6 のバグっぽい気がします...
— Toru Nagashima (@mysticatea) 2015, 11月 7