- 投稿日:2019-09-29T22:10:29+09:00
【Lambda】zipファイルの圧縮解凍サンプル【nodejs】
やること
Lambdaで
・S3バケットにzipファイルを作成する(つまり圧縮)
・S3バケットに格納されている、zipファイルから中身を取り出す(つまり解凍)手順概要
- node-zip が動くようにLayerファイル(zip)を作る
- LambdaにLayerファイルをアップロードする
- 関数を作る
Layerファイルを作ります
クライアントで作業します。
例:新規でAmazonLinux2 を作成して、nodejsをインストールからやります。
たったこれ?だけです。curl --silent --location https://rpm.nodesource.com/setup_10.x | sudo bash - yum install -y nodejs mkdir nodejs cd nodejs npm init -y npm install -y node-zip cd .. zip -r layer.zip nodejs/.zipをLambdaに登録します
AWS管理コンソールで作業します。
「レイヤーの作成」で、先ほど作ったLayer.zipファイルを入れれば終わりです。関数の準備
Layersを選択して
「レイヤーの追加」押下で先ほど作ったレイヤーが出てきますので、選択。
関数の作成
下のコードを先頭あたりに入れて、実行してみます。エラーが出なければレイヤーが効いています。
const node_zip = require('node-zip');ちなみに、Layerを外すと、上記コードはエラーになります。
解凍
S3に置いてあるzipファイルを解凍します。
IAMで、s3:GetObject が必要です抜粋const aws = require('aws-sdk'); const s3 = new aws.S3({ apiVersion: '2006-03-01' }); const node_zip = require('node-zip'); s3.getObject({Bucket:s3bucket, Key:s3object},function(err,data) { if (err) { return; } else { var zip = new node_zip(data.Body, {base64: false, checkCRC32: true}); for (var fileinzip in zip.files) { let pKey=""; let pBody=""; for (var para in zip.files[fileinzip]) { if(para=="name") pKey = zip.files[fileinzip][para]; if(para=="_data") pBody = zip.files[fileinzip][para]; } // pKey にファイル名、pBodyにファイル内容が入っている // putObjectするとか。。 } // 次のファイル });圧縮
圧縮対象のファイルはあらかじめ準備する必要はないです。
ファイル名とファイル内容があれば良いです。IAMで、s3:PutObject が必要です
const node_zip = require('node-zip'); const zip = new node_zip(); zip.file("ファイル名1", "内容文字列1"); zip.file("ファイル名2", "内容文字列2"); zip.file("ファイル名3", "内容文字列3"); let zipContent = zip.generate({base64: false,compression:'DEFLATE'}); var buf = Buffer.from(zipContent, 'binary'); let param ={ Bucket:bucket, Key : "hoge.zip", Body: buf, ContentType: 'application/zip'}; s3.putObjct(param);
- 投稿日:2019-09-29T21:57:56+09:00
Node.js&google-home-notifierでGoogle Homeに喋らせよう
Node.jsを使って、Google Homeに喋らせました。
その際詰まったところなどを解説します。1.google-home-notifierをインストール
$ npm init $ npm install google-home-notifier※npm initはデフォルトでいい場合は全てEnterする。
2.node.jsでサンプルコードを書く
sample.js3.実行
$ node sample.jsこれでいけるはず...
Error: get key failed from googleはまりました...orz
色々調べてこちらの対策を発見:pull request
修正するも、動かない...。
調べても解決に至らずだったので、
google-home-notifierのソースコードを確認してみる事にしました。
気になるところを発見!
node_modules/google-tts-api/lib/key.jsvar match = html.match("TKK='(\\d+.\\d+)';"); if(!match) throw new Error('get key failed from google');htmlが何かのhtmlを読んでmatchさせているが失敗している。
そして、恐らく、それが、node_modules/google-tts-api/lib/key.jsvar host = 'https://translate.google.com';というわけで、
https://translate.google.com/のhtmlを解析していきます。
1.右クリックで「ページのソースを表示」
2.command+fで「TKK検索」
3.気になる内容が引っかかる。view-sourcetkk:'436044.375069185' TKK = mobileWebapp.tkkそして、ソースコードと照らし合わせてみると、
node_modules/google-tts-api/lib/key.jsvar match = html.match("TKK='(\\d+.\\d+)';");恐らく以前は、
TKK='(\d+.\d+)'; //つまり、TKK=436044.375069185;数字は任意と書いてあったけど、
今は、https://translate.google.com/ のhtmlコードが変わってしまいview-sourcetkk:'436044.375069185', TKK = mobileWebapp.tkkとなり、TKK='(\d+.\d+)';にmatchしなくなった。
そのため、tkk:'436044.375069185',にmatchするように修正。node_modules/google-tts-api/lib/key.js//var match = html.match("TKK='(\\d+.\\d+)';"); var match = html.match("tkk:'(\\d+.\\d+)',");動きました!
ちなみにソースコードはこちらにupしています。
- 投稿日:2019-09-29T21:57:56+09:00
Node.js&google-home-notifierでGoogle Homeに喋らせよう(Error解析付き)
Node.jsを使って、Google Homeに喋らせました。
その際詰まったところなどを解説します。こちらのサイトを参考にnode.js作成
https://qiita.com/SatoTakumi/items/c9de7ff27e5b705080660.node.jsのバージョン
$ npm -v 6.9.0 $ node -v v10.16.01.google-home-notifierをインストール
$ npm init $ npm install google-home-notifier※npm initはデフォルトでいい場合は全てEnterする。
2.node.jsでサンプルコードを書く
sample.js//ライブラリ参照 var googlehome = require('./node_modules/google-home-notifier'); // 言語設定 var language = 'ja'; // GoogleHomeのIPアドレスに書き換えてくださいね googlehome.ip('0.0.0.0',language); // 第一引数を自分のもっているGoogleHomeの名前に書き換えてくださいね //googlehome.device('googlehome', language); // Google Homeにしゃべって欲しい文章をここに記入してくださいね var text = 'こんにちは'; // メイン処理 try { googlehome.notify(text, function(notifyRes) { console.log(notifyRes); }); } catch(err) { console.log(err); };3.実行
$ node sample.jsこれでいけるはず...
Error: get key failed from googleはまりました...orz
色々調べてこちらの対策を発見:pull request
修正するも、動かない...。
調べても解決に至らずだったので、google-home-notifierのソースコードを確認してみる事にしました。
気になるところを発見!
node_modules/google-tts-api/lib/key.jsvar match = html.match("TKK='(\\d+.\\d+)';"); if(!match) throw new Error('get key failed from google');htmlが何かのhtmlを読んでmatchさせているが失敗している。
そして、恐らく、それが、node_modules/google-tts-api/lib/key.jsvar host = 'https://translate.google.com';というわけで、
https://translate.google.com/1.右クリックで「ページのソースを表示」
2.command+fで「TKK」検索
3.気になる内容が引っかかる。view-sourcetkk:'436044.375069185' TKK = mobileWebapp.tkkそして、ソースコードと照らし合わせてみると、
node_modules/google-tts-api/lib/key.jsvar match = html.match("TKK='(\\d+.\\d+)';");恐らく以前は、
TKK='(\d+.\d+)'; //つまり、TKK=436044.375069185;数字は任意と書いてあったけど、
今は、
https://translate.google.com/
のhtmlコードが変わってしまいview-sourcetkk:'436044.375069185', TKK = mobileWebapp.tkkとなり、TKK='(\d+.\d+)';にmatchしなくなった。
そのため、tkk:'436044.375069185',にmatchするように修正。node_modules/google-tts-api/lib/key.js//var match = html.match("TKK='(\\d+.\\d+)';"); var match = html.match("tkk:'(\\d+.\\d+)',");動きました!
ちなみにソースコードはこちらにupしています。
これで喋らせる事ができましたが...
日本語を喋るけど、発音が英語っぽい日本語になる問題が発生。
修正方法:https://qiita.com/sohsatoh/items/69bcad398ffae11359f0この部分を
google-home-notifier.jsvar ip = function(ip) { deviceAddress = ip; return this; }こう直すと直るようです。
google-home-notifier.jsvar ip = function(ip, lang = 'ja') { deviceAddress = ip; language = lang; return this; }完成!
- 投稿日:2019-09-29T21:02:51+09:00
ピクセルアートフレーム Divoom PIXOO で遊んでみた
はじめに
これは社内LTイベンント向けの資料です。
今日のお題「ガジェット」
- Maker's Fair Tokyo 2019 行ってきました
- 2019.08.03 - 08.04
- https://makezine.jp/event/mft2019/
- 買ったもの: ピクセルアートフレーム Divoom PIXOO
Divoom PIXOO
- ハード
- 16x16 の LEDパネル
- スピーカーは無し(付いている機種もある)
- Bluetooth接続
- WiFiは無し
- スマートフォンの専用アプリでコントロール
- Divoom (iTunes)
- 共有ギャラリーもある
Divoom アプリでできること
- 静止画の描画、PIXOOへの転送
- アニメーションの作成、PIXOOへの転送
- テキストから、簡易電光掲示板の作成
- つくった静止画/アニメーションの公開、他の人の作品のダウンロード
- 組み込みアプリの利用(ここでデモ)
- 時計、カレンダー、天気情報
- 通知の連携
- 各種VJ風アニメーション
- 音のビジュアライズ
PIXOO ハック!
- やりたいこと: パトランプ的に使いたい
- PC/Macから、自分のプログラムでコントロール
- 参考情報
- 解析情報 ... protocol (github/MarcG046/timebox)
- Bluetooth シリアルで 「01 04 00 45 00 49 00 02 」等を送る
- 解説動画 ... Timebox Hacks (youtube)
- Python ライブラリ ... derHeinz/divoom-adapter
- Node.js ライブラリ ... RomRider/node-divoom-timebox-evo
Node.js でやってみる
不安定だが、限定的に動作
- 接続できないことがある
- 一部の機能しか使えない
(1) 接続
const TIMEBOX_ADDRESS = "xx:xx:xx:xx:xx:xx"; // Bluetoothアドレス const btSerial = new (require('bluetooth-serial-port')).BluetoothSerialPort(); const Divoom = require('node-divoom-timebox-evo'); btSerial.findSerialPortChannel(TIMEBOX_ADDRESS, function (channel) { btSerial.connect(TIMEBOX_ADDRESS, channel, function () { console.log('connected'); btSerial.on('data', function (buffer) { console.log(buffer.toString('ascii')); }); showTime(); // <-- ここのタイミングで、表示処理を呼び出す //sendImage(); }, function () { console.log('cannot connect'); }); }, function () { console.log('found nothing'); });
Node.jsで時刻を表示
(2) 時刻を表示
function showTime() { const d = new Divoom.TimeChannel; const buf = d.messages[0].asBinaryBuffer()[0]; //console.log(buf); btSerial.write(buf, function (err, bytesWritten) { if (err) console.log(err); else { console.log('bytes sent=' + bytesWritten); } btSerial.close(); } ); }
Node.jsでイメージを表示
(3) イメージ表示
なぜか、表示できるイメージとできないイメージがあるconst imageFile = 'img16x16.png'; function sendImage() { const d = new Divoom.DisplayAnimation; d.read(imageFile) .then(result => { result.asDivoomMessages().forEach((message /*: TimeboxEvoMessage*/, index /*: number*/) => { const buf = message.asBinaryBuffer()[0]; console.log(buf); btSerial.write(buf, function (err, bytesWritten) { if (err) console.log(err); else { console.log('bytes sent=' + bytesWritten); } btSerial.close(); } ); }) }) .catch(err => { console.error('read ERROR:', err); }) }
ハマりどころ1: デバイスのbluetoothアドレスが見つからない
- スマートフォンとの接続を解除
- Macに接続
- システム環境設定 - Bluetooth - Pixoo に接続
- Macで調べる
- [Apple]メニュー - [このMacについて] - [概要]タブ - [シルテムレポート]ボタン
- [ハードウェア] - [Bluetooth] を選び、右のリストで「Pixoo」のアドレスを探す
ハマりどころ2: サンプルコードが古い(動かない)
- Divoomライブラリのソースコード自体は結構新しい
- テストコードもある(シリアル通信は除く)
- readme.md にあるサンプルの説明があってない(古い)
- コードを読んでサンプルを直す
- テストコードと、ライブラリの実装
- 外部ライブラリ bluetooth-serial-port
まとめ
- PIXOO ちょっとしたディスプレイ装置として、おもしろい
- イベントごとの簡易電光掲示板にベスト
- スマートフォンアプリから、素直に使える
- ハックしても使えそう(が、まだ上手くは使えていない)
- 通信の確立に失敗することがある
- 動くコマンドと動かないコマンドがある
- 表示できる画像と、表示できない画像がある(違いは不明)
- → 気が向いたら調べます
- 積みガジェットを消化するには、LTに取り上げるのが良い
- 今回も買ったまま放置していたが、今回のネタとして調べてみた
おまけ: もう1つ買ったもの
- 木製ブロック玩具「もくロック」
- http://mokulock.com
- キッチリはまる感覚が心地よい
- 投稿日:2019-09-29T15:51:49+09:00
Node.jsでゼロインストールを実現する
Node.jsのパッケージマネージャはnpmおよびその代替となるyarnが有名です。共に、次期バージョンアップ(npm tinkおよびyarn v2であるberry)ではゼロインストールを目指しています。
今回は、モジュールハックを使ってゼロインストールを実装してみました。
ゼロインストールとは
npm i
やyarn
を実行するとnode_modules/
以下にnpmが展開されます。node_modules
はブラックホールよりも深いと言われています。この反省点をもとにパッケージシステムそのものを否定したプロジェクトを、Node.jsのオリジナル作者であるryがdenoとして立ち上げております。
実際node_modulesはプロジェクトによっては簡単に数百MBytes以上の容量を食い散らかします。手元で様々なJavaScriptプロジェクトを動かしていると、128GBや256GB程度のSSDではいとも簡単にディスクフルを招きます。
そこで実行時にパッケージをfetchするかキャッシュからのみ読み込むことで、オンメモリでパッケージを解決するというアプローチがゼロインストールです。
ゼロインストールのためにパッケージをオンメモリに展開する
npmパッケージは、npmpkg.comやyarnpkg.comなどでホスティングされています(最近GitHubでもnpmやgemなどのホスティングサービスのbetaが始まっています)。
さて、npmpkg.com や yarnpkg.com では、パッケージ情報の取得やダウンロードだけならとても簡単です。所定のURLにGETでアクセスするだけで、jsonもしくはgzip/tarされたアーカイブを取得できます。
import fetch from 'node-fetch' export const fetchPkg = async (name: string, version?: string) => { const url = `https://registry.yarnpkg.com/${name}` const json = await fetch(url).then(res => res.json()) let pkgs: { [props: string]: { info: any; data: any } } = {} if (!version) { const { latest } = json['dist-tags'] version = latest } const info = json.versions[version!] if (info.dependencies) { await Promise.all( Object.keys(info.dependencies).map(async name => { pkgs = { ...pkgs, ...(await fetchPkg(name, info.dependencies[name])), } }), ) } const tarball = await fetch(info.dist.tarball).then(res => res.body) const data = await extractTar(tarball) pkgs[name] = { info, data } return pkgs }この TypeScript のコードでは register.yarnpkg.com から、情報(JSON)を取得してバージョンなどの情報を取得し、バージョンを指定していなければ最新版(latest)をダウンロードします。またdependencies も同様にダウンロードします。
やっていることは全体的には、async/await および Promise.all を使って、並列でパッケージダウンロードしているだけです。
最後のほうの
const data = await extractTar(tarball)
では、取得した.tgzのtarballを展開しています。この関数が返すデータは、パッケージ名をキーとして、
info
プロパティにJSONデータ、data
プロパティにtarballを展開したものを格納しています。tarballの展開
import zlib from 'zlib' import tar from 'tar-stream' const extractTar = (tarball: NodeJS.ReadableStream) => { return new Promise((resolve, reject) => { const files: { [props: string]: string } = {} const gunzip = zlib.createGunzip({}) tarball .pipe(gunzip) .on('error', err => reject(err)) .pipe(tar.extract()) .on('error', err => reject(err)) .on('finish', () => resolve(files)) .on('entry', (headers, stream, next) => { let { name } = headers name = name.replace(/^package\//, '') if (!(name in files)) { files[name] = '' } stream.on('data', data => { files[name] += data.toString() }) stream.on('error', err => reject(err)) stream.on('end', () => next()) }) }) }tarballはまずgzipで圧縮されたバイナリを伸張しなければいけません。それについては、Node.js zlib APIを使います。また Node.js では Node.js Stream API の Stream で大体のエコシステムができあがってしまっているため、全体を Stream 処理で行っています。
node-fetch
でres.body
を使うとbody
をStream
として取得できるので、const gunzip = zlib.createGunzip({}) tarball .pipe(gunzip)で、前処理として、gzipの伸張を行います。これをさらにpipeで渡してtar-stream npm パッケージでエントリごとに取り出します。
この
tar-stream
はかなりクセが強い挙動を示すのでご注意ください。.pipe(tar.extract()) .on('error', err => reject(err)) .on('finish', () => resolve(files)) .on('entry', (headers, stream, next) => { let { name } = headers name = name.replace(/^package\//, '') if (!(name in files)) { files[name] = '' } stream.on('data', data => { files[name] += data.toString() }) stream.on('error', err => reject(err)) stream.on('end', () => next()) })
.pipe(tar.extract())
で継続されるStream
では通常とは異なりfinish
イベントとentry
イベントを使います。
entry
イベントではヘッダ情報の取得と、データ本体の為の新規Stream
を受け取ります。そのため、このようなストリーム処理のネストが発生します。データ本体のストリームは比較的素直なストリームです。パッケージは、
package/
のprefixが付いてるため問答無用で剥がしておきます。データストリームでは、
data
イベントでデータをオンメモリに追加しておき、end
イベントで、ストリームの次を促すためにnext()
を実行します。
finish
イベントが流れてくるとこれらの処理が一通り完了です。今回はまとめて全部を1つのPromise化しているため、
finish
時に、resolve
でPromiseを完了状態に持って行きます。モジュールハックでオンメモリのパッケージを返すようにする
ここがゼロインストールの本領です。
その前にモジュールハックのコードを
export const Module = require('module') as any export const originalLoad = Module._load export const originalExts: { [props: string]: any } = {} Object.keys(Module._extensions).forEach(ext => { originalExts[ext] = Module._extensions[ext] }) type Hook = (m: any, filename: string) => any export const hackExt = (ext: string, hook: Hook) => { Module._extensions[ext] = function(m: any, filename: string) { return hook(m, filename) } } export const builtinModules: string[] = Module.builtinModules type Loader = (name: string, parent: any, isMain: boolean) => any let loaders: Loader[] = [] export const hackLoader = (loader: Loader) => { loaders.push(loader) Module._load = function(name: string, parent: any, isMain: boolean) { for (const l of loaders) { const res = l(name, parent, isMain) if (res !== undefined) { return res } } return originalLoad(name, parent, isMain) } return () => { loaders = loaders.filter(l => l !== loader) } }モジュールハックのコードを汎用化したものです。今回は
hackLoader
関数を使います。export const hackZeroinstall = (pkgs: any) => { const requireStack: [string, string][] = [] const pkgNames = Object.keys(pkgs) if (pkgNames.length === 0) { return } console.log(`hackZeroinstall ${pkgNames}`)ここで
export
しているhackZeroinstall
を、先ほどのfetchPkg
で取ってきたpkgs
データをぶち込むと、オンメモリ展開されたパッケージを読み込むことができる、つまりゼロインストール処理が完成します。
requireStack
は、ゼロインストール処理中のモジュール呼び出しスタックです。文字列2つのタプルの配列になっています。後ほど説明しますが、文字列は、モジュール名と、モジュールのパスです。ここでは
pkgs
が空なら何もしません。const unhack = hackLoader((name, parent, isMain) => { let [modname, modpath] = name.split('/', 2) const isRelative = name.startsWith('.') || name.startsWith('/') if (requireStack.length === 0 && isRelative) { return }ここでは、まずimportのソースパスやrequireの引数
name
を '/' で区切ることによりモジュール名とパスを切り離します。
.
や/
で始まるローカルモジュールの場合でかつ、hack中でなければそのまま何もしません。if (isRelative) { ;[modname, modpath] = resolveRelative(requireStack, name) }相対パスの場合、
requireStack
と新しいパス指定を元に、modname
とmodpath
を書き換えます。const resolveRelative = (requireStack: [string, string][], name: string) => { const prev = requireStack[requireStack.length - 1] console.log('prev', prev) return [prev[0], path.join(path.dirname(prev[1]), name)] }まずスタックの1つ前を読み込みます。前述の通り、タプルの1つめはモジュール名
modname
なのでそのまま返します。タプルの2つめはモジュールパスなので、path.dirname
でパス名を取り出し、今回のパス指定を結合することで、パス解決を行います。if (!pkgNames.includes(modname)) { return }ここまでの処理で
modname
は完全に確定しているため、modname
がもしpkgsの中Object.keys(pkgs)
になければ、hack処理をせずに帰ります。console.log('load hack:', name, modname, modpath) const filename = resolveFile(pkgs[modname], modpath)次にファイル名を確定します。
const resolveFile = (pkg: any, modpath: string) => { let name = modpath || pkg.info.main
modpath
が空の場合、package.info
のmain
に書かれたモジュールを新たな名前として採用します。const exts = ['.js', '.json', '.node'] if (exts.find(ext => name.endsWith(ext))) { return name }
modpath
が拡張子で終わる場合はその拡張子をそのまま使えるためここでファイル名としては完成です。const files = Object.keys(pkg.data).filter(n => exts.find(ext => n === `${name}${ext}` || n === `${name}/index${ext}`), ) console.log(files) if (files.length > 0) { return files[0] } console.log('------------------------') console.log(modpath) console.log(Object.keys(pkg.data)) console.log('------------------------') throw new Error(`not found ${modpath}`) }処理がここまで及ぶ場合は、拡張子無しでファイル名を指定しているか、ディレクトリそのものを指しています。
まず、そこで拡張子を付与したものや、
/index
+ 拡張子を付与したものにヒットするかどうか調べます。もし複数ヒットすれば、ヒットした一番はじめのファイルを採用します。
※本当はもっと真面目に https://nodejs.org/api/modules.html に従ってファイル名決定をする必要がありそうです。
さてここまでで、モジュール名とファイル名が完全に確定しました。
requireStack.push([modname, filename]) console.log('PUSH', [modname, filename])
requireStack
にmodname
とfilename
をpush
します。const code = pkgs[modname].data[filename].toString() const module = new Module() module._compile(code, filename)
modname
とfilename
が確定しているためオンメモリしたファイルからソースコードを取り出し、module._compule
により実行をします。requireStack.pop() console.log('R', requireStack.length, name, module.exports) return module.exports || null }) return unhack }あとは、
requireStack
をpop
してから、module.exports
を返せばモジュールハックの完了です。まとめ
パッケージレジストリからパッケージ情報とパッケージのtarballを取得できるので、オンメモリでgzip+tarを展開しておく。モジュールハックを使って対象モジュールを指定された場合にはそのモジュールのソースコードを取り出し、
module._compile
でNode.js環境に応じた処理を実行させてから、module.exports
を取り出す。これらによりゼロインストールは実現可能です。
- https://github.com/noxtjs/gateway/tree/master/src
- module_hack.ts
- npm/
ソースコードに関しては、これらを見るといいでしょう。
ただし、このやり方が正しいか?というと検証がまだ全然足りていません。おそらく
package.json
のscripts
でpre-install
だのpost-install
だのをしているもの、ネイティブモジュールなどでは動作しないでしょう。黒魔術めいたコードの場合も動かない可能性は普通に考えられます。筆者の手元では、
uuidv4
npm パッケージのゼロインストール実行はできています。宣伝
技術書典7にお越しいただいた皆様ありがとうございました。
技術書典7で出した本のPDF電子版を、Pixiv Boothで頒布しております。よろしければどうぞ。
- 投稿日:2019-09-29T15:37:46+09:00
ESLint v6.5.0
前 v6.4.0 | 次 (2019/10/12 JST or 2019/10/26 JST)
ESLint 6.5.0 has been released: https://t.co/D7WheS5wmc
— ESLint (@geteslint) September 29, 2019ESLint
6.5.0
がリリースされました。
小さな機能追加とバグ修正が行われています。質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。
? 本体への機能追加
--env-info
CLI option? #12270
ESLint の実行環境を表示する CLI オプションが追加されました。
バグ報告の際にご利用ください。
$ eslint --env-info Environment Info: Node version: v12.11.0 npm version: v6.11.3 Local ESLint version: v6.5.0 Global ESLint version: Not found? 新しいルール
特になし。
? オプションが追加されたルール
use-isnan
enforceForSwitchCase
浮動小数点数の仕様により、
NaN
との比較は常にfalse
になります。これを報告するuse-isnan
ルールに、switch
文も報告するオプションが追加されました。/*eslint use-isnan: [error, { enforceForSwitchCase: true }]*/ //✘BAD switch (foo) { case NaN: bar(); break; }✒️
eslint --fix
をサポートしたルール特になし。
⚠️ 非推奨になったルール
特になし。
- 投稿日:2019-09-29T14:49:51+09:00
Node.jsでModule hack (require hack)する
Node.jsには
require
というモジュールを読み込む機能があります。これはNode.js Module APIで提供されている機能で、割と簡単にハックすることができます。よく Module hack とか require hack と呼ばれるものです。
- @babel/register
- ts-node/resigter
などが有名どころでしょうか。これらのモジュールでModule hackをすると、本来のNode.jsでは動作しないTypeScriptや拡張されたJavaScriptが動作します。
この記事では Module hack のやり方について説明します。
Module hackのやり方
// TypeScriptの場合 export const Module = require('module') as anyまず
module
を読み込みます。拡張子に応じたハックをするのは簡単です。
Module._extensions[ext] = function(m: any, filename: string) { return m._compile('console.log("hoge")', filename) }先ほどから、Module を
as any
でany
アサーションをしたり、今回の関数の第一引数m
もanyで定義していますが、これは、@types/node
で定義されているModule
型の定義では、非公開プロパティ、たとえば_extensions
や_compile
にアクセスできないためです。実例
// test.js const Module = require('module') Module._extensions['.js'] = function(m, name) { return m._compile('console.log("piyopiyo")', name) } require('./hoge')// hoge.js console.log('hoge')これら2つのファイルを同じディレクトリにおいて、
node test.js
を実行すると、Module hack をしてなければ本来hoge
と表示されるところがpiyopiyo
と表示されます。解説
Module._extensions
は、拡張子ごとにファイルの処理をする関数群です。.js
のようにドット込みの拡張子を指定します。ts-node/registerや@babel/registerのように、ソースコードをトランスパイルして実行する場合には、大体定番となるのが、
Module._extensions['.js'] = function(m, name) { return m._compile('console.log("piyopiyo")', name) }のように最終的にNode.jsが解釈できる状態にまでトランスパイルしたソースコードを、
m._compile(code, name)
のようにすることです。補足
Module._extensions
を書き換えると、もどす時に工夫は必要となります。export const originalExts: { [props: string]: any } = {} Object.keys(Module._extensions).forEach(ext => { originalExts[ext] = Module._extensions[ext] })大体はこのように、本来の関数を保存しておきます。
もっとも、このような hack を元に戻す必要がある機会はあまりないでしょう。
モジュール読み込み自体をhackする
ここまでの hack で、拡張子ごとの hack は可能になりましたが、モジュールそのものを hack するためには別の隠しプロパティにアクセスする必要があります。
const Module = require('module') const originalLoad = Module._load Module._load = function(name, parent, isMain) { if (name === 'hoge') { return { hoge: () => console.log('hoge') } } return originalLoad(name, parent, isMain) } const { hoge } = require('hoge') hoge()
Module._load
を書き換えると、モジュール読み込みそのものにちょっかいを出せます。ただし本来の関数は保存しておいた方が良いため、const originalLoad = Module._load
で保存しておきます。このコードでは、
hoge
という名前のモジュールだけhackします。他のモジュールを読み込んだ場合は、従来読み込むべきモジュールが読み込まれます。任意のソースコードをモジュールとして返す
先ほどのhackでは、ダイレクトに書いたコードをそのまま実行しています。もちろん、それはそれで用途もあるかもしれませんが、拡張子hackのときと同じように任意のコードを実行して、その結果を返したいときもあるでしょう。
const module = new Module() module._compile(code, filename) return module.exportsさきほどもでてきた
_compile
は、Node.jsの実行環境に応じた処理をやってくれるので便利です。同等の処理を自前、たとえばeval
Function
vm.runInContext
などでやろうとするとかなり面倒です。注意点
ここに書いた undocumented なものです。これで今後も含めて正しく動作することは保証されていません。また他の黒魔術を使ったソースコードで問題が生じる可能性があります。
pirates
というnpmモジュールを使うと、より安全に Module hack を試すことはできます。自前でやるよりはいいかもしれません。参考: https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js
宣伝
技術書典7で出した本のPDF電子版を、Pixiv Boothで頒布しております。よろしければどうぞ。
- 投稿日:2019-09-29T02:44:36+09:00
Sequelizeのwhere句でbetweenを使う方法
環境
Node,js + Express + MariaDB
Sequelize V5若干ハマった
Sequelizeにおいて、オペレータの使用方法は、バージョンによって差異があるようです。
前は、($文字列)のエイリアスを設定することで、オペレータを使用していましたが
V5では非推奨となっていて
現在はSequelize.Opから提供されるSymbolオペレータを使用することが推奨されているようです。const Sequelize = require("sequelize") const Op = Sequelize.Op router.get("/user",(req,res,next)=> { models.user.findAll( { where:{birth:{[Op.between]:["2019-09-09","2019-09-10"]}} }) } )悲しみ
適当に検索してたら、文字列オペレータを使った方法がかなり出てきて戸惑いました。
リファレンス最強ですね。Sequelizeリファレンス : https://sequelize.org/master/manual/querying.html
- 投稿日:2019-09-29T00:30:16+09:00
ndenv環境時にWebStormでpackage.jsonのタスク実行時「npmパッケージが指定されていない」と怒られる
社内でもSublimeTextおじさん
という名の老害として君臨していたのですが、この度JetBrains軍門に下りました。エディタ生活に慣れきっていたので個人的には困ってる困ってないでいえば困ってはいなかったのですが、品質担保という面から自分ひとりだけSublimeおじさんしているわけにもいかなくなったので、丁度いい機会かと思いWebStormを使い始めることにしました。試しに過去の個人プロジェクトを開いてみたところニョロニョロだらけでげんなりしたのでそっ閉じしました。
前提
- Mac
- WebStorm
anyenv
のndenv
を使ってNode.jsのバージョン管理をしております。npmタブからスクリプト実行時に怒られる
WebStormではnpmタブからpackage.jsonに記述したスクリプトをターミナルを経由せずに実行することが出来ます。早速やってみましょう。
23:02 ' ' の実行中にエラーが発生しました: java.util.concurrent.ExecutionException: com.intellij.execution.ExecutionException: Please specify npm or yarn package
えぇ…。
設定を確認してみる
Preference
>Node.js および NPM
タブを開くと、Nodeインタープリター
欄にはきちんとanyenvのパスが入っていましたが、パッケージ・マネージャー
の欄は空欄でした。こいつが怪しそうです。
設定しようとしてハマる
三点リーダからパスを指定しようとしたら、ディレクトリ一覧にanyenvが出てきません。先頭にピリオドがついている隠しディレクトリ扱いのためか、表示されないようです。仕方ないので直接パスを指定します。
指定先のパスがわからずハマる
いざパスを指定しようとしたところ、じゃあどこを指定すればいいのか、という問題にぶち当たり1時間近く頭を抱える羽目になりました。
ndenv
のバージョン変えてもきちんと動くのか非常に不安ではありますが、グローバルにインストールしているNode.js
でインストールされているnpm
のインストール先を指定したところ、package.json
に記述のあるパッケージのバージョンを確認してくれるようになりました。
~/.anyenv/envs/ndenv/versions/v10.15.0/lib/node_modules/npm
パスは適時読み替えて、自身のインストール状況に合わせたパスを指定してください。
npmタブからスクリプトを走らせてみる
設定を保存して再びnpmタブに戻り、スクリプトの実行に再チャレンジしてみます。
構成の編集
ウィンドウが出てきた場合は、パッケージマネージャー
が空欄になっていると思いますので、プルダウンを開くとProject
のところに先程登録したnpmのパスが入っていると思いますので、そちらを指定すれば実行出来るかと思います。終わり。
これバージョン変えるたびに都度変えなきゃいけないのはプロジェクト行き来するケースだと結構面倒だと思うのですが、実はプラグインでそのへんカバーされてるのでは、と8割くらい書き終わってから思いました。
とりあえず色々弄くり倒してみようかと思います。