技術探し

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

簡単なアプリケーションでwebpackとbabelの設定をしなくて済むライブラリを作った

github.com

最初に

ターゲット

簡単なwebアプリケーション

モチベーション

同じ設定ファイルを書くことが多いのでそれを避けたい

ゴール

babelの設定とwebpackの設定を書かなくてもある程度のことはできるようになる。
ただ、複雑なアプリケーションだとwebpackは無限大なのでそこまでサポートする気はない。
webサイトとかを作るときに、プロダクションや開発の設定(minify, hashed name, server, etc…)を書くのがめんどくさいのでそれを解決する。


sweetpack

できること

webpackとbabelの設定ファイルを書かずにできることは主に以下の通りです。

  • reactを使用できる
  • flowを使用できる
  • css-modulesを使用できる
  • postcssを使用できる
  • htmlテンプレートエンジンが使える
  • 各環境の.envを読み込める
  • 開発時にwebpack-dev-serverが立ち上がり、HMRも有効化される
  • 開発時にreact-hot-loaderが使える
  • ビルド時にファイル名のハッシュを付く
  • ビルド時にコードの最適化が行われる(js, react)
  • ビルド時にファイルの分割ができる

プラグイン・設定

設定ファイル

.sweetpack.ymlで設定します。
以下の設定をサポートします。(値はデフォルト値)

entry: src/index.js
output: dist
js:
 flow: false
  react: false
css:
  modules: false
  postcss: false
html:
  filename: null
  template: null
dev:
  port: 8080
  dashboard: true
prod:
  extract: false

設定ファイルがない場合は、上記が適応されます。

共通プラグイン

  • html-webpack-plugin
    • 基本的にhtmlは自動生成(or テンプレートを指定)
  • dotenv-webpack
    • .envを読み込む
  • case-sensitive-paths-webpack-plugin
    • モジュールのファイル名の大文字、小文字を判断しエラーを出します(osxの標準ファイルシステムのHFS+は判断しないため)
  • file-loader
  • style-loader
  • css-loader
    • css-modulesにも対応(default: false)
  • postcss-loader(default: false)
    • これはローダを挟むだけなので、postcss.config.jsで自分の使いたいプラグイン(e.g. sass, cssnext, precss, etc..)を指定する
  • babel-preset-react(default: false)
    • onの時、react-hot-loaderも使用可能になる(devのみ)
  • babel-preset-flow(default: false)
    • onの時、flow-status-webpack-pluginも使用される
  • babel-preset-env
  • babel-preset-stage-1

開発時プラグイン

webpack-dev-serverが起動し、HMRもオンになります。
webpack-dashboardもデフォルトで使用されます。
また、reactを使用している場合はreact-hot-loader@nextも自動で入ります。

有効化されるプラグイン

  • webpack-dev-server
  • react-hot-loader(reactオプションがtrueの時のみ)
  • webpack-dashboard

github.com github.com

ビルド時プラグイン

NODE_ENV=productionを基本的には使用する前提で書かれています。
また、ビルド時にはindex.html以外のファイル名が自動でハッシュが付与されます。
このときに、reactが有効化されていてもreact-hot-loaderは無効化されます。
clean-webpack-pluginにより出力ディレクトリが削除されます。
最適化としてbabel-minify-webpack-plugin, OccurrenceOrderPlugin, AggressiveMergingPluginが走ります。
もしこのときにextract-text-webpack-pluginを有効化していれば、styles.[hash].cssという形でindex.htmlに挿入されます。

有効化されるプラグイン

  • clean-webpack-plugin
  • babel-minify-webpack-plugin
  • babel-preset-react-optimize(reactオプションがtrueの時のみ)
  • extract-text-webpack-plugin(extractオプションがtrueの時のみ)
  • webpack.optimize.OccurrenceOrderPlugin
  • webpack.optimize.AggressiveMergingPlugin

github.com github.com github.com


サンプル

package.json

{
  "scripts": {
    "start": "sweetpack watch",
    "build": "cross-env NODE_ENV=production sweetpack build"
  }
}

.sweetpack.yml

entry: ./lib/index.js
output: dist/bundle.js

ディレクトリ構成

 .
├── lib
│   └── index.js
└── package.json
1 directory, 2 files

npm startをしたらwebpack-dev-severwebpack-dashboardが起動します。
そして、npm run buildをしたら、/distにビルド済みの生成ファイルができます。

dist
├── bundle.2ea3ca43d449dd5fdc16.js
└── index.html
0 directories, 2 files

html, jsはファイル名にハッシュ値が振られ、最適化済みになります。

すべての設定ファイルを有効化したサンプルはこちらです。
sweetpack/samples/all at master · abouthiroppy/sweetpack · GitHub


さいごに

詳しくはREADME.mdとサンプルを見るとわかると思います。

まだまだやるべきことはあると思いますが、一旦ここである程度サポートできたのではないかなと思い書きました。
もちろんまだ足りないプロパティはあると思います。。。

もし興味あれば、是非使ってみてください;)

今の自分がどのように生成されたか考えた

なんとなく、タスクを捌きながら思ったことを殴り書きしている。
さて、今の自分がどのように形成されたかと考えたときに新卒の頃の周りの環境が大きいんじゃないかなって振り返って思った。

特に新卒の頃は、仕事というフィールドに触れて、特にインフラに詳しくなった気がする。
ドワンゴは知っての通りトラフィックが多いので、どうしても個人で運用して学ぶ知識ではカバーできない部分が多かった。
自分はフロントエンドエンジニアだったが、主にAWS周りのネットワーク設計やDB設計など色々と同期、先輩と討論したお陰である程度知識はあると思っている。

ドワンゴギークな人が多い。これは実際そうでエンジニアとしてのスキルが高い人が多い。(ただ生活リズムが終わってる人も多い)
だから面白いけど真剣な討論が多かった。(多方面の知識を持ってる人が多いので、様々な視野があり指摘がある)

この二年は、自分の専門領域でない部分の学びが多かったと書いていて思った。
なので広く浅く知っていって、興味があれば調べて深掘りしていくという学びができた気がする。(これがいわゆるI型ではなくT型?)

ところで、新卒の頃を思い出してみると、周りの環境が新卒の自分にとってはとても良かった。 自分で言うのもあれだが、新卒の時からある程度実力はあると思っていた。
入社当時、自分は研修スキップの早期配属で新卒が10名ぐらいいた部署で辞めるまで働いていた。

新卒の自分から見た同期は、優秀な人が多かった。 また特に分野が被ってなかったのでキラキラして見えた。Ruby, RoR, iOS, Android, etc..
著名なOSSのコミッターがいたり、iOSめっちゃ詳しい人いたり、RoR詳しい人いたりしていた。
基本聞けば回答が帰ってくる。

どのように自分が学生時代から今の自分へ変わったのか考えたが、一番の理由は多分危機感だったんだと思う。
しかも自分と同じ年齢だったから更に刺激的だったんだと思う。
そこから本格的にOSSとかを始めてコードを読む量を増やしたり、コミットしたり、I/Oを重視して学習していた気がする。
なんでもいいんだけど、コミットだってコード読まないといけないし、登壇だって学びがないと話せないし、ブログも同じ。
I/Oは本当に大切である。
もし新卒のときにこの環境じゃなかったら今の自分は正直ないと思っている。
なので本当に運が良かったなーって思う。

さて、次は中途の自分がそういう風に見られる立場だと思うとこんな自分でいいのか謎である。
まぁとくにそういうことを考えて仕事をしているわけではないが、自分みたいな人間がいたらそうなんだろうなって感じ

次は自分が知識を発信していく番なので、ワクワクしつつ、楽しく自分自身も学んでいきたい。
これからも日本ではNodeの活動をしていけたらと思います;)


何が結局言いたいかって言うと、新卒の同期は一生に一回しかないものだと思うし、仕事する上での環境はとても大切だと思う。
人それぞれだと思うけど、互いに磨き合える同期がいる環境が一番、実力が伸びる近道なのかもしれない。
普通に技術の話を永遠としているだけでも楽しいと思える人がいると良さそう。

という自分の経験上の話をまとめてみた。

なんかすっごい意識高いこと書いているただの老害になってしまったけど、文字で残しておきたかった。

おわり

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

Babel7がリリースされるまでは更新されます。

注意: 量が多いので、BabelのInternal, Bug Fix, DocumentsとBabylonについては書きません。
また6.xへバックポートされたものも入っていますので注意してください。

もし、間違えや質問があれば、 @about_hiroppy までどうぞ;)

Index


Links

Milestone

Babel 7 Beta Milestone · GitHub

Wiki

Babel 7 · babel/babel Wiki · GitHub

Releases

Releases · babel/babel · GitHub

Revision History of This Article

2017/09/04(first)

Babelのstableがv6.26.0でプレリリースがv7.0.0-alpha.20です。
Babylonのv7.0.0-beta.22です。

The branches in babel have been moved. The previous master is now called 6.x, and the 7.0 branch is now master. Pull requests need to be based on master from now on. (2017/9/1)

masterブランチが7.0になったためここにまとめたいと思います。


Notable Changes

Babel

  • TypeScriptのサポート
  • .babelrc.jsのサポート
  • spread-operatorが仕様準拠となる
  • stageパッケージの追加と変動

Packages Status of Stage-X

従来通り、数値以上のパッケージはそこにすべて含まれます。

Tracking for Spec

github.com

stage-3

  • babel-plugin-syntax-dynamic-import(new)
  • babel-plugin-transform-async-generator-functions
  • babel-plugin-transform-class-properties(new)
  • babel-plugin-transform-object-rest-spread
  • babel-plugin-transform-optional-catch-binding(new)
  • babel-plugin-transform-unicode-property-regex(new)

stage-2

  • babel-plugin-transform-export-namespace
  • babel-plugin-transform-function-sent(new)
  • babel-plugin-transform-numeric-separator(new)
  • babel-preset-stage-3

stage-1

  • babel-plugin-transform-decorators
  • babel-plugin-transform-export-default
  • babel-plugin-transform-optional-chaining(new)
  • babel-preset-stage-2

stage-0

  • babel-plugin-transform-do-expressions
  • babel-plugin-transform-function-bind
  • babel-preset-stage-1

Flow

  • Object typeに対し、speadが使用可能になる
  • declare exportが使用可能になる
  • predicatesエイリアスサポート
  • opaqueエイリアスサポート

babel-preset-env

polyfillを動的に入れてくれるようになる


Details of Changes

Babel

babel-preset-es2015

specモード時に変換されたアロー関数に名前が付与される

// input
return {
  g: () => this
}

// output
return {
  g: function g() {
    babelHelpers.newArrowCheck(this, _this);
    return this;
  }.bind(this)
}
Related Plugins

babel-plugin-transform-es2015-arrow-functions

Versions

v7.0.0-alpha.10

PRs

Add function name to spec-transformed arrow functions by Kovensky · Pull Request #5620 · babel/babel · GitHub

check-es2015-constantsが仕様準拠となる

constコンパイル時にエラーではなく、その前にthrowが追加されました。

Related Plugins

babel-plugin-check-es2015-constants

Versions

v7.0.0-alpha.17, 20

PRs

配列でのfor-ofの最適化

Related Plugins

babel-plugin-transform-es2015-for-of

Versions

v7.0.0-alpha.15

PRs

test for for-of optimization on arrays and add it for array type anno… by hzoo · Pull Request #4747 · babel/babel · GitHub

looseモードでのClassCallCheckpossibleConstructorReturnが削除

Related Plugins
  • babel-helpers
  • babel-plugin-transform-es2015-classes
  • babel-plugin-transform-flow-comments
  • babel-plugin-transform-flow-strip-types
Versions

v7.0.0-alpha.15

PRs

Remove ClassCallCheck, possibleConstructorReturn in loose mode by hzoo · Pull Request #4850 · babel/babel · GitHub

looseモードにes2015-parametersが追加

argumentsを使用しません。

Related Plugins

babel-plugin-transform-es2015-parameters

Versions

v7.0.0-alpha.17

PRs

2nd try: Add loose option for es2015-parameters transformation by maurobringolf · Pull Request #5943 · babel/babel · GitHub

looseモードにclass-fieldsが追加

Object.definePropery を使わずに代入式でコンパイルされます。

var Bork = function Bork() {
  babelHelpers.classCallCheck(this, Bork);
  this.x = 'bar';
  this.y = void 0;
};
 
Bork.a = 'foo';
Bork.b = void 0;
Related Plugins

babel-plugin-transform-class-properties

Versions

v7.0.0-alpha.20

PRs

Update Class Fields to Stage 3 and change default behavior by kedromelon · Pull Request #6076 · babel/babel · GitHub

template-literalsはデフォルトでconcatを使用し、looseモードでは+を使用する

Related Plugins

babel-plugin-transform-es2015-template-literals

Versions

v7.0.0-alpha.20

PRs

default to spec mode for template literal transform by kedromelon · Pull Request #6098 · babel/babel · GitHub

spread-operatorが仕様準拠となる

// Invalid
( {...{}} = {} ); ( {...[]} = {} );
var { ...{ z } } = { z: 1 };
({ x, ...{ y, z } } = o);

// valid
let {...{}} = {}; let {...[]} = {};
let { ...z } = { z: 1 };
({ x, y, ...z } = o);

May 23, 2017 Meeting Notes

Related Plugins
  • babel-plugin-transform-es2015-destructuring
  • babel-plugin-transform-object-rest-spread
Versions

v7.0.0-alpha.20

PRs

Adjusted Object Rest/Spread tests to use only allowed syntax from the… by Andarist · Pull Request #6102 · babel/babel · GitHub

babel-preset-stage-3

stage-3からstage-4のプラグインが削除

Versions

v7.0.0-alpha.1

PRs

[7.0] Remove stage 4 plugins from stage 3 preset by varemenos · Pull Request #5126 · babel/babel · GitHub

dynamic-importの追加

for (const link of document.querySelectorAll('nav > a')) {
  link.addEventListener('click', e => {
    e.preventDefault();

    import(`./section-modules/${link.dataset.entryModule}.js`)
      .then(module => module.loadPageInto(main))
      .catch(err => main.textContent = err.message);
  });
}
Related Plugins

babel-plugin-syntax-dynamic-import

Versions

v7.0.0-alpha.8

PRs

Move syntax-dynamic-import to stage-3 by dkaoster · Pull Request #5610 · babel/babel · GitHub

Spec

GitHub - tc39/proposal-dynamic-import: import() proposal for JavaScript

optional-catch-bindingの追加

try {} catch {}
try {
  bar;
} catch {
  foo();
} finally {
  yay();
}
Related Plugins

babel-plugin-transform-optional-catch-binding

Versions
  • v7.0.0-alpha.17
  • v7.0.0-alpha.18
PRs
Spec

Optional catch binding

class-fieldsの追加

class Bork {
  static a = 'foo';
  static b;
 
  x = 'bar';
  y;
}

looseモードの指定がない場合、コンパイル時に代入式からObject.defineProperyを使うように変更されました。

Related Plugins

babel-plugin-transform-class-properties

Versions

v7.0.0-alpha.20

PRs

Update Class Fields to Stage 3 and change default behavior by kedromelon · Pull Request #6076 · babel/babel · GitHub

Spec

GitHub - tc39/proposal-class-fields: Orthogonally-informed combination of public and private fields proposals

transform-unicode-property-regexの追加

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π'); // true
Related Plugins

babel-plugin-transform-unicode-property-regex

Versions
  • v7.0.0-alpha.1
  • v7.0.0-alpha.15
PRs
Spec

GitHub - tc39/proposal-regexp-unicode-property-escapes: Proposal to add Unicode property escapes `\p{…}` and `\P{…}` to regular expressions in ECMAScript.

babel-preset-stage-2

function.sentの追加

function* generator() {
  console.log('Sent', function.sent);
  console.log('Yield', yield);
}
 
const iterator = generator();
iterator.next(1); //  Sent 1
iterator.next(2); // Yield 2
Related Plugins
  • babel-helper-wrap-function
  • babel-plugin-transform-function-sent
Versions

v7.0.0-alpha.17

PRs

Function sent by nicolo-ribaudo · Pull Request #5920 · babel/babel · GitHub

Spec

ESideas/Generator metaproperty.md at master · allenwb/ESideas · GitHub

export-default-fromの追加

export var v;
export * as ns from 'mod';
export v, {x, y as w} from 'mod';
Related Plugins
  • babel-plugin-transform-export-default
  • babel-plugin-transform-export-namespace
Versions

v7.0.0-alpha.20

PRs
Spec

GitHub - tc39/proposal-export-default-from: Proposal to add `export v from "mod";` to ECMAScript.

numeric-separatorの追加

1_000_000_000  // Ah, so a billion
101_475_938.38  // And this is hundreds of millions

let fee = 123_00;   // $123 (12300 cents, apparently)
let fee = 12_300;   // $12,300 (woah, that fee!)
let amount = 12345_00;  // 12,345 (1234500 cents, apparently)
let amount = 123_4500;  // 123.45 (4-fixed financial)
let amount = 1_234_500; // 1,234,500

let budget = 1_000_000_000_000;
// What is the value of `budget`? It's 1 trillion!

console.log(budget === 10 ** 12); // true
Related Plugins

babel-plugin-transform-numeric-separator

Versions

v7.0.0-alpha.20

PRs

Add numeric separator to stage 2 preset by rwaldron · Pull Request #6071 · babel/babel · GitHub

Spec

GitHub - tc39/proposal-numeric-separator: A proposal to add numeric literal separators in Javascript. https://tc39.github.io/proposal-numeric-separator/

babel-preset-stage-1

optional-chaining-operatorの追加

abouthiroppy.hatenablog.jp

const obj = {
  foo: {
    bar: {
      baz: 42,
    },
  },
};

const baz = obj?.foo?.bar?.baz; // 42
const safe = obj?.qux?.baz; // undefined

// Optional chaining and normal chaining can be intermixed
obj?.foo.bar?.baz; // Only access `foo` if `obj` exists, and `baz` if `bar` exists
Related Plugins

babel-plugin-transform-optional-chaining

Versions

v7.0.0-alpha.15

PRs

Optional Chaining Operator (Stage 1) by jridgewell · Pull Request #5813 · babel/babel · GitHub

Spec

GitHub - tc39/proposal-optional-chaining

babel-plugin-transform-class-properties

configurableプロパティの追加

Object.defineProperty(REF, KEY, {
  configurable: true,
  enumerable: true,
  writable: true,
  value: VALUE
});
Versions

v7.0.0-alpha.20

PRs

Add 'configurable' property to class fields by reznord · Pull Request #6123 · babel/babel · GitHub

babel-polyfill

それぞれの機能のポリフィルファイルを個別に追加

Versions

v7.0.0-alpha.7

PRs

add individual polyfill files by hzoo · Pull Request #5584 · babel/babel · GitHub

babel-cli

--inspect-brkオプションの追加

Node7.6で入ったオプションです。

Versions

v7.0.0-alpha.11

PRs

Allow --inspect-brk option to be used with babel-node by noinkling · Pull Request #5785 · babel/babel · GitHub

--config-fileオプションの追加

.babelrcのパスを渡すオプションです。

Versions

v7.0.0-alpha.20

PRs

add --config-file option to CLI to pass in .babelrc location by bdwain · Pull Request #6133 · babel/babel · GitHub

babel-plugin-transform-runtime

useBuiltInsuseESModulesオプションの追加

Versions

v7.0.0-alpha.3

PRs

Add useBuiltIns and useESModules options to transform-runtime by Kovensky · Pull Request #5442 · babel/babel · GitHub

babel-core

babelの環境変数が取得できるようになった

Versions

7.0.0-alpha.3

PRs

Export Babel's environment by xtuc · Pull Request #5448 · babel/babel · GitHub

babel-register

キャッシュディレクトリを見つけ、作成する

Versions

v7.0.0-alpha.10

PRs

Find cache dir by pwmckenna · Pull Request #5669 · babel/babel · GitHub

Flow

Object typeに対し、speadが使用可能に

type U = {};
type V = {};
type T = { ...U };
type T = { ...U, ...V, };
type T = { p: V, ...U, };
type T = { ...U, p: V, };
type T = { ...{} | { p: V } };
Versions

v7.0.0-alpha.10

PRs

Add support for object type spread by conartist6 · Pull Request #5525 · babel/babel · GitHub

declare exportが使用可能に

declare export var foo;
declare export function foo(): void;
declare export function foo<T>(): void;
declare export function foo(x: number, y: string): void;
declare export class A<T> extends B<T> { x: number }
declare export class A { static foo(): number; static x : string }
declare export class A mixins B<T>, C {}
declare export default function foo(): void;
declare export * from 'asd';
declare export { a, b };
declare export { c, d } from 'bar';
Versions

v7.0.0-alpha.15

PRs

Support declare export statements by danez · Pull Request #5589 · babel/babel · GitHub

predicatesエイリアスサポート

declare function foo(x: mixed): boolean %checks(x !== null);

function is_string(x): boolean %checks {
  return typeof x === "string";
}
Versions

v7.0.0-alpha.17

PRs

Add support for flow predicates in babel-generator by existentialism · Pull Request #5984 · babel/babel · GitHub

opaqueエイリアスサポート

opaque type ID = string;
opaque type Foo<T> = Bar<T>;
opaque type Maybe<T> = _Maybe<T, *>;
export opaque type overloads =
  & ((x: string) => number)
  & ((x: number) => string)
;

medium.com

Versions

v7.0.0-alpha.18

PRs

Flow opaque type aliases by jbrown215 · Pull Request #5990 · babel/babel · GitHub

babel-preset-reactはflowアノテーションがあるときのみflowの処理するように変更

TypeScriptとの互換性を持つためにbabel-preset-reactからflowのサポートはなくなります。

Versions
  • v7.0.0-alpha.19
  • v7.0.0-alpha.20
PRs

その他使用者向け

Node 0.12のサポートが切られました

Versions

v7.0.0-alpha.1

PRs

Drop support for Node 0.12 :skull: by siddharthkp · Pull Request #5025 · babel/babel · GitHub

.babelrc.jsがサポートされました

Versions

v7.0.0-alpha.2

PRs

Add support for .babelrc.js files by kaicataldo · Pull Request #4892 · babel/babel · GitHub

.babelrc.jsで関数使用の許可・キャッシュ

Versions

v7.0.0-alpha.8

PRs

Cache configs based on mtime and allow .babelrc.js functions by loganfsmyth · Pull Request #5608 · babel/babel · GitHub

.babelignoreで否定パターンが使えるようになる

Versions

v7.0.0-alpha.8

PRs

Allow negation of ignore and only patterns. by loganfsmyth · Pull Request #5625 · babel/babel · GitHub

babel-plugin-transform-new-targetの追加

Versions

v7.0.0-alpha.15

PR・Issue

TypeScriptのサポート

{
  "presets": ["typescript"]
}
Related Plugins
  • babel-plugin-syntax-typescript
  • babel-plugin-transform-typescript
  • babel-preset-typescript
Versions
  • v7.0.0-alpha.18
  • v7.0.0-alpha.19
  • v7.0.0-alpha.20
PRs

その他開発者向け

for-awaitは新しいASTに変更

// new forOfStatement
t.forOfStatement(left, right, body, await) // @param {boolean} await - default: false
Versions

v7.0.0-alpha.1

PRs

Change for-await to use new AST by danez · Pull Request #5321 · babel/babel · GitHub

babel-traverseにString.rawの追加

Versions

v7.0.0-alpha.15

PRs

Add support for evaluating `String.raw` expressions by josephfrazier · Pull Request #5681 · babel/babel · GitHub

babel-standaloneがbabelレポへ移動

github.com

Versions

v7.0.0-alpha.20

PRs

Move babel-standalone into main Babel repo by Daniel15 · Pull Request #6029 · babel/babel · GitHub

babel-preset-env

useBuiltInsオプションの変更

以前はfalseでbooleanでしたが、"usage""entry"falseへ変更されました。
useBuiltInsというオプションはbabel-polyfillを入れるかどうかオプションです。
これにより、import 'babel-polyfill' が必要なくなります。

FYI: GitHub - babel/babel-preset-env at 2.0

Versions

v2.0.0-alpha.6

PRs

make useBuiltIns: false default, rename true to 'usage' by hzoo · Pull Request #285 · babel/babel-preset-env · GitHub

Website

REPLがreplaceされた

Babel · The compiler for writing next generation JavaScript


In Progress

Babel

decoratorsのアップデート

class Foo {
  @dec1 method1() {}
  @dec2(a, b, c) method2() {}
  @dec3outer @dec3inner method3() {}
  undecorated() {}
}

elementDescriptor`というspecの概念の追加等

Versions

N/A

PRs

Decorators 2 Transform [WIP] by peey · Pull Request #6107 · babel/babel · GitHub

Spec

現在stage-2
Decorators

bigIntの追加

(1073741824n ** 2n).toString() === (new BigInt('1152921504606846976')).toString()
Versions

N/A

PRs

BigInt (Stage 2) [WIP] by wdhorton · Pull Request #6015 · babel/babel · GitHub

Spec

現在stage-3(PRの段階では2ですが現在3)
GitHub - tc39/proposal-bigint: Arbitrary precision integers in JavaScript

Babylon

Pattern Matchingの追加

let getLength = vector => match (vector) {
  { x, y, z }: Math.sqrt(x ** 2 + y ** 2 + z ** 2),
  { x, y }:    Math.sqrt(x ** 2 + y ** 2),
  [...]:       vector.length,
  else: {
    throw new Error("Unknown vector type");
  }
};
Versions

N/A

PRs

WIP Pattern Matching (Stage 0) by krzkaczor · Pull Request #635 · babel/babylon · GitHub

Spec

現在stage-0
GitHub - tc39/proposal-pattern-matching: Pattern matching syntax for ECMAScript

classへprivate methodsのサポート

class Counter extends HTMLElement {
  #xValue = 0;

  #clicked() {
    this.#x++;
  }

  constructor() {
    super();
    this.onclick = this.#clicked.bind(this);
  }

  connectedCallback() {
    this.#render();
  }

  #render() {
    this.textContent = this.#x.toString();
  }
}
Versions

N/A

PRs

Add support for class private methods by Qantas94Heavy · Pull Request #703 · babel/babylon · GitHub

Spec

現在stage-2
GitHub - tc39/proposal-private-methods: Private methods and getter/setters for ES6 classes

メモ: Node.jsとAyo.jsに分裂したという話

この記事は自分用のリンク集メモです。(色々と聞かれるときにリンク探すの大変なため)

注意: 追記・変更が頻繁に行われるでしょう

原因

Rodが複数回のNode.js Foundationの行動規範に対し違反を行っていた。
コレに関して、TSCで辞任の投票が行われたが60%の反対で否決となった。(技術力や今までの貢献度がすごかったから)
この意思決定に関して疑問を持った人がTSCから自主的に辞任をした。

Rodの違反行為一覧

画像じゃなければこっち meta: vote regarding Rod's status on the TSC · Issue #310 · nodejs/TSC · GitHub

また、Rodの違反行為はModerationまでエスカレーションしている。

参考Issues

TSC投票ステータス

github.com

privateを除く、一番始めに表に出てきたIssue
ココから今回の騒動は始まった。

編集前魚拓: meta: vote regarding Rod's status on the TSC · Issue #310 · nodejs/TSC · GitHub

結果: 6人がNo, 4人がYes, 2人が棄権, 1人が拒否

自主的に辞退した人

Anna, Brya, Myles, Jeremiah

Pull Requests · nodejs/TSC · GitHub

Mylesの意見・考え

medium.com

Bryanの意見・考え

medium.com

自主辞退要求とRodの声明

github.com

簡単に言うと、辞退を拒否

Nodeの将来

github.com

Hacker News

Effective immediately I am stepping down from the Nodejs TSC | Hacker News

ZDNet

www.zdnet.com

TSC

github.com

TSCとはNode.js FoundationのTop Working Groupである。
今回の投票はここにいるメンバーで行われた。


Node.js Foundationの動き

事態が起こってから急速に動いている。

主な変更

TSCとCTCのリマージ

CTCがデチャータリングされ、TSCにCTCメンバーが加わります。

github.com github.com github.com

規約の更新

CoCなどの規約が改定されます。

新しいModerationチームを立ち上げるための取り組み

github.com

github.com

ボード

Coreへの全体周知

github.com

Board

github.com

大変重要な問題なので8/28に理事会がMTGを開く。
長期的に続くガバナンスモデルの構築のため、憲章・規範の改定を主に検討される。

質問リスト

github.com

このissueへの反応

github.com


Ayo.js

node.js/nodeからforkされた。
読み方は「アイヨ」、「アイオ」、「Awooooo」
Awoooooが結構押されている気がする(e.g. discord, etc…)

github.com

NodeのCore Collaboratorが多いが、決してNodeを捨てたつもりはないと思っています。
ただ自分としては、新しい規約提案ができるのではないかと期待している。

フォークに関して

github.com

Ayoも7000以上あるフォークの内の一つであり、それがOSSなのであまり敏感になる必要はないと思っています。
Node.jsのコアメンバーがいるただのフォークです。

github.com

意見交換場所

招待 discordapp.com

チャンネルURL(アカウントがある場合はこちら) discordapp.com

npm

http://blog.npmjs.org/post/164575965335/values-inclusion-and-the-nodejs-foundation
blog.npmjs.org

結構Ayoにノリノリっぽい気がした。(それぐらいNode.js Foundationの規範に不満があったのかな?)

目的

github.com

コード自体は当分ミラーリングの予定です。(が将来的には機能追加するとfishrockは言っていました)
それよりも規範等のガバナンスの再構築が目的です。

github.com

ここに書いてあるとおり、参加しているのはNode Coreで活動している人が主です。

github.com

Values

今現在、草稿が上がっています。 github.com

新しいガバナンスモデルとマネージメントの構築を目的としています。
全体的に人間が重要視されています。
価値や幸福を技術や企業より主軸に置きます。

アイコン

検討中

github.com

CI

インフラは検討中。
現在Travisで動いているが、各プラットフォームのテストができないのでつらそう。
今はTravisLinuxのみのテストを行っています。

ちなみにNode.jsはJenkinsでCIが行われています https://ci.nodejs.org/


さいごに

今回はio.jsと似て非なるものである。
io.jsと違い、あくまでも身内の話である。(また技術的な話が要因ではない)

だからこそ今回の件の理由は、外から見ると何が起こっているのかわかりづらい。

8/28に開催される理事会を待つ。

NodeにPerformance Timing APIが追加される

NodeへPerformance Timing APIの初期実装が入ります。

資料

developer.mozilla.org

W3C

Performance Timeline

High Resolution Time Level 2

github.com

NodeへのPR

github.com

まだ、masterやリリースラインに入っていないのでドキュメントはPRから見てください。
今回は、PerformanceFramePerformanceNodeTiming の説明は書かないので詳しくはドキュメントへ。

Nodeのイベントループの時間測定を測定するのも実装されている。

Performance Timing API

マイクロ秒で経過時間を取得するAPI である。

Dateを使い計測を行うことがあると思うが、以下のような場合がある。

const markStart = Date.now();

(() => {})();

const duration = Date.now() - markStart;

console.log(duration); // 0

実際は、正の値, 負の値, 0の値になり得てしまう。

実装

Nodeで使ってみる

processperformanceというメソッドが追加された。

> process.performance
{ timeOrigin: 14194760.508743,
  nodeTiming:
   PerformanceNodeTiming {
     duration: 2735.97479,
     startTime: 14194760.508743,
     type: 'node',
     name: 'node',
     arguments: 14194763.6652,
     initialize: 14194764.104207,
     inspectorStart: 14194769.714933,
     loopStart: 14194854.360176,
     loopExit: 0,
     loopFrame: 14196891.511942,
     bootstrapComplete: 14194854.356646,
     third_party_main_start: 0,
     third_party_main_end: 0,
     cluster_setup_start: 0,
     cluster_setup_end: 0,
     module_load_start: 14194813.331148,
     module_load_end: 14194813.33181,
     preload_modules_load_start: 14194813.332309,
     preload_modules_load_end: 14194813.356287 },
  nodeFrame:
   PerformanceFrame {
     prior: 0.012497,
     type: 'frame',
     name: 'frame',
     duration: 605.560913,
     startTime: 14196891.511942 } }
>

手順

基本的には、

  1. 開始区間のマーキング(mark)
  2. 終了区間のマーキング(mark)
  3. その区間の名前を定義して保存されます。(measure)
  4. すべて保存されたリストから3で定義した区間の名前を指定し取得(getEntriesByName)

また、同じ区間の名前をつけたときはスタックしていきます。
なのでgetEntriesByNameで取得した時に配列になっています。
これは新しいのから順にpushされます。(つまりタイムライン順)

区間を逆(未来 to 過去)にした場合、数値がすごいことになるのは仕様なのかな・・・?

少し長いですが、以下のコードを読めばわかると思います。

コード

const fs = require('fs');

const perf = process.performance;

perf.mark('A');

(() => {})();

perf.mark('B');

perf.measure('A to B', 'A', 'B'); // tagname, startMark, endMark

const measure1 = perf.getEntriesByName('A to B')[0]; // measure1's length is 1
console.log(measure1.duration); // 0.009461

/* ----------------------------------------------------- */

perf.mark('C');

fs.readFileSync('./node');

perf.mark('D');

perf.measure('C to D', 'C', 'D');

const measure2 = perf.getEntriesByName('C to D')[0]; // measure2's length is 1
console.log(measure2.duration); // 28.502531

/* ----------------------------------------------------- */

perf.measure('A to B', 'C', 'D'); // push to `A to B`

const measure3 = perf.getEntriesByName('A to B'); // measure3's length is 2
console.log(measure3); // display the performance timeline of `A to B`
/*
  [ PerformanceMeasure {
      duration: 0.009461,
      startTime: 15995656.619165,
      type: 'measure',
      name: 'A to B' },
    PerformanceMeasure {
      duration: 28.502531,
      startTime: 15995675.467252,
      type: 'measure',
      name: 'A to B' } ]
*/

console.log(perf.getEntries()); // display the performance timeline(combined `mark` and `measure` into the timeline)
/*
  [ PerformanceNodeTiming {
      duration: 107.495859,
      startTime: 15995598.683741,
      type: 'node',
      name: 'node',
      arguments: 15995602.156617,
      initialize: 15995602.750924,
      inspectorStart: 15995609.924404,
      loopStart: 0,
      loopExit: 0,
      loopFrame: 0,
      bootstrapComplete: 0,
      third_party_main_start: 0,
      third_party_main_end: 0,
      cluster_setup_start: 0,
      cluster_setup_end: 0,
      module_load_start: 15995653.258513,
      module_load_end: 15995653.313254,
      preload_modules_load_start: 15995653.313785,
      preload_modules_load_end: 15995653.341459 },
    PerformanceFrame {
      prior: 0,
      type: 'frame',
      name: 'frame',
      duration: 15995706.323224,
      startTime: 0 },
    PerformanceMark {
      duration: 0,
      startTime: 15995656.619165,
      type: 'mark',
      name: 'A' },
    PerformanceMark {
      duration: 0,
      startTime: 15995656.628626,
      type: 'mark',
      name: 'B' },
    PerformanceMeasure {
      duration: 0.009461,
      startTime: 15995656.619165,
      type: 'measure',
      name: 'A to B' },
    PerformanceMark {
      duration: 0,
      startTime: 15995675.467252,
      type: 'mark',
      name: 'C' },
    PerformanceMark {
      duration: 0,
      startTime: 15995703.969783,
      type: 'mark',
      name: 'D' },
    PerformanceMeasure {
      duration: 28.502531,
      startTime: 15995675.467252,
      type: 'measure',
      name: 'C to D' },
    PerformanceMeasure {
      duration: 28.502531,
      startTime: 15995675.467252,
      type: 'measure',
      name: 'A to B' } ]
 */

API

common

now()

現在の高分解能時刻タイムスタンプを返します。

mark(name: string)

パフォーマンスタイムラインへ新しいPerformanceMarkを追加します。
performanceEntry.typeは常にmarkです。

measure(name: string, startMark: string, endMark: string)

パフォーマンスタイムラインへ新しいPerformanceMeasureを追加します。
performanceEntry.typeは常にmeasureです。

name区間の名前です。
startMarkは開始地点の名前を設定します。もし名前が存在しない場合、timeOriginが基準となります。
endMarkは終了地点の名前を設定します。もし指定しない場合は、エラーが投げられます。

get

getEntries()

startTimeを基準にすべてのPerformanceEntry Objectのリストを時間順に返します。
mark 及び measure のタイムラインとなります。

getEntriesByType(type: string)

performanceEntry.typeと同じtypeを持つすべてのPerformanceEntry ObjectのリストをstartTimeを基準に時間順に返します。
古い物から先頭となります。

getEntriesByName(name: string, type?: string)

performanceEntry.typeと同じnameを持つすべてのPerformanceEntry ObjectのリストをstartTimeを基準に時間順に返します。
古い物から先頭となります。

clear

clearMarks(name?: string)

名前を指定すれば、そのマークを消し、
指定しなければ、すべてのマークをパフォーマンスタイムラインから消します。

clearMeasures(name?: string)

名前を指定すれば、そのメジャーを消し、
指定しなければ、すべてのメジャーをパフォーマンスタイムラインから消します。

仕様分類

High Resolution Time Level 2

High Resolution Time Level 2

toJson()

High Resolution Time

High Resolution Time

now()

Navigation Timing

timing, navigation

Performance Timeline Time Level 2

Performance Timeline Level 2

getEntries()

Performance Timeline

Performance Timeline

getEntries(), getEntriesByType(), getEntriesByName()

Resource Timing Level 1

Resource Timing Level 1

clearResourceTimings(), setResourceTimingBufferSize(), onresourcetimingbufferfull

User Timing

User Timing Level 2

User Timing

mark(), clearMark(), measure(), clearMeasure()

まとめ

Performance Timing APIに関する初期実装がもうすぐ入りそう。
まだまだ未実装な部分が多いので、今後に期待。

Node8.3.0でデフォルトになるTF/Iに関わるベンチマークについて

buildersconでのスライド

speakerdeck.com

8.3.0 プロポーザル

github.com

大きな変更はやはりV8の6.0とWHATWG Encoding Standardが入ることです🎉
今回はV8の6.0の話をします。
これはLTS対象であるNode8へのバックポートもされNodeのバージョンを9へ上げる準備ができました。

github.com

github.com

今現在のcurrent(master branch)はv9.0.0-preです。

TurboFan + Ignition

V8の5.9からデフォルトになるTurboFanとIgnitionというものがあります。 今までは、Crankshaftと呼ばれるコンパイルインフラストラクチャがありましたがそれが置き換わります。

v8project.blogspot.jp

TurboFanとIgnitionを簡単に説明します。

TurboFan

JITコンパイラ
Crankshaftから置き換わるもの。
Crankshaftではtryスコープ、ES2015の最適化(e.g. scope, class literal, for of, etc…)ができなかったですが、TurboFanではそれを可能とします。
chrome 41から段階的に使用されてました。(その頃はbaselineでTurboFanとCrankshaftでルーティングしていました)

Ignition

レジスタマシン。
モバイル端末のメモリ消費削減を目標として作られたインタプリタで、JSのコードをbytecodeにします。
V8のJITの問題点として、コードが一回実行されただけでも大量のメモリ消費をする可能性があり、メモリーオーバーヘッドを避ける必要がありました。

Flow

f:id:about_hiroppy:20170803092506j:plain

docs.google.com

V8の5.9は・・・?

Nodeでは8.3.0でV8の5.9を入れる予定でしたが、6.0を待つことになりました。

github.com

github.com

6.0ではTF関連のパフォーマンスが5.9よりも改善されるからです。
どうしてもエッジケースでパフォーマンスが以前から落ちる可能性があったからです。(もちろん6.0ですべて改善されたわけではないです)

パフォーマンス

medium.com

github.com

$ node benchmark/compare.js --old ./current --new ./8.3.0 arrays assert buffers child_process cluster crypto dgram dns domain es events fs http misc module net os path process querystring stream string_decoder timers tls url util v8 vm > compare-8.3.0.csv

Nodeのコアのベンチマークは上記のようにとります。(といっても普通に数日かかる量なので今回はissueのデータを使います。)

NodeのInternal ModulesのV8の5.8と5.9のパフォーマンスは以下のとおりです。
この頃は6.0がまだNodeのPRとして存在しなかったため5.9

github.com

量が多いので結論だけ言うと、劇的に改善される部分(100%以上)もあれば、劇的に遅くなる部分もあります。
例えば、buffers, events, http (!!), querystring, streams, string_decoder, url, utilは改悪になってしまってます。
今後、コアコードのパフォーマンスチューニングが必要なのではないかと思います。

Node.js Benchmarking

webpack

github.com

8.2.1(V8 5.8)

node benchmark.js  390.80s cpu 8:16.27 total

8.3.0(V8 6.0)

node benchmark.js  385.03s cpu 7:47.66 total

結果

webpackのベンチマークはあまり詳細がでないのでこれぐらいしか言えないですが、数秒程度速くなる可能性が見込まれると思います。

ESLint

$ node Makefile.js perf

8.2.1(V8 5.8)

Loading:
  Load performance Run #1:  370.81079ms
  Load performance Run #2:  138.677693ms
  Load performance Run #3:  162.385871ms
  Load performance Run #4:  147.627503ms
  Load performance Run #5:  138.628092ms

  Load Performance median:  147.627503ms


Single File:
  CPU Speed is 3100 with multiplier 13000000
  Performance Run #1:  5423.646407ms
  Performance Run #2:  5004.476339ms
  Performance Run #3:  5003.407218ms
  Performance Run #4:  5906.832898ms
  Performance Run #5:  5831.056636ms

  Performance budget exceeded: 5423.646407ms (limit: 4193.548387096775ms)


Multi Files (0 files):
  CPU Speed is 3100 with multiplier 39000000
  Performance Run #1:  15058.862498ms
  Performance Run #2:  13073.789987ms
  Performance Run #3:  12624.443172ms
  Performance Run #4:  12712.246881ms
  Performance Run #5:  16854.266736ms

  Performance budget exceeded: 13073.789987ms (limit: 12580.645161290322ms)

8.3.0(V8 6.0)

Loading:
  Load performance Run #1:  353.895106ms
  Load performance Run #2:  146.332188ms
  Load performance Run #3:  137.545153ms
  Load performance Run #4:  137.215065ms
  Load performance Run #5:  141.143209ms

  Load Performance median:  141.143209ms


Single File:
  CPU Speed is 3100 with multiplier 13000000
  Performance Run #1:  5469.148856ms
  Performance Run #2:  5300.968599ms
  Performance Run #3:  5379.501312ms
  Performance Run #4:  4955.262333ms
  Performance Run #5:  5242.219478ms

  Performance budget exceeded: 5300.968599ms (limit: 4193.548387096775ms)


Multi Files (0 files):
  CPU Speed is 3100 with multiplier 39000000
  Performance Run #1:  13120.178363ms
  Performance Run #2:  12653.286066ms
  Performance Run #3:  12639.912582ms
  Performance Run #4:  16274.700631ms
  Performance Run #5:  20859.144258ms

  Performance budget exceeded: 13120.178363ms (limit: 12580.645161290322ms)

結果

LoadingとSingle Fileは速くなっているが、Multi Filesが遅くなっている。
また、部分部分のパフォーマンスを見ると、必ずしもNode8.3.0が速いわけではない。
しかし、基本的には改善されていると思える。

まとめ

  • 8月中に8.3.0がリリースされる
    • 8.3.0でTurboFanとIgnitionがデフォルトになる v5.8 -> v6.0へ
    • WHATWG Encoding Standard(TextDecoderTextEncoder)が実装された
  • 今後、Node、Chromeはどんどん速くなる

PWAの実装をしてみた

一年以上前の記事なので、コードが古いです。気をつけて読んでください。

そういえばPWAの実装したことがなかったなと思ったので少し触ってみた。

PWAとは?

PWA(Progressive Web Apps)

インストールが不要で、不安定なネットワークでも素早く起動し、プッシュ通知を可能にします。
また、ホーム画面にアイコンも表示でき、アプリと同様の扱いをすることが可能となります。

つまり、アプリに近づけたwebですね。

以下の記事が詳しいのでそちらを見てください;)

developers.google.com

目的

  • https, localhostでしかService Workerは動かないので常に安全
  • Service Workerの更新プロセスにより常に最新
  • App Shellモデルによる構成でUIをネイティブにさらに近づける
  • プッシュ通知による再エンゲージメント
  • キャッシュすることにより、ネットワークの依存度を下げる

技術スタック

Service Worker

対応状況は以下の通り
f:id:about_hiroppy:20170727174456p:plain

Can I use... Support tables for HTML5, CSS3, etc

代表的な提供機能

  • オフライン機能
  • push通知
  • バックグラウンドコンテンツの更新
  • コンテンツキャッシュ

レシピ: ServiceWorker Cookbook

PWAのview

PWAにはApp ShellとContentというものがあります。
以下の図を見るとわかりやすいと思います。

f:id:about_hiroppy:20170727175055p:plain

developers.google.com

つまり、アプリケーションシェルというのはダイナミックコンテンツじゃない部分を指します。

App Shell

一番、PWAの実現で難しい部分であり、一番パフォーマンスの向上を図ることが期待される部分(らしい)
シンプルなデザインコンセプトで設計されます。
Service Workerのキャッシング機能により、パフォーマンスの向上が可能です。
最上位のアプリのロジック、ルーターなどがあります。

Content

動的なビューです。例えばTwitterのタイムラインとか。
ここもそれぞれのコンテンツで必要に応じてJSのチャンクは細かく切られます。

キャッシュ戦略

基本的に、App ShellとContentのJSは別チャンクにするべきです。
Service Workerにそれぞれのチャンクを保持させることにより、ユーザが前のページに戻った時に早く読み込むことが可能です。
理論上、App Shellの読み込みとContentの読み込みを別にすることにより、パフォーマンスとユーザビリティの向上が図れるらしいです。
App Shellはどのページでも常に読み込まれ、Contentは必要に応じて読み込むという感じです。

読み込みフロー

PWAは先にその時に必要なものだけを取り、それをキャッシュする仕組みです。
なのでwebpackでチャンクを細かく切ることにより、必要なリソースだけを読み込めるように設計します。
その後、Service Worker側で追加のリソースを事前に取得して、将来的な読み込みを行います。 また、ローディングの順序はApp Shellで基本的なUIを構築し、その後にコンテンツです。

Web App Manifest

manifest.jsonに名前、カラー、ホーム画面に置くアイコンの設定等を書きます。
PWAには必須です。

以下のようなスキーマになります。

{
  "name": "My PWA Sample",
  "orientation": "portrait",
  "display": "standalone",
  "start_url": ".",
  "short_name": "MyPWA",
  "description": "This is a sample App!",
  "background_color": "#f5f5f5"
}

また、HTMLでは以下のように指定します。

<link rel="manifest" href="/manifest.json">

developers.google.com

今回は、webpack-pwa-manifestというプラグインがあったのでそれを使いました。

github.com

デザイン

PRPL patternというのがあります。
より高速にモバイルでwebのエクスペリエンスを提供します。
Push、Render、Pre-cache、Lazy-loadで構成されます。

以下のAddy Osmaniの記事がとてもわかりやすいです!
The PRPL Pattern  |  Web Fundamentals  |  Google Developers

また今度、ブログにでも書こうかと思います。

FirefoxChromeしかService Workerないけどどうするの?

普通のHTML、CSS、JSなので問題はありません。
Service Workerはあくまでもネイティブ機能に近づける実現方法なので今までどおりにフォールバックします。

ネットワーク

Twitter Liteでhttp/2, GraphQLが使われています。

実装

github.com

上記のリポジトリで開発してみました。
SPA + Service Workerで実現しています。
今回はCSRのみです。

ライブラリ

react, react-routerで構築しました。
詳しくは、リポジトリwebpack.config.jsとかを見てください。

webpack-offline

Service Workerのファイル吐き出しと結合を行ってくれるプラグイン

注意点として、キャッシュファイルの保存容量が超えた時のエラーがわかりづらい。
Uncaught (in promise) DOMException: Quota exceeded. service worker は多分そのエラー。

なので、基本的にはdevではofflineを使わないでプロダクションのときにだけ使うようにしたほうがいい。(HMRもおそらくできないので)
ただ、もちろんswに本当に接続できているか確認したいときはあるのでそのときはライブラリ群のチャンクをキャッシュから外してデバッグしている。

様々なオプションがあるのでチューニングによるパフォーマンスとかの変化はありそう。

構成

ファイル

                                    Asset       Size  Chunks                    Chunk Names
                       vendor.2a193704.js     806 kB       5  [emitted]  [big]  vendor
node-8f3bc311d3d7fbab90a659d57e126fbf.png    3.73 kB          [emitted]
         Boron.content.bundle.2a193704.js     2.3 kB       1  [emitted]         Boron.content
         Argon.content.bundle.2a193704.js     2.3 kB       2  [emitted]         Argon.content
          List.content.bundle.2a193704.js    1.89 kB       3  [emitted]         List.content
                       bundle.2a193704.js    9.28 kB       4  [emitted]         bundle
        Carbon.content.bundle.2a193704.js     2.3 kB       0  [emitted]         Carbon.content
                            manifest.json  367 bytes          [emitted]
                               index.html  462 bytes          [emitted]
                                    sw.js    23.2 kB          [emitted]
               appcache/manifest.appcache  265 bytes          [emitted]
                   appcache/manifest.html     3.3 kB          [emitted]

ファイルは上記のように分けました。
vendor.jsではライブラリのコードのみが入っています。
なので一番サイズが大きいです。
ライブラリのバージョンが頻繁に変わらないためコアコードから隔離します。

bundle.jsがApp Shellです。
ルーティングとToolbarを持っています。
ベストプラクティスがわからないですが、これは一緒にしないほうがいいかもです。

*.content.bundle.jsがcontentです。
今回は、Argon, Boron, Carbonの三種類とルートページのリスト、合計4チャンクあります。
ちなみにNodeのLTSの名前がこの三種類です(v4, v6, v8)

ルーティング

慣れているreact-routerを使う。
必要な時にcontentのチャンクを取得するためにlazy loadを使います。

react-routerには慣れているつもりだったが、v4からgetComponentがなくなっていることに気付いてなかった。。

昔は、

<Route
    path="/"
    getComponent={(location, callback) => {
      require.ensure([], require => {
        callback(null, require('./Root'))
      }, 'Root')
    }}
  />

みたいに書けたはずなのに今は書けなくなっている。

調べた限り多分、FBの方が書いてたこれが一番キレイな書き方。

Quick and dirty code splitting with React Router v4 · GitHub

うーん。。。 ラッパーを書かないといけないの。。(あとreact-routerのリファレンス読みづらいんだよね。。)

ということで、これをラップしてたライブラリがあったので今回はそれを使った。

github.com

ただ、残念なことに<Switch>が対応してなくて、つらい。。
今PRが出ているのでそれ待ちという状態です。
Can not use with Switch · Issue #4 · mhaagens/lazy-route · GitHub

しょうがないので、/に対してexactを付けることにした。

// 長いので省略部あり

const Routes = () => (
  <App>
    <Route
      exact
      path="/"
      render={() =>
        <LazyRoute
          component={
            import(/* webpackChunkName: 'List.content' */ './components/contents/List')
          }
        />
      }
    />
    <Route
      path="/argon"
      render={() =>
        <LazyRoute
          component={
            import(/* webpackChunkName: 'Argon.content' */ './components/contents/Argon')
          }
        />
      }
    />
  </App>
);

AppはView全体を構築します。
この中にToolbarが入っており、this.props.childrenが上記に当てはまったルートになりそれをレンダリングします。
よくreact-routerでやる部分的更新の手法ですね。

しかし、今回はレンダリング時にファイルを取得し読み込み流したいので動的に取得する実装が必要です。
なのでstage-3のdynamic importを使う必要があります。(webpack2以降はデフォルトで入っています)

アセットのインストール

f:id:about_hiroppy:20170727190449p:plain

このような感じで保存される。 キャッシュの種類は main, additional, optional の三種類ある。

mainはinstallイベント時にservice workerにキャッシュされ、もし失敗したら全部のキャッシュはされません。
additionalmainが正常にロードされた後、ロードされます。
optionalはサーバからfetchされたときのみキャッシュされるので、事前にダウンロードしません。

github.com

今回はindex.htmlと各種JS,画像を保存しているので、これでたとえserveしていなかったりネットを切っている状態でも恐竜が現れるのではなく通常のページが表示されます。

ネットワークタイムラインはこのようになります。
f:id:about_hiroppy:20170727191352p:plain

sizeのところを見るとわかりますが、Service Workerからコードを取得しています。
ネットワークが切れているので、sw.jsの取得は失敗しますがコアコードはすでに取得済みなのであたかも生きているようにレンダリングされる。
また高速です。(vendor.jsとか重いのに。。)

API周り

今回は、通信周りの実装を行っていないのでコードはありませんが、
Twitter LiteではAPIを叩いた後Normalizrを通してReduxに効率よくデータを渡しています。 また、IndexedDBにもその結果を保存します。

SSR

今回のサンプルではやっていませんが、基本的にはやったほうがいいと思っています。
Twitter Liteでは、認証をし、初期状態の構成をし、App Shellのレンダリングをする設計になっています。
Twitter Liteをインスペクタで色々見ると楽しいかも。

資料

developers.google.com

The PRPL Pattern  |  Web Fundamentals  |  Google Developers

blog.twitter.com

medium.com

さいごに

手探りでやっていてこれがすべてベストプラクティスではなかったり、誤読があるかもしれないので、何かあればPRやIssue、コメントなど出してもらえると助かります:p