夢見がちWeb屋の浮かれ雑記帳

フロントエンド周りの技術ネタやらなんやらね

自動生成用のテンプレートを自動生成してみることにした

四六時中プログラムを書いていると自然にお約束パターンが出てきてソースコードの雛形的なのを作りたくなることがままある。 俺様Scaffold的なやつが欲しくなる。

テンプレートエンジン的なやつはいくらでも転がっているので、Scffold的なのは割と簡単と作れる。

そして気づく。

テンプレート管理めんどくせぇ。。。

最初に

一回動くものを作る -> ソースコードをテンプレート化する -> 値を渡してレンダリングする -> 挙動を試す

とやるのは良いのだけど、その後何か変更が生じると、

ちょっと直す -> テンプレートをいじる -> 値を渡してレンダリングする -> 挙動を試す

的になるのだけどこれが地味にめんどくさい。

テンプレート状態だとSyntaxチェックとかもできない場合が多いので、つまらん記述ミスもレンダリングして挙動を試して発覚したりする。

どうしたものか。

そうだ、テンプレート自体を自動生成すれば良いじゃないか。

と気がついて作ったのがこれ ↓↓

github.com

まずベタ書きで動くものを作り、ルールに沿ってそれをテンプレート化するというものだ。

my_awesome_func.js

function myAwesomeFunc () {
  /* ... */
}

   ↓↓            ↑↑

Tmplify    Render

   ↓↓            ↑↑

name@snakecase.js.tmpl

function ____name@camelcase____ () {
  /* ... */
}

"myAwesomeFunc"というベタの値を抽象的な"name"とテンプレート化し、"name"に他の値を埋め込むことで他のファイルを自動生成できるようにする、というものだ。

個人的には宗教上の理由でJavascriptのファイル名で大文字使うのやめてほしいという信念があるので、Snake Case / Camel Case変換的なやつもきちんとするように作り込んだ。

実際の使い方としては、

の三つを以下の要領で呼び出すことでテンプレート化ができる。

'use strict'

const tmplconv = require('tmplconv')

// Generate template from existing directory
tmplconv.tmplify('demo/demo-app', 'asset/app-tmpl', {
  // Patterns of files to tmplify
  pattern: [
    'lib/*.js',
    'test/*_test.js'
  ],
  // Rule to tmplify
  data: {
    'name': 'my-awesome-app',
    'description': "This is an example for the app templates."
  }
}).then((result) => {
  /* ... */
})

逆に、作ったテンプレートから生成を行うときは

'use strict'

const tmplconv = require('tmplconv')

// Render files from existing template
tmplconv.render('asset/app-tmpl', 'demo/demo-app', {
  // Data to render
  data: {
    'name': 'my-awesome-app',
    'description': "This is an example for the app templates."
  }
}).then((result) => {
  /* ... */
})

的な。

JavaScriptのソースコードのインデントは空白2文字である。異論は認めない。

まあw3schools.comのJavaScript Style Guide and Coding Conventionsでは思いっきり4だって言ってんだけどね。

Always use 4 spaces for indentation of code blocks:

WebStormもデフォルトだと4だったりする。

でも、最近人気のStandard.jsでは

2 spaces – for indentation

って言ってるし。

github.com

最近はRubyやらPythonも2space indentが優勢な気がするんでJavaScriptもそうなるだろう。と勝手に思っている。

React.jsでPureRenderをHOCパターン化したらReduxでも捗った件

React.jsでのパフォーマンス最適化に関しては、昔はPureRenderMixinを使ってRenderを最小化しろと言われてた。 でもある時、公式ブログでやっぱMixin難しくね?って話が出てきて、shallowCompareを使えとなった。

facebook.github.io

var shallowCompare = require('react-addons-shallow-compare');

var Button = React.createClass({
  shouldComponentUpdate: function(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  },

  // ...

});

的な。

一方Gihubの星がやたら多いReduxさんは

github.com

Renderingの最適化はreact-reduxに任せておけばえーねんと宣まい、 ReactのComponentは全部Stateless Functionにしとけばいい(classにする必要はない)と言う。

const Button = (props) => { /* ... */ }

確かにReduxをちゃんと実装すればshouldComponentUpdateはほとんど気にしなくていい。

が、やっぱりちゃんとやろうと思うと必要な場面が出てくる。 繰り返し項目とかね。

react-reduxは内部的にshallowCompareを使うので、connectでbindしたプロパティがArrayの場合、 Arrayの一部だけ変化だとしてもArrayそのもののインスタンスが変われば子要素全てが再レンダリングされてしまう。

const list = (entities) => entities.map((entity) => (
   <MyListItem key={ entitye.id } entity={ entity } />
)

みたいな場合、entitiesの一部だけが変わった場合の最適化はそれぞれのMyListItem側で最適化しなければならない。

この場合、MyListItemがContainer Componentだったら同じくreact-reduxに最適化に任せれば良いのだけど、Presentational Componentだったら結局、自前でshouldComponentUpdate判定する必要がある。

medium.com

でもそのためだけにStateless Functionをやめてclass化するのはなんかやだ。

const MyListItem = (props) => { /* ... */ }

的なシンプルさを保ちたい。

どうすれば良いのか。

そこでHOC (Higher-Order Components)ですよ。

shouldComponentUpdateを個別に書くのをがめんどくさいのならwrapperで共通化しちゃえば良い。

つまり、

const asPure = (Component) => 
  class AsPure extends React.Component {
      shouldComponentUpdate (nextProps) {
        const s = this
        let { props } = s
        return !shallowEqual(props, nextProps)
      }

      render () {
        const s = this
        let { props } = s
        return React.createElement(Component, props)
      }
    }

export default asPure

的なwrapper関数を用意して、

const MyListItem = (props) => { /* ... */ }
export default asPure(MyListItem)

にすりゃいいじゃないか!

と言うことに気がついた。

しかしあれだな、書いてて思ったけど、 HOCだのPresentational Componentだの、どうしてReact周りはこんな難しい単語まみれになっちゃうのやら。。。

あの頃、軽い気持ちでpublishしたnpmパッケージはもう消せない。

結構前からnpmはunpublishができなくなった。依存先での混乱を防ぐため、publishから24時間を過ぎたパッケージは基本的に消せなくなったのだ。

The npm Blog — changes to npm’s unpublish policy

つまり昔あげちゃったやつは永遠に残るのだ。それがどんなカスだろうと。

ということをふと思い出し、自分のあげたパッケージを確認してみた

www.npmjs.com

2017/01/22現在、

753 Packages by okunishinishi

となっている。753。 改めてみて眺めてみるとあれだな。このうち700個ぐらいは無価値なゴミだな。

Githubのレポジトリは結構頻繁に掃除しているのだけど、npmはほったらかしだった。 何だろう、中学校の卒業アルバムの黒歴史を見るような気分。申請すれば消してくれるっぽいけど、リストアップもめんどくせぇ。

見なかったことにしよう。

JavascriptでCamelやらSnakeやらの変化をば

作ったのは結構昔なのだけど。

CamelCaseとかSnakeCaseとか変換するためのnpmパッケージ。

github.com

もちろん似たようなのは腐るほどあった。 けど結局痒いところに手が届かず、いつも通りの車輪の大発明。

以下のように文字列を変形する

'use strict'

const {
  camelcase,
  capitalcase,
  constcase,
  cramcase,
  decapitalcase,
  enumcase,
  lowercase,
  pascalcase,
  pathcase,
  sentencecase,
  snakecase,
  spacecase,
  spinalcase,
  titlecase,
  trimcase,
  uppercase
} = require('stringcase')

// ------------------------
// Convert to camelcase
// ------------------------
camelcase('foo_bar') // => "fooBar"
camelcase('FOO_BAR') // => "fooBar"
camelcase('fooBar') // => "fooBar"
camelcase('FooBar') // => "fooBar"

// ------------------------
// Convert to capitalcase
// ------------------------
capitalcase('foo_bar') // => "Foo_bar"
capitalcase('FOO_BAR') // => "FOO_BAR"
capitalcase('fooBar') // => "FooBar"
capitalcase('FooBar') // => "FooBar"

// ------------------------
// Convert to constcase
// ------------------------
constcase('foo_bar') // => "FOO_BAR"
constcase('FOO_BAR') // => "FOO_BAR"
constcase('fooBar') // => "FOO_BAR"
constcase('FooBar') // => "FOO_BAR"

// ------------------------
// Convert to cramcase
// ------------------------
cramcase('foo_bar') // => "foobar"
cramcase('FOO_BAR') // => "foobar"
cramcase('fooBar') // => "foobar"
cramcase('FooBar') // => "foobar"

// ------------------------
// Convert to decapitalcase
// ------------------------
decapitalcase('foo_bar') // => "foo_bar"
decapitalcase('FOO_BAR') // => "fOO_BAR"
decapitalcase('fooBar') // => "fooBar"
decapitalcase('FooBar') // => "fooBar"

// ------------------------
// Convert to enumcase
// ------------------------
enumcase('foo_bar') // => "foo:bar"
enumcase('FOO_BAR') // => "foo:bar"
enumcase('fooBar') // => "foo:bar"
enumcase('FooBar') // => "foo:bar"

// ------------------------
// Convert to lowercase
// ------------------------
lowercase('foo_bar') // => "foo_bar"
lowercase('FOO_BAR') // => "foo_bar"
lowercase('fooBar') // => "foobar"
lowercase('FooBar') // => "foobar"

// ------------------------
// Convert to pascalcase
// ------------------------
pascalcase('foo_bar') // => "FooBar"
pascalcase('FOO_BAR') // => "FooBar"
pascalcase('fooBar') // => "FooBar"
pascalcase('FooBar') // => "FooBar"

// ------------------------
// Convert to pathcase
// ------------------------
pathcase('foo_bar') // => "foo/bar"
pathcase('FOO_BAR') // => "foo/bar"
pathcase('fooBar') // => "foo/bar"
pathcase('FooBar') // => "foo/bar"

// ------------------------
// Convert to sentencecase
// ------------------------
sentencecase('foo_bar') // => "Foo bar"
sentencecase('FOO_BAR') // => "Foo bar"
sentencecase('fooBar') // => "Foo bar"
sentencecase('FooBar') // => "Foo bar"

// ------------------------
// Convert to snakecase
// ------------------------
snakecase('foo_bar') // => "foo_bar"
snakecase('FOO_BAR') // => "foo_bar"
snakecase('fooBar') // => "foo_bar"
snakecase('FooBar') // => "foo_bar"

// ------------------------
// Convert to spacecase
// ------------------------
spacecase('foo_bar') // => "foo bar"
spacecase('FOO_BAR') // => "foo bar"
spacecase('fooBar') // => "foo bar"
spacecase('FooBar') // => "foo bar"

// ------------------------
// Convert to spinalcase
// ------------------------
spinalcase('foo_bar') // => "foo-bar"
spinalcase('FOO_BAR') // => "foo-bar"
spinalcase('fooBar') // => "foo-bar"
spinalcase('FooBar') // => "foo-bar"

// ------------------------
// Convert to titlecase
// ------------------------
titlecase('foo_bar') // => "Foo Bar"
titlecase('FOO_BAR') // => "Foo Bar"
titlecase('fooBar') // => "Foo Bar"
titlecase('FooBar') // => "Foo Bar"

// ------------------------
// Convert to trimcase
// ------------------------
trimcase('foo_bar') // => "foo_bar"
trimcase('FOO_BAR') // => "FOO_BAR"
trimcase('fooBar') // => "fooBar"
trimcase('FooBar') // => "FooBar"

// ------------------------
// Convert to uppercase
// ------------------------
uppercase('foo_bar') // => "FOO_BAR"
uppercase('FOO_BAR') // => "FOO_BAR"
uppercase('fooBar') // => "FOOBAR"
uppercase('FooBar') // => "FOOBAR"

React界隈の人はひょっとしてみんな`process.env.NODE_ENV === "development"`を毎回手で書いているのだろうか?

React関連のソースを見ると

if (process.env.NODE_ENV === "development") {
  /* ... */
}

みたいな記述をめっちゃ見るのだけれど、 長くね?そしてtypoのリスクがあるんじゃね?

個人的にはdevelopmentすらよく打ち間違えるのだけど、英語Nativeはそんなことないのだろうか。

まあ世間はどうあれ自分はよく間違えるので共通化することにした。 環境変数の判定をするだけのためのしょぼパッケージ を作ってnpmに登録した。

github.com

const { isDevelopment } = require('asenv')
if (isDevelopment()) {
  /* ... */
}

結局isDevelopmentのスペルを書くことになっているけど、エディタ(WebStorm)が補完してくれるので問題なし。 万が一間違えても関数実行時にエラーになるので、文字列比較よりも気付きやすい

Node.jsでローカルの空きPortを探すぜ

Webサーバのテストコード書くときに、ポート固定させたくない場合が多々ある。環境依存だし、適当な空きポート使ってほしい。

5分ほどnpmを探したけどいいのがない。ポート探すのはあるけどインターフェースがCallbackだ。欲しいのはそう、Promiseだ。

探すのは早々に諦め、自前で実装。

github.com

実行すると空いてるポートを探して返すだけの超簡単なモジュール

'use strict'

const aport = require('aport')
const co = require('co')

co(function * () {
  // Find free port
  let port = yield aport()
  console.log('Free port :', port)
}).catch((err) => console.error(err))

実装全部書いてもこんだけ。

/**
 * Find a free port
 * @function aport
 * @param {Object} [options] - Optional settings
 * @param {string} [options.host='127.0.0.1'] - Host to aport port
 * @returns {Promise.<number>}
 */
'use strict'

const net = require('net')
const co = require('co')

/** @lends aport */
function aport (options) {
  options = options || {}
  let host = options.host || '127.0.0.1'

  return co(function * () {
    let server = net.createServer()
    let port = null
    server.on('listening', () => {
      port = server.address().port
      server.close()
    })

    return new Promise((resolve, reject) => {
      server.on('close', () => resolve(port))
      server.on('error', (err) => reject(err))
      server.listen(0, host)
    })
  })
}

module.exports = aport

処理としてはビルドインのnetモジュールでサーバを一瞬立ててportを取得し、すぐ閉じる。

Javascriptのファイル名で大文字使うのやめてほしい

React.jsの実装とか見るとファイル名に大文字小文字混ざっているんだけど、個人的にはこの習慣ホントやめてほしい。

canDefineProperty.js
checkReactTypeSpec.js
deprecated.js
flattenChildren.js
getIteratorFn.js
getNextDebugID.js
KeyEscapeUtils.js
LinkedStateMixin.js
onlyChild.js
PooledClass.js
React.js
ReactAddonsDOMDependencies.js
ReactAddonsDOMDependenciesUMDShim.js
ReactChildren.js
ReactClass.js
ReactComponent.js
ReactComponentTreeDevtool.js
ReactComponentTreeHook.js
ReactComponentTreeHookUMDShim.js
ReactComponentWithPureRenderMixin.js
ReactCSSTransitionGroup.js
ReactCSSTransitionGroupChild.js
ReactCurrentOwner.js
ReactCurrentOwnerUMDShim.js
ReactDOMFactories.js
ReactElement.js
ReactElementSymbol.js
ReactElementType.js
ReactElementValidator.js
ReactFragment.js
ReactLink.js
ReactNoopUpdateQueue.js
reactProdInvariant.js
ReactPropTypeLocationNames.js
ReactPropTypeLocations.js
ReactPropTypes.js
ReactPropTypesSecret.js
ReactPureComponent.js
ReactStateSetters.js
ReactTransitionChildMapping.js
ReactTransitionEvents.js
ReactTransitionGroup.js
ReactUMDEntry.js
ReactUMDShim.js
ReactVersion.js
ReactWithAddons.js
ReactWithAddonsUMDEntry.js
shallowCompare.js
sliceChildren.js
traverseAllChildren.js
update.js

なぜならmac使っているとファイル名はCase-Insensitiveだから。 大文字の小文字が区別しないのでrequire('./foo')require('./Foo')が全く同じ挙動になったりする。

そこで事故るのが嫌だからファイル名は全部SnakeCaseにしたくてたまらない。つうか"node_modules"ディレクトリとかも全小文字なんだからそれに合わせたくて仕方ない。

W3CJavaScript Style Guideとかにも同じことが書いてある

Use Lower Case File Names Most web servers (Apache, Unix) are case sensitive about file names:

london.jpg cannot be accessed as London.jpg.

Other web servers (Microsoft, IIS) are not case sensitive:

london.jpg can be accessed as London.jpg or london.jpg.

If you use a mix of upper and lower case, you have to be extremely consistent.

If you move from a case insensitive, to a case sensitive server, even small errors can break your web site.

To avoid these problems, always use lower case file names (if possible).

けど誰もこんなの読まないだろうし、みんなFacebookの方に右に倣えするんだろーな。。。

JavaScriptでDestructuringするときに別名やらデフォルト値が設定できるのを最近まで知らなかったのはここだけの話な。

Node.jsの6やES2015から使えるようになったDestructuring。変数宣言するときに、オブジェクトのプロパティを一括して取り出せる機能だ。

const user01 = { name : 'Okunishinishi' }
let { name } = user01  // `let name = user01.name`と書くのと同じ

なんと便利な!

でもスコープが変わるから名前衝突が起きるじゃん使えねー

let { name } = user01
let { name } = user02 // 変数名としての`name`が被る!

と思っていた時期もありました。

まあJSだししゃーないわ、と、3ヶ月ほどそのまま過ごした。

だけどある時ドキュメントをふと見ると、その辺がとっくに考慮済みであることに気がついた。

developer.mozilla.org

Destructing時に':'で別名を宣言できるとな。

let { name: name01 } = user01
let { name: name02 } = user02 // `let name02 = user.name`と同等

しかもデフォルト値の宣言もできると来た。

let { name: name02 = 'New Name' } = user02
// `let name02 = typeof user02 === 'undefined' ? 'New Name' : user02.name`と同等

他の高級言語だと当たり前の機能なのだけど、JSでこれができるようになったとは。感慨深し。

というか仕様をちゃんと読んどきゃよかった。

JavaScriptでネストしたオブジェクトのプロパティへのアクセスを工夫してみる

例えばRubyだとSafe Navigation Operator(&.)を使えばネストしたプロパティに安全にアクセスできる。

name = product&.retailer&.name

とやれば

retailerの部分がnilでも例外を吐かずにnameが安全にnilになる。

これをJavaScriptでもやりたいぜ。

もちろん言語レベルではそんなもんなはないので、何にかしらの工夫が必要。

RFCで提唱されている仕様としてはJSON Pointerというやつがあって、これを使えばName Pathからのアクセスが実現できる。

実装としてはnpmにjson-pointerというモジュールが登録されていて、

const { get, set } = require('json-pointer')

let product = { /* ... */ }
set(product, '/retailer/name', "あいうえお商店")
let retailerName = get(product, '/retailer/name')
/* ... */

みたいな感じで使える。

あら便利!と思ってしばらく使ってみた。

が。

やっぱめんどくせぇ。。。

Name Pathが直感的じゃないのだ。取得のたびに関数に渡すのも微妙。

もっと良い術がないか。ないな。探した限り。じゃあ作るか。

そして作ったのがこれ ↓↓

github.com

Pathを指定してアクセスするのをやめた。代わりShallowな構造とDeepな構造を相互変換できるようにした。

flatten(nested)で、Nest構造を平たいオブジェクトに変換する。その際、プロパティ名は"."つなぎになる

const { flatten } = require('objnest')

let flattened = objnest.flatten({
    'foo': {'bar': 'baz'}
})
console.log(flattened) // => {'foo.bar': 'baz'}

逆に構造化したい場合はexpand(flattend)する。"."つなぎのプロパティがネストされたオブジェクトになる。

const { expand } = require('objnest')

let expanded = expand({
    'foo.bar': 'baz'
})
console.log(expanded) // => {foo: {bar: 'baz'}}

使って見るとこれがなかなか便利。Reactの高速化の時にShallowCompareで扱えるようにしたりね。ビバ俺様仕様。

参考

Gitで間違えてMasterにPushしちゃった時に

本来ならローカルで別ブランチを切るべきところを間違えてmasterのまま作業してCommitしてPushしてああ、やべえ!となった時に。

git push -f origin cc4b63bebb6:master

-fオプションをつけて"<commit>:<branch>"とやるとRemote側のHistoryが改ざんされて、恥ずかしい誤Pushがなかったことに。マジ助かった。

参考 ↓↓

stackoverflow.com

furを使ったFaviconの自動生成

Web開発とかしていると、ファビゴンやらバナーを手抜きでちゃちゃっと作りたくなる時がよくある。

多くの場合は、それっぽい色でタイトルやら頭文字やら書いてあるだけで十分だったりする。 でもそのためにわざわざペイントツールを開いてなんやかんやとやりたくない。

コマンドラインちょちょっと済ませたい!

というわけで作ったのがこれ ↓↓

github.com

Node.js製のCLIツールで、

npm install fur -g

としたあと、コマンドラインから手抜き画像の生成ができる。

例えばこのブログのfavicon

 fur favicon "blog-favicon.png" --text="浮" --color="bi" --font="cn" --shape="g"

とやると、こんな画像が生成される

f:id:okunishinishi:20170122103721p:plain

バナーの方は

fur banner "blog-banner.png" --text="夢見がちWeb屋の浮かれ雑記帳" --color="bi" --font="cn" --shape="c" --font-size=48

f:id:okunishinishi:20170122103726p:plain

仕組みとしては、js内でSVG画像を生成したあと、phantom.jsを使ってpngに変換している。