技術探し

技術探し

JavaScriptを中心に記事を書いていきます :;(∩´﹏`∩);:

package.jsonで値を展開する

🎍今年初の記事です🎍

そういえばこんな機能あったなーって感じだったので記事にしてみました。

本当は今年最初の記事用意していたのですが、まだ終わってないので後ほど。。

github.com

今回は、タスクのstartとbuildに同じ変数をwebpackへ渡すために共通化したくて、量が多くなってきたのでリファクタリングしました。

手順

$npm_package_にpackage.jsonに書いたkeyをつなげるとそれのvalue展開されます。

コンソール上では、展開されませんが、JS上では展開されます。

// 確認用のJS
console.log(process.argv[2]);
$ npm run main
> node main.js $npm_package_foo

bar

文字列の場合

{
  "foo": "bar",
  "scripts": {
    "main": "node main.js $npm_package_foo"
  }
}

オブジェクトの場合

オブジェクトを渡すことはできないので、末端まで指定する必要があります。

{
  "foo": {
    "bar": 1
  },
  "scripts": {
    "main": "node main.js $npm_package_foo_bar"
  }
}

もし、$npm_package_fooと書いた場合は、undefinedとなります。

配列の場合

配列を渡すことはできないので、添字を指定する必要があります。

{
  "foo": ["bar"],
  "scripts": {
    "main": "node main.js $npm_package_foo_0"
  }
}

もし、$npm_package_fooと書いた場合は、undefinedとなります。

まとめ

  • $npm_package_というプレフィックスが使える
  • 必ず値はプリミティブでなければなりません。

2017年を振り返る

激しい一年でした。

振り返り

今年は転職を二回しました。
5月にドワンゴを辞めて、6月にメルカリに入って、12月にドワンゴに戻りました。
個人的には、社会的な多くの経験を積めたと思うし特に後悔とかはしてません。

しかしながら、多くの方々に御迷惑をかけたと思っています。
今年は多くの方に支えていただき感謝しています。
本当にありがとうございました。

コミュニティ

多くのエンジニアの方に会えたと思います。
JSエンジニアはもちろんですが、別分野のエンジニアの方々に多く出会えた年だった気がします。
今年は国内での活動をしていきたいと思っていたので、その目標は達成したのではないかなと思います。

来年もNode学園祭・学園ともよろしくお願いします!

nodefest.jp

nodejs.connpass.com

登壇

buildersconや情報科学若手の会などでも話してきました。
おそらく、10回ぐらい今年は登壇した気がします。

speakerdeck.com

今はNode9.3.0です
そういえば、入社初日にドワンゴインターン生にbuildersconで発表していた方ですよねって言われました。(JS関係なく)

abouthiroppy.hatenablog.jp

若い方々に囲まれての発表でした:)

ブログ

1, 2週間に1回は必ず記事としてアウトプットしていこうと思っていました。
その目標は達成できたかと思います。

アクセス数

このブログは6月から本格稼働し始めて、本当に多くの方の訪問がありました。
ありがとうございます。
来年もJSに関する記事を上げていきたいと思っています。

f:id:about_hiroppy:20171229095715p:plain

記事数

36 + これ

abouthiroppy.hatenablog.jp

人気

abouthiroppy.hatenablog.jp

abouthiroppy.hatenablog.jp

物作り系

abouthiroppy.hatenablog.jp

abouthiroppy.hatenablog.jp

abouthiroppy.hatenablog.jp

GitHub

f:id:about_hiroppy:20171229093112p:plain

今年は、githubの方があまり出来なかったので来年はOSSを頑張りたいと思っています。

旅行・出張

アイコンの変更

絵師さんに書いていただきました。
モーグリみたいなのって言ったらめっちゃかわいいのが出てきた!

f:id:about_hiroppy:20171225002710j:plain

来年に向けて

とりあえずジグソーパズルを完成させます。

来年はもっともっと自分のスキルアップへの時間へ投資したいと思います。
もちろん、プログラミングだけではなく、英語とか諸々。。
あと海外登壇してみたいなとかも思っていたり。。。(CFPは出している状態です)

今後共こんな自分ですが、よろしくお願いします!!!

次のリリースであるwebpack 4の主な変更点まとめ

ドワンゴアドベントカレンダーの17日が空いているので本来その予定で書かれた記事ではないですが、そこに埋めます。
2日連続になってしまった。。

qiita.com

さて、今年は、webpack3.0.0が2017/06にリリースされました🎉 (現在、3.10.0)

medium.com

After we released webpack v2, we made some promises to the community. We promised that we would deliver the features you voted for. Moreover, we promised to deliver them in a faster, more stable release cycle.

ということでv4😎😎😎

v4は来年2018年の2月のどこかでリリース予定です。
一ヶ月前にアナウンスをし、RC上でリリース日が公開されます。


この記事は、rcが出るたびに更新していく予定です。

現在: rc2

使用側目線でまとめていくのでプラグインを作る人はissueを読んで下さい。

webpack 4

RC

変更

Node4のサポートがなくなりました

LTS対象である6, 8を使うか、currentの9を使ってください。

webpack-cli

CLIが移行されました。

The CLI moved into a separate package: webpack-cli.
Please install 'webpack-cli' in addition to webpack itself to use the CLI.
-> When using npm: npm install webpack-cli -D
-> When using yarn: yarn add webpack-cli -D

github.com

もし、webpack みたいにコマンドを実行している場合はcliを入れる必要があります。

CLI経由でプラグインを入れた場合は、設定ファイルのプラグインよりも優先されます。
また、webpack-cliには設定ファイルを自動生成してくれる機能も持ちます。

github.com

詳しくはリポジトリのdocs等を参照してください。

Modeオプションの追加

production, development, noneのモードを提供します。

--mode         Mode to use (production or development)
$ webpack --mode production
// 最低限、必要な設定
const path = require('path');

module.exports = {
  entry: 'index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve('dist')
  }
};

コードは以下 github.com

共通として、optimization.nodeEnvは各Modeの値となります。

production

webpack --mode production
すべての種類の最適化を行ったバンドルファイルが生成されます。
以下の有効化される機能を見るとわかりますが、今までplugins内に書いていたものがデフォルトでオンとなります。
また、ウォッチモードは持ちません。

有効化される機能
  • performance
    • hints: warning
  • optimization
    • flagIncludedChunks
    • occurrenceOrde
    • sideEffects
    • usedExports
    • concatenateModules
    • noEmitOnErrors
    • minimize

development

webpack --mode development

devtoolはevalで実行されます。

https://webpack.js.org/configuration/devtool/#development

また、ウォッチモード時のために差分ビルドの高速化がされています。

有効化される機能
  • output
    • pathinfo
  • devtool: eval
  • cache: true
  • optimization
    • namedModules
    • namedChunks

none

すべての設定を無効にします。

importの変更

コードが破壊される可能性があるので注意

import()は常に名前空間のオブジェクトを返すようになります。 __webpack_require__.r

また、CJS(CommonJS Modules)の場合はデフォルトのexportへラップされます 。
なので、import()を使いCJSをimportしている場合、そのコードはおそらく壊れます。

モジュールタイプのサポート

モジュールタイプは自動的にmjs, json, wasmに対し選択されます。
他のモジュールタイプはmodule.rules[].typeで設定する必要があります。
また、ローダ内の探査順序は以下のようになりました。
.wasm -> .mjs -> .js -> .json

javascript/auto

現行のwebpack3がこれにあたります。

CJS、AMD、ESMのすべてをサポートします。

javascript/esm

ESMのみをサポートし、他のモジュールをサポートしません。(importのみ可能)
javascript/autoよりもより厳密にESMを処理します。

  • importされた名前のものは必ずimportされたコードに存在する
  • ESMでないものはデフォルトのインポートでのみインポートでき、named importはエラーを出します
    • つまり、Node.jsと同じ挙動
.mjs

javascript/esmが使用されます。
また、import時には拡張子が必要です。

json

JSONのデータ。 解析はされません。

webassembly/experimental

WebAssemblyモジュールのサポート。
JSとWASMのモジュールのインポートが可能となります。
WASMからのexportはESMのimportにより検証され、存在しないものをWASMのexportをimportする場合はエラーがでます。

sideEffects

package.jsonsideEffects: falseがサポートされました。
Tree Shakingに優しいモジュールのために作られました。

github.com

importへのマジックコメントの追加

webpackIncludewebpackExcludeがサポートされました。
Dynamic Importをしている時に、ファイルのフィルタリングを可能とします。

変更された機能・プラグイン

追加

  • optimization.minimizer
    • optimization.minimizeに対する設定を書ける
  • module.rules[].resolve
  • loaderContext.rootContext
    • contextオプション

削除

  • module.loaders
    • v2から入ったrulesを使ってください
  • loaderContext.options
  • Compilation.notCacheable
  • NoErrorsPlugin
  • Dependency.isEqualResource
  • NewWatchingPlugin

名前の変更

  • NoEmitOnErrorsPlugin -> optimize.noEmitOnErrors
    • productionモード時は標準で有効
  • ModuleConcatenationPlugin -> optimize.concatenateModules
    • productionモード時は標準で有効

その他

  • ProgressPlugin(--progress)にプラグイン名が出るようになった
  • uglifyjs-webpack-pluginがデフォルトでES2015のサポートとキャッシュと並列処理できるようになった
    • optimization.minimizeで使われている

速度

多くのパフォーマンス改善が行われました。
特にビルド周りが変わったと思います。

parcelとの比較です。

github.com

速いぞ!!!


さいごに

今年最後の技術記事となりましたが、リリースまでは更新していこうと思っています。

それでは、お楽しみに!

botたちの家を作っている

今日が入社日です。

abouthiroppy.hatenablog.jp

この記事は第2のドワンゴ Advent Calendar 2017の18日目です。


12/11に旅行行っててなんとなく現地で作りたいと思ってそこから書いていますので、まだ下地の段階です。今後に期待してください:)

とりあえず最低限のベースの機能実装を行いました。

github.com

これはなに?

Slack Appです!
名前の通り、botたちを収容する施設です。
BotがJSのコードを持っていて、親がそれを動かします。

以下を見てもらえるとわかりやすいかも。
f:id:about_hiroppy:20171218082718g:plain

招待されたのは親botで実行されたのは子供botとなります。

完全にPC用で作っているので、レスポンシブデザインとかないです。

目的

botを書く -> どこかにデプロイする -> slackでbotの設定をする

このフローを毎回やるの辞めたい!(めんどくさい)

ブラウザ上でJSの実行させたいコードを書くだけで後はやってくれる感じにしてほしいのでそれが目的です。

現在の進捗

  • botの登録、実行、編集、削除が行える
  • botからのコマンド

今後やりたいこと

機能面

  • 今現在、botに対する呼び出しの単語が一つなので正規表現を使えるようにする
  • 一つのbotに対して複数のチャンネルを登録できるようにする
  • ストレージ機能の実装(例: TODOアプリ)
  • private機能と権限周り
  • SlackのRTMを使っているため様々なイベントフックができるので、それにも反応できる
  • アイコンの画像対応
    • 表示はできるがアップロード画面作るのめんどくさくてまだやってない

コード面

  • botのeval実行(Functionでも可)に対して、キャンセリングを実装する
  • セキュリティやばい
    • 今はslackチームという身内で使うので優先度が低い
  • 例外処理が雑魚
  • デザインの変更
  • テストを書く
  • NodeのコードもBabelを通すか迷う
    • フロントエンドのコードがFlowで型定義されているため、APIの型を使いたい
  • dockerを真面目な設定に書き換える
  • 連打とかローディングとか

仕組み

親のbot(Slack app)がいて、それが子供のscriptを実行することより、擬似的に子供のbotがいるように見える。
簡単にいうと、postMessageという関数にiconやnameを付けれるのでそれを変更して別botが動いている仕組みです。

なので、web上でJSのコードを書いたらそれが応答で動くbotになります。

ドワンゴ社内slackでも動いているAlfaさんのbot天国のJS版実装です。
github.com

今現在、子供botができること

fetch, dom操作, メッセージを送るができます。(attachmentはまだ)
async/awaitに対応しているので、ある程度のことはできますが、まだキャンセリングの実装してないのでそれも落ち着いたらやろうと思っています。

Cancel Promise pattern (no cancellable promises)

技術スタック

書く量が多くなるので、ピックアップしていきます。
トランスパイラがBabelでバンドラーがwebpackです。

  • react
  • react-loadable
    • JSチャンクを読み込むルーティング時にローディングする
  • react-alert
  • react-ace
    • JSのエディタで補完とかもできる
  • redux
  • redux-saga
  • express
  • sequelize
    • 今回はpostgresqlとsequelize-cliでmigrate等を行う
    • 今後、bookshelfに戻すかも
  • emoji-mart
  • brace
  • riek
    • inlineフォームの実装
  • classnames
  • workbox
    • googleのService Workerライブラリ
  • postcss-cssnext
  • node-slack-sdk

あとはpackage.jsonを見てください。

DB

ORMはsequelizeでDBはpostgresqlを使いました。

github.com

書くまでに実装する時間があまり無かったため、bookshelfよりも慣れているsequelize, sequelize-cli, postgresqlを使います。

基本的には、sequelize-cli経由でマイグレーションを行ったり、シードを差し込んだりします。

docker-compose

当初、docker-compose内で開発を行っていたのですが、どうもnodemonが上手く動かなかったので一旦ローカルでの開発に戻しました。
もちろん、プロダクションではdocker-composeで動きます。

ちなみにdockerでのwebpack-dev-serverのHMR

github.com

.envを使って環境変数を操作しているのですが、プロダクションやディベロップメントとかの.envファイルを読み込ませるのはどうするのが最適解なのだろうか気になります。。。

またherokuへのデプロイが本当に楽でした。

$ heroku container:push web --app bot-house

これでbuildしてpushしてくれて動くので最高です。

node-slack-sdk

github.com

slack社公式のNode.jsクライアント。
基本ドキュメントが無いので、コード読んだほうが明らかに速いです。
コードに書かれたコメントやdocはスゴイ丁寧なのでそれを一番最初に見るべきだと感じました。
RTMはまだ未実装な部分(例えばRTMでattachmentのメッセージを送れない等)が多いのでそういうときはWEBというnode-slack-sdkのメソッドを使うといいです。

また、Slackの認証そのものがよくわからなかったので以下の記事をよみました。

qiita.com

fastify

github.com

速いことで有名なwebフレームワーク。
初めて今回使ってみましたが使いやすかったです。
ただ、やはりexpressと比べるとエコシステムが充実していなくて、アプリケーションを作っていくと大変でした。
view周りや認証(passport対応)が弱いという印象があります。
例えば、webpack-hot-middlewareが使えなかったりします。
github.com

まだまだ始まったばかりのプロジェクトなので、使ってみて使いやすいという印象が強かったので、コミットしていきたいと思った反面、構築ですごい時間がかかったのでやはりプロダクションのコアな部分はエコシステムが充実しているライブラリを使うべきだと思います。
今後周りのエコシステムの作成やコアにコミットしてみたいと思っています。
ググっても情報は基本ないので、最初にissueとコードを読んだほうが速いです。

今回は2日間ぐらいハマった部分があり、結局expressへ乗り換えました。

ちなみにチートシート

devhints.io

クライアント側ルーティング及びチャンク

react-routerを使っています。
また、今回はdynamic importのためreact-loadableを使っています。

github.com

react-loadableはチャンクで切られたパーツを読み込むまでに表示させるローダーの設置などができます。
もちろん、Preloadingもできますが今回はまだやってません。

                                                 Asset       Size  Chunks                    Chunk Names
StratumNo1 Medium-06a6ac1709970857f419dc78aca05353.ttf    62.3 kB          [emitted]
                           BotsShow.bundle.54f65aa8.js     304 kB       0  [emitted]  [big]  BotsShow
                              Index.bundle.54f65aa8.js    10.3 kB       1  [emitted]         Index
                       ChannelsShow.bundle.54f65aa8.js     8.3 kB       2  [emitted]         ChannelsShow
                         UsersIndex.bundle.54f65aa8.js    4.39 kB       3  [emitted]         UsersIndex
                                    bundle.54f65aa8.js    31.2 kB       4  [emitted]         bundle
                                    vendor.54f65aa8.js    1.46 MB       5  [emitted]  [big]  vendor
                  1f42da94f3ff91d479713521dee90778.css     1.5 kB       4  [emitted]         bundle
                                            index.html  326 bytes          [emitted]
import Loadable from 'react-loadable';

export const BotsShow = Loadable({
  loader : () => import(/* webpackChunkName: 'BotsShow' */ '../containers/pages/Bots/Show'),
  loading: () => (<div>Loading...</div>)
});

const Router = () => (
  <App>
    <Switch>
      <Route
        path="/bots/:id"
        component={() => (<BotsShow />)}
      />
    </Switch>
  </App>
);

フロントエンド用のライブラリはvendor.jsへすべてまとめ、ルーティングごとにファイルを切ります。

react-universal-componentとどちらを使うか迷いましたが、react-loadableにしました。

github.com

history

github.com

今回は、browserHistoryではなく一旦hashHistoryにしています。
理由としては、watch時にhtml-webpack-pluginとexpressがルーティング周りで競合するため開発効率が落ちるからです。
普段、SSRの時にhtml-webpack-pluginを使っていなかったため気付かずハマってしまってました。。

SSR

今回は、とくにやる必要がないのでしません。
SSRをやるメリットとしては、SEO対策とFirst Viewに関する速度がありますが2つとも今回は必要の無いことだからです。

そのかわりに表示パフォーマンスの面では、dynamic importとService Workerで補います。

PWAのためのライブラリであるWorkboxを使い、今回はService Workerを扱います。

github.com

今はhtml-webpack-pluginでhtmlファイルを吐き出してますが、今後SSR同様にinlineへと変えたいと思います。

感想

意外と大規模なものを作っていて時間がありませんでした。
特にSlackのデバッグが本当に大変なのとSlack Appというのがなんなのかみたいな部分から知らないといけなくて少しSlackに詳しくなった気がする。

まだまだやることが多いのでもう少しこの開発に楽しもうと思っています。


簡単に導入できるので是非使ってみてください!!

ドワンゴに入社しました

abouthiroppy.hatenablog.jp

12/16(18開始)から新卒で入社したドワンゴへもう一度入社することになりました。

今現在、このような状況ですがニコニコ動画ドワンゴのサービスが好きな1ユーザーとして、また1開発者として微力ながらドワンゴの力になれるように頑張りたいと思います。

今後共、よろしくお願いします。

登壇する時に使っているスライド発表ツールの紹介

一ヶ月前に勉強会で話してきました。

abouthiroppy.github.io

2017年の2月から運用しています。

リポジトリ

github.com

目的

  • gitで管理したい
  • 自己紹介とかの共通ページを毎回スライド書くごとに書きたくない
  • Markdownでスライドを書きたい
  • 極力JSを書かないようにして、もしカスタマイズしたいときは書けば良い。(基本MarkdownCSSだけ)
  • 発表者モードができる(new!!)

ツール・フレームワーク

JavaScript

CSS

  • postcss-cssnext
  • postcss-mixins
  • postcss-smart-import
  • postcss-reporter
  • postcss-browser-reporter

ビルドフロー

webpack

Markdown -> HTML -> React

// in webpack

{
  test: /\.md$/,
  use: [
    'html-loader',
    'markdown-loader'
  ]
}

ローダーチェインはmarkdown-loaderを通し、html-loaderを通します。

slideを取得する

require.contextというwebpackが提供しているメソッドを使います。

Dependency Management

  // fetch all slides 
  const context = require.context('./slides', true, /(md|html)$/);
  const res = {
    id    : context.id,
    slides: context
      .keys()
      .sort() // sort by File Name
      .map((e) => context(e))
  };

ファイル名とディレクトリ名は数字で記載し、それをスライドの並び順として定義します。
e.g. 0-title.md, 01-context.md, 02-dir/0-title.md

注意点として、require.contextは動的には使えないので、第一引数の目的のパスは文字列でなければなりません。

取得したHTMLをReactへ

ReactのdangerouslySetInnerHTMLを使います。

const Base = (props) => (
  <article>
    {
      props.slides.map((slide, i) => (
        <section
          key={slide.meta.id}
          className={slide.meta.className}
          data-bespoke-backdrop={slide.meta.background}
          dangerouslySetInnerHTML={{ __html: slide.context }}
        />
      ))
    }
  </article>
);

これで完成。

プロダクション

Service Worker

offline-pluginを使い、Service WorkerによりJSとCSSと画像のキャッシュをしています。

// webpack.prod.config.js

module.exports = {
  plugins: [
    new OfflinePlugin(),
    ...
  ]
};

// offline.js

import offlineRuntime from 'offline-plugin/runtime';

offlineRuntime.install();

github.com

imagemin

imageminのwebpackのloaderを使います。

github.com

// webpack.config.js

{
  test: /\.(png|jpg|gif|svg?)$/,
  use: [
    'file-loader',
    'image-webpack-loader'
  ]
}

ローダーチェインはimage-webpack-loader -> file-loader。

Dynamic Import

発表者モードは使う場面が限られるのでdynamic importで制御しています。
閲覧の場合は、プレゼンターモードを読み込ませません。 Dynamic Importは現在stage-3です。

github.com

// https://github.com/abouthiroppy/slides/blob/master/src/lib/AppContainer.js#L31

// AppContainer.js

constructor(props) {
  if (mode === 'view') {
    import(/* webpackChunkName: 'presenter.view' */ './ContentView/View')
      .then((e) => {
        ...
      });
  }
  else if (mode === 'host') {
    import(/* webpackChunkName: 'presenter.host' */ './ContentView/Host')
      .then((e) => {
        ...
      });
  }
}
Hash: 08e7000cd507fa241759
Time: 77409ms
                    Asset       Size  Chunks                    Chunk Names
0.08e7000cd507fa241759.js    14.4 kB       0  [emitted]
1.08e7000cd507fa241759.js    3.65 kB       1  [emitted]         presenter.host
2.08e7000cd507fa241759.js    1.45 kB       2  [emitted]         presenter.view
  08e7000cd507fa241759.js    1.27 MB       3  [emitted]  [big]  main

発表者モード

普段は発表者ノートなしでやっていますが、英語の発表だと発表者ノートがほしくなったので、プレゼンターモードを実装しました。

f:id:about_hiroppy:20171207094242p:plain

現在のページの表示も実装したけど、いいデザインがなくてコメントアウト

表示用に新しいwindowが立ち上がり、スピーカー用に上記のページがリロードされます。

これは、localstorageを使いページ情報を表示用とスピーカー用で共有をしています。

// host.js
window.slide.bespoke.on('activate', (e) => {
  localStorage.setItem('page', JSON.stringify({
    date: Date.now(),
    page: e.index
  }));
});

// view.js
window.addEventListener('storage', (e) => {
  if (e.key === 'page') {
    const page = JSON.parse(e.newValue).page;

    window.slide.bespoke.slide(page);
  }
});

github.com

送っている情報はタイムスタンプと現在のページだけですが、実際イベントをリッスンしてページを動かしているのでタイムスタンプは使いません。

2015年ぐらいにニコナレってサービスで同じ実装をしたので、特に困らずにできた。

niconare.nicovideo.jp

操作

Appleの公式サイトでも紹介されているLogicool Spotlight Presentation Remoteをいつも使っています。

www.apple.com

すごい便利でアプリを入れれば、ポインターとかも使えます。

マークダウンで書けるサービス

spectacle

github.com

ライブコーディングもできてすごいハイスペック。
マークダウンで書けるけど、メインはJSX。

GitPitch

gitpitch.com

githubmarkdownをpushするとスライドになる。

Object.freeze / Object.seal の糖衣構文

もともとの発端はここから。

明示的にconstかどうかを示すプロポーザルでまだチャンピオンは取得してない。

気付いたら

  • Brian Terlson(現在のTC39のエディタ)
  • Jordan Harband(AirbnbのTC39の会員)
  • Mikeal Rogers(Node.js Foundationの設立者)
  • Bradley Farias(TC39の会員でESMに一番くわしい人)
  • Benedikt Meurer(V8チームのスゴイ人)
  • Ingvar Stepanyan(ASTスゴイ詳しい人)

たちが議論していた。

今回の話でも上がってきたし、TC39のミーティングでのstageの変動があったObject.freeze/sealの話をしようと思う。
途中でnull prototypesの話になったのでそれはもし書きたくなったら書く。

プロポーザル

github.com

Brian TerlsonがFishrock123のプロポーザルとこれ似ているからって話があって思い出した。

今回のミーティングでstage-1となった。

github.com

JSの問題点

関数の引数に対して、代入はしても元はもちろん変わらないが、Objectの中身の変更はできる。
JSのconstはあくまでも再代入をさせないだけであるので、中身の変更は可能である。

const a = { b: 1 };

function modify(a) {
  // a = {c: 1}; // 再代入はしても意味がない
  a.b += 1;
  a.c = 2;
}

modify(a);
console.log(a); // { b: 2, c: 2 }

Objectの制御について

ES5から入った、preventExtensionssealfreezeというメソッドがあります。
以下が対応表です。

method プロパティの追加 プロパティの変更 プロパティの削除 プロパティの属性の変更 確認
preventExtensions N Y Y Y Object.isExtensible
seal N Y N N Object.isSealed
freeze N N N N Object.isFrozen

このような属性へ変更されます。

{
  const a = { b: 1 };

  console.log(Object.getOwnPropertyDescriptors(a));
  // { b: { value: 1, writable: true, enumerable: true, configurable: true } }

  Object.preventExtensions(a);
  console.log(Object.getOwnPropertyDescriptors(a));
  // { b: { value: 1, writable: true, enumerable: true, configurable: true } }

  Object.seal(a);
  console.log(Object.getOwnPropertyDescriptors(a));
  /*
   { b:
     { value: 1,
       writable: true,
       enumerable: true,
       configurable: false } }
  */

  Object.freeze(a);
  console.log(Object.getOwnPropertyDescriptors(a));
  /*
   { b:
     { value: 1,
       writable: false,
       enumerable: true,
       configurable: false } }
  */
}

つまり、sealではconfigurablefalseへ、freezeではwritableconfigurablefalseとなる。

また、強い方へしか変更ができなくなります。

const a = { b: 1 };

Object.freeze(a);
Object.preventExtensions(a);
Reflect.deleteProperty(a, 'b');

console.log(a); // { b: 1 }

他の言語もおそらく同じですが、これらはそのObject以下のすべてを適応するわけではなく、直下しか適用しません。

{
  const a = {
    b: {
      c: [ 'test' ]
    }
  };

  console.log(Object.getOwnPropertyDescriptors(a));
  /*
   { b:
     { value: { c: [Array] },
       writable: true,
       enumerable: true,
       configurable: true } }
  */

  const b = Object.freeze(a);

  console.log(Object.getOwnPropertyDescriptors(b)); // 適応
  /*
   { b:
     { value: { c: [Array] },
       writable: false,
       enumerable: true,
       configurable: false } }
  */

  console.log(Object.getOwnPropertyDescriptors(b.b)); // 適応されてない
  /*
   { c:
     { value: [ 'test' ],
       writable: true,
       enumerable: true,
       configurable: true } }
  */
}

つまり、複数の階層構造になっているObjectを制御したければ各行で明記しなければなりません。

糖衣構文

さすがに、毎回Object.~~って書くのはめんどいのでそのために提案された。

freeze

const foo = {#
  a: {#
    b: {#
      c: {#
        d: {#
          e: [# "some string!" #]
        #}
      #}
    #}
  #}
#}

// 展開
const foo = Object.freeze({
  a: Object.freeze({
    b: Object.freeze({
      c: Object.freeze({
        d: Object.freeze({
          e: Object.freeze([ "some string!" ])
        })
      })
    })
  })
})

seal

const foo = {|
  a: {|
    b: {|
      c: {|
        d: {|
          e: [| "some string!" |]
        |}
      |}
    |}
  |}
|}

// 展開
const foo = Object.seal({
  a: Object.seal({
    b: Object.seal({
      c: Object.seal({
        d: Object.seal({
          e: Object.seal(["some string!"])
        })
      })
    })
  })
})

また、以下のような使い方もできる。

function ajax({| url, headers, onSuccess |}) {
  fetch(url, { headers }).then(onSuccess)
}
ajax({ url: 'http://example.com', onsuccess: console.log })
// throws TypeError('cannot define property `onsuccess`. Object is not extensible')
function add(| a, b |) {
  return a + b
}
add(2, 2, 2) === 6
// throws TypeError('invalid third parameter, expected 2`)
function add1(# a #) {
  a += 1 // throws TypeError `invalid assignment...`
  return a
}
add1(1) === 2
const foo = { a: 1, b: 2 }
const {| a, b, c |} = foo
// Throws TypeError 'invalid assignment to unknown property c'

このように関数のパラメータの束縛したりすることによりデストラクチャリングに対してバリデートしやすくなる。

さいごに

ライブラリのコードではよく使われるが、業務で使われていたのを見たことがない気がする。