Programming」カテゴリーアーカイブ

Node.js: ESM

Node.js って令和になってから全然話題にならないよね。
と思っていたら v20 になっていた。

Node.js ? Run JavaScript Everywhere

ちょっとまて、require は import に変わっていたの?
拡張子が mjs みたいなんですけど、これ何だ?

CJS, UMD, ESMとは?その違い。 | milestones

変わったのではなく標準に合わせて拡張したってことみたい。
でもトップページが ESM ってことは今後はこっちを使えなんだろうな。
ちなみに以下のコードを書いた時はたしか v6 だか v8 だったような。

Gjs, JXA, Node.js – Paepoi

標準である import に合わせたのは Gjs だけじゃなかったのね。
JXA は Apple がやる気ゼロだから今後も放置だろうけど。
書き換えしなきゃ、使わないと解っているけど確認のため導入。

node

うん Fedora 29 の dnf も v20 ですね。
とりあえず readline を試してみよう。

Readline | Node.js v21.7.1 Documentation

スイッチを CJS から ESM に切り替えると import のコードに変わる。
モジュール変数は UpperCamelCase 推奨だったけど小文字推奨に変わった?
無名関数ではなく promises を使えなの?とにかく書いてみる。

#!/usr/bin/env node

/** CJS
const readline = require('readline');
const tty = require('tty');

let rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal: false
});
let [prpt, prefix] = tty.isatty(0) ? ['何か入力して return :', '端末入力'] : ['', 'パイプ'];
rl.question(prpt, (line)=> {
    console.log(`${prefix}から ${line} が渡されました`);
    rl.close();
});
*/
// ESM (extention as 'mjs')
import * as readline from 'node:readline/promises';
import * as tty from 'node:tty';
import { stdin as input, stdout as output } from 'node:process';

let rl = readline.createInterface({ input, output, terminal:false });

let [prpt, prefix] = tty.isatty(0) ? ['何か入力して return :', '端末入力'] : ['', 'パイプ'];

let line = await rl.question(prpt);
console.log(`${prefix}から ${line} が渡されました`);
rl.close();

test.mjs

拡張子は mjs にしないと動かなかった。
Gjs は -m オプションだけど合わせてくるかな。
シバンを書いても問題なく動作しますね。

node

ぶっちゃけ ESM でも process は import せず使えるし無名関数でも動く。
とはいえ公式サンプルががこうなので今後はこうさせたいのだろうと思う。
promises を使うと上級者って感じでかっこいい、ような気がする。
もう少し調べて四月までに tips ページは書き換えよう。

JavaScript prototype: static method

JavaScript Tips を更新しました。
JavaScript Tips – Paepoi

古くさいコードを書き直しだけならすぐ終わるんだけど。
無意味に class のコードを prototype で書いてみたりとか。

// ベースクラスのようなもの、ES5 表記なのは意図的です
var TestClassBase = function(num) {
    this._num = num;
}
TestClassBase.staticFunc = function() {
    console.log('staticFunc 呼び出し');
}
TestClassBase.prototype.instanceFunc = function() {
    console.log('num は '+ this._num + ' です');
}
 
// 継承のようなもの
var TestClass = function(num) {
    TestClassBase.call(this, num);
}
TestClass.prototype = Object.create(TestClassBase.prototype);
TestClass.prototype.constructor = TestClass;
// 静的メソッドは無理やり継承
for (var p in TestClassBase) {
    if (TestClass[p] === undefined) {
        TestClass[p] = TestClassBase[p];
    }
}
// 新たな定義
TestClass.prototype.getNum = function() {
    return '百倍すると ' + this._num * 100 + ' です';
}
  
// 直接定義したものはそのまま呼び出す
TestClass.staticFunc();
  
// prototype で定義したものは object 化してから呼び出す
var test = new TestClass(7);
test.instanceFunc();
//tc.staticFunc(); // インスタンス化すると静的関数は呼び出しできない
  
console.log(test.getNum());

こんなの。

MDN で見つけたコードでは静的メソッドが継承できなくて。
ネットを探しても「できません」としか見つからなくて。
実現させろよ、プログラミングってそういうものだろみたいな。

意地になって色々試してやっと実現、安全は保証しないけど。
別にいいよね、今は普通に class を使うはずだもんね。
わざわざ ES5 縛りでやった意味は別に無い、楽しかったわ。

Gjs setInterval

先日 Gjs で TextDecoder が使えると書きましたが。

for (let s in this) print(s);

this

グローバル関数だったんですね、無理やりな実装だなって。
てか setTimeout や setInterval も追加されているじゃないの。
console.log も使えるようになったし、Web に合わせてきたのね。
Web や Node.js と同じなのかな、チト試してみよう。

#!/usr/bin/gjs -m

import GLib from 'gi://GLib';

const mainloop = new GLib.MainLoop(null, false);

let count = 1;
let id = setInterval(()=> {
    if (count == 5) {
        clearInterval(id);
        mainloop.quit();
    }
    console.log(count);
    count++;
}, 1000);

print('__do__');

mainloop.run();

print('__done__');

log

基本 Web と同じですけど、やっぱりメインループが必要ですね。
Web の場合はブラウザがメインループを回しているから不要なだけで。
Node.js が不要だからややこしい、繰り返し処理にはループが必要。
GtkApplication から使う等の場合は当然別途のループはいりません。

console.log は print と同じでいいのに、スタンドアロンでこの表示いる?
とにかく Web 標準と同様に使えます、ということで。

というか、Gjs って三年前とは全然変わってしまったなと。
tips ページはそろそろ丸ごと書き換えしなきゃ、面倒くさいなぁ。。。。。

Javascript TextDecoder

知らぬ間に Gjs の挙動が変わっていた。
普段は Python ばかりなのでいつからかは解らない。

#!/usr/bin/gjs -m

import GLib from 'gi://GLib';

/**
 * res is Uint8Array
 */
let res =  GLib.spawn_command_line_sync('pwd')[1];
print(res.toString()); //=> Warning

warn

よく見ると実行はされているので警告されているだけですね。
今まで普通に動いた、なんなら toString さえ暗黙実行されていた。

Google translate 等を使えば解るけど TextDecoder 使えってさ。
互換性のために残しているけどはよ移行しろということらしい。
警告メッセージも親切になったもんだ、ということで。

TextDecoder – Web API | MDN

主要ブラウザは全部が既に実装済みなんですね。
Web API なんだが Gjs や JXA でも使えるのだろうか?

#!/usr/bin/gjs -m

import GLib from 'gi://GLib';

let res =  GLib.spawn_command_line_sync('pwd')[1];
// TextDecoder
let dec = new TextDecoder();
let text = dec.decode(res);
print(text.trim());

動くじゃん、Spider Monkey 自体に実装されているのだろうか。
import 何も無しで使えるのって筆者は正直気持ち悪いんですけど。
string に変換されたので trim メソッドで改行を削除しています。
ちなみに。

#!/usr/bin/gjs

/**
 * old gjs
 */

const GLib = imports.gi.GLib;
const ByteArray = imports.byteArray;

let res =  GLib.spawn_command_line_sync('pwd')[1];
let text = ByteArray.toString(res);
print(text.trim());

byteArray モジュールを使う以前の正規な手段もまだ使えます。
というか、imports でのインポートもまだ普通に使えるんだよね。
byteArray も imports も Gjs 独自実装だから排除したかったのだろうけど。
Gjs で新しい手段の import では -m オプションを忘れずに。

jxa

JXA では TextDecoder 等は使えない、やはり実装はバラバラ。
というか JXA の UTF8String って正体は string じゃないか。
普通に string のメソッドが使えるし、まあ Cocoa がそうなのね。
Uint8Array は JXA ではほぼ扱われることが無いということだろう。
Node.js やその他は知らないしどうでもいい。

非推奨の機能、廃止された機能 – JavaScript | MDN

てか escape や unescape は非推奨になっていたのか。
Tips ページをいくつか書き換えなきゃなぁ面倒だなぁ。
え?今日は公園には行きましたよ、察して。

Bookmarklet

ブログに他サイトのリンクを貼る用のブックマークレット。
長年どこかのサイトで見つけた以下の奴をずっと使っていた。

javascript:var url=location.href;var title=document.title;var link ='<a href="'+url+'" target="_blank">'+title+'</a>';var x = prompt('',link);

これ骨董品の ES5 ではないですか。
2023 年な現在でも問題なく動くけれど、流石に書き換えたいぞ。

それと、この手段では一度プロンプトを出すと二度目が出せないのがたまに困る。
今までページの読み直しで対処していたけどせっかくなのでソレもなんとかしたい。
ということで色々探してみる。

【必見】ブックマークレットの使い方・作り方と便利なおすすめ13選を解説 – WEBCAMP MEDIA

そうか、即時関数にしてしまえばいいんだ!
ということでこう書き換えてみました。

javascript:(()=> {prompt('',`<a href="${location.href}" target="_blank">${document.title}</a>`);})();

テンプレートリテラルなら普通に一行で終わるんだよね。
同一ページで何度でも出せるようになったし、狙いどおり。
まあ、使い勝手は何も変わらないんですけど。

ところで、上記リンク先なように replace で URL を書き換えるのって使えそう。
localhost で編集した自サイトをアップロードしてお知らせをブログでって時。
今まで手書きで URL を書き換えしていたけれど、たまに忘れて再編集とかやったけど。

javascript:(()=> {prompt('',`<a href="${location.href.replace('http://localhost', 'https://palepoli.skr.jp')}">${document.title}</a>`);})();

そんなことまで自動化できるじゃん。
プログラミングとは定形作業の自動化が主体ですからね。