- 投稿日:2020-02-23T22:43:13+09:00
Gatsbyのアクセシビリティテスト用プラグイン「gatsby-plugin-a11y-report」
Gatsbyのアクセシビリティテスト用のプラグインを書きました。
gatsby-plugin-a11y-reportです。もともとGatsbyにはgatsby-plugin-react-a11yというreact-axeを使ったプラグインがあるのですが、その元となったreact-axeがviolationsしか表示してくれないので、incompleteも表示するように書き直したりしているうちに差分が大きくなりすぎたので独立したような感じです。
また、
gatsby develop
時だけでなく、build時にはaxeのレポートを以下のようにlogsディレクトリに出力します。{"title":"Gatsby Build: A11y Check Start","pages":14,"logging":["violations","incomplete"],"ignore":["/404*","/tag/*"],"device":{"name":"Chrome","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/80.0.3987.0 Safari/537.36","viewport":{"width":1280,"height":600,"deviceScaleFactor":1,"isMobile":false,"hasTouch":false,"isLandscape":false}},"level":"info","service":"axe-report","timestamp":"2020-02-23T08:20:12.697Z"} {"path":"/offline-plugin-app-shell-fallback","result":"violations","id":"document-title","impact":"serious","description":"各HTMLドキュメントに空ではない<title>要素が含まれていることを確認します","html":"<html>","target":["html"],"level":"error","service":"axe-report","timestamp":"2020-02-23T08:20:19.766Z"} {"path":"/offline-plugin-app-shell-fallback","result":"violations","id":"html-has-lang","impact":"serious","description":"すべてのHTMLドキュメントにlang属性が存在することを確認します","html":"<html>","target":["html"],"level":"error","service":"axe-report","timestamp":"2020-02-23T08:20:19.767Z"} ~省略~ {"path":"/posts/humane-typography-in-the-digital-age","result":"violations","id":"region","impact":"moderate","description":"ページのすべてのコンテンツがlandmarkに含まれていることを確認します","html":"<div class=\"Post-module--post__footer--1BvmJ\">","target":[".Post-module--post__footer--1BvmJ"],"level":"error","service":"axe-report","timestamp":"2020-02-23T08:20:20.840Z"} {"title":"Gatsby Build: A11y Check Complete","complete":true,"violations":45,"incomplete":3,"level":"info","service":"axe-report","timestamp":"2020-02-23T08:20:20.855Z"}
gatsby build
をGitのpush時などに自動実行しているような場合にviolationsの数をチェックしてCIするのに便利に使えるかなと思います。使い方
インストール
通常のGatsbyプラグイン同様、セットアップしたGatsbyサイトのディレクトリで以下のようにプラグインをインストールします。
Puppeteerとexpress、Winstonを内部で利用しているので結構重いプラグインになっています。npm install --save gatsby-plugin-a11y-reportあるいはYarnを利用してモジュールを追加。
yarn add gatsby-plugin-a11y-reportgatsby.configに設定を追加
インストール出来たらプラグインを利用するようにGatsbyを設定します。
gatsby-config.js
に以下のように設定します。// gatsby-config.js module.exports = { plugins: [ { resolve: 'gatsby-plugin-a11y-report', options: { showInProduction: false, toastAutoClose: false, query: ` { allSitePage( filter: { path: { regex: "/^(?!/404/|/404.html|/dev-404-page/)/" } } ) { edges { node { path } } } } `, ignoreCheck: [ '/404*', '/tag/*' ], serverOptions: { host: 'localhost', port: '8341' }, axeOptions: { locale: 'ja', }, loggingOptions: { result: ['violations', 'incomplete'] } }, }, ], }オプション設定
以下のオプションは
query
以外すべて省略可能です。
showInProduction
デフォルト:
false
トーストとConsoleへの出力をproductionでも行う場合、true
にしてください。(gatsby-react-axe互換。まぁ、一般的にはfalseのまま使うかと思いますが。)
toastAutoClose
デフォルト:
false
gatsby develop
時に表示するトーストを一定時間で自動的に閉じる設定です。
エラーにすぐ気が付くようにviolationsの件数などをトースト表示するようにしたのですが、そもそもトーストがaxeのチェックでコントラスト不足だったり、画面遷移時にうまく消えてくれなかったりアクセシビリティ的によろしくない状況でして、この機能は将来廃止するかデフォルトでは非表示にするなどしようかと考えています。
もう少し調整してみるつもりですが。
query
(必須)GatsbyJSの特徴でもあるGraphQLで、Build時にチェックするサイトのパスを指定します。
filterによりテストでログに残したい対象ページを柔軟に絞り込むことができます。
ignoreCheck
queryのfilterで絞り込めることに気が付かなかったときに作ってしまった機能ですが、正規表現でパスを記述するのこっちのほうが楽な気がするので残してます。作っていきなり後方互換みたいになっていますが、たぶんGraphQL上手な人はいらないやつですが、GraphQLで絞り込んだあとに追加で絞り込みが実行されますので、使い方次第で便利かも。
serverOptions
デフォルト:
{ host: 'localhost', port: '8341' }
プラグインはBuild時にExpressの検証用サーバを立ち上げますが、その検証サーバのホストとポートを指定します。ちなみにプラグインはPuppeteerで検証サーバにアクセスしてaxeのレポートをログとして出力します。
axeOptions
axeのオプションを指定します。
通常はログに出力するレポートの言語を指定します。指定がない場合はブラウザの設定から推測してロケール設定します。
そのほかの指定可能なオプションはaxe-coreのドキュメントを参照してください。
loggingOptions
デフォルト:result: ['violations']
Build時に出力するログの種類を配列で指定します。指定可能なのは'violations', 'incomplete', 'inapplicable', 'passes'
の4つです。デフォルトではviolations
だけ出力します。今後の予定
より柔軟にロギングの設定がしやすいようにしたいのですが、
gatsby-config
の設定が肥大化するのは避けたいので何か方法を検討中。ほかのプラグインでよさげな方法をとっているものがないか調べてみるつもりですが、何か情報をお持ちの方がいれば教えてください。
- 投稿日:2020-02-23T21:58:39+09:00
Reactのアプリを簡単にDocker上のnginxに搭載してみた
目標
Reactのcreate-react-appで作成したアプリをnginxに載せてみました。
簡単にできたのでメモしておきます。Reactアプリの作成
これはcreate-react-appでサクッと作成しましょう。
DockerでReactの環境を作成してみたでDocker上で構築できるはずです。
アプリが作成できたらyarn build
でnginxに載せるファイルを出力しましょう。niginxのDockerfile
nginxのDockerfileです。
yarn build
で出力したファイルとnginxの設定ファイルをコピーしています。FROM nginx:1.17 COPY ./app/build /opt/app/ COPY ./nginx.conf /etc/nginx/nginx.conf CMD ["nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"]nginxの設定ファイル
locationはbuildしたファイルを置く場所に設定しましょう。
nginx.confuser nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 75; server { listen 80; charset utf-8; location /{ root /opt/app/; index index.html; } } }まとめ
あとは、nginxのDockerfileをbuildしてコンテナを立ち上げれば完了です。
- 投稿日:2020-02-23T18:21:24+09:00
dynamic importでnamed exportにする際に躓いた点
named exportする点で行うとエラーになる理由と解決策
理由
- Poor Discoverabilityの回避
- Autocompleteなどで保管性を保ちたい
問題
- export defaultでないためimportする際にエラーになる
解決策
- https://stackoverflow.com/questions/54318485/dynamic-import-named-export-using-webpack
- 投稿日:2020-02-23T18:20:18+09:00
7年間使ってきたWordPressを捨ててContentful+Gatsby+Netlifyにしたら爆速になったし経緯とか教訓とか語る
こんにちは、古都ことと言います。普段はブログやらなんやらをやっているのですが、今回ブログのお引っ越しをしたのでその経緯などについてお話ししたいと思います。
先にまとめ
- ブログをVPSとWordPressで7年間運営してきた
- 速度面やメンテナンス面でそろそろガタがきていた
- Contentful+Gastby+Netlifyの構成に移行した
- Lighthouseで高スコア叩き出せた
- 技術選択って難しいね
運営しているブログ
Subterranean Flower Blogというブログをやってます。
主にフロントエンド周りのことを取り扱っており、たまにマリオ64の記事や、転職の記事などでもバズってます。
7年間頑張ってくれたWordPress
私がブログを始めたのは2013年でした。当時は大学4年生でDartにハマっており、「この良さをなんとか世に広めたい!」という気持ちで記事を書いてました。
採用したのはさくらVPSとWordPressでした。AWSも検討しましたが「インスタンス立てて管理するのめんどい…」という気持ちが先行してVPSになりました。WordPressを選択した理由は深くはありません。ただなんとなくCMSならWordPressかな、と思って採用しました。jekyllも既に登場してはいたのですが、難しそうという理由で採用を見送りました。
当時はページのパフォーマンスには無頓着でしたし、なによりWordPressの使いやすさには助けられました。プラグインを入れるだけでなんでもできて、テーマを差し替えるだけでガラッと見た目が変わるというのは、ブログ運営の素人にとっては天からの恵みでした。
徐々に表面化してくる問題
初めは問題なかったのです。全てがうまく行っており、まったくの幸せでした。ですが徐々に問題が出てきます。
まず問題になったのは管理の大変さでした。VPSなので自分で管理せねばならず、apacheの設定をがんばったり死活監視ツール入れたりLet's Encryptのcron動くようにしたり、難しい部分は少ないですがかなり面倒でした。設定ミスでphp-fpmがメモリ馬鹿食いしてるの発見するまでは定期的に再起動とかしてました。
次にパフォーマンスの問題が出てきました。WordPressでは動的にページを生成しているため、生のまま使うとかなり遅いし、負荷も高くなります。同時アクセス3ケタでもろくに捌けなかったり、スパイクが発生したときにはブログが落ちるなんてことも何度かありました。
最後に問題になったのは記事のポータビリティの低さでした。WordPressにはGutenbergという優秀なWYSIWYGエディタがついているのですが、これが吐き出すHTMLはポータビリティが大して高くないです。また様々なプラグインの介入で更に混沌としたHTMLが吐き出され、WordPressへの依存度がどんどん高くなっていました。
マイナスからゼロへ向かう作業のつらさ
様々な問題に対して場当たり的に対応していました。Let's Encrypt動かすのつらいからmod_md採用したり、PHPの速度が出ないからiniいじったりVPSのスペックあげたり、通信速度出すためにHTTP/2やBrotli導入したり、負荷減らすためにページキャッシュ系のプラグイン入れたり、高速な表示を謳うテーマを導入してみたり。
でもどれも本質的ではないよなと思っていました。私がやりたいのはブログの運営であって泥臭いチューニング作業ではないと感じていました。問題点を発見して弄ってパフォーマンスを計測する技術的な楽しさはあるけど、徐々に疲れてきました。
これらの作業ってマイナスが出発点なんですよね。「証明書が更新されない」「表示が遅い」「サイトごと落ちる」、どれもマイナスです。「できて当たり前」ができていない。よって必然的にマイナスからゼロに戻す作業になります。これらも大切な作業ではあるのですが、個人的にはマイナスからゼロに戻す作業は疲労感の方が勝ります。正直つらかったです。
自分が頑張ってチューニングしたブログより友達が適当に借りてるFC2ブログの方が圧倒的に早いって状況、耐えられなかったです。
移行の検討
「あっ、これもう無理だな」
そう思ったのはここ2年ぐらいです。何度かサイトが落ちて、世界中の技術者がウェブのパフォーマンス改善に注力し始めて、検索エンジンの評価でも速度が重視されて。
運用面も不安でした。ど素人がどんなにがんばったところでセキュリティホールはできてしまうし、何かトラブルのたびにメンテできるかと言われると、正直そこまでのやる気はないとしか言いようがないです。
もう逃げられんな、と。そしてシステムの移行を検討し始めました。
私は「やりたい」と思ってから実際にやるまで1年か2年ぐらいのタイムラグがあるのでだいぶ遅くはなりましたが、今年に入ってからContentful+Gatsby+Netlifyに決めました。できるだけマネージドなサービスを採用し、データのポータビリティを確保しつつページ表示速度を稼ぐ、ということを考えていたらこうなりました。
WordPressを捨てるとはいえ何かしらのCMSは欲しかったのでContentfulを採用しました。コンテンツ管理だけを担当するHeadlessCMSなのでフロントエンド等は後から付け替えられます。WYSIWYGエディタもついているのですが絶対に使わずマークダウンだけを使うようにしました。無料だと5000レコードまでしか使えませんが、そもそもそんなに記事書かないしメディアもアップロードしないので5000も行かないです。実際に移行済の今見てもまだ500レコードも行ってない。
ページの生成には静的サイトジェネレータであるGatsbyを採用しました。前々からjekyll採用しなかったのは後悔していましたし、今度は今までのReactの知見が使えるGatsbyかなと。あとGraphQL触ってみたかったという気持ち。そしてMarkdownなら最悪何かあっても他の形式に変換して脱出しやすいですし、フレームワークにベッタリすることはないのかなと。
デプロイ先はNetlifyです。ここはあんまり深く考えてません。正直自分で管理しなくていいならどこでもいい。
移行作業
WordPressはREST APIが生えてるのでデータをぶっこぬくのは簡単でした。管理画面にはエクスポート機能もあるのですが、WordPressからWordPressへの引っ越し用なので役に立たんです。118記事しかなかったのでAPIからサクっと引っこ抜いてきました。タグやカテゴリも同様に。
メディアの類はsshでログインしてフォルダをzipで固めて落としてきて作業です。WordPressのメディアは
2020/02/23/sample.jpg
みたいな階層構造になってるのでシェルスクリプト書いていったんフラット構造に変換しました。2020_02_23_sample.jpg
みたいなファイルに。またリサイズ済画像も保管されてるので正規表現で適当にフィルタかけて破棄しました。さあデータぶっこぬいた後が大変。WordPressのRESTが返す構造、結構ごちゃごちゃしてます。その中から必要そうなものだけをピックアップするスクリプトをNode.js+JavaScriptで作成。そしてその中のHTMLはそのままでは使えないので、いらない要素やclassなどを削るコードと、メディアへの参照をWordPressのものからContentfulのURLに差し替えるコードを書きました。
Contentful側でのデータモデルは以下のような感じにしました。
compatは互換性を表すための識別子です。移行した記事での値は
sbfl_wp_2013
で固定にしてます。この値が入っていたらHTMLとしてそのままレンダリングするようにしてます。このあたりは完全なる妥協の産物ですが、これ以上のことはちょっと思いつかなかったです。あとはメディアをContentfulにアップロードするスクリプトと、記事をアップロードするスクリプトを書いて実行。118記事中117記事が移行成功です。1記事はContentfulの5万文字制限を超えた7万文字の記事だったので諦めました。
あとはGatsbyでテンプレートをゴリゴリ書いて終わり。
デプロイはGitHubへのpushまたはContentfulのpublishをフックにNetlifyのビルドを動かします。通知はZapier使ってDiscordに投げます(Zapier通さない場合はNetlifyのProプランじゃないとダメでした)。
できた!!
そんなこんなでできました: https://sbfl.net/blog/
ソースはこれです: https://github.com/subterraneanflower/sbfl-site計測!速い!
Lightouseの計測結果は以下のような感じです。
なんと96点。TTIがだいぶ速くなってます。
個別記事ページは以下のスコアでした。
Prism.jsによるコードハイライティングとかあるにもかかわらずだいぶ速いです。Gatsbyいいですね。なおページ遷移も高速で、リンクをクリックすると一瞬で切り替わります。私のブログはほぼ検索流入なのであんまり意味はないですが……。
さいごに
技術選択の重みを知った
2013年の当時は自分のブログが7年続くとか考えてなかったです。とりあえず立ち上げてとりあえず動けばいいやと。そんな中で雑に選んだ技術が徐々に牙を剥き始め、トラブルの対応に追われ、泣きながら別のプラットフォームに逃げ出すことになるとは思ってもいなかったです。
自分では安定している選択肢をとったつもりが、7年も経てば立派な負債となっていました。7年も先のことなんて考えられませんが、考えられるかどうかではなく時代は確実に流れていくのです。どういう理由で選択したにしろ、必ず自分の選択と向き合わなければいけない時がきます。
現実問題としては7年後にもきれいに動作する選択なんて無理なので、逃げたいときにいつでも逃げられるように作っておくのが大事なのかなと思ってます。今回のContentful+Gatsby+Netlifyも、どこかに逃げられるように作っています。ContenfulはWYSIWYGエディタを持っているのですが、それ使うとまた同じはめになるのでMarkdownのみを使います。そしてGatsbyは変換と表示するだけ。加工はしない。Netlifyも固有の機能はほぼ使わずビルドだけです。
失敗しないことよりも、失敗したときにいかに逃げ出せるようにしておくかが重要かもですね。
WordPressにはお世話になりました
なんだかんだ言ってWordPressにはお世話になりました。よくわからなくても管理画面いけばだいたいどうにかなるお手軽さと、プラグインの豊富さには助けられました。テーマのソースいじってのチューニングとかもいろいろやってたので愛着は湧いてました。
今のウェブの思想にはマッチしないなとは思いつつも、そのとっつきやすさとか情報量の多さとかで役立つ部分は多いはずなので、適材適所で頑張って欲しいです。
まとめ
VPS+WordPressで運用していてここ数年間ずっと苦しんでいましたが、なんとか良い感じの形で脱出できました。こっちはこっちでまたいろいろなトラブルあるんだろうなーとは思いつつも、現段階では結構綺麗にまとまったのではないかなと。
苦しみながらここまでずるずる来てしまいましたが、もっと早くに決断すべきだったのかなとも感じています。一度組んだらもうそこから動かしたくないという気持ちが邪魔して何もできなかったのですが、勇気を持ってより迅速な行動ができたほうがいいよなーと実感しました。
実はまだ終わりではなく、VPSにはDiscordのbotを相乗りさせていたのでそれを移行する作業なんかも残っています。こっちはもうちょっと大変な作業になるのですが、頑張ってきます。
- 投稿日:2020-02-23T12:22:42+09:00
wasm(Rust) + Web Audio API + Reactで音を鳴らしてみた
やほー✨アッキーたよ!今回はReactでガワを作ってWeb Audio APIで音を鳴らして見たいと思いまーっす♫ただ鳴らすんじゃつまらないんで、Rustでwasmを書いたりしてみましたっ⚙
準備
まずはこちらを参考に下準備から。
wasm-bindgen + wasm-pack + webpack で フロントエンド - Qiita
ただRustのコードがネスト深くなるのが嫌だったので浅くしてます。
あとは$ yarn add react react-dom
と
$ yarn add -D @types/react @types/react-domしておき、必要なファイルも入れると構成はこんな感じ。
├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── client │ ├── components │ │ ├── App.tsx │ │ ├── Slidebar.tsx │ │ └── Switch.tsx │ ├── index.html │ ├── index.tsx │ └── reducer.ts ├── package.json ├── src │ └── lib.rs ├── tsconfig.json ├── webpack.config.js └── yarn.lock目標
こんな感じの簡素なUI。Playを押すと音がなってスライドバーを動かすと音の大きさが変わる。これを目標にしましょう。Reducer
Reduxは使いませんが、状態管理にReact hooksの
useReducer
を使っていこうかと思います。というわけでReducerはいドーン。client/reducer.tsimport { Oscillator } from '../pkg' export type State = { playing: boolean, gain: number, osc: Oscillator, } export const SwitchPlaying = Symbol("SwitchPlaying") export const ChangeGain = Symbol("ChangeGain") export const StoreOsc = Symbol("StoreOsc") export const switchPlaying = () => ({ type: SwitchPlaying, }) export const changeGain= (payload: number) => ({ type: ChangeGain, payload, }) export const storeOsc = (payload: Oscillator | null) => ({ type: StoreOsc, payload, }) export type Actions = ReturnType< typeof switchPlaying | typeof changeGain | typeof storeOsc> export const initial: State = { playing: false, gain: 0.2, osc: null, } export const reducer = (state: State | null, action: Actions): State => { if(!state) { return initial } switch (action.type) { case SwitchPlaying: if(state.playing) { return {...state, playing: false} } else { return {...state, playing: true} } case ChangeGain: let gain = action.payload if(gain > 1.0) { gain = 1.0 } else if (gain < 0.0) { gain = 0.0 } return {...state, gain: gain} case StoreOsc: if(action.payload){ return {...state, osc: action.payload} } else { return {...state, osc: null} } default: throw new Error('Invalid action type') } }状態として再生中かどうかを表す
playing
、Gainの数値gain
、あとこれは後で説明するのですがWeb Audio APIで使うAudioSourceNode
というやつを格納しておきます。あとは更新するActionsとReducer。UI Components
Switch
再生・停止を切り替えます。
client/components/Switch.tsximport React, {Dispatch} from "react" import { SwitchPlaying, Actions } from "../reducer" type Props = { playing: boolean; dispatch: Dispatch<Actions>; } export const Switch: React.FC = ({playing, dispatch}: Props) => { return( <button onClick={() => dispatch({type: SwitchPlaying})}>{playing ? 'stop' : 'play'}</button> ) }渡された
playing
によって表示を変えてるだけですね。dispatch
を受け取るやり方はここを参考にしました。
useReducerの本質:良いパフォーマンスのためのロジックとコンポーネント設計 - QiitaSlidebar
Gainを調節するやつです。Gainは小数なら何でも入れてよいのですが、大きい数字を入れるとそのまま倍された音が出てきてうるさいので0.0から1.0の値に収める処理を入れてやります。1
client/components/Slidebar.tsximport React, {Dispatch} from "react" import { useState, useCallback } from "react" import {Actions, ChangeGain} from "../reducer" type Props = { gain: number; dispatch: Dispatch<Actions>; } export const Slidebar: React.FC = ({gain, dispatch}: Props) => { const [scale, setScale] = useState(Math.round(gain * 255)) const handler = useCallback( (event: React.ChangeEvent<HTMLInputElement>) => { event.persist() const newScale = Number(event.target.value) setScale(newScale) const newGain = newScale / 255.0 dispatch({type: ChangeGain, payload: newGain}) }, [event] ) return( <label>Gain: <input type="range" min="0" max="255" value={scale} onChange={handler} />[{gain}]</label> ) }App
そしてUIをまとめてAppを作ります。
client/components/App.tsximport * as React from 'react' import { useEffect, useReducer } from 'react' import { Slidebar } from './Slidebar' import { Switch } from './Switch' import { reducer, initial, StoreOsc, SwitchPlaying } from '../reducer' export const App = ({ wasm }) => { const [state, dispatch] = useReducer(reducer, initial) useEffect (() => { if(state.playing){ if(state.osc){ state.osc.set_gain(state.gain) } else { const osc = new wasm.Oscillator() osc.set_gain(state.gain) dispatch({type: StoreOsc, payload: osc}) } } else { if(state.osc){ state.osc.free() dispatch({type: StoreOsc, payload: null}) } } }, [state.playing, state.gain]) return ( <main> <Slidebar gain={state.gain} dispatch={dispatch} /> <Switch playing={state.playing} dispatch={dispatch} /> </main> ) }ここでまとめて
Oscillator
の初期化やgain
の設定、Oscillator
の解放といった副作用を取り扱っています。これらは本来Web Audio APIに存在するものですが、今回はPropsから流し込まれたwasmモジュールからやってきています!2index.tsxとindex.html
index.tsx
ではwasmを遅延ロードし、App
に流し込んで実行しています。client/index.tsximport * as React from "react" import * as ReactDOM from "react-dom" import { App } from "./components/App" import('../pkg').then((wasm) => { ReactDOM.render( <App wasm={wasm}/>, document.getElementById("app") ) })それとあとは
index.html
ですね。client/index.html<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>wasm-audio</title> </head> <body> <div id="app"></div> </body> </html>以上でReact側のコードは終わりとなります。次はRust側です。
wasm with Rust
と言っても
wasm_bindgen
のおかげでかなり書くことは少ないです。use wasm_bindgen::prelude::*; use web_sys::{AudioContext, OscillatorType}; #[wasm_bindgen] pub struct Oscillator { ctx: AudioContext, osc: web_sys::OscillatorNode, gain: web_sys::GainNode, } impl Drop for Oscillator { fn drop(&mut self) { let _ = self.ctx.close(); } } #[wasm_bindgen] impl Oscillator { #[wasm_bindgen(constructor)] pub fn new() -> Result<Oscillator, JsValue> { let ctx = web_sys::AudioContext::new()?; let osc = ctx.create_oscillator()?; let gain = ctx.create_gain()?; osc.set_type(OscillatorType::Sine); osc.frequency().set_value(440.0); gain.gain().set_value(0.0); osc.connect_with_audio_node(&gain)?; gain.connect_with_audio_node(&ctx.destination())?; osc.start()?; Ok(Oscillator { ctx, osc, gain }) } #[wasm_bindgen] pub fn set_gain(&self, gain: f32) { let new_gain = if gain > 1.0 { 1.0 } else if gain < 0.0 { 0.0 } else { gain }; self.gain.gain().set_value(new_gain); } }基本的にやっていることはJavaScriptでやっていることをRustの
web_sys
crateを通じてやっているだけに過ぎません。なのでこれだけだとあまりwasmでやる良さがないのかも。AudioBufferに直接計算した波形を書き込んで処理をして……なんて使い方だと高速に処理できるwasmが光りそうですね!そのあたりも今後実験していきたいところです。