clusterで使うためのjavascript講座(Cluster Scriptじゃないほう)
この記事は「clusterユーザーと創造する非公式 Advent Calendar 2022」の6日目の記事です
Cluster Conference 2022で、Cluster ScriptでJavascriptを用いたギミック開発ができるようになったわけですが、非公式アドベンドカレンダーということで、いつもの平常運転で逆張りをしていこうと思います。
というわけでやっていきましょう。
はじめに
今回の記事は、Cluster Scriptを使わない、逆張りJS講座です。
相当雑ですし、参考にならないですし、参考にしなくていいです。
サンプルがクソコードなので、そこはご容赦ください。
もう一度言います。Cluster Scriptの解説はしません。
というわけで、やっていきましょう。
題して、「clusterのワールドで読めるポスターを返すAPIサーバーをnode.jsで作ろう」です。
ある意味去年のアドベンドカレンダーの記事の続きなので、こちらも読んでね。
今回の目標物
今日の主要都市の天気(18:00~0:00は明日の天気)のポスターをワールドに配置されたURL Textureに返すAPIを作っていきます。
URLにアクセスすると、ポスター画像がpngのblobとして返される
使うもの
とりあえず使うフレームワークとかライブラリです。
- node.js
JavaScriptを実行するためのサーバー - Express.js
Webアプリケーションを作るためのフレームワーク - node-fetch
外部APIをたたくためのライブラリ - puppeteer
ヘッドレスなChromeを実行するためのライブラリ
とりあえず、node.jsのプロジェクトを作る
新規フォルダを作って、ターミナルで
npm init -y
これで、package.jsonというファイルが生成されます。
package.jsonを書く
今回のpackage.jsonです
{
"name": "cluster-whether-news",
"version": "1.0.0",
"description": "whether image api",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.2",
"node-fetch": "^2.6.7",
"puppeteer": "^19.3.0"
}
}
express.jsを使ってみよう
とりあえず、express.jsの使い方を少しだけ。
テキストを返す
//index.js
const express = require('express');
const app = express();
app.get('/', async (req, res) => { //indexページにアクセスしたとき
res.send("hello world"); //テキスト(hello world)をブラウザーに返す
});
// サーバーを起動する
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
これを実行してみましょう。ターミナルで、次のように打つことで実行できます。
node index.js
実行したら、ブラウザーで、http://localhost:3000
にアクセスします。
hello world!
ブラウザーにhello worldと表示されました。
htmlを返す
これは、単純なテキストを送信した例ですが、htmlを送信することで、装飾をすることもできます。
//index.js
const express = require('express');
const app = express();
app.get('/', async (req, res) => {
//htmlを返す
let html = `
<body>
<style>
body {
background-color: antiquewhite;
}
</style>
<h1>Hello World!</h1>
</body>
`;
res.send(html);
});
// サーバーを起動する
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
実行してアクセスすると、黄色っぽい背景に太字でHello World!と表示されました。
画像をblobで返す
もうひとつやってみましょう。
//index.js
const express = require('express');
const app = express();
app.get('/', async (req, res) => {
//base64でエンコードされた画像
let buffer = "iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABrSURBVFhH7daxDcAgDERRk5aWJViGuTwPYhfGSSLFtwAUpPivQdf9wgXpftmG3rvNOWOtueI9igghQogQIoQIIUKIkF9EpDHG1qemlGI551hrkrtvRbTWrNYaaw03IUQIEUKEECFECBEfswegQxBLF8ZfLgAAAABJRU5ErkJggg"
res.writeHead(200, { //ブラウザーに返す形式を宣言
'Content-Type': 'image/png',
'Content-Length': Buffer.byteLength(buffer, 'base64')
});
res.end(buffer, 'base64'); //base64形式で画像を返す
});
// サーバーを起動する
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
実行してアクセスすると、灰色と白のチェッカー模様の画像が表示されます。
//base64でエンコードされた画像
let buffer = "iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABrSURBVFhH7daxDcAgDERRk5aWJViGuTwPYhfGSSLFtwAUpPivQdf9wgXpftmG3rvNOWOtueI9igghQogQIoQIIUKIkF9EpDHG1qemlGI551hrkrtvRbTWrNYaaw03IUQIEUKEECFECBEfswegQxBLF8ZfLgAAAABJRU5ErkJggg"
res.writeHead(200, { //ブラウザーに返す形式を宣言
'Content-Type': 'image/png',
'Content-Length': Buffer.byteLength(buffer, 'base64')
});
res.end(buffer, 'base64'); //base64形式で画像を返す
この部分が肝になっていて、画像データをbase64という文字データに変換(エンコード)して、ブラウザーに返しています。
imgタグを用いたhtmlをブラウザーに返しても画像を表示することができます。
が、URL Textureで使用する場合は、画像をエンコードして返すことで確実に画像を表示できるでしょう。
外部APIから情報を受け取る
外部のAPIから情報を取得してみましょう。
今回は、天気のデータを取得したいので、気象庁のAPIを使用します。
例えば、札幌の天気を取得したい場合、
https://www.jma.go.jp/bosai/forecast/data/forecast/016000.json
試しにブラウザーでアクセスすると、札幌の天気がJSON形式で帰ってきます。
APIを叩くときは、FireFoxでたたくと、JSONが見やすくて便利
では、ブラウザーではなく、node.jsで札幌の天気を取得してみましょう。
const fetch = require('node-fetch');
const getWeather = async (code) => {
const res = await fetch(`https://www.jma.go.jp/bosai/forecast/data/forecast/${code}.json`);
const json = await res.json();
return json;
}
(async () => {
const json = await getWeather("016000");
console.log(json);
})();
node-fetchライブラリを使用して、APIにアクセスし、取得したjsonをコンソールに出力しています。
APIにアクセスするのは一瞬では終わらないので、async/awaitを使って、順次処理するようにしましょう。
コンソールには、
[
{
publishingOffice: '札幌管区気象台',
reportDatetime: '2022-12-07T17:00:00+09:00',
timeSeries: [ [Object], [Object], [Object] ]
},
{
publishingOffice: '札幌管区気象台',
reportDatetime: '2022-12-07T17:00:00+09:00',
timeSeries: [ [Object], [Object] ],
tempAverage: { areas: [Array] },
precipAverage: { areas: [Array] }
}
]
と表示され、取得できていることがわかります。
ポスター画像を作る
データをAPIから取得して、ポスター画像を生成するための、部分の流れとしては、以下のようになります。
- ポスターサイズでhtml、cssを書く
- テキストを外部APIから取得した情報を基にreplaceしたりしておいていく。
- puppeteerで、htmlをレンダリング
- puppeteerでページ全体のスクリーンショットを撮影
本当は、canvasを使って書いた方がいいんでしょうけど、canvasと仲が悪いので、htmlで生成しています。
なんなら、htmlもAdobe XDとプラグインで自動生成して、整形しています。
XDで試作してた時の
1~2は割愛して、3~4の部分はこんな感じです。
const browser = await puppeteer.launch(); //ブラウザーの起動
const page = await browser.newPage(); //ページの作成
await page.setContent(html); //htmlをページにセット
// ページ全体が表示されるように、画像のサイズを指定する
const dimensions = await page.evaluate(() => {
return {
width: document.body.scrollWidth,
height: document.body.scrollHeight
};
});
// htmlをpng形式の画像に変換する
const image = await page.screenshot({
type: 'png',
width: dimensions.width,
height: dimensions.height,
fullPage: true
});
// 画像をブラウザーに返す
res.set('Content-Type', 'image/png');
res.send(image);
await browser.close(); //ブラウザーを閉じる
完成したもの
これらを色々とねれねりして、完成したものが記事の初めに出した画像です。
途中相当クソみたいなコードになっていますが、許してください。実運用はしないの?
サーバーを用意できなかったので、この天気ポスターAPIは実運用はしないです。
Vercelで運用したかったんですけど、puppeteerがどうしてもうまく動きませんでした。
最初からhtmlじゃなくてcanvasと仲良くなってcanvasで実装すれば、Vercelで運用できた気もしたけど疲れました
Discussion