typescript 2.4 の新機能
typescript2.4がでたので新機能を確認。
Dynamic Import Expressionsのサポート
import('...')
式がサポートされた。
import
式を使うことで多くのバンドラーがコード分割をすることが可能になるので、
module: esnext
で出力するのがおすすめだそう。
async function getZipFile(name: string, files: File[]): Promise<File> { const zipUtil = await import('./utils/create-zip-file'); const zipContents = await zipUtil.getContentAsBlob(files); return new File(zipContents, name); }
String Enumsのサポート
待望?の文字列enumがサポートされた。
enum Colors { Red = "RED", Green = "GREEN", Blue = "BLUE", }
ただし制限として、数値enumの際に可能だった、メンバプロパティからプロパティ名の取得はできない。
Colors[Colors.Red] // これはできない。
interfaceのgeneric型のサポートを強化
戻り値の推論能力の強化
戻り値から型パラメータを導出できるようになった。
function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[] { return a => a.map(f); } const lengths: (a: string[]) => number[] = arrayMap(s => s.length);
Promiseもこのようにエラーとすることができるように。
let x: Promise<string> = new Promise(resolve => { resolve(10); // ~~ Error! });
文脈からの型パラメータの導出
いかのような定義があった場合
let f: <T>(x: T) => T = y => y;
y
はany
になってしまっていた。
そのため、
let f: <T>(x: T) => T = y => y() + y.foo.bar;
のような式は型チェックをスルーしてしまっていたが、
2.4からy
が正しく導出され、エラーとなるようになった。
generic関数の方チェックを厳格化
以下のような関数があった場合、
type A = <T, U>(x: T, y: U) => [T, U]; type B = <S>(x: S, y: S) => [S, S]; function f(a: A, b: B) { a = b; // Error b = a; // Ok }
AがBと互換性があるかをチェックできるようになり、互換性があれば、代入も可能になった。
コールバック関数の方の反変性チェックを厳格化
2.4以前のバージョンでは以下のプログラムではエラーが起きなかった。
interface Mappable<T> { map<U>(f: (x: T) => U): Mappable<U>; } declare let a: Mappable<number>; declare let b: Mappable<string | number>; a = b; b = a;
なぜなら、a
とb
がmap関数のパラメータf
を通して変換可能であると判断されていたため。
しかし2.4以降は実際のa
とb
の型を比較を行うため、このプログラムはコンパイルエラーとなる。
この変更は破壊的変更となるので注意
Weak Typesの導入
以下のように、すべてがoptionalな型をWeakTypeとして区別することになった。
interface Options { data?: string, timeout?: number, maxRetries?: number, }
2.4からはこのWeakTypeに対しても、存在しないプロパティを持つ型を代入するとエラーになる。
function sendMessage(options: Options) { // ... } const opts = { payload: "hello world!", retryOnFail: true, } // Error! sendMessage(opts); // optsとOptions型で一致するプロパティがないためエラー
この変更は破壊的変更となるので注意
現在以下のWorkaroundが提案されている。
- 実際にプロパティが存在する場合のみ宣言する。
- WeakTypeには
{[propName: string]: {}}
のようなインデックス型を定義する。 opts as Options
のように型アサーションを使って変換する。
まとめ
型チェックの強化がメインの変更点になった。
破壊的変更が幾つかありますが、WeakTypeのとこはちょっと注意した方が良さそう。
あとはimport
式をどう使うか。
babelのAsyncIterationバグ
問題
for-await-ofのボディで配列への分割代入を行うと、
Cannot read property 'file' of undefined
というエラーを投げてトランスパイルに失敗する。
どうやらscopeの解析に失敗しているらしい。
サンプルコード
async function g(t) { return new Promise(r => setTimeout(() => r([true]), t)); } async function r() { for await (const t of [1000, 2000, 3000]) { const [result] = await g(t); } } r();
package.json
"dependencies": { "babel-cli": "^6.24.1", "babel-plugin-transform-async-generator-functions": "^6.24.1", "babel-plugin-transform-regenerator": "^6.24.1", "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.23.0", "babel-preset-es2015": "^6.22.0", "babel-preset-stage-3": "^6.24.1", "babel-runtime": "^6.23.0" }, "babel": { "plugins": [ [ "transform-runtime", { "helpers": false, "polyfill": false, "regenerator": true, "moduleName": "babel-runtime" } ] ], "presets": [ "es2015", "stage-3" ] }
解決
とりあえずbugとしてbabel側にはissueを立てておいた。
現状で取れる選択肢
配列ではなくオブジェクト形式で受け取る
データ構造の変更が必要だがまあ許容できるか?
async function g(t) { return new Promise(r => setTimeout(() => r({result: true}), t)); } async function r() { for await (const t of [1000, 2000, 3000]) { const {result} = await g(t); } } r();
分割代入で受け取らない
その後にインデックスアクセスしなければならないのでちょっと面倒
async function g(t) { return new Promise(r => setTimeout(() => r([true]), t)); } async function r() { for await (const t of [1000, 2000, 3000]) { const result = await g(t); } } r();
まとめ
はやく直ってくれると嬉しい
Webpackでpolyfillをちゃんと動かす
面倒だったので備忘録
Polyfillの設定
とりあえずwebpackはrequireしないと駄目。
でも既存のコードを一切変えずにpolyfillをインストールしたい。
webpack.ProvidePlugin
を使う。
new webpack.ProvidePlugin({ 'Promise': 'es6-promise', 'Symbol': 'es6-symbol', 'fetch': 'imports?this=>global!exports?global.fetch?window.fetch!whatwg-fetch', 'Response': 'imports-loader?this=>global!exports-loader?global.Response!whatwg-fetch' }),,
こんな感じで書いた。
次に
npm i imports-loader exports-loader -D
を実行。
Promise、Symbolはrequire('es6-promise')
、require('es6-symbol')
がそれぞれPromise
、Symbol
という変数名でモジュール毎に定義される。
fetchはとても読みづらいし考えたやつ頭おかしいが、
まず、whatwg-fetchの定義の下のthis
がglobal
を参照している箇所でmodule.exports = fetchg
を定義して、
fetchを参照しているモジュールに(function(fetch){ ... })(require('whatwg-fetch'))
のようなコードを追加する。
Responseも同じく。
まとめ
webpackの独自仕様を追加すぎるとESModuleへの移行がつらそうなので程々に。
jspmからWebpackに移行した
とりあえず、Webpackを導入したがそのままだと色々問題が多かったので以下の事をやった。
まあ今更感あるが。
- typescriptのallowSyntheticDefaultImportsをfalseにする。
- production用とdev用のconfigをいい感じにわける
- node_modulesのdllを生成する。
typescriptのallowSyntheticDefaultImportsをfalse
今回はtypescriptとwebpackの組み合わせだったのだが、webapckに移行する前にはjspmを利用していたので、
allowSyntheticDefaultImports: true, module: system,
で運用していた。ちなみにこのallowSyntheticDefaultImports
が何かというと、
本来typescriptのES6 Moduleはexport default ...
の構文しか、直接importできない。
// a.js module.exports = function() {}
// b.ts import fn from './a'; // Error import * as fn from './a'; // OK
しかし、allowSyntheticDefaultImports
をtrue
にすると、commonjsスタイルのmoduleも直接importできるようになる。
のだが、webpackはsystemjsを取り扱えず、module: commonjs
にしたところ、このallowSyntheticDefaultImports
がうまく動作しなくなった。
// a.js module.exports = function() {}
// b.ts import fn from './a'; fn(); // Error
ので、allowSyntheticDefaultImports
はfalse
にして、すべてimport * as ...
の方式で読み込むことにした。
production用とdev用のconfigをいい感じにわける
まあ、webpack.config.js自体がnodejsのモジュールだったので、webpack.config.jsに設定を書いてもいいのだが、
凄くごちゃごちゃするので、webpack.dev.config.jsを作って、そこでwebpack.config.jsを読み込み、
_.clone
で設定をコピーしてdev用の設定を追加するようにした。
node_modulesのdllを生成する。
コンパイルがあまりに遅かったので、DllPlugin
を利用して、変更されない外部モジュールを全てまとめておいた。
dll側
output: { filename: '[name].dll.js', library: 'vendor_library' // Bundleが定義されるグローバル変数名。参照する時に使用する。 }, plugins: [ new webpack.DllPlugin({ path: path.join(__dirname, 'dll', '[name]-manifest.json'), // manifestファイルを出力する。とりあえず-manifestにしとけばok name: 'vendor_library' // output.libraryで指定した名前 }) ]
使う側
plugins: [ ... new webpack.DllReferencePlugin({ // Dllを参照する context: __dirname, manifest: require('./dll/vendor.production-manifest.json') // 上のmanifestファイルへのパス }) ]
こうすることでnode_modulesとかのあまり変化しないファイルをコンパイルしておくことができ、
そのコンパイルしたファイルを参照することでコンパイル時間を短縮できる。
ただし、DllReferencePluginはconcatしてくれないので、dllは手動でconcatするか、別途scriptで読み込む必要がある。
今回はconcatした。
gulp.task('minify', done => { const webpack = require('webpack'); const config = _.clone(require('./webpack.config.js')); config.output = _.clone(config.output); config.output.path = path.join(__dirname, DIST); const compiler = webpack(config); compiler.run((err, stats) => { if (err) { throw err; } console.log(stats.toString('minimal')); try { const main = `${DIST}/main.js`; const app = fs.readFileSync(main, 'utf8'); const vendor = fs.readFileSync(`dll/vendor.production.dll.js`, 'utf8'); fs.writeFileSync(main, `!function(){${vendor};\n${app}}();`, 'utf8'); //ここでconcatしてる。 } catch(e) { throw e; } done(); }); });
さらに新たにnode_modulesがロードされても更新されるように、package.jsonにも以下の記述をした。
"scripts": { "postinstall": "node ./node_modules/.bin/gulp bundle-dll" }
これでnpm install/yarn add
してもbundleが更新される。
あと、はまった点として、予めbundleしておく中にreactとかも入れていたので、process.env.NODE_ENVの値に困ってしまった。
なので、dllファイルをdev用、production用と2つ用意して対処した。
function bundleDll(env, done) { const webpack = require('webpack'); const config = _.clone(require('./webpack.dll.config.js')); config.entry = _.clone(config.entry); config.output = _.clone(config.output); config.output.path = path.join(__dirname, 'dll'); config.plugins = config.plugins.slice(); const vendor = config.entry.vendor; const keyName = `vendor.${env}`; config.entry = {}; config.entry[keyName] = vendor; if (env === 'production') { config.plugins.push(new webpack.optimize.UglifyJsPlugin()); } config.plugins.push(new webpack.DefinePlugin({ 'process.env.NODE_ENV': `'${env}'` })); const compiler = webpack(config); compiler.run((err, stats) => { if (err) { throw err; } console.log(stats.toString('minimal')); done(); }); } /** * dev dll */ gulp.task('bundle-dev-dll', done => { bundleDll('development', done); }); /** * production dll */ gulp.task('bundle-prod-dll', done => { bundleDll('production', done); }); gulp.task('bundle-dll', () => { const rs = require('run-sequence'); return rs( 'clean-dll', 'bundle-dev-dll', 'bundle-prod-dll' ); });
まとめ
jspmよりwebpackの方が快適でした。
さよならjspm
V8 Iginition Interpreter
以前、東京Node学園25時限目で発表した内容を修正して書いていこうと思う。
というわけで、V8にバイトコードインタープリタ Ignition が搭載された。
このインタープリタは単純そうに見えて非常にわかりづらいので解説していく。
バイトコードインタープリタとは
インタープリタとは、ソースコードを逐次実行する形式のエンジン。
今までのV8はソースコードを即アセンブラにコンパイルし実行していたが、
インタープリタはそれとは違い、一度ソースコードを高レベルなバイト命令に変換し、そのバイト命令を逐次実行していく。
高級アセンブラみたいな感じ。
Ignition概要
Ignitionはレジスタベースのバイトコードインタープリタである。Javaのスタックベースとは違って、CPUのレジスタに実際に値を割り付けて実行する。
IgnitionはBytecodeHandler
と呼ばれるバイトコード処理関数を予め生成しておき、バイトコードから配列のインデックスを取得、
そのインデックスに生成された処理関数を割り当て、Bytecodeの配列を次々巡回して、対応するインデックスの関数を呼び出しコードを実行する。
JSで非常に単純化されたコードを書くと以下の様になる。
var Bytecodes = [0,1,2,3,4,5]; var index = 0; function dispatch(next) {BytecodeHandlers[next]();} const BytecodeHandlers = { ['0']() {...; dispatch(Bytecodes[index++])}, ['1']() {...; dispatch(Bytecodes[index++])}, ['2']() {...; dispatch(Bytecodes[index++])}, ['3']() {...; dispatch(Bytecodes[index++])}, ['4']() {...; dispatch(Bytecodes[index++])}, ['5']() {...; dispatch(Bytecodes[index++])}, }
このモデルを念頭にV8のコードベースを確認していく。
Ignitionの構造
バイトコード生成までの道のり
IgnitionはJavascript ASTからバイトコードを生成する。
このバイトコード生成のステップを確認していく。
BytecodeGenerator
がAstVisitor
を実装しているので、Javascript ASTを巡回しながら対応しているバイトコードを生成していく。
BytecodeGenerator
はsrc/interpreter/bytecode-generator.h
にあり、バイトコード生成メソッドはBytecodeGenerator::GenerateBytecode
である。
さて、BytecodeGenerator::GenerateBytecode
はどこから呼ばれるかというと、InterpreterCompilationJob::ExecuteJobImpl(src/interpreter/interpreter.cc)
内で呼び出される。
InterpreterCompilationJob::ExecuteJobImpl
はstatic Interpreter::NewCompilationJob
で実行される。
Interpreter::NewCompilationJob
の階層は以下のようになっている。
Interpreter::NewCompilationJob | InterpreterCompilationJob::ExecuteJobImpl | BytecodeGenerator::GenerateBytecode
このstatic Interpreter::NewCompilationJob
はコンパイラパイプラインのジョブを生成するメソッドなので、compiler.cc(src/compiler.cc)
を見ていこう。
compiler.cc(src/compiler.cc)
は非常に複雑でわかりづらい呼び出し階層をもっており、さらにオプションの設定パーサーの設定も相まって非常に読みづらい。
static Interpreter::NewCompilationJob
を呼び出すまでのコールスタックは以下の様になっている。
ScriptCompiler::Compile | ScriptCompiler::CompileUnboundInternal | Compiler::GetSharedFunctionInfoForScript | Compiler::CompileToplevel | CompileUnoptimizedCode(compiler.cc) | CompileUnoptimizedInnerFunctions | GenerateUnoptimizedCode | GetUnoptimizedCompilationJob | ---- Iginitionオプションによってfullcodegenと分岐 | | Interpreter::NewCompilationJob | FullCodeGenerator::NewCompilationJob
ScriptCompiler::Compile
がV8のJavascript Compilerのエントリーポイントとなっており、そこから順次関数を呼び出し、最終的にInterpreterのJobを生成する。
最終的なBytecodeGenerator::GenerateBytecode
までの呼び出しコールスタックは以下のようになる。
ScriptCompiler::Compile | ScriptCompiler::CompileUnboundInternal | Compiler::GetSharedFunctionInfoForScript | Compiler::CompileToplevel | CompileUnoptimizedCode(compiler.cc) | CompileUnoptimizedInnerFunctions | GenerateUnoptimizedCode | GetUnoptimizedCompilationJob | ---- Iginitionオプションによってfullcodegenと分岐 | | | FullCodeGenerator::NewCompilationJob | Interpreter::NewCompilationJob | InterpreterCompilationJob::ExecuteJobImpl | BytecodeGenerator::GenerateBytecode
バイトコード生成
さて、呼び出し階層を把握したところで、バイトコードの生成方法を見ていく。
バイトコード生成は先程も書いたとおりAstVisitorを継承しているので、各種Visit***
メソッドを実装する必要がある。
ので、各種Visit***
の実装を見ていけば何をしているか理解できるはず。
ただ、闇雲にコードを見てもバイトコード自体は理解できないので、一旦trace_bytecodeでd8を実行してみる。
var a = 1;
bytecodes
0 [generating bytecode for function: ] 1 Parameter count 1 2 Frame size 32 3 0x3f5e20aafdf6 @ 0 : 09 00 LdaConstant [0] 4 0x3f5e20aafdf8 @ 2 : 1f f9 Star r1 5 0x3f5e20aafdfa @ 4 : 02 LdaZero 6 0x3f5e20aafdfb @ 5 : 1f f8 Star r2 7 0x3f5e20aafdfd @ 7 : 20 fe f7 Mov <closure>, r3 8 0x3f5e20aafe00 @ 10 : 55 aa 01 f9 03 CallRuntime [DeclareGlobalsForInterpreter], r1-r3 9 0 E> 0x3f5e20aafe05 @ 15 : 92 StackCheck 10 116 S> 0x3f5e20aafe06 @ 16 : 09 01 LdaConstant [1] 11 0x3f5e20aafe08 @ 18 : 1f f9 Star r1 12 0x3f5e20aafe0a @ 20 : 02 LdaZero 13 0x3f5e20aafe0b @ 21 : 1f f8 Star r2 14 0x3f5e20aafe0d @ 23 : 03 01 LdaSmi [1] 15 0x3f5e20aafe0f @ 25 : 1f f7 Star r3 16 0x3f5e20aafe11 @ 27 : 55 ab 01 f9 03 CallRuntime [InitializeVarGlobal], r1-r3 17 0x3f5e20aafe16 @ 32 : 04 LdaUndefined 18 118 S> 0x3f5e20aafe17 @ 33 : 96 Return 19 Constant pool (size = 2) 20 0x3f5e20aafda1: [FixedArray] 21 - map = 0x1cfd2a282309 <Map(FAST_HOLEY_ELEMENTS)> 22 - length: 2 23 0: 0x3f5e20aafd71 <FixedArray[4]> 24 1: 0x2315b1a87ef9 <String[1]: a>
そうするとこのような結果が得られる。
さてバイトコードを出力したのはいいが、見方がわからないと意味が無いので、見方も解説。
ここは関数のbytecodeの場合に関数名が入る。今回はグローバルなので空。
0 [generating bytecode for function: ]
これはstackのパラメータの数。
今回のバイトコードはグローバルなので無視。
1 Parameter count 1
FrameSizeは割り当てたレジスタの数 * ポインタのサイズ
ポインタのサイズは大体の環境で、32bitでは4byte、64bitでは8byteになる。
今回の場合、割り当てたレジスタ数の数が4 64bit環境なので、ポインタサイズが8byte
4 * 8 = 32
となる。
2 Frame size 32
各バイト列は
現在のアドレス アドレスのオフセット バイトコードの数値 バイトコードの名前 オペランド
となっている。
3 0x3f5e20aafdf6 @ 0 : 09 00 LdaConstant [0]
ここは定数値プールの中身。
今回は変数名のaがプールされている。
19 Constant pool (size = 2) 20 0x3f5e20aafda1: [FixedArray] 21 - map = 0x1cfd2a282309 <Map(FAST_HOLEY_ELEMENTS)> 22 - length: 2 23 0: 0x3f5e20aafd71 <FixedArray[4]> 24 1: 0x2315b1a87ef9 <String[1]: a>
さてこれらの情報を踏まえて、先程のソースコードとバイトコードを見ていこう。
以下の部分はすっとばしてよい。ここはインタープリタの準備なので。
3 0x3f5e20aafdf6 @ 0 : 09 00 LdaConstant [0] 4 0x3f5e20aafdf8 @ 2 : 1f f9 Star r1 5 0x3f5e20aafdfa @ 4 : 02 LdaZero 6 0x3f5e20aafdfb @ 5 : 1f f8 Star r2 7 0x3f5e20aafdfd @ 7 : 20 fe f7 Mov <closure>, r3 8 0x3f5e20aafe00 @ 10 : 55 aa 01 f9 03 CallRuntime [DeclareGlobalsForInterpreter], r1-r3 9 0 E> 0x3f5e20aafe05 @ 15 : 92 StackCheck
本番はここから
解説はコード中に書いていく。
// 定数プールのインデックス1(変数名a)から値をaccumulatorにロードする。 10 116 S> 0x3f5e20aafe06 @ 16 : 09 01 LdaConstant [1] 11 // accumulator(変数名a)からr1レジスタに値をロードする。 12 0x3f5e20aafe08 @ 18 : 1f f9 Star r1 13 // accumulatorに0をロードする。 14 0x3f5e20aafe0a @ 20 : 02 LdaZero 15 // accumulator(0)からr2レジスタに値をロードする。 16 0x3f5e20aafe0b @ 21 : 1f f8 Star r2 17 // accumulatorに即値1をロードする。 18 0x3f5e20aafe0d @ 23 : 03 01 LdaSmi [1] 19 // accumulator(1)からr3レジスタに値をロードする。 20 0x3f5e20aafe0f @ 25 : 1f f7 Star r3 21 // r1レジスタからr3レジスタの値(a, 0, 1)を使ってInitializeVarGlobalランタイムを呼び出す。 22 0x3f5e20aafe11 @ 27 : 55 ab 01 f9 03 CallRuntime [InitializeVarGlobal], r1-r3 23 // accumulatorにundefinedをセット 24 0x3f5e20aafe16 @ 32 : 04 LdaUndefined 25 // 終了 26 118 S> 0x3f5e20aafe17 @ 33 : 96 Return
これがバイトコードの実行である。
ちなみにCallRuntimeの場合、各Runtime毎に呼び出し規約が決まっているので、それぞれに合わせたレジスタの割り当てが必要になる。
InitializeVarGlobal
ランタイム呼び出しは以下のレジスタを期待している。
- r0 = 束縛される変数名
- r1 = LaunguageMode SLOPPY(通常) STRICT(strictモード) LAUNGUAGE_END(不明)
- r2 = 束縛される値
そのため、上記のコードは
- accumulatorに値をロード
- レジスタに値をロード
を繰り返して、Runtime呼び出しのコードを生成している。
とこの調子でIgnitionはバイトコードを実行していくが、
そのバイトコードを実行しているのはBytecodeHandler
とよばれるクラスである。
バイトコード実行
BytecodeHandler
バイトコードの実行はBytecodeHandler
によって行われる。
このBytecodeHandler
はV8の初期化時に生成され、配置される。
以下がBytecodeHandler
の例である。
IGNITION_HANDLER(LdaZero, InterpreterAssembler) {
Node* zero_value = NumberConstant(0.0);
SetAccumulator(zero_value);
Dispatch();
}
LadZeroの処理を行うBytecoeHandler
で、中では単純にaccumulatorに0をセットするだけ。
このような調子で各バイトコードにつき一つのBytecodeHandler
が実装されている。
各BytecodeHandler
は直接次のBytecodeHandler
を呼び出す。
このDispatchが次のBytecodeHandler
を呼び出している。
Dispatch();
しかし、このBytecodeHandler
の実装をみるとわかるのだが、BytecodeHandlerはあくまで、
実行予定Nodeを組み立てているだけで、実際には何かを実行するわけではない。
Ignitionインタープリタは最初にBytecodeの処理手順をグラフノードで生成し、生成したグラフからマシンコードを生成する。
これをBytecodeのdispatch-tableに設定することで、各バイトコード毎に行う処理が設定されたBytecodeHandler
が実装される。
以下の図はBytecodeHandlerの生成
InterpreterEntryTrampoline
Ignitionは最終的にBytecodeArray
を生成し終わった後に、
InterpreterEntryTrampoline
というbuiltinsからIgnitionのDispatchTableを発火するコード生成し、
BytecodeArrayからバイトコードを取り出し、対応するDispatchTableの処理を実行して回っていく。
以下の図はIgnitionが実行される様子
まとめ
一通りIgnitionの実行パスを眺めた。
また、Ignitionのバイトコードがアセンブラコードのキーとして振る舞い、
実際にはベースラインで生成されたコードが実行されている事を確認した。
TurboFan経由の最適化部分等については今後の記事を書く予定。
typescript 2.3 RC
typescript 2.3 rcがアナウンスされた
主な変更点は以下の通り
–strictオプションの追加
以下の型チェックオプションを有効にする
- –noImplicitAny
- –strictNullChecks
- –noImplicitThis
- –alwaysStrict
以下の様に部分的にOFFにもできる
{ "compilerOptions": { "strict": true, "noImplicitThis": false } }
generateor、iteratorのES3、ES5対応
--downlevelIteration
フラグをONにすることで、
generatorとiteratorがES3、ES5共にトランスパイルできるようになった。
Async generators & iterators
ES Proposalのasync iteratorとasync generatorに対応した。
async iteratorの構文
for await (let item of items) { /*...*/ }
async generatorの構文
async function* asyncGenName() { /*...*/ }
ただし、Async generatorとAsync iteratorを使うためには、
Symbol.asyncIterator
が必要なので、以下のようにして、polyfilを作る必要がある。
(Symbol as any).asyncIterator = Symbol.asyncIterator || Symbol.from("Symbol.asyncIterator");
か
(Symbol as any).asyncIterator = Symbol.asyncIterator || "__@@asyncIterator__";
まとめ
遂に(async) generator、iteratorがES3、ES5でも使えるようになってよかったね。
gulp-uglifyでプロパティ名をmangleする
全然ドキュメントがなかったので備忘録。
ClosureCompilerみたいにプロパティ名もmangleしたい。
こんなの
const x = { doSomething() {return ...} doNothing() {} } x.doSomething(); x.doNothing();
const x = { a() {return ...} b() {} } x.a(); x.b();
設定
gulp.task('minify', () => { const uglify = require('gulp-uglify'); gulp.src(['src/index.js']) .pipe(uglify({ mangle: true, compress: true, mangleProperties: { ignore_quoted: true } })); });
これが基本。
で、特定のプロパティ名のリネームを防ぎたい場合は、reserved
という機能を使う
gulpfile.js
gulp.task('minify', () => { const uglify = require('gulp-uglify'); const Uglify = require('uglify-js'); let reserved = Uglify.readReservedFile('./reserved.json'); reserved = Uglify.readDefaultReservedFile(reserved); gulp.src(['src/index.js']) .pipe(uglify({ mangle: true, compress: true, mangleProperties: { reserved: reserved.props, ignore_quoted: true } })); });
reserved.json
{ "vars": [ ], "props": [ "doSomething", "doNothing" ] }
これでdoSomething
とdoNothing
はmangleされなくなる。
上記の記述の
let reserved = Uglify.readReservedFile('./reserved.json'); reserved = Uglify.readDefaultReservedFile(reserved);
の部分ではUglifyjs2が必要なので、別途npm install
してくだはい。
後は必要なプロパティ名をガンガンpropsに突っ込んでいけばOK。
ただ、ClosureCompilerもそうだけど、プロパティ名のmangleにはそれなりのリスクがあるので、
コンパイル後にも統合テストをしたほうが良い。
まとめ
gulp-uglifyが不親切でつらい。