- 投稿日:2020-01-19T23:17:04+09:00
Node.js install for mac
何からNode.jsを入手したか忘れないためのメモ。
- 公式からLTS版mac用のinstallerをダンロードする
- installerを起動し、各設問をデフォルトのまま進めてinstallする
- nodeのバージョンを確認する
$ node -v v12.14.1
- npm(パッケージマネージャ)のバージョンを確認する
$ npm -v 6.13.4以上です。
- 投稿日:2020-01-19T20:01:54+09:00
Unity WebGL なゲームをNode.JS + jsdom + headless-glで動かしたかった
えっ このネタ続くの。。?
前回( Unity WebGLで使われているシェーダを抜き出してARBアセンブリを眺める )はWebGLビルドのUnityゲームをトレースして、使われているシェーダ命令があんまり多くないことを確認した。
ブラウザ上の動作では同期APIの実装に制約がありちょっと手を入れづらいため、Node.jsで動かしたかった。別案としてNW.jsを使うというのもあったが、今回の手法でもWebブラウザ側のDOMを使わないといけないところは一応クリアしている。
結果と手法
結局成功したんだか失敗したんだかよくわからないところまでは来たと思う。
結果
- Node.jsでもUnity WebGLは起動して描画コマンドも発行する
- でも描画がまっくらなので、まだ描画が正常かどうかは確認できていない
JSdomでWebAssemblyを使ったサイトがそのまま動くのは地味にすごい気はする。
手法
Node.jsにはWebAssemblyがあり、Webプラットフォームの実装としては:
- JSdom : https://github.com/jsdom/jsdom -
createElement
とかXMLHttpRequest
のようなDOM APIのNode.js上の実装。- headless-gl : https://github.com/stackgl/headless-gl - Node.js上のWebGL実装
- fake-indexeddb : https://github.com/dumbmatter/fakeIndexedDB - Indexeddb APIのfake実装。Unity WebGLはEmscriptenのIDBFSをセーブデータの保存とかキャッシュに使っているっぽいのでそれ用。
のようなものが既にある。これらはUnity WebGLビルドを動かすには十分に見える。なので、 JSdomで作成したNode.js上の仮想ブラウザ環境の
window
、document
各オブジェクトを都合よくpolyfillし、Unity WebGLが生成したWebページをそこにロードする が基本的な方針となる。結果、実際に描画コマンドの発行は確認でき内容は正しそうだが(
Error
と出ているのは単にError.captureStackTrace
でスタックトレースを拾っているからで、glError
になるようなエラーが無いことは確認している)、出画は真っ暗だった。
描画内容自体はゼロ埋めではない(濃いグレーになっている)し、描画コマンドが出ているのは確認できているので、headless-gl側の問題だと考えている。
Unity WebGL ビルドの構造
(今回は Unity
2019.2.17f1
のWebGL 1.0ビルドを元に書いている。)Unity WebGLビルドは、要するに Emscripten でビルドしたUnityエンジンを単に動作させているだけで、Webプラットフォームに移植されている部分は殆んどない。例外は通常のビルドではFMODを使用しているオーディオエンジンで、WebGLビルドでは自前のオーディオミキシングを.wasm側に持っているようだ。
重要な構成ファイルは4つある。これらのファイルを直接見るには、Unityのビルド設定で圧縮を事前に無効化しておく必要がある。
UnityLoader.js
Build/UnityLoader.js
はJavaScript製の起動ルーチンで、こちらはUnity手製とみられる。通常のビルドではMinifyされるがDevelopment buildを選択することでMinify前のソースを見ることができる。UnityLoaderでは:
- ゲーム本体のIndexeddbへのキャッシュ (HTTPキャッシュを使わないのは安全と圧縮のため?)
- ブラウザ検出とUser Agentベースでの非対応ブラウザの起動抑止
- Emscriptenの出力に
Math.fround
の省略パッチを当てる- プログレスバーの出力
- Web Workerを使用した gzip、brotli のデコード
- アセットのEmscriptenファイルシステム側への投入
- 実際のゲーム本体のロード
といった作業をしている。
対応ブラウザチェックは、
"Please note that your browser is not currently supported for this Unity WebGL content. Press OK if you wish to continue anyway."
のようなメッセージを表示して起動を止めてしまう。ここは回避できなかったので今回の実験ではパッチしてしまった。
NAME.wasm.framework.unityweb
Build/<NAME>.wasm.framework.unityweb
はEmscriptenが出力したJavaScript側のコードで、C++(IL2CPP)部分とDOMのインターフェースは基本的にここに集約されている。... これはEmscripteのランタイムほぼそのままなので特にコメントはなし。ただ、JSdomで要素の
width
、height
を変更する方法がわからなかったので、こちらに手を入れて無理矢理表示状態を作りだしている。NAME.wasm.code.unityweb
Build/<NAME>.wasm.code.unityweb
は、IL2CPPとEmscriptenによって生成されたエンジン本体およびゲームコードで、少くともDevelopment buildでは完全にシンボル入りになるためwasm2wat
コマンド等で逆アセンブルできる。例えば、jsdomで作ったElementは自動的に
width = height = 0
となるが、この状況だと:call $__Z12InputProcessv block ;; label = @3 block ;; label = @4 call $__Z14GetPlayerPausev i32.const 2 i32.eq br_if 0 (;@4;) i32.const 0 call $_emscripten_is_webgl_context_lost br_if 0 (;@4;) call $_JS_SystemInfo_GetCurrentCanvasWidth i32.eqz br_if 0 (;@4;) ;; ★ ゼロだったらレンダリングループを中断 call $_JS_SystemInfo_GetCurrentCanvasHeight i32.eqz br_if 0 (;@4;) ;; ★ ゼロだったらレンダリングループを中断となっていて、何らかの方法でfakeしないといけないことが判る。今回は
NAME.wasm.framework.unityweb
の方をパッチした。NAME.data.unityweb
Build/<NAME>.data.unityweb
はゲームアセットを格納したアーカイブで、UnityLoader.js
でEmscriptenのファイルシステム側に投入される。現時点ではメモリ上に完全なファイルシステムを構築している。つまり、WebGLビルドではアセットのサイズがそのままメモリ消費量になる。
DOM API のfake
JSdomやheadless-glでそれなりの量のAPIを実装できているが、いくつかは手で実装する必要があった。
createElement
JSdomは実は
canvas
要素を実装しており、2d
コンテキストは使うことができる。 ...が、残念ながらWebGLコンテキストはサポートしていないため自前の実装で置き換える必要がある。今回はロードしたスクリプトから呼ばれる
createElement
のみをhookして置き換える方向とした。function proxyCEl(nam){ console.log("PROXY CEL", nam); if(nam == "canvas"){ let cv = d.super_createElement("div"); // ★ 適当に div 要素でお茶を濁す cv.getContext = function(type, attr){ // ★ div要素に getContext メソッドを追加する ...Emscripten側は実際に生成された要素が
div
であっても、getContext
メソッドさえ有れば正常に動作する。Duck typing。
getContext
で得たコンテキストには WebGLDebugTools( https://github.com/KhronosGroup/WebGLDeveloperTools )を入れて呼び出しのトレースを実施している。ただ WebGLDebugTools はextensionを正常に処理できなかったため、extensionについてはhookしないように適当にパッチした。createObjectURL 、 revokeObjectURL
createObjectURL
は、ブラウザ内部のポインタを指すURL(Blob URL)を生成するもので、HTML5 File APIの一部になっていて( https://www.w3.org/TR/2019/WD-FileAPI-20190911/#creating-revoking )、ここで生成したURLがfetch
で使えるようになることが期待される( https://www.w3.org/TR/2019/WD-FileAPI-20190911/#blob-url )。しかし、JSdomにはこの機能が無いため、自前で実装してやる必要がある。
revokeObjectURL
は、Development buildではデバッグの都合で使用されていない。(ブラウザ側のDeveloper toolsで見るのに不便だからと考えられる)今回は
Promise
を生成するクロージャの形で適当なところに保存し、fetch
操作ではそのPromise
そのものを返す(2回目以降はPromise.resolve
で直接値を返す)ことにした。createObjectURL
で生成されるURLの形式は決まっているが今回は適当に付けている。function createObjectURL(blob){ let cache = false; function cb(){ // ★ このクロージャをBlob URLと関連付けて保存する if(cache){ return Promise.resolve(cache); // ★ 2回目以降は cache を直接resolveする }else{ return new Promise((res, rej) => { const the_reader = new w.FileReader(); // ★ 初回はFile APIで読み出す the_reader.onload = (e => { const bv = the_reader.result; cache = Buffer.from(bv); res(cache); }); // FIXME: ??? It seems JSDOM ArrayBuffer cannot move to // Buffer object. Use readAsText instead for now... the_reader.readAsText(blob); }); } } return blob_to_url(cb); }function fetch_blob(uri){ // => promise const r = blobs[uri](); return r; } class MyLoader extends ResourceLoader { // ★ ResourceLoaderはJSdom本来のローダー fetch(url, options){ // (JSdomのfetchを実装している 、 HTML5のfetchではないことに注意) console.log("LOADER",url,options); if(url == "xblob:1"){ // ★ 1番のblobはローカルファイルに差し替え console.log("PATCH!!!"); const buf = fs.readFileSync(path.resolve(__dirname, "patch1.js")); return Promise.resolve(buf); }else if(url.indexOf("xblob:") != -1){ return fetch_blob(url); // ★ Promiseを生成して返す }else{ return super.fetch(url, options); // ★ blob以外では本来のローダーを使う } } }Unityでは、この
createObjectURL
は、JavaScriptで展開される可能性のある.unityweb
を保持するのに使用している。このため、MyLoader
クラスにはデバッグ用にファイルを差し替える機能も付けている。Web Workers
UnityではHTTP経由で取得するファイルを解凍するためにWeb workerを使用している。実際の展開処理はプレイヤー設定で "Uncompressed" を選べばskipでき、そのためのコードは非常に単純なため、今回は真面目にWeb Workerを実装するのではなく単に関数だけ実行できるようにした。
Unityが供給するWeb workerコードは、
this.onmessage
ハンドラでコマンドを受けとり、グローバルのpostMessage
手続きでデータを返す簡単なものなので、const text = buf.toString(); const src = "const obj = function (postMessage) {" + text + "}; obj"; const ex = eval(src); // ★ 元のUnityのWorkerコードをwrapしたもの const home = this; const recvmsg = function(x,y){ if(home.the_handler){ let ev = {data: x}; console.log("RECVMSG", x, y); home.the_handler(ev); }else{ console.log("!! Q RECV MSG", x, y); home.recvq.push([x,y]); } } this.workerobj = {}; this.workerobj._init = ex; // ★ 元のコードが this にアクセスしているため、適当なオブジェクトに付ける this.workerobj._init(recvmsg); console.log("WORKER STANDUP", this.workerobj);のように
function(postMessage){ /* 元のUnityのWorkerコード */}
をeval
してWorkerコードのグローバル変数postMessage
をエミュレートする形にした。元のコードはJavaScript的な
this
にonmessage
手続きを新設するため、wrapしたコードを呼び出す前に適当なオブジェクトに付け、後からthis.workerobj.onmessage(ev);のように使用(Workerへのメッセージングのエミュレート)している。
requestAnimationFrame
requestAnimationFrame
は、通常のブラウザではVSYNC(物理的な画面の更新完了)のたびに呼ばれる。ただし、今回は面倒なのでフリーラン(全力で描画する)とした。つまり、単にNode.jsのprocess.nextTick
にコールバックを登録して即呼び出すだけとしている。function sleep(ms){ return new Promise((res) => setTimeout(res, ms)); } function proxyRAF(cb){ //console.log("RAF", cb); process.nextTick(async function(){ //await sleep(100); // ★ 描画速度の調整用 const now = w.performance.now(); // ★ JSdomの Window.performance を使用して現在時刻を返す console.log("RAF", now); cb(now); update_screenshot(); }); return 99.99; // ★ これは cancel 用だが使われないので適当な値を返す }かんそう
... いきなりUnityをやらないで、Emscriptenで書いたコードで小さく試すべきだったね。。ただ、もう必要なWebAPIは揃えてしまったし面倒なところは結局Unity固有だったので何とも。。
次はheadless-glを自前のWebGL実装に置き換えてみる。
- 投稿日:2020-01-19T16:38:17+09:00
Express.jsコードリーディング
Express.jsのコードを読んでみたのでまとめることにしました。
普段の業務ではNode.jsもExpressも利用していないので、JavaScriptの基本的なコーディングやライブラリの内容なども合わせてまとめておこうと思います。
間違っている内容がありましたら編集リクエストをいただけますと幸いです。Expressコードリーディング
対象のコード
今回コードリーディングする対象のコードです。
ただHello Worldするだけのコードになります。index.jsconst express = require('express') const app = express() app.get('/', (req, res) => { res.send('Hello World'); }) app.listen(3000);nodeでindex.jsを起動します。
起動後curlでレスポンスを確認します。$ node index.js $ curl localhost:3000 Hello Worldexpressインスタンス生成
まずは
const app = express()
の処理を追っていきます。ここで最初に呼び出されるのは、expressライブラリのルートディレクトリ上に存在するindex.jsファイルです。
index.jsmodule.exports = require('./lib/express');上記はmoduleの作成のみで、処理本体は
lib/express.js
ファイルですlib/express.js/** * Expose `createApplication()`. */ exports = module.exports = createApplication; /** * Create an express application. * * @return {Function} * @api public */ function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, EventEmitter.prototype, false); mixin(app, proto, false); // expose the prototype that will get set on requests app.request = Object.create(req, { app: { configurable: true, enumerable: true, writable: true, value: app } }) // expose the prototype that will get set on responses app.response = Object.create(res, { app: { configurable: true, enumerable: true, writable: true, value: app } }) app.init(); return app; }一つずつ処理を見ていきます。
アプリケーション作成
まずはこちらになります。
lib/express.jsvar app = function(req, res, next) { app.handle(req, res, next); };appのfunctionを生成していますが、こちらがExpressの入り口となるアプリケーション部分です。
HTTPリクエストをされるとここが実行されます。プレーンなNode.jsだけでここを表現すると以下のような処理になります。
サンプルconst http = require('http') var app = function(req, res, next) { app.handle(req, res, next); }; app.handle = function handle(req, res, callback) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); } http.createServer(app).listen(3000);アプリケーションメソッドをコピーしてくる(継承する)
appはEventEmitterとprotoをコピーします。
lib/express.jsvar EventEmitter = require('events').EventEmitter; var mixin = require('merge-descriptors'); var proto = require('./application'); var req = require('./request'); var res = require('./response'); 中略 mixin(app, EventEmitter.prototype, false); mixin(app, proto, false); // expose the prototype that will get set on requests app.request = Object.create(req, { app: { configurable: true, enumerable: true, writable: true, value: app } }) // expose the prototype that will get set on responses app.response = Object.create(res, { app: { configurable: true, enumerable: true, writable: true, value: app } })ここでコピーしている
proto(application.js)
がapplicationのメイン処理が入っている部分です。
具体的な処理は後々出てくるのでそこで説明します。その他、EventEmitterライブラリやExpressプロジェクトのルートディレクトリにあるrequest.js、response.jsの内容もコピーしています。
アプリケーション初期設定
lib/express.jsapp.init();ここで読みだしているのは、
lib/application.js
内に設定されているinit
メソッドです。
この中で行なっているのは、アプリケーションをデフォルトの設定で設定しています。例えば、HTTPヘッダーに
x-powered-by
を返却するなどはこの中で設定しています。Routingの設定
次にRouting設定です。
app.get('/', (req, res) => { res.send('Hello World'); })の処理です。
こちらで呼び出されるapp.get
はlib/application.js
ファイルの以下処理になります。lib/application.jsmethods.forEach(function(method){ app[method] = function(path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }; });HTTPメソッドに紐づくfunctionの作成
以下の処理でHTTPメソッド(GET,POST,PUT,PATCH,DELETEなど)と同一名称のメソッドをappクラスに作成します。
lib/application.jsvar methods = require('methods'); methods.forEach(function(method){ app[method] = function(path){ // 中略 }; });Routerクラスの移譲
次は
this.lazyrouter();
の部分です。
ここは以下の処理になります。lib/application.jsvar Router = require('./router'); app.lazyrouter = function lazyrouter() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled('case sensitive routing'), strict: this.enabled('strict routing') }); this._router.use(query(this.get('query parser fn'))); this._router.use(middleware.init(this)); } };appクラスの
_router
プロパティにRouter
クラスを委譲しています。
Routerクラスはlib/router/index.js
ファイルを参照しています。
this._router.use
はMiddlewareの設定です。
this._router.use(query(this.get('query parser fn')));
の設定は、クエリストリングをObjectに変換してくれます。
this._router.use(middleware.init(this));
はMiddlewareの初期設定を行います。
X-Powered-Byヘッダーを付与するのもこちらになります。Routeクラスの取得とLayerクラスをスタックに追加
続いては以下の処理です。
var route = this._router.route(path);
こちらは先ほど作成した_routerのrouteメソッドを利用して、RouteクラスとLayerクラスを作成します。
実際の処理はこちらです。lib/router/index.jsvar Route = require('./route'); var Layer = require('./layer'); /** * Create a new Route for the given path. * * Each route contains a separate middleware stack and VERB handlers. * * See the Route api documentation for details on adding handlers * and middleware to routes. * * @param {String} path * @return {Route} * @public */ proto.route = function route(path) { var route = new Route(path); var layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; };RouteクラスとLayerクラスを生成します。
そしてLayerクラスのrouteプロパティにRouteクラスを移譲しています。
作成したLayerクラスはRouterクラスのstackに格納しています。呼び出し元にはRouteクラスを返却しています。
RouteクラスにLayerクラスをセット
続いては以下の処理です。
route[method].apply(route, slice.call(arguments, 1));
こちらは先ほど作成したRouteクラスに新しいLayerクラス作成したうえでセットします。
実際の処理はこちらです。lib/router/route.jsmethods.forEach(function(method){ Route.prototype[method] = function(){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires a callback function but got a ' + type throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; }; });ここではLayerクラスを作成してかつ、指定したメソッドをLayerクラスにセットします。
そして作成したLayerクラスをRouteクラスのstackに追加しています。httpのlisten開始
最後に以下の構文でhttpをport3000で開始します。
app.listen(3000);
実際の処理はこちらです。
lib/application.jsapp.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); };中身については、
アプリケーション作成
の部分で説明した内容と同じなので省略します。リクエストを受け付けた時の挙動
最後にリクエストを受け付けた時の挙動についてです。
このような順番で処理されます。app->app.handle->router.handle->Layer.handle->Route.dispatch->Layer.hanldle->Express利用者が設定したfunction
ここでRouter、Layer、Routeクラスの関係性について図にまとめてみました。
Expressで利用しているNode.jsの構文やライブラリについて
上記まででExpressの処理の流れをざっと確認してきましたが、実際にコードリーディングするにあたり、普段見慣れないNode.jsの構文やライブラリがいつかあると思います。
そこでそれぞれの構文がどういうものなのかをまとめます。mixin
参考:merge-descriptors
まずはmixinからです。
Expressでは、以下のように利用しています。lib/express.jsvar app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);mixinがどういうものか見ていきます。
サンプルvar mixin = require('merge-descriptors'); var app = function() { }; var application = function() {} application.handle = function() { return 'Hello mixin'; } mixin(app, application, false); console.log(app.handle()); // Hello mixinこのようにappクラスにapplicationクラスのメソッドを継承させています。
これがmixin(merge-descriptors)の利用方法になります。EventEmitter
続いてはEventEmitterです。
EventEmitterを利用することでイベント駆動型の設計が可能になります。サンプルvar mixin = require('merge-descriptors'); var EventEmitter = require('events').EventEmitter; var app = function() {}; mixin(app, EventEmitter.prototype, false); app.init = function() { app.on('mount', () => { console.log('emit!'); }); console.log('init') } app.send = function() { app.emit('mount'); } app.init(); // init app.send(); // emit!上記のように、
on.('イベント名')
で事前に処理を受け付けるタイミングを作成してemit('イベント名')
を実行する際に、onの中に指定したfunctionが実行されます。Object.create
続いてはObject.createについてです。
Expressでは以下のように利用されています。Expressでの利用// expose the prototype that will get set on requests app.request = Object.create(req, { app: { configurable: true, enumerable: true, writable: true, value: app } })こちらもmixinと同じようにメソッドを継承します。
サンプルvar app = function() {}; var req = function() {}; req.headers = function() { return 'req!'; } app.request = Object.create(req); console.log(app.request.headers()); // req!apply&call&bind
applyとcallとbindについてです。
Express.jsでは様々な場所でこのAPIが利用されています。サンプルroute[method].apply(route, slice.call(arguments, 1)); var args = slice.call(arguments, 1);こちらは様々な記事で解説されているため、詳細は割愛しますが、簡単に試して見ます。
サンプルvar app = function() {} app.say = function(name, age) { return name + 'は' + age + '歳です。'; } console.log(app.say('Taro', 16)); // Taroは16歳です。 console.log(app.say.apply(app, ['Taro', 16])); // Taroは16歳です。 console.log(app.say.call(app, 'Taro', 16)); // Taroは16歳です。 var say = app.say.bind(app, 'Taro', 16); console.log(say());参考
他にもExpressの解説をされている方がいらっしゃいましたので紹介します。
エンジニアの教養、フレームワーク解剖学【Express編】
- 投稿日:2020-01-19T15:34:33+09:00
Promiseとasync/awaitを使った同期処理
Node.jsを使ったプログラムを書いていて、同期的に処理してほしいところを全て非同期でやっちゃう可愛いnodeのおかげでいくつか勉強になったことをまとめる。
同期処理を考える手順
1.まずはコールバック関数を使う
まず最初に思いつくのはこれ。コールバックを使えば、処理1の値を使った処理2みたいなことができる。
が、簡単な処理に関してはこれで十分だが、処理が複雑になってくるといわゆる「コールバック地獄」と言われる見た目も処理も地獄のようなプログラムができてしまう。要はネストが深くなって何がなんだかよくわからなくなる。sample.jsfunction 処理2{ callback(処理1) }2. Promiseを使う
そこで登場するのがPromise。こいつは結構優れもので、書き方を覚えてしまえばコールバック地獄を回避できる。
そもそもPromiseは何をしているかを簡単に説明すると、オブジェクト(引換券的なもの)を先に渡しておいて、resoleが返ってきた、つまり処理が終わればそのオブジェクトと結果を交換するよって感じ。
もっと砕いて言うと、電子レンジがPromiseで、人は何かを温めるとき電子レンジに食べ物を入れる。これが自分のものだよって言うことを示す引換券をもらうとして、温めが終わったら取りにいくイメージ。その間は何をしててもokだから要はPromise単体だと非同期処理を行なっている。sample.jsvar result_1 = new Promise(function(resolve) { resolve("result_1成功"); }); result_1.then(function(){ //処理 }).then(fucntion(){ }) ・・・同期処理にしたければPromiseのthenを使う。これは次々に処理を記述しているが、結局処理が終わらない(resoleが返ってこない)と次に進まないので同期的な処理になる。thenを使えば、コールバックを綺麗に記述でき、メンテナンス性も向上する。
async/awaitを使った方がもっと綺麗
これに関してはいろんなサイトでごちゃごちゃ書いているが、要は使えたらいいので簡潔に言うと、asyncな関数で待ちたい処理をawaitにするだけ。
sample.js// await式を使いたいので、async関数を宣言する const main = async () => { // 処理1のPromiseを宣言する const routine1 = new Promise(resolve => { //略 resolve('end') }) const result1 = await routine1; // 処理1の完了を待ち結果を受け取る // 処理2以下を記述…… } } // 関数を実行 main();async/awaitが最も綺麗にかけるのでおすすめ。
注意事項 Promiseを変数に入れるか関数のreturnにするか
sample.jsconst routine1 = new Promise(resolve => { //略 resolve('end') }) function routine1(){ return new Promise(resolve =>{ //略 }); }この違いは結構大事。変数のroutine1を呼び出す場合は、呼び出す前にこの代入式を記述するとその時点で処理が実行される。一方で関数の場合は、呼び出した時点で初めてPromiseの処理が始まるという違い。
また、ループでなんどもPromise処理を使いたい時には、functionで毎回呼び出さす必要がある。変数だと一回resolveを返してしまえばそのPromiseは御役御免となり一生使えない。
- 投稿日:2020-01-19T13:29:04+09:00
ejsの<%= >と<%- >の違い
- 投稿日:2020-01-19T00:15:49+09:00
node.js製のlamdaアプリをコマンド一発でアップロードする方法
node.jsでlambdaのアプリを作っているのですが、コードやパッケージが増えてくるとインライン編集出来なくなったり、zipでアップロードする必要が出てきます。ちょっとした変更でもわざわざawsのサイトに行ってアップロードするのは面倒ですし、複数のlambdaアプリを作っていたら間違えて別の関数にアップロードしてしまった、といった事故も起きるかもしれません。そこで、コンソールからコマンドでアップロードできるようにする方法を書きます。
※動作確認環境: macOS 10.14.6
aws CLIの設定
まずawsのCLIをインストールします。バージョンが出ればOKです。
$ brew install python3 $ pip3 install aws $ aws --version次にawsのユーザー情報を設定する必要がありますが、アクセスキーが必要になります。IAMでAdministratorAccessポリシーを持ったユーザーを作成しておきます。
https://blog-asnpce.com/technology/313アクセスキー、シークレットアクセスキー、リージョンを
aws configure
で設定します。$ aws configure AWS Access Key ID [None]: xxxxxxxxxxxxxxx AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Default region name [None]: ap-northeast-1 Default output format [None]: // 設定せずそのままEnterこれでコンソールからawsのcliを使用するときに自分のアカウントを操作することができるようになりました。
package.jsonの編集
package.jsonを編集し、lambdaアプリのディレクトリで
npm run deploy
を実行するとアップロード出来るようにします。
package.jsonのscripts内にdeployというプロパティ名でコマンドをセットします。セットするコマンドは以下です。このコマンドを実行することにより、package.jsonが入っているディレクトリをzipに圧縮してからCLIでアップロード出来ます。
{}内はご自身のlambdaアプリに合わせてください。zipのファイル名はなんでもいいです。zip -r {zipファイルの名前}.zip ./ -x \"*.zip\" && aws lambda update-function-code --function-name {lambda関数の名前} --zip-file fileb://{zipファイルの名前}.zippackage.json{ "name": "prodfunc", "version": "1.0.0", "engines": { "node": ">=12.0.0" }, "description": "", "main": "index.js", "scripts": { // ここに追加 "deploy": "zip -r prodfunc.zip ./ -x \"*.zip\" && aws lambda update-function-code --function-name prodfunc --zip-file fileb://prodfunc.zip" }, "author": "", "license": "ISC", "dependencies": { // 略 } }コンソールで以下を実行し、エラーが出なければ成功です。
$ npm run deploy参考
https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html
- 投稿日:2020-01-19T00:15:49+09:00
node.js製のlambdaアプリをコマンド一発でアップロードする方法
node.jsでlambdaのアプリを作っているのですが、コードやパッケージが増えてくるとインライン編集出来なくなったり、zipでアップロードする必要が出てきます。ちょっとした変更でもわざわざawsのサイトに行ってアップロードするのは面倒ですし、複数のlambdaアプリを作っていたら間違えて別の関数にアップロードしてしまった、といった事故も起きるかもしれません。そこで、コンソールからコマンドでアップロードできるようにする方法を書きます。
※動作確認環境: macOS 10.14.6
aws CLIの設定
まずawsのCLIをインストールします。バージョンが出ればOKです。
$ brew install python3 $ pip3 install aws $ aws --version次にawsのユーザー情報を設定する必要がありますが、アクセスキーが必要になります。IAMでAdministratorAccessポリシーを持ったユーザーを作成しておきます。
https://blog-asnpce.com/technology/313アクセスキー、シークレットアクセスキー、リージョンを
aws configure
で設定します。$ aws configure AWS Access Key ID [None]: xxxxxxxxxxxxxxx AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Default region name [None]: ap-northeast-1 Default output format [None]: // 設定せずそのままEnterこれでコンソールからawsのcliを使用するときに自分のアカウントを操作することができるようになりました。
package.jsonの編集
package.jsonを編集し、lambdaアプリのディレクトリで
npm run deploy
を実行するとアップロード出来るようにします。
package.jsonのscripts内にdeployというプロパティ名でコマンドをセットします。セットするコマンドは以下です。このコマンドを実行することにより、package.jsonが入っているディレクトリをzipに圧縮してからCLIでアップロード出来ます。
{}内はご自身のlambdaアプリに合わせてください。zipのファイル名はなんでもいいです。zip -r {zipファイルの名前}.zip ./ -x \"*.zip\" && aws lambda update-function-code --function-name {lambda関数の名前} --zip-file fileb://{zipファイルの名前}.zippackage.json{ "name": "prodfunc", "version": "1.0.0", "engines": { "node": ">=12.0.0" }, "description": "", "main": "index.js", "scripts": { // ここに追加 "deploy": "zip -r prodfunc.zip ./ -x \"*.zip\" && aws lambda update-function-code --function-name prodfunc --zip-file fileb://prodfunc.zip" }, "author": "", "license": "ISC", "dependencies": { // 略 } }コンソールで以下を実行し、エラーが出なければ成功です。
$ npm run deploy参考
https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html