JavaScriptの可能性を大きく広げるDeno、現在公式サイトは Next.js フレームワークを使って構築されたオープンソース。

このように、誰でもGitHubからダウンロードやクローンして、自分の環境で動かしてみることができる。

npm i npm run dev

しばらく待って「event - compiled successfully」と出たら、「http://localhost:3000」を開く。そう、DenoのwebサイトはまだNodeで動いている。 Deno x React や、何か新しいフレームワークが誕生するでしょう。


denoland/deno_website2: deno.land website
モダンなwebサイトづくり、Denoのように熱くて若いプロジェクトの公式サイトを参考にするのは生きた学習教材としてオススメです!


Denoのトップページのサンプルコード。インデントが間違っていることが気になって、Issueを書き、プルリクを送ってみたところ、コードは不採用ながら修正は完了。 こうして世界中、みんなで協力して作っていくのがオープンソースのステキなところ。


さばえマスクポスト」(src on GitHub)
日本政府から届くマスク、不要な人の分を回収して寄付するプロジェクトが鯖江でスタート。

こちら Code for Sabae のオープンソース。シンプルなHTMLとCSSのみ。日本語でOK、失敗歓迎、礼儀作法気にしなくてOK。気軽に参加して、世界を動かすビッグプロジェクトへの予行練習にお使いください。

お気に入りのDeno(ディーノ)、WebSocketを使ったチャットづくりなど、いい感じですが、Nodeで作った既存プロジェクトとの並行はしばらく続きます。 そこで、DenoとNodeに両対応する方法を整理するのを兼ねて、世界で2番目に古いというプログラミング言語、LISPのS式っぽいものを作ってみました。

const n = S.eval([ S.PLUS, 1, [ S.MUL, 2, 3 ] ])

LISPの特徴はツリー構造(LISPでは、S式という)をそのままプログラムとして使うところ。上記、JavaScriptの配列で表現したS式を評価をするサンプルです。


minimum S, src on GitHub
プログラムはとってもシンプル。四則演算とIF文しかありません。興味があれば,forkして、いろいろ機能を追加するなどして遊んでみてください。 (import.metaとglobalThis.processを使い、Deno/Nodeでの直接起動時に説明表示)

ブラウザ上や、Denoでは、import文を使って、簡単に組み込めます(Nodeの場合ダウンロードしてファイルとして使用)

import S from 'https://taisukef.github.io/minimum_S/S.mjs' console.log('S.eval([S.PLUS, 1, 2]) is ' + S.eval([S.PLUS, 1, 2]))

deno test で簡単テストな S_test.ts が走ります。TypeScriptが基本のDenoはテストコードの拡張子はtsとします。 TypeScriptはJavaScriptの上位互換のプログラミング言語なので、拡張子を変えるだけでOKです。

Denoに刺激されてか、Node.jsも試験的実装ながら関数外でも await が使えるように!

const sleep = msec => new Promise(resolve => setTimeout(resolve, msec)) await sleep(3000)

指定された数分ミリ秒待つ関数 sleep を定義して、3000 = 3秒待つ。Nodeで普通に動かそうとすると「SyntaxError: Unexpected reserved word」とエラーになりますが、Node v14.3.0 で下記のように指定すれば、deno同様、動きます。 ただし、deno/node共に対話型アプリ上や、ブラウザ上では引き続き使えないので注意!(src on GitHub)

node --experimental-top-level-await sleep_test.mjs

Denoでの場合

deno run test.mjs

JavaScriptなど、最近のプログラミング言語には便利表記方法がいろいろ増えているので、少しずつ使える幅を増やすと楽できます。 例えば、=> は、関数定義のちょっと変わった書き方、reduceは配列の中身全部に対して処理して一つの値をだすものです。

sleepは、下記のようにも書けます。お好みでどうぞ!(thisを使う時は => と function で挙動が違うので注意)

async function sleep (msec) { return new Promise(function (resolve, reject) { setTimeout(resolve, msec) }) }

Deno情報を調べると気になる、汁なし担々麺「DENO」(こちらの読みは、での)

ファミマで販売中とのことで、こちらも試食!もっちりした麺が良い感じでした!札幌発、行くことがあればぜひ実店舗も巡礼したいところ。(東京、新潟、福島にもあった!)

links
- DenoとWebScoketで作る匿名チャットサーバー&webアプリ

Javaで作ったサーバーサイドのプログラム、少しずつリファインしつつDenoへ移行していきます。 まずはジェネラルにチャットサーバー。WebSocketベースで軽量実装!

AnonymousChat in deno」(src on GitHub)
部屋を作ってリアルタイムにやりとりできるだけのシンプルな匿名チャット。SSL化に別途調査が必要なので、テスト的に立ち上げています。

部屋があって、ユーザー管理する、オーソドックスなサーバー設計にしつつ、シンプルに匿名チャットとして作ったもの。「thecodeholic/deno-websocket-chat: Realtime Chat App with Deno and WebSockets」が参考になりました。Node.js風fsを使ったwebサーバーを除いて200行ほど。(server.mjs for Deno)

import { listenAndServe } from 'https://deno.land/std/http/server.ts' import { acceptWebSocket, acceptable, isWebSocketCloseEvent } from 'https://deno.land/std/ws/mod.ts' import { v4 } from 'https://deno.land/std/uuid/mod.ts' import { serveWeb } from 'https://taisukef.github.io/denolib/webserver.mjs' import dotenv from 'https://taisukef.github.io/denolib/dotenv.mjs' dotenv.config() const PORT = parseInt(Deno.env.get('PORT')) || 3000 class House { constructor () { this.rooms = new Map() this.users = new Map() } async serve (port) { listenAndServe({ port }, async req => { if (req.method === 'GET' && req.url === '/ws') { if (acceptable(req)) { const wsreq = { conn: req.conn, bufReader: req.r, bufWriter: req.w, headers: req.headers } const wsock = await acceptWebSocket(wsreq) await this.accept(wsock) } } else { serveWeb(req) } }) console.log(`started on port ${PORT}`) } async accept (ws) { let user = null for await (const data of ws) { if (isWebSocketCloseEvent(data)) { // code: 1001 break } const event = JSON.parse(data) const e = event.event if (e === 'login') { user = this.users.get(event.id) if (user) { if (user.passcode !== event.passcode) { await ws.send(JSON.stringify({ event: 'error', data: 'please login' })) } else { user.attachWebSocket(ws) } } if (!user) { user = new User(ws, this.getValidName(event.name)) this.users.set(user.id, user) } user.sendOne(ws, user.getData()) } else if (user === null) { await ws.send(JSON.stringify({ event: 'error', data: 'please login' })) } else if (e === 'create') { let room = this.rooms.get(event.room) if (room) { await user.sendOne(ws, { event: 'error', data: 'already exists' }) continue } room = new Room(event.room, user) this.rooms.set(event.room, room) user.send(room.getData()) } else if (e === 'enter') { const room = await this.getRoom(user, event.room) if (!room) { continue } if (room.users.indexOf(user) < 0) { room.users.push(user) const rep = { event: 'enter', data: { id: user.id, name: user.name } } room.users.forEach(u => u.send(rep)) } user.sendOne(ws, room.getData()) } else if (e === 'leave') { const room = await this.getRoom(user, event.room) if (!room) { continue } const n = room.users.indexOf(user) if (n < 0) { await user.sendOne(ws, { event: 'error', data: 'not member' }) continue } const rep = { event: 'leave', data: { id: user.id, name: user.name } } room.users.forEach(u => u.send(rep)) room.users.splice(n, 1) } else if (e === 'message') { const room = await this.getRoom(user, event.room) if (!room) { continue } if (room.users.indexOf(user) < 0) { await user.sendOne(ws, { event: 'error', data: 'not member' }) continue } // special command if (event.data.text === '/clearall') { room.messages = [] return } const message = { fromid: user.id, name: user.name, data: event.data } // console.log(room, room.messages) room.messages.push(message) const e = { event: 'message', data: { room: room.name, message } } room.users.forEach(u => u.send(e)) } else { await user.sendOne(ws, { event: 'error', data: 'unknown event' }) } } } async getRoom (user, rname) { const room = this.rooms.get(rname) if (!room) { await user.send({ event: 'error', data: 'not found ' + rname }) return null } return room } getValidName (name) { if (name == null) { name = '' } name = name.toString().trim() if (name.length === 0) { name = 'no name' } for (;;) { let flg = false this.users.forEach(v => { flg = flg || v.name === name }) if (!flg) { break } name += '\'' } return name } } class Room { constructor (name, owner) { this.id = v4.generate() this.name = name this.owner = owner this.users = [owner] this.messages = [] } getData () { return { event: 'room', data: { id: this.id, room: this.name, users: this.users.map(u => { return { id: u.id, name: u.name } }), messages: this.messages } } } } class User { constructor (ws, name) { this.ws = [ws] this.name = name this.id = v4.generate() this.passcode = v4.generate() this.rooms = [] } async sendOne (ws, json) { try { ws.send(JSON.stringify(json)) } catch (e) { } } async send (json) { let remflg = false for (let i = 0; i < this.ws.length; i++) { try { this.ws[i].send(JSON.stringify(json)) } catch (e) { this.ws[i] = null remflg = true // console.log('send', e) } } if (remflg) { this.ws = this.ws.filter(w => w) } } attachWebSocket (ws) { this.ws.push(ws) } getData () { return { event: 'user', data: { id: this.id, passcode: this.passcode, name: this.name, rooms: this.rooms.map(r => r.name) } } } } const house = new House() house.serve(PORT)

コミュニケーションツールも、ネットワークゲームもベースは一緒!

dotenv, fs, webserver など、Denoに使うちょっとしたライブラリは「taisukef/denolib」こちらでまとめて管理していきます。

JavaScriptでサーバーサイドもサクサク書けて便利な Node.js。 jsからmjsへ移行したもののブラウザ上のコードとの互換性が無いことにと、プロジェクトごとに増える node_modules が気になっていたところ、Node.jsの作者 Ryan Dahl 氏自身による Deno を発見。 いい感じだったので「PUSHかんたんオープンデータ」を移行してみました。


福井県施設 オープンデータ - PUSHかんたんオープンデータ」(src on GitHub)
バックエンドのエンジン変更なので、見た目は変わりません。 それでは寂しいので、複数の施設をひとまとめにする「集約オープンデータ」に対応! 複数の施設や店舗をひとまとめにしたオープンデータリンクを自由に作成できます。追加、削除、順番変更も思いのまま!


福井県、県有施設等の混雑状況をオープンデータとして公開:民間有志が同データを利用して混雑状況の一覧をスマートフォン等からも閲覧可能なダッシュボードを作成し公開 | カレントアウェアネス・ポータル
国立国会図書館さん、中日新聞さんにご紹介いただきました!オープンデータで切り拓く、ライトな官民連携の仕組み。ぜひ他の地域でもどうぞ!
県有施設の混雑状況をオープンデータに 出発前に確認、スマホでOK:福井:中日新聞(CHUNICHI Web)


Deno
恐竜アイコンがかわいい、Deno(ディーノ)は、Node.js作者が反省を踏まえて開発した、webフレンドリーなもうひとつのJavaScriptバックエンドエンジンです。ブラウザとの互換性の高さと、package.json不要で、URLでimportし、適宜キャッシュしてくれるパッケージ管理システムが特徴です。 (Denoの登場でNode.jsの時代は終わるのか? - Qiita

Node.js 用プログラムからの移行、ファイルアクセスの標準モジュール fs を必要なものだけ独自に用意。(node_fs.mjs on GitHub)

const fs = {} fs.readFileSync = function (fn) { const data = Deno.readFileSync(fn) const decoder = new TextDecoder('utf-8') const s = decoder.decode(data) return s } fs.writeFileSync = function (fn, s) { const d = new TextEncoder('utf-8').encode(s) Deno.writeFileSync(fn, d) } fs.appendFileSync = function (fn, s) { const d = new TextEncoder('utf-8').encode(s) Deno.writeFileSync(fn, d, { append: true }) } fs.readdirSync = function (dirn) { return Deno.readDirSync(dirn) } fs.mkdirSync = function (dirn) { return Deno.mkdirSync(dirn) } export default fs

あとは、expressからDenoのhttpに移植して、完了!(push_deno.mjs on GitHub)

セットアップに npm install はもういりません!下記の起動コマンドで即実行!(ファイルアクセスやネットワークアクセスを許可する -A オプションが必要です)

deno run -A push_deno.mjs

mjs x Deno の組み合わせで作ってみることにします!

Tweet
クリエイティブ・コモンズ・ライセンス
この作品は「Creative Commons — CC BY 4.0」の下に提供されています。
CC BY / @taisukef / アイコン画像 / プロフィール画像 / RSS