diff --git a/README.md b/README.md index c3a3f630..b09f424c 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,115 @@ Chrome のタブ名が書き変わりました。 +開発環境道場 +------------ + +(あるいはポエム) + + +JavaScript を効率的に書けるようにするために +最低限の開発環境を整えます。 + + +JavaScript に精通していない人に +「どんなツールを使ったらいいですか?」と +聞かれることがよくあります。 + +私は、「構文ハイライトと lint を使うといいですよ」、 +と答えることにしています。 + +[](http://mixi-inc.github.io/JavaScriptTraining/images/syntax-error.png) + + +JavaScript には静的型検査がないことや、 +記号が多い構文や、まずい仕様がいくつもあるので、 +ミスを犯しやすい言語の一つです。 + + +たとえば、JavaScript には `"use strict"` という +宣言があります。この宣言は JavaScript の +まずい仕様の一部をエラーにすることで修正を +促す効果があり、よく記述を推奨される宣言です。 + + +あるプロジェクトで気を利かした +プログラマーが `"use strict"` を +新しいスクリプトで使い始めました。 + +```javascript +"use strict"; + +// Do something... +``` + +これは、開発時のミスを早期発見できるように +するよい方法です! + + +しかし、リリース後、新しいスクリプトとは +関係ないはずの JavaScript による +メニューが動作しなくなっていました。 + +なぜでしょうか? + + +これは、パフォーマンスチューニングの一環で +複数の JavaScript ファイルを結合していたこと、 +ファイルに対する `"use strict"` を利用していた +ことの2つが原因となって起きた不具合です。 + +`"use strict"` が結合された +すべてのファイル全体に効くように +なり、`"use strict"` に対応していない +古いファイルがエラーを出すように +なってしまっていたのでした。 + + +この説明を読んで、「何それ意味わからない😱」と +思いませんでしたか? + + +悲しいことに、このような落とし穴は +JavaScript の仕様・文化に数多く +潜んでいます。 + +これらのミスをどのようにしたら +防げるのでしょうか? + + +ESLint や JSHint、 JSLint のような lint を +使いましょう。たとえば、ESLint を実行すると +下のような警告が出されます。これによって、 +潜在的な不具合をだいぶ減らせるようになるのです。 + + Use the function form of "use strict". + (関数形式の "use strict" を使ってね) + + +ミスを防ぐためには、「頑張る」とか +「注意する」のような精神論ではなく、 + lint によって防止することが必要です。 +あなたが JavaScript にまだ精通していないので +あれば、真っ先にいれるべきは lint なのです。 + + +1. lint をかけてみる + + ターミナルで下のコマンドを実行してください。 + ESLint が実行されます。 + + gulp lint-stage1 + +2. 構文ハイライトを効かせる + + ほとんどのエディタは構文ハイライトを + サポートしています。Vim のように + 構文ハイライトが選べるのであれば、 + よりミスのわかりやすい構文ハイライトを + 利用します。 + + + トレーニング ------------ @@ -235,7 +344,7 @@ Chrome のタブ名が書き変わりました。 ### ステージ1 -意図した DOM 要素を取得するトレーニング +DOM 要素を取得するトレーニング #### DOM とは @@ -246,7 +355,7 @@ Web ページは HTML のタグによって [](http://mixi-inc.github.io/JavaScriptTraining/images/github.png) [](http://mixi-inc.github.io/JavaScriptTraining/images/github-elements.png) -この 3D ビューを見ると、複数の HTML タグから +この 3D ビューを見ると、複数の HTML タグから Web ページが構成されていることがよくわかります。 @@ -322,11 +431,11 @@ CSS セレクタがあります。 var div = document.querySelector('#foo'); ``` -というように、ID の先頭に ID セレクタ `#` -をつけることで、取得したい DOM 要素を指示します。 +というように、ID の先頭に `#` をつけた +IDセレクタ `#foo` で、取得したい DOM 要素を指示します。 -この方法の利点は、複雑な目印を使って DOM 要素を +この方法の利点は、複雑な位置にある DOM 要素を 取得することができるということでしょう。 下の HTML の bar を包む `div` タグにあたる @@ -373,8 +482,7 @@ CSS セレクタについては、[MDN のCSS リファレンス](https://develo ### ステージ2 -意図通りに DOM 要素のスタイルを -変更するトレーニング +DOM 要素のスタイルを変更するトレーニング #### DOM 要素のスタイル @@ -447,7 +555,7 @@ HTML/JavaScript/CSS 分業の基本です。 ### ステージ3 -意図通りに DOM の構造を変更するトレーニング +DOM の構造を変更するトレーニング #### DOM の構造 @@ -460,7 +568,8 @@ HTML/JavaScript/CSS 分業の基本です。 DOM の構造を変更する必要性を考えてみます。 -書籍検索サービスは、検索結果を下のように返したとしましょう。 +書籍検索サービスの API は、検索結果を +下のように返したとしましょう。 ```javascript [ @@ -529,7 +638,7 @@ DOM 構造の操作が重要なのです。 ### ステージ4 -意図通りに DOM イベントを利用するトレーニング +DOM イベントを利用するトレーニング ### DOM イベント解説編 @@ -705,7 +814,139 @@ button.addEventListener('click', function(event) { ### ステージ5 -意図通りにサーバーと通信するトレーニング +サーバーと通信するトレーニング + + +#### サーバーとの通信 + +JavaScript にはサーバーと通信するための API が +用意されています。 + +- [fetch API](http://www.hcn.zaq.ne.jp/___/WEB/Fetch-ja.html) + + 現在策定中の新しい標準仕様 + +- [XMLHttpRequest](https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest) + + jQuery.ajax のようなショートハンドが使われる + ことが多く、実際手で書くことはほとんどない + + +今回は、JavaScript の将来を見据えて、 +fetch API によるサーバーとの通信を +トレーニングします。 + + +#### fetch API + +fetch API は下のように書きます。 +このコードは、`/users.json` を +取得します。 + +```javascript +fetch('/users.json') + .then(function(response) { + return response.json() + }) + .then(function(json) { + console.log('parsed json', json) + }) + .catch(function(error) { + console.log('parsing failed', error) + }); +``` + +`.then`、`.catch` という不思議なメソッドで +つながっています。 + + +#### Promise を使った非同期処理 + +さきほどの `.then`、`.catch` は、非同期処理の +結果を、引数に渡した関数で受け取るために +用意されています。 + +たとえば、`.then` を使うと、正常にレスポンスが +受け取れた場合に関数を実行できます。 + +```javascript +fetch('/users.json') + .then(function(response) { + + // /users.json を正常に取得できたときに、 + // response をログに出力する + console.log(response); + }); +``` + +エラーがあった場合は、ログ出力は実行されません。 + + +また、`.catch` を使うと、エラーが発生した場合に +関数を実行できます。 + +```javascript +fetch('/users.json') + .catch(function(errror) { + + // /users.json の取得時にエラーがでたときに、 + // error をログに出力する + console.log(error); + }); +``` + +こちらは、正常にレスポンスを受け取れた場合は、 +ログ出力は実行されません。 + +なお、先ほどの例のように `.then` と `.catch` を +同時につけることもできます。 + + +サーバーと通信するだけなのに、 +なんか複雑すぎるような…? + + +実はこの Promsie という複雑な仕組みを使う理由は、 + +- 並行非同期処理 +- 直列非同期処理 + +を書きやすくする、ということなのです。 + + +#### Promise による平行非同期処理 + +`Promise.all` を使います。 + +```javascript +// 2つの Web API からレスポンスが欲しい! + +Promise.all([ + fetch('/api/foo'), + fetch('/api/bar') +]) +.then(function(responses) { + var responseFoo = responses[0]; + var responseBar = responses[1]; + doSomething(responseFoo, responseBar); +}); +``` + + +#### Promise による直列非同期処理 + +`.then` で次々に処理を連結できます。 + +```javascript +// Web API の結果を利用して別の API を実行したい! + +fetch('/api/foo') + .then(doSomething) + .then(function() { return fetch('/api/bar'); }) + .then(doSomething) + .then(function() { return fetch('/api/buz'); }) + .then(doSomething); +``` #### 実習 @@ -717,10 +958,18 @@ button.addEventListener('click', function(event) { [http://localhost:8000/stage5/](http://localhost:8000/stage5/) +#### 参考になる資料 + +- [Promise に関する参考情報](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise) +- [Promise 参考情報(重量級)](http://azu.github.io/promises-book/) +- [fetch API に関する参考情報](https://github.com/github/fetch) +- [Github API に関する参考情報](https://developer.github.com/v3/search/) + + ### ステージ6 -意図通りにモジュールを実装するトレーニング +モジュールを実装するトレーニング #### 実習 @@ -745,3 +994,167 @@ button.addEventListener('click', function(event) { 修正してください。 [http://localhost:8000/stage6/](http://localhost:8000/stage6/) + + + +付録 +---- + + +### Promise について + + +#### Promise による平行非同期処理 + +Promise による平行非同期処理を通常のやりかたと、 +Promise らしいやり方とでやってみました。 + +コードを比較してみてください。 + + +```javascript +// 2つの Web API からレスポンスが欲しい! + +var done = { foo: false, bar: false }; +var responses = { foo: null, bar: false }; +fetch('/api/foo').then(function(responseFoo) { + if (!done.bar) { + done.foo = true; + responses.foo = responseFoo; + return; + } + doSomething(responseFoo, responses.bar); +}); +fetch('/api/bar').then(function(responseBar) { + if (!done.foo) { + done.bar = true; + responses.bar = responseFoo; + return; + } + doSomething(responses.foo, responseBar); +}); +``` + +レスポンス取得の待ち合わせ処理があり、 +状態を複数もつ厄介なコードにしあがっていますね。 + + +```javascript +// 2つの Web API からレスポンスが欲しい! + +Promise.all([ + fetch('/api/foo'), + fetch('/api/bar') +]) +.then(function(responses) { + var responseFoo = responses[0]; + var responseBar = responses[1]; + doSomething(responseFoo, responseBar); +}); +``` + +`Promise.all` を使うと、待ち合わせ処理が +なくスッキリ! + + +#### Promise による直列非同期処理 + +直列非同期処理についても、通常のやり方と、 +Promise らしいやり方でやってみました。 + +コードを比較してみてください。 + + +```javascript +// Web API の結果を利用して別の API を実行したい! + +fetch('/api/foo').then(function(responseFoo) { + doSomething(responseFoo); + fetch('/api/bar').then(function(responseBar) { + doSomething(responseBar); + fetch('/api/buz').then(function(responseBuz) { + doSomething(responseBuz); + }); + }); +}); +``` + +コードがネストしているので、後ろの方の +関数のスコープが深くなってしまっています。 +変数を追跡するのに手間がかかりそうです。 + + +ネストの外に出すだけならば、終了コールバックを +呼び出す[継続渡しスタイル](http://ja.wikipedia.org/wiki/%E7%B6%99%E7%B6%9A%E6%B8%A1%E3%81%97%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB) で +書くことができます。 + +```javascript +// Web API の結果を利用して別の API を実行したい! + +fetch('/api/foo').then(callbackFoo); + +function callbackFoo(responseFoo) { + doSomething(responseFoo); + fetchBar(callbackBar); +} + +function fetchBar(callback) { + fetch('/api/bar').then(callback); +} + +function callbackBar(responseBar) { + doSomething(responseBar); + fetchBuz(callbackBuz); +} + +function fetchBuz(callback) { + fetch('/api/buz').then(callback); +} + +function callbackBuz(responseBuz) { + doSomething(responseBuz); +} +``` + +流れが追いづらい! + +クロージャー + 継続渡しスタイルを使うと… + +```javascript +// Web API の結果を利用して別の API を実行したい! + +fetch('/api/foo').then(fetchBar(fetchBuz(doSomething))); + +function fetchBar(callback) { + return function(responseFoo) { + doSomething(responseFoo); + fetchBar(callback); + }; +} + +function fetchBuz(callback) { + return function(responseBar) { + doSomething(responseBar); + fetchBuz(callback); + }; +} +``` + +これはこれで美しい…😌 + +(JS に慣れるまではちょっと読みづらいと思います) + + +Promise らしいやり方をとると `.then` で +次々に処理を連結できます。 + +```javascript +// Web API の結果を利用して別の API を実行したい! + +fetch('/api/foo') + .then(doSomething) + .then(fetch('/api/bar')) + .then(doSomething) + .then(fetch('/api/buz')) + .then(doSomething); +``` diff --git a/public/index.html b/public/index.html index ac743beb..6d41f7bf 100644 --- a/public/index.html +++ b/public/index.html @@ -25,12 +25,12 @@

mixi JavaScript Training

    -
  1. 意図した DOM 要素を取得するトレーニング
  2. -
  3. 意図通りに DOM 要素のスタイルを変更するトレーニング
  4. -
  5. 意図通りに DOM の構造を変更するトレーニング
  6. -
  7. 意図通りに DOM イベントを利用するトレーニング
  8. -
  9. 意図通りにサーバーと通信するトレーニング
  10. -
  11. 意図通りにモジュールを実装するトレーニング
  12. +
  13. DOM 要素を取得するトレーニング
  14. +
  15. DOM 要素のスタイルを変更するトレーニング
  16. +
  17. DOM の構造を変更するトレーニング
  18. +
  19. DOM イベントを利用するトレーニング
  20. +
  21. サーバーと通信するトレーニング
  22. +
  23. モジュールを実装するトレーニング
  24. よくあるイディオムを読み書きするトレーニング
diff --git a/public/stage5/tests.js b/public/stage5/tests.js index b208f6e9..8055bc72 100644 --- a/public/stage5/tests.js +++ b/public/stage5/tests.js @@ -1,7 +1,104 @@ 'use strict'; describe('ステージ5(意図通りに非同期処理を利用できる)', function() { - describe('通信編', function() { + describe('Promise 編', function() { + it('resolve ハンドラーを書ける', function(testDone){ + var promise = Promise.resolve('resolved!'); + + // チュートリアル + // + // ここに下記のコードを記述してください。 + // + // promise.then(function(msg) { + // expect(msg).to.equal('resolved!'); + // testDone(); + // }); + }); + + + it('reject ハンドラーを書ける', function(testDone){ + var promise = Promise.reject('rejected!'); + + // reject ハンドラーを使って、下の assertion が promise の + // エラー値を検証できるように記述してください。 + // + // expect(msg).to.equal('rejected!'); + // testDone(); + + // ここにコードを記述してください。 + + + }); + + + it('複数の promise すべての完了を待つ promise を作成できる', function(){ + var messageFragments = ['あなたと', 'java', '今すぐダウンロード']; + var promise1 = createWaitPromise(messageFragments[0], 10); + var promise2 = createWaitPromise(messageFragments[1], 20); + var promise3 = createWaitPromise(messageFragments[2], 30); + + // 作成した promise を promise 変数に代入してください。 + var promise; + + + return expect(promise).to.eventually.deep.equal(messageFragments); + }); + + + it('複数の promise のうち最も速く解決された値をもつ promise を作成できる', function(){ + var messageFragments = ['30億の', 'デバイスで', '走るjava']; + var promise1 = createWaitPromise(messageFragments[0], 30); + var promise2 = createWaitPromise(messageFragments[1], 10); + var promise3 = createWaitPromise(messageFragments[2], 30); + + // 作成した promise を promise 変数に代入してください。 + var promise; + + + return expect(promise).to.eventually.equal(messageFragments[1]); + }); + }); + + + describe('fetch API 編', function() { + it('/api/friends API を使って Shen の友人を取得できる', function(){ + var api = '/api/friends/Shen'; + + // 作成した promise を promisedFriends 変数に代入してください。 + var promisedFriends; + + + return expect(promisedFriends).to.eventually.deep.equal( + ['jisp', 'TeJaS'] + ); + }); + + + it('/api/friends API を使って Shen の友人の友人を取得できる', function(){ + var api = '/api/friends/Shen'; + + // 作成した promise を promisedFriends 変数に代入してください。 + var promisedFriends; + + + return expect(promisedFriends).to.eventually.deep.equal(['TypeScript']); + }); + + + it.skip('/api/friends API を使って CoffeeScript の友人を再帰的に取得できる', function(){ + // 難易度高いので、自信のある人だけ挑戦してください。 + // it.skip の .skip を消せば、テストが走るようになります。 + + // 作成した promise を promisedFriends 変数に代入してください。 + var promisedFriends; + + + return expect(promisedFriends).to.eventually.deep.equal( + ['Taijilang', 'purescript', 'Wind.js', 'ScriptBlocks', 'jangaroo'] + ); + }); + + it('Github の mixi-inc の organization の情報を取得できる', function() { var mixiOrg; @@ -24,13 +121,15 @@ describe('ステージ5(意図通りに非同期処理を利用できる)', it('Github API を使って、mixi-inc/JavaScriptTraining の情報を取得できる', function(){ var repositry = 'mixi-inc/JavaScriptTraining'; + + // 作成した promise を mixiRepo 変数に代入してください。 var mixiRepo; - // コードをここに記述してください。 + return expect(mixiRepo).to.eventually.have.property('full_name', repositry); - return expect(mixiRepo).to.eventually.have.property( - 'full_name', repositry); + // Github API に関する参考情報 + // https://developer.github.com/v3/orgs }); @@ -39,24 +138,20 @@ describe('ステージ5(意図通りに非同期処理を利用できる)', var languages = [ 'VimL', '"Emacs Lisp"' ]; var mostPopularRepos; - // コードをここに記述してください。 - // なお、 mostPopularRepos は Promise インスタンスであることを - // 意図しています。 + // 作成した promise を mostPopularRepos 変数に代入してください。 return expect(mostPopularRepos).to.eventually.have.length(2); - // fetch API に関する参考情報 - // https://github.com/github/fetch - // // Github API に関する参考情報 - // https://developer.github.com/v3/search/ - // - // Promise に関する参考情報 - // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise - // - // Promise 参考情報(重量級) - // http://azu.github.io/promises-book/ + // https://developer.github.com/v3/search }); }); + + + function createWaitPromise(value, msec) { + return new Promise(function(resolve) { + setTimeout(resolve, msec, value); + }); + } }); diff --git a/server.js b/server.js index 45d741f7..39ff94e3 100644 --- a/server.js +++ b/server.js @@ -19,6 +19,13 @@ app.get('/api/heavy', function(req, res) { }, 1000); }); +var friendMap = require('./server/response/friendsmap.json'); + +app.get('/api/friends/:username([\\w.-]+)', function(req, res) { + var username = req.params.username; + res.json(friendMap[username]); +}); + var PORT = process.env.PORT; diff --git a/server/response/friendsmap.json b/server/response/friendsmap.json new file mode 100644 index 00000000..49779464 --- /dev/null +++ b/server/response/friendsmap.json @@ -0,0 +1,649 @@ +{ + "CoffeeScript": [ + "Taijilang", + "purescript", + "Wind.js" + ], + "Coco": [], + "LiveScript": [ + "Red" + ], + "IcedCoffeeScript": [ + "JSX" + ], + "Parsec CoffeeScript": [ + "Six" + ], + "Contracts.coffee": [ + "jisp" + ], + "Uberscript": [ + "ADsafe", + "Java2Script", + "Chlorinejs" + ], + "ToffeeScript": [], + "Caffeine": [], + "heap.coffee": [], + "EmberScript": [], + "Kaffeine": [ + "Quby", + "O'Browser" + ], + "Jack": [ + "Pyjs" + ], + "move": [ + "asm.js", + "WootzJs" + ], + "Moescript": [], + "pogoscript": [ + "Restrict Mode", + "Opal" + ], + "LispyScript": [ + "Continuation.js", + "EdgeLisp" + ], + "wisp": [ + "Uberscript", + "Mochiscript" + ], + "Hot Cocoa Lisp": [ + "Whalesong" + ], + "Sibilant": [ + "node-jvm" + ], + "ki": [ + "Contracts.coffee", + "MoonScript", + "Caja" + ], + "jisp": [], + "Ham": [ + "BiwaScheme" + ], + "GorillaScript": [], + "RedScript": [ + "Mascara" + ], + "Daonode": [], + "LiteScript": [ + "LispyScript", + "LZX (Laszlo XML)" + ], + "Radium": [ + "LLJS" + ], + "ColaScript": [ + "Reb2Static" + ], + "Taijilang": [], + "MoonScript": [ + "uniter" + ], + "Earl Grey": [ + "CirruScript", + "JSX" + ], + "Khepri": [], + "Spider": [ + "Sibilant", + "jmacro", + "phype" + ], + "CirruScript": [], + "Stripes": [ + "Cheerp", + "Gnusto" + ], + "Caja": [], + "ADsafe": [], + "FBJS": [ + "Skulpt" + ], + "Jacaranda": [ + "AtScript" + ], + "Microsoft Web Sandbox": [ + "Flapjax" + ], + "Gatekeeper": [ + "Local.js", + "MileScript", + "scalagwt" + ], + "Dojo Secure": [ + "PythonJS" + ], + "Local.js": [ + "JSPipe", + "PyCow", + "JScriptSuite" + ], + "Dart": [ + "Moescript" + ], + "TypeScript": [], + "TeJaS": [ + "TypeScript" + ], + "asm.js": [], + "JavaScript++": [ + "Js_of_ocaml" + ], + "MileScript": [ + "WebSharper", + "JsMaker", + "Meemoo" + ], + "Mascara": [ + "Parsec CoffeeScript" + ], + "Roy": [ + "Swym" + ], + "Elm": [ + "Silver Smalltalk" + ], + "JSX": [ + "Ralph", + "LispyScript" + ], + "Este.js": [ + "Dart", + "ColdRuby", + "Go2js" + ], + "Swym": [ + "GWT" + ], + "Typecast.js": [ + "Oppo" + ], + "purescript": [ + "ScriptBlocks" + ], + "AtScript": [ + "EmberScript", + "Netjs", + "haste", + "Amber" + ], + "Flow": [], + "Streamline.js": [ + "Objective-J", + "Mandreel", + "GopherJS" + ], + "mobl": [ + "JavaScript++" + ], + "StratifiedJS": [ + "Outlet" + ], + "NarrativeJS": [ + "Flow" + ], + "jwacs": [ + "sql-parser", + "Dogescript" + ], + "Wind.js": [], + "TameJS": [ + "TIScript", + "jshaskell", + "Smart Mobile Studio", + "browserl" + ], + "Continuation.js": [ + "wisp", + "FunScript" + ], + "Kal": [ + "Streamline.js", + "rb2js", + "Ur", + "wForth" + ], + "JSPipe": [], + "promiseLand": [ + "pythonscript" + ], + "ContextJS": [], + "Objective-J": [ + "Elm" + ], + "Mochiscript": [], + "jangaroo": [], + "Flapjax": [], + "jLang": [ + "move" + ], + "Restrict Mode": [], + "TIScript": [ + "NemerleWeb" + ], + "Six": [ + "qb.js" + ], + "js--": [ + "IcedCoffeeScript", + "Radium" + ], + "Latte JS": [ + "RapydScript", + "Blue Storm" + ], + "oj": [ + "heap.coffee", + "Pit" + ], + "Opal": [], + "HotRuby": [], + "ColdRuby": [ + "Jack" + ], + "rb2js": [ + "ContextJS" + ], + "RubyJS": [], + "Red": [ + "GorillaScript", + "QWT", + "Clamato" + ], + "Quby": [ + "Moby Scheme" + ], + "8ball": [ + "sqld3", + "uilang" + ], + "PYXC-PJ": [ + "NarrativeJS" + ], + "Pyjamas": [], + "Pyjaco": [ + "Typecast.js", + "Latte JS" + ], + "Pyjs": [ + "js--", + "p2js" + ], + "Skulpt": [], + "PyCow": [ + "LuvvieScript" + ], + "PyvaScript": [], + "RapydScript": [], + "Brython": [ + "Plaid" + ], + "PythonScript": [ + "E" + ], + "pythonscript": [ + "mobl", + "jsForth" + ], + "PythonJS": [ + "Gatekeeper", + "Clue" + ], + "PyPyJS": [ + "RubyJS" + ], + "Shen": [ + "jisp", + "TeJaS" + ], + "LuvvieScript": [], + "Perlito": [ + "Processing" + ], + "GWT": [], + "Java2Script": [ + "Topaz" + ], + "j2js": [ + "jwacs", + "Fun" + ], + "BicaJVM": [ + "Nimrod" + ], + "Doppio": [ + "Shen" + ], + "Processing": [ + "Perlito", + "NoFlo", + "Agda" + ], + "Kotlin": [], + "Ceylon": [ + "DuoCode" + ], + "GrooScript": [ + "jLang" + ], + "node-jvm": [ + "LiteScript", + "JSIL" + ], + "Bck2Brwsr": [], + "QWT": [ + "Spider", + "Ceylon", + "ErlyJS" + ], + "TeaVM": [ + "Khepri" + ], + "Dragome SDK": [ + "NGN APL" + ], + "Scala.js": [ + "Jacaranda" + ], + "js-scala": [], + "scalagwt": [ + "Opa", + "Waterbear" + ], + "JScala": [], + "jsc": [], + "JSIL": [ + "HotRuby" + ], + "Script#": [ + "forml" + ], + "Prefix": [ + "Este.js", + "JScala" + ], + "Blade": [ + "CoffeeScript", + "Kotlin" + ], + "SharpKit": [ + "Scala.js", + "ki" + ], + "Saltarelle": [ + "Script#", + "NS Basic/App Studio" + ], + "FunScript": [], + "Pit": [], + "WebSharper": [], + "NemerleWeb": [ + "Illumination" + ], + "Blue Storm": [], + "JScriptSuite": [ + "Brython" + ], + "DotNetWebToolkit": [ + "Snap", + "Logo Interpreter" + ], + "Netjs": [], + "WootzJs": [], + "DuoCode": [ + "Scriptjure", + "TARDISgo", + "XLCC" + ], + "ClojureScript": [], + "ClojureJS": [ + "ghcjs", + "Wortel" + ], + "Chlorinejs": [ + "Sibilant" + ], + "Scriptjure": [ + "Microsoft Web Sandbox", + "Pyjamas" + ], + "BiwaScheme": [ + "StratifiedJS", + "Blade" + ], + "Fargo": [ + "Roy", + "Brozula" + ], + "Moby Scheme": [], + "nconc": [ + "BicaJVM", + "Blockly" + ], + "scheme2js": [ + "8ball" + ], + "Spock": [], + "Whalesong": [ + "Daonode" + ], + "EdgeLisp": [], + "Parenscript": [ + "Parenscript", + "Little Smallscript" + ], + "Ralph": [ + "Kal" + ], + "Oppo": [ + "Fantom", + "CobolScript" + ], + "Outlet": [ + "Bck2Brwsr", + "lua.js" + ], + "Ocamljs": [], + "O'Browser": [ + "defrac" + ], + "Js_of_ocaml": [ + "TameJS" + ], + "UHC": [], + "ghcjs": [ + "DotNetWebToolkit" + ], + "jmacro": [], + "YHC": [], + "jshaskell": [], + "haste": [], + "fay": [ + "TeaVM" + ], + "forml": [], + "Amber": [ + "ToffeeScript", + "PyvaScript", + "Dragome SDK", + "Fargo" + ], + "Clamato": [], + "Silver Smalltalk": [ + "SqueakJS" + ], + "Lively Kernel": [ + "php.js" + ], + "Little Smallscript": [], + "SqueakJS": [], + "Emscripten": [], + "Cheerp": [ + "Spock", + "UHC" + ], + "maja": [ + "ki", + "Pygmy" + ], + "Clue": [ + "Coco" + ], + "LLJS": [ + "oj", + "scheme2js" + ], + "Mandreel": [], + "NS Basic/App Studio": [ + "promiseLand" + ], + "qb.js": [ + "SMLtoJs" + ], + "Smart Mobile Studio": [ + "RedScript" + ], + "Elevate Web Builder": [ + "LiveScript", + "SharpKit" + ], + "Go2js": [ + "FBJS", + "ClojureJS" + ], + "GopherJS": [ + "ColaScript" + ], + "TARDISgo": [ + "Monkey" + ], + "Haxe": [], + "Fantom": [], + "LZX (Laszlo XML)": [], + "Nimrod": [ + "Ocamljs" + ], + "Monkey": [ + "Emscripten" + ], + "defrac": [ + "mobl" + ], + "Fun": [], + "Ur": [ + "Pyjaco", + "Elevate Web Builder" + ], + "E": [ + "Doppio", + "Jotlin" + ], + "Sugar": [ + "PYXC-PJ" + ], + "Opa": [], + "Waterbear": [ + "Oia" + ], + "Snap": [ + "pogoscript", + "PyPyJS", + "jsc" + ], + "ScriptBlocks": [ + "jangaroo" + ], + "Illumination": [], + "JsMaker": [], + "Meemoo": [ + "maja" + ], + "NoFlo": [], + "Blockly": [], + "Maeda Block": [ + "GrooScript" + ], + "sql-parser": [ + "Caffeine" + ], + "sqld3": [ + "ClojureScript" + ], + "Alasql": [ + "Lively Kernel" + ], + "phype": [ + "js-scala", + "Saltarelle" + ], + "uniter": [], + "php.js": [], + "Oia": [], + "Plaid": [], + "Quixe": [], + "Gnusto": [ + "Dojo Secure", + "Idris" + ], + "Logo Interpreter": [], + "p2js": [ + "Kaffeine", + "Haxe" + ], + "Reb2Static": [ + "Earl Grey", + "Hot Cocoa Lisp", + "fay" + ], + "RPN": [ + "Hot Cocoa Lisp" + ], + "jsForth": [ + "PythonScript" + ], + "wForth": [ + "Stripes", + "Prefix" + ], + "Agda": [], + "XLCC": [ + "Alasql", + "Quixe" + ], + "SMLtoJs": [], + "lua.js": [ + "nconc" + ], + "Brozula": [], + "Pygmy": [ + "Maeda Block" + ], + "browserl": [ + "wisp" + ], + "ErlyJS": [], + "Topaz": [], + "NGN APL": [], + "CobolScript": [], + "Idris": [ + "Ham" + ], + "Dogescript": [], + "Wortel": [ + "j2js" + ], + "JEnglish": [ + "RPN", + "JEnglish" + ], + "Jotlin": [ + "YHC", + "Sugar" + ], + "uilang": [] +}