自動生成用のテンプレートを自動生成してみることにした
四六時中プログラムを書いていると自然にお約束パターンが出てきてソースコードの雛形的なのを作りたくなることがままある。 俺様Scaffold的なやつが欲しくなる。
テンプレートエンジン的なやつはいくらでも転がっているので、Scffold的なのは割と簡単と作れる。
そして気づく。
テンプレート管理めんどくせぇ。。。
最初に
一回動くものを作る -> ソースコードをテンプレート化する -> 値を渡してレンダリングする -> 挙動を試す
とやるのは良いのだけど、その後何か変更が生じると、
ちょっと直す -> テンプレートをいじる -> 値を渡してレンダリングする -> 挙動を試す
的になるのだけどこれが地味にめんどくさい。
テンプレート状態だとSyntaxチェックとかもできない場合が多いので、つまらん記述ミスもレンダリングして挙動を試して発覚したりする。
どうしたものか。
そうだ、テンプレート自体を自動生成すれば良いじゃないか。
と気がついて作ったのがこれ ↓↓
まずベタ書きで動くものを作り、ルールに沿ってそれをテンプレート化するというものだ。
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
って言ってるし。
最近はRubyやらPythonも2space indentが優勢な気がするんでJavaScriptもそうなるだろう。と勝手に思っている。
React.jsでPureRenderをHOCパターン化したらReduxでも捗った件
React.jsでのパフォーマンス最適化に関しては、昔はPureRenderMixinを使ってRenderを最小化しろと言われてた。
でもある時、公式ブログでやっぱMixin難しくね?って話が出てきて、shallowCompare
を使えとなった。
var shallowCompare = require('react-addons-shallow-compare'); var Button = React.createClass({ shouldComponentUpdate: function(nextProps, nextState) { return shallowCompare(this, nextProps, nextState); }, // ... });
的な。
一方Gihubの星がやたら多いReduxさんは
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
判定する必要がある。
でもそのためだけに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
つまり昔あげちゃったやつは永遠に残るのだ。それがどんなカスだろうと。
ということをふと思い出し、自分のあげたパッケージを確認してみた
2017/01/22現在、
753 Packages by okunishinishi
となっている。753。 改めてみて眺めてみるとあれだな。このうち700個ぐらいは無価値なゴミだな。
Githubのレポジトリは結構頻繁に掃除しているのだけど、npmはほったらかしだった。 何だろう、中学校の卒業アルバムの黒歴史を見るような気分。申請すれば消してくれるっぽいけど、リストアップもめんどくせぇ。
見なかったことにしよう。
JavascriptでCamelやらSnakeやらの変化をば
作ったのは結構昔なのだけど。
CamelCaseとかSnakeCaseとか変換するためのnpmパッケージ。
もちろん似たようなのは腐るほどあった。 けど結局痒いところに手が届かず、いつも通りの車輪の大発明。
以下のように文字列を変形する
'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に登録した。
const { isDevelopment } = require('asenv') if (isDevelopment()) { /* ... */ }
結局isDevelopment
のスペルを書くことになっているけど、エディタ(WebStorm)が補完してくれるので問題なし。
万が一間違えても関数実行時にエラーになるので、文字列比較よりも気付きやすい
Node.jsでローカルの空きPortを探すぜ
Webサーバのテストコード書くときに、ポート固定させたくない場合が多々ある。環境依存だし、適当な空きポート使ってほしい。
5分ほどnpmを探したけどいいのがない。ポート探すのはあるけどインターフェースがCallbackだ。欲しいのはそう、Promiseだ。
探すのは早々に諦め、自前で実装。
実行すると空いてるポートを探して返すだけの超簡単なモジュール
'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"ディレクトリとかも全小文字なんだからそれに合わせたくて仕方ない。
W3CのJavaScript 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ヶ月ほどそのまま過ごした。
だけどある時ドキュメントをふと見ると、その辺がとっくに考慮済みであることに気がついた。
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が直感的じゃないのだ。取得のたびに関数に渡すのも微妙。
もっと良い術がないか。ないな。探した限り。じゃあ作るか。
そして作ったのがこれ ↓↓
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がなかったことに。マジ助かった。
参考 ↓↓
furを使ったFaviconの自動生成
Web開発とかしていると、ファビゴンやらバナーを手抜きでちゃちゃっと作りたくなる時がよくある。
多くの場合は、それっぽい色でタイトルやら頭文字やら書いてあるだけで十分だったりする。 でもそのためにわざわざペイントツールを開いてなんやかんやとやりたくない。
コマンドラインちょちょっと済ませたい!
というわけで作ったのがこれ ↓↓
Node.js製のCLIツールで、
npm install fur -g
としたあと、コマンドラインから手抜き画像の生成ができる。
例えばこのブログのfaviconは
fur favicon "blog-favicon.png" --text="浮" --color="bi" --font="cn" --shape="g"
とやると、こんな画像が生成される
バナーの方は
fur banner "blog-banner.png" --text="夢見がちWeb屋の浮かれ雑記帳" --color="bi" --font="cn" --shape="c" --font-size=48