- 投稿日:2021-03-14T23:13:24+09:00
npmの復習
目次
1.この記事の目的
2.概要
3.パッケージ管理システム
4.もしnpmがなかったら
5.npmの使い方
6.便利なコマンド
7.最後に
8.参考記事
1. この記事の目的
npmに関する知識の復習
- もやっとした理解だったため記事を読んで理解を深め言語化する
実務でも使えそうな+αの知識を紹介
- 調べる中で、便利なコマンドなどを学べたので紹介読んでくれる人の理解の手助けになれば
2. 概要
npmとは?
「Node Package Manager」の略。
Node.jsのパッケージ管理を行うツール。Node.js
サーバーサイドで動くJavaScript。
特徴
- 大量のデータ処理が可能
- C10K問題に対応
- Apache HTTP Serverなどウェブサーバーとクライアント通信においてクライアントが約1万台に達するとウェブサーバーのハードウェア性能に余裕があるにもかかわらず、レスポンス機能が大きく下がる問題
- メモリ消費が少ない
- イベントループ(詳細省略)
- 処理速度が速い
- Google V8 JavaScript Engine
- Googleが開発したコンパイルエンジン
- Chromeなどで使用
- 中間コード、インタープリタを搭載せず、最初の実行時からコンパイルされる
- JIT(Just-In-Time Compiler)
- ソフトウェアの実行時にコードのコンパイルを実行。実行速度の向上を図るコンパイラ
- (AOTコンパイラ)
- 事前コンパイラ
- サーバーサイドでJavaScriptが使える
- 「サーバーサイドは新しい言語」ではなく、クライアント側で使用するJavaScriptをそのままサーバーサイドで使用できるため基礎的な書き方は同じ。
パッケージ
あらかじめ用意された便利な機能をまとめたもの
代表例
- Express
- Node.jsのMVCフレームワーク
- promiss、async
- 非同期処理
など
3. パッケージ管理システム
一つの環境で各種ソフトウェアの導入や削除、依存関係を管理するシステムのこと。
機能
主に4つの機能がある!
リポジトリの購読
- インターネット上のパッケージリポジトリを指定し、必要に応じてパッケージリストを更新
ソフトウェアパッケージのインストール・削除
- ソースコードや設定ファイルをパッケージ化してインストール可能
依存関係の解決
- あるパッケージに必要なソフトウェアを自動的にインストールしたり更新することができる
- 同時にインストール、更新できないソフトウェアパッケージのインストールや更新を回避できる
設定管理
- 設定スクリプトを使って自動的に設定を行える
- npmならpackage.json、composerならcomposer.jsonとか
- 競合する機能を持つソフトウェアで、どちらを優先して使うかを設定できる
言われてみたら「確かにな」って感じるけど、言語化するとしっくりくる。
4. もしnpmがなかったら
開発するのに色々手間暇時間がかかります。
→「手間暇かけるのが愛情」が正だとすると、別のところに愛情を使えるようになる
使用するパッケージ、ライブラリなどをいちいち公式サイトなどからダウンロードしないといけない
作ったnpmパッケージをチームで共有したいが共有や更新などの管理などが面倒
つまり、楽に速くコードを書くことができるようになりサービスの開発時間の短縮や他のことに時間をかけられるようになる。
5. npmの使い方
Node.jsのダウンロード
公式サイトからダウンロード。
Node.jsをダウンロードしたらnpmもダウンロードされますバージョンの確認
ダウンロードしたら
terminalnpm --versionでnpmのバージョンを確認します
(npmのパスが通ってなかったら通してください)使用するパッケージをインストールする
ここでは例でjQueryをインストールします
プロジェクトルート配下npm install jquery -g※ 「-g」などのオプションは後述
既にpackage.jsonがあるプロジェクトの場合
package.jsonに記述されているパッケージを復元(インストール)します
(package.jsonはパッケージ管理スクリプトの一つ。詳細は後述)package.jsonがディレクトリnpm installパッケージの更新
- 未更新パッケージの確認
未更新パッケージを確認npm outdated
※パッケージの最新バージョンを使用するにはpackage.jsonに記述が必要※
パッケージを更新する際、いちいちpackage.jsonに内容を確認、記述するのが面倒。
→パッケージのバージョンを一括で最新にしてくれるパッケージnpm-check-updatesが便利!npm-check-updatesをインストールnpm install -g npm-check-updates npm-check-updates -uインストール後、アップデートで最新になるnpm updateこれでnpmパッケージのバージョン管理が楽になる。
オプションについて
たくさんあるけど、この資料で使う分を紹介。
-g
グローバルインストール。どのディレクトリでも使えるようにパッケージをインストールする
(グローバルインストールについては後述)-gnpm install jquery -g--save
package.jsonのdependenciesに依存関係が追加される。
要は「このpackage.jsonが入ったプロジェクトに必要なパッケージ」
(dependenciesについては後述)--savenpm install jquery --save--save-dev
package.jsonのdevDependenciesに依存関係が追加される。
要は「パッケージの開発をするときに必要なパッケージ」。テストするためのパッケージとか。
(devDependenciesについても後述)
ここでnpmの説明から離れた小休憩。
グローバルインストールとローカルインストールの違い
恥ずかしながら、二つの違いをはっきり理解できていなかったため復習。
グローバルインストール
npm installするときに「-g」オプションをつけることで実行可能。
「npm」のルート配下にあるnode_modulesにパッケージがインストールされるため、一度グローバルインストールしたパッケージはどのディレクトリのプロジェクトでも使用することができるローカルインストール
npm installした時に何もオプションを付けなかった時はデフォルトでローカルインストールされる。
インストールされるパッケージの場所はプロジェクトルート配下にあるnode_modules。従って、どのプロジェクトでも使いそうなパッケージをインストールするときはグローバルインストール、既存のプロジェクトを始めるとき(package.jsonが既にあったら)ローカルインストールの方が良い…のか?
※要確認
小休憩になるかわからないがnpmから直接離れてもう一度休憩。
package.jsonのdependenciesとdevDependencies
dependencies
npmパッケージを使うときに必要なパッケージ。
これがないとこのパッケージが動かない。
必要なパッケージ=依存しているパッケージdependenciesはインストールされるnpm install [パッケージ名]これでもインストールされる。
devDependencies
npmパッケージを開発する時に必要なパッケージ。
package.jsonがあるディレクトリでnpm install
をしないとインストールされない。
例えば
「jQuery」のパッケージをnpm install
でインストールした時は「jQuery」を開発する上でのテスト関係のパッケージなどはインストールされない。
(パッケージをテストするためのパッケージ)
パッケージのアンインストール
グローバルインストールされたパッケージのアンインストール
グローバルインストールされたパッケージnpm uninstall jquery -gローカルインストールされたパッケージのアンインストール
ローカルインストールされたパッケージnpm uninstall jquerypackage.jsonのdependenciesから依存関係を削除する場合
依存関係を削除npm uninstall jquery --savepackage.jsonのdevDependenciesから依存関係を削除
devDependenciesから削除npm uninstall jquery --save-devちなみに※uninstallは省略記号も使える
uninstallを省略npm un jquery
6. 便利なコマンド
npm dedupe
パッケージの依存関係を整理してくれる.
例
モジュールBがCに依存
モジュールDがCに依存
- 上記がそれぞれ別々の記述だった場合、記述が重複してバージョンが異なると不整合が起きる
これらを「モジュールB、DがCに依存」というふうに書き換えてくれるのがnpm dedupe
→パッケージをいくつか個別でinstallした後にやっておくと便利?
→npmに依存関係解決してくれる機能が既にあるから今はあまり使わない?david
モジュールの最新安定バージョンをとってきてくれるツール
※依存モジュールの依存モジュールは最新にしないdavidnpm install david -gdavid updateオプションに
-u
を付けたら安定バージョンではない、最新のバージョンをとってきてくれるツール.audit
依存関係でトラブった時にterminalでこのコマンド叩いてくださいって言われるやつ。
依存関係でトラブった時に解決手段を表示してくれる
npm audit依存関係でトラブった時に解決してくれる
npm audit fix
7. 最後に
参考記事を読んで僕なりにまとめてみましたが、もやっとしたところが言語化されて理解がスッキリした…かも。笑
特にグローバルインストールとローカルインストール、dependenciesとdevDependenciesははっきり理解できてなかったからとても勉強になりました。
あと便利コマンドも役に立ちそうだ!積極的に使って身につけていこう。僕が下記の参考記事によって助けられたように
この記事が少しでも誰かの手助けになりますように。
ありがとうございました。
8. 参考記事
- 便利なパッケージ管理ツール!npmとは【初心者向け】
- npmとは
- npm入門
- 【いまさらですが】package.jsonのdependenciesとdevDependencies
- 初めてのnpm パッケージ公開
- NPM / Bower / Composer-違いは?
- npmのあまり知られていない機能10選
Wikipedia
- npm
- パッケージ管理システム
- 投稿日:2021-03-14T22:12:34+09:00
Bootstrapをnodejsで利用するために調べたこと
BootStrap使ってカッコつけたい
今回、掲示板アプリ作成してます。
javascriptとhtmlとcssで出来ないこともないけど、せっかくならプラスオンして新しい技術に触れたいと思い、色々調べた結果bootstrapにたどり着きました。ドキュメントを読んでサラッと使えると思いきや、
豪快につまづいてもがいていました。「webpack」が必要みたい。
今回の引用と参考サイトはこちらです。
神の手が差し出された瞬間です。
とってもわかりやすかった。
最新版で学ぶwebpack 5入門 Bootstrapをバンドルする方法webpackは簡単に言えば、複数のJavaScriptを1つにまとめること。
1つにまとめることができれば、htmlやejsへの適用もシンプルになります^^
javascriptだけではなく、sassファイルやcssファイルもloaderと呼ばれるモジュールをインストールすることで,webpackで1つにまとめることが出来る優れものです。今回、bootstrapをnode.jsに適用するまでの3ステップをメモ
1.モジュールのインストール
npm i -D webpack webpack-cli style-loader css-loader2.bootstrapを動かすためのライブラリをインストール
npm i -S bootstrap jquery popper.jsドキュメントにはjqueryとpopperに依存しているので、言われるがままにインスト
3.webpackの設定と実行
webpack.config.jsにファイルの処理方法を記述します。
ここでは、単純に調理方法を記載していると考えてください。module.exports = { // モード値を production に設定すると最適化された状態で、 // development に設定するとソースマップ有効でJSファイルが出力される mode: "production", module: { rules: [ { // 対象となるファイルの拡張子(cssのみ) test: /\.css$/, // Sassファイルの読み込みとコンパイル use: [ // スタイルシートをJSからlinkタグに展開する機能 "style-loader", // CSSをバンドルするための機能 "css-loader" ], }, ], }, };次に食材を用意します。
srcフォルダー内にindex.jsファイルを作成。
index.jsファイルにはimport文を用いて、Bootstrapを取り込みます。// Bootstrapのスタイルシート側の機能を読み込む import "bootstrap/dist/css/bootstrap.min.css"; // BootstrapのJavaScript側の機能を読み込む import "bootstrap";最後に食材と調理方法が準備できれば早速調理開始です。
npx webpackと実行すると、
'dist'フォルダが生成され、一つになったjsファイルが生成されます。
これを同じようにhtmlまたはejsファイルに取り込みすれば、環境設定は完了です。
bootstrapドキュメントのレイアウトのコピーを貼り付けすれば、使えるようになります。まとめ
かなりざっくりすぎてビックリですが、忘備録として残しておきます。
webpackの記述方法は、後に身につけたいなと思います。
- 投稿日:2021-03-14T15:28:16+09:00
2021年 MacにNode.jsをインストールする - zsh, nodebrew
環境
- MacOS Catalina 10.15.7
- zsh
nodebrew install
ターミナルで以下のコマンドを実行してソースのダウンロードをする
$ curl -L git.io/nodebrew | perl - setup % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 100 24634 100 24634 0 0 12010 0 0:00:02 0:00:02 --:--:-- 150k Fetching nodebrew... Installed nodebrew in $HOME/.nodebrew ======================================== Export a path to nodebrew: export PATH=$HOME/.nodebrew/current/bin:$PATH ========================================
.zshrc
に以下のパスを追加するexport PATH=$HOME/.nodebrew/current/bin:$PATH追加を終えたら適用するためのコマンドを実行しnodebrewのインストールは完了
source ~/.zshrcNode.js install
インストール可能なNode.jsを確認する
$ nodebrew ls-remoteバージョンを指定してインストールする
$ nodebrew install-binary vX.X.Xstable版をinstallしたい場合はこちら
$ nodebrew install-binary stable Fetching: https://nodejs.org/dist/v14.16.0/node-v14.16.0-darwin-x64.tar.gz ############################################################################################### 100.0% Installed successfullynodeがインストールされていることを確認する
$ nodebrew ls v14.16.0 current: none使用するnodeのバージョンを指定する
$ nodebrew use v14.16.0 use v14.16.0nodeが使えるか確認する
$ node -v v14.16.0 $ npm -v 6.14.11
- 投稿日:2021-03-14T15:28:16+09:00
2021年 MacにNode.jsをインストールする - zsh, nodebrew, yarn
環境
- MacOS Catalina 10.15.7
- zsh
nodebrew install
ターミナルで以下のコマンドを実行してソースのダウンロードをする
$ curl -L git.io/nodebrew | perl - setup % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0 100 24634 100 24634 0 0 12010 0 0:00:02 0:00:02 --:--:-- 150k Fetching nodebrew... Installed nodebrew in $HOME/.nodebrew ======================================== Export a path to nodebrew: export PATH=$HOME/.nodebrew/current/bin:$PATH ========================================
.zshrc
に以下のパスを追加するexport PATH=$HOME/.nodebrew/current/bin:$PATH追加を終えたら適用するためのコマンドを実行しnodebrewのインストールは完了
source ~/.zshrcNode.js install
インストール可能なNode.jsを確認する
$ nodebrew ls-remoteバージョンを指定してインストールする
$ nodebrew install-binary vX.X.Xstable版をinstallしたい場合はこちら
$ nodebrew install-binary stable Fetching: https://nodejs.org/dist/v14.16.0/node-v14.16.0-darwin-x64.tar.gz ############################################################################################### 100.0% Installed successfullynodeがインストールされていることを確認する
$ nodebrew ls v14.16.0 current: none使用するnodeのバージョンを指定する
$ nodebrew use v14.16.0 use v14.16.0nodeが使えるか確認する
$ node -v v14.16.0 $ npm -v 6.14.11yarn install
yarnをインストールする
$ npm install -g yarnインストールされているか確認する
$ yarn -v 1.22.10
- 投稿日:2021-03-14T02:17:13+09:00
Fire Storageを監視してサイレントプッシュ通知を送信
概要
Fire Storageに画像ファイルのアップロードがなされるとCloud Functionsを通してサイレントプッシュ通知を送信します。
今更感はありますが、ググってもpayloadの記述に統一性がなかったりして割と苦戦しました
Stack OverFlowの方でも同じ悩みを抱えていた人がいたので参考になれば幸いです。
なお、本記事ではFirebase CLIや証明書周りについては触れません。
下準備ができている前提で進めます。環境
- Xcode 12.4
- Swift 5
- nodejs 12
Fire Storage
デフォルトバケットを使用
クライアント
Capability
TARGETS > CapabilitiesよりBackground ModesとPush Notificationsを有効にします。
Background ModesはRemote notificationsにチェックを入れます。
(バックグラウンドで通知を受け取りたい場合はBackground fetchもチェック)Swift
今回はTopic購読で実装します。
AppDelegate.swiftimport Firebase @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { FirebaseApp.configure() // 1. 通知許可 let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization( options: authOptions, completionHandler: { (granted, error) in // todo }) application.registerForRemoteNotifications() return true } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { // 2. Topic購読 Messaging.messaging().subscribe(toTopic: "hoge") { _ in // todo } } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // 3. 画像ファイルの名称を取り出す guard let imageName = userInfo["imageName"] as? String else { return } print("image name: \(imageName)") } }Cloud Functions
onFinalize関数でバケットに画像がアップロードされたことを検知します。
index.js'use strict'; const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(functions.config().firebase); exports.sendSilentNotificationWithTopic = functions.storage.object().onFinalize((object) => { const topic = 'hoge'; const payload = { data: { imageName: object.name, // 画像名をセット }, }; const options = { contentAvailable: true // サイレントプッシュはtrueにする }; return admin.messaging().sendToTopic(topic, payload, options) .then(function(response) { return console.log("Successfully sent message:", response); }) .catch(function(error) { return console.log("Error sending message:", error); }); });firebase deployを実行してデプロイします。
実行
- Xcodeに実機をつないでRun
- Fire Storageに画像ファイルを追加
Xcodeのコンソールログに画像名が表示されれば成功です。
自分はこれで成功したのですが、失敗したらすいません。。。
- 投稿日:2021-03-14T00:20:39+09:00
Node.jsでglTFモデルを圧縮してthree.jsで読む込む(DRACO/meshoptimizer)
概要
WebGL表現でハイポリ(30K ~ 140K Poly)のglTFモデルを複数使用した際に、転送量やローディングパフォーマンスの観点からできる限りファイルサイズを落としたくて、Node.jsでglTFファイルを圧縮する環境を作ったのでその紹介です。
glTFのファイルサイズを軽量化する方法はいくつかありますが、本記事ではDRACOとmeshoptimizerによる2通りの圧縮方法を紹介します。
環境構築
yarnとNode.jsのバージョンは以下です。
yarn@v1.22.5 node@v12.21.0プロジェクトディレクトリを作成、移動して
package.json
を生成します。$ mkdir gltf-compressor $ cd gltf-compressor $ yarn init -y
src
ディレクトリを作成しglTFを格納します。$ mkdir src今回はサンプルデータとして、mixamoからダウンロードしたアニメーション付きXBOTのモデルがどのくらい軽量化できるか比較してみます。
mixamoからダウンロードできるのは
.fbx
か.dae
形式なので、BlenderでglTF
にエクスポートしておきます。
アニメーション マテリアル 頂点 ポリゴン 1 2 28,312 49,112 gltf-compressor ├── package.json └── + src └── + Capoeira ├── + Capoeira.bin └── + Capoeira.gltf圧縮前のglTFは2.3MBありました。
DRACO圧縮
DRACO圧縮にはgltf-pipelineパッケージを使用します。
gltf-pipeline
fs-extra
glob
path
をインストールし、DRACO圧縮用のJSファイルcompress-draco.js
を作成します。$ yarn add gltf-pipeline fs-extra glob path $ touch compress-draco.jsgltf-compressor ├── + compress-draco.js ├── node_modules │ └── ... ├── package.json ├── src │ └── ... └── yarn.lock
yarn draco
コマンドを実行した際に対応するJSが実行されるように、package.json
にscripts
を追加します。package.json{ "name": "gltf-compressor", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "draco": "node compress-draco.js" }, "dependencies": { "fs-extra": "^9.1.0", "glob": "^7.1.6", "gltf-pipeline": "^3.0.2", "path": "^0.12.7" } }Node.jsでDRACO圧縮をするコードです。
compress-draco.jsconst glob = require('glob'); const fs = require('fs-extra'); const path = require('path'); const gltfPipeline = require('gltf-pipeline'); const srcDir = 'src'; const distDir = 'dist'; /** * glTFをDRACO圧縮 * @param {string | string[]} globs */ const compressGltfWithDraco = (globs) => { glob(globs, async (err, files) => { if (err) return; for (const file of files) { const filePath = path.resolve(file); const gltf = fs.readJsonSync(filePath);// gltfのJSONを読み込む const options = { resourceDirectory: path.dirname(filePath),// gltfのリソースディレクトリ(親フォルダ) dracoOptions: { compressionLevel: 10 }// DRACO圧縮率MAX }; const { glb } = await gltfPipeline.gltfToGlb(gltf, options);// gltf -> glb const outFilePath = filePath.replace('.gltf', '-draco.glb').replace(srcDir, distDir);// 出力先 await fs.mkdirp(path.dirname(outFilePath));// distディレクトリがなかったら作成 await fs.writeFileSync(outFilePath, glb);// glbファイル出力 console.log(`[draco] ${ outFilePath }`); } }); }; compressGltfWithDraco(`./${ srcDir }/**/*.gltf`);
glob
でsrc
フォルダ内にある.gltf
ファイルの配列を取得し、.gltf
をJSON
として読み込ませたものとオプションをgltfPipeline.gltfToGlb()
に渡して圧縮します。glTFを圧縮しているのは以下の部分です。
const options = { resourceDirectory: path.dirname(filePath),// gltfのリソースディレクトリ(親フォルダ) dracoOptions: { compressionLevel: 10 }// DRACO圧縮率MAX }; const { glb } = await gltfToGlb(gltf, options);// gltf -> glb用意しておいたコマンドを実行してDRACO圧縮をかけます。
$ yarn draco
dist
ディレクトリにDRACO圧縮後の.glb
ファイルが出力されます。gltf-compressor ├── compress-draco.js ├── + dist │ └── + Capoeira │ └── + Capoeira-draco.glb ├── node_modules │ └── ... ├── package.json ├── src │ └── Capoeira │ ├── Capoeira.bin │ └── Capoeira.gltf └── yarn.lock
Before After 圧縮率 2.3MB 804KB -64.79% three.jsで読み込む
GLTFLoader.setDracoLoader()
にDRACOLoader
のインスタンスを渡して読み込みます。
DRACO圧縮のデコードに必要なdraco_decoder.js
draco_decoder.wasm
がthree/examples/js/libs/dracoに用意されているので、自分のサーバーにコピーしてDRACOLoader.setDecoderPath()
でパスを指定する必要があります。参考:https://threejs.org/docs/index.html#examples/en/loaders/GLTFLoader
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('/path/to/draco_decoder/'); const loader = new GLTFLoader(); loader.setDRACOLoader(dracoLoader); loader.load('Capoeira-draco.glb', (gltf) => { scene.add(gltf.scene); });meshopt圧縮
meshopt圧縮にはgltfpackパッケージを使用します。
gltfpack
をインストールして、meshopt圧縮用のJSファイルcompress-meshopt.js
を作成します。$ yarn add gltf-pack $ touch compress-meshopt.jsgltf-compressor ├── compress-draco.js ├── + compress-meshopt.js ├── dist │ └── ... ├── node_modules │ └── ... ├── package.json ├── src │ └── ... └── yarn.lock
yarn meshopt
コマンドを実行した際に対応するJSが実行されるように、package.json
にscripts
を追加します。package.json{ "name": "gltf-compresser", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "draco": "node compress-draco.js", "meshopt": "node compress-meshopt.js" }, "dependencies": { "fs-extra": "^9.1.0", "glob": "^7.1.6", "gltf-pipeline": "^3.0.2", "gltfpack": "^0.15.2", "path": "^0.12.7" } }Node.jsでmeshopt圧縮をするコードです。
compress-meshopt.jsconst cp = require('child_process'); const glob = require('glob'); const gltfPack = require('gltfpack'); const fs = require('fs-extra'); const path = require('path'); const srcDir = 'src'; const distDir = 'dist'; const paths = { 'basisu': process.env['BASISU_PATH'], 'toktx': process.env['TOKTX_PATH'] }; /** * gltfpack用インターフェース */ const gltfPackInterface = { read: (path) => { return fs.readFileSync(path); }, write: (path, data) => { fs.writeFileSync(path, data); }, execute: (command) => { // perform substitution of command executable with environment-specific paths const pkv = Object.entries(paths); for (const [k, v] of pkv) { if (command.startsWith(k + ' ')) { command = v + command.substr(k.length); break; } } const ret = cp.spawnSync(command, [], { shell: true }); return ret.status == null ? 256 : ret.status; }, unlink: (path) => { fs.unlinkSync(path); } }; /** * compress gltf -> glb * @param {string} inputPath * @param {string} outputPath * @return {Promise<string>} */ const packGltf = (inputPath, outputPath) => { const output = outputPath || inputPath.replace('.gltf', '.glb'); const command = `-i ${ inputPath } -o ${ output } -cc`;// コマンドライン引数(必要に応じてオプションを追加) const args = command.split(/\s/g);// コマンドライン引数の配列 return gltfPack.pack(args, gltfPackInterface).catch(err => { console.log(err.message); }); }; /** * glTFをmeshoptimizer圧縮 * @param {string | string[]} globs */ const compressGltfWithMeshopt = (globs) => { glob(globs, async (err, files) => { if (err) return; for (const file of files) { const filePath = path.resolve(file); const outFilePath = filePath.replace('.gltf', '-meshopt.glb').replace(srcDir, distDir);// 保存先 await fs.mkdirp(path.dirname(outFilePath));// distディレクトリがなかったら作成 await packGltf(filePath, outFilePath);// gltf -> glb console.log(`[meshopt] ${ outFilePath }`); } }); }; compressGltfWithMeshopt(`./${ srcDir }/**/*.gltf`);glTFひとつを圧縮する
packGltf
関数を用意して、glob
で取得したファイルリストに対して回しています。
gltfPackInterface
の部分はgltfpack/cli.jsの実装から引っ張ってきたので、なぜこういう実装になっているかわかりません。(拡張性をもたせるため?)const packGltf = (inputPath, outputPath) => { const output = outputPath || inputPath.replace('.gltf', '.glb'); const command = `-i ${ inputPath } -o ${ output } -cc`;// コマンドライン引数(必要に応じてオプションを追加) const args = command.split(/\s/g);// コマンドライン引数の配列 return gltfPack.pack(args, gltfPackInterface).catch(err => { console.log(err.message); }); };用意しておいたコマンドを実行してmeshopt圧縮をかけます。
$ yarn meshopt
dist
ディレクトリにmeshopt圧縮後の.glb
ファイルが出力されます。gltf-compressor ├── compress-draco.js ├── compress-meshopt.js ├── dist │ └── Capoeira │ ├── Capoeira-draco.glb │ └── + Capoeira-meshopt.glb ├── node_modules │ └── ... ├── package.json ├── src │ └── Capoeira │ ├── Capoeira.bin │ └── Capoeira.gltf └── yarn.lock
Before After 圧縮率 2.3MB 247KB -89.16% three.jsで読み込む
GLTFLoaderのDocumentationには記載されていませんが、GLTFLoader.setMeshoptDecoder()というメンバ関数が用意されていて、デコーダーもthree.js内に含まれています。
こちらは
GLTFLoader.setMeshoptDecoder()
にMeshoptDecoder
をそのまま渡します。参考:https://threejs.org/examples/#webgl_loader_gltf_compressed
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js'; const loader = new GLTFLoader(); loader.setMeshoptDecoder(MeshoptDecoder); loader.load('Capoeira-meshopt.glb', (gltf) => { scene.add(gltf.scene); });比較
圧縮形式 Before After 圧縮率 DRACO 2.3MB 804KB -64.79% meshoptimizer 2.3MB 247KB -89.16% 今回のサンプルモデルだけで比較すると
DRACO
よりmeshoptimizer
のほうが圧縮率が高かったです。
元ファイルのサイズが2.3MBもあったのに247KBまで落とせるというのはWebパフォーマンス的にもかなりありがたいですね。いくつかモデルを圧縮して比較した印象では、モデルの作り方やジオメトリの構造によってはDRACO圧縮のほうがサイズを落とせる場合があったり、meshopt圧縮をすると一部のメッシュが破綻することがあったので、どちらか一択という訳にはいきませんでした。
使用するモデルの中身に応じて圧縮形式を適宜使い分ける必要がありそうです。Links
gltf-pipeline
gltfpack
gltf-transform(試してないが多機能そうなnpmパッケージ)
RapidCompact(ポリゴン数やテクスチャサイズなどを自動で最適化してくれるオンラインサービス)