Closed2

Kuromoji.js を Vite で動かす

いなにわうどんいなにわうどん

JavaScript 上で日本語の形態素解析を実現する有名なライブラリに、kuromoji.js が存在する。これを Vite(+ React)の環境下で使用させようとすると、ブラウザのコンソールに以下のエラーが表示される。

(1) development 環境(yarn run dev)にて

Error: invalid file signature:60,33
    at $.g (kuromoji.js?v=6d025e79:1081:44)
    at xhr.onload (kuromoji.js?v=6d025e79:4777:30)

(2) production 環境(yarn run preview)にて

index-DQDeigRE.js:40 Uncaught TypeError: Cannot read properties of undefined (reading 'Gunzip')
    at n.onload (index-DQDeigRE.js:40:120830)
いなにわうどんいなにわうどん

解決策

kuromoji.js は最終更新が 6 年前であり、メンテナンスされていない。したがって、(1), (2) ともにパッチを当てて解決する。

パッチの適用には、patch-package パッケージを使用する。yarn で利用する場合は postinstall-postinstall も併せてインストールする。

yarn add patch-package postinstall-postinstall

package.json に以下の記述を追加。

"scripts": {
  "postinstall": "patch-package"
}

(1) への対処

Vite の public ディレクトリに .gz ファイルを配置すると、データが欠損する現象が報告されている。
そのため、辞書データの拡張子を .gz から .gzip に変更して public ディレクトリに配置する。

また、/node_modules/kuromoji/src/loader/DictionaryLoader.js.gz.gzip に置換する。以下のような記述が 12 箇所あるので、これを全て置換する。

DictionaryLoader.js
- async.map([ "base.dat.gz", "check.dat.gz" ], function (filename, _callback) {
+ async.map([ "base.dat.gzip", "check.dat.gzip" ], function (filename, _callback) {

(2) への対処

Chrome 80 から、gunzip に相当する機能が Compression Streams API として標準で提供されているため、zlibjs の代替としてそちらを利用する。

/node_modules/kuromoji/src/loader/BrowserDictionaryLoader.js の以下の箇所を置換する。

BrowserDictionaryLoader.js
// zlibjs は使用しないのでインポートを削除
- var zlib = require("zlibjs/bin/gunzip.min.js");
 
// zlibjs の代わりに Compression Streams API を用いる
- var gz = new zlib.Zlib.Gunzip(new Uint8Array(arraybuffer));
- var typed_array = gz.decompress();
- callback(null, typed_array.buffer);
+ const ds = new DecompressionStream("gzip");
+ const blob = new Blob([arraybuffer]);
+ const decompressedStream = blob.stream().pipeThrough(ds);
+ new Response(decompressedStream).arrayBuffer().then(buffer => {
+    callback(null, buffer);
+ });

パッチの適用

以下のコマンドを実行してパッチを作成し、kuromoji.js を再インストールする。

yarn patch-package kuromoji
yarn install

パッチ作成時、/patches/kuromoji+0.1.2.patch には以下のファイルが作成される。

kuromoji+0.1.2.patch
kuromoji+0.1.2.patch
diff --git a/node_modules/kuromoji/src/loader/BrowserDictionaryLoader.js b/node_modules/kuromoji/src/loader/BrowserDictionaryLoader.js
index 04bfdcd..302f59d 100644
--- a/node_modules/kuromoji/src/loader/BrowserDictionaryLoader.js
+++ b/node_modules/kuromoji/src/loader/BrowserDictionaryLoader.js
@@ -17,7 +17,6 @@
 
 "use strict";
 
-var zlib = require("zlibjs/bin/gunzip.min.js");
 var DictionaryLoader = require("./DictionaryLoader");
 
 /**
@@ -47,9 +46,13 @@ BrowserDictionaryLoader.prototype.loadArrayBuffer = function (url, callback) {
         }
         var arraybuffer = this.response;
 
-        var gz = new zlib.Zlib.Gunzip(new Uint8Array(arraybuffer));
-        var typed_array = gz.decompress();
-        callback(null, typed_array.buffer);
+        // zlibjs の代わりに Compression Streams API を用いる
+        const ds = new DecompressionStream("gzip");
+        const blob = new Blob([arraybuffer]);
+        const decompressedStream = blob.stream().pipeThrough(ds);
+        new Response(decompressedStream).arrayBuffer().then(buffer => {
+            callback(null, buffer);
+        });
     };
     xhr.onerror = function (err) {
         callback(err, null);
diff --git a/node_modules/kuromoji/src/loader/DictionaryLoader.js b/node_modules/kuromoji/src/loader/DictionaryLoader.js
index 5f88c0b..ed69460 100644
--- a/node_modules/kuromoji/src/loader/DictionaryLoader.js
+++ b/node_modules/kuromoji/src/loader/DictionaryLoader.js
@@ -47,7 +47,7 @@ DictionaryLoader.prototype.load = function (load_callback) {
     async.parallel([
         // Trie
         function (callback) {
-            async.map([ "base.dat.gz", "check.dat.gz" ], function (filename, _callback) {
+            async.map([ "base.dat.gzip", "check.dat.gzip" ], function (filename, _callback) {
                 loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) {
                     if(err) {
                         return _callback(err);
@@ -67,7 +67,7 @@ DictionaryLoader.prototype.load = function (load_callback) {
         },
         // Token info dictionaries
         function (callback) {
-            async.map([ "tid.dat.gz", "tid_pos.dat.gz", "tid_map.dat.gz" ], function (filename, _callback) {
+            async.map([ "tid.dat.gzip", "tid_pos.dat.gzip", "tid_map.dat.gzip" ], function (filename, _callback) {
                 loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) {
                     if(err) {
                         return _callback(err);
@@ -88,7 +88,7 @@ DictionaryLoader.prototype.load = function (load_callback) {
         },
         // Connection cost matrix
         function (callback) {
-            loadArrayBuffer(path.join(dic_path, "cc.dat.gz"), function (err, buffer) {
+            loadArrayBuffer(path.join(dic_path, "cc.dat.gzip"), function (err, buffer) {
                 if(err) {
                     return callback(err);
                 }
@@ -99,7 +99,7 @@ DictionaryLoader.prototype.load = function (load_callback) {
         },
         // Unknown dictionaries
         function (callback) {
-            async.map([ "unk.dat.gz", "unk_pos.dat.gz", "unk_map.dat.gz", "unk_char.dat.gz", "unk_compat.dat.gz", "unk_invoke.dat.gz" ], function (filename, _callback) {
+            async.map([ "unk.dat.gzip", "unk_pos.dat.gzip", "unk_map.dat.gzip", "unk_char.dat.gzip", "unk_compat.dat.gzip", "unk_invoke.dat.gzip" ], function (filename, _callback) {
                 loadArrayBuffer(path.join(dic_path, filename), function (err, buffer) {
                     if(err) {
                         return _callback(err);

以上の修正により、development, production 環境の両方で kuromoji.js が正常に使用可能となる。

このスクラップは6ヶ月前にクローズされました