diff --git a/CHANGELOG.md b/CHANGELOG.md index bb7b741f5..6bb2b081a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # ChangeLog +## 3.16.4 +不具合修正 + * `require()` で末尾の "index" や "index.js" を省略する表記としない表記を混在させた時、スクリプトが複数回評価される問題を修正 + ## 3.16.3 * 3.16.2 の不具合回避のため 3.16.1 と同じ内容にリバート diff --git a/package-lock.json b/package-lock.json index 787b6251d..71ff93bf6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@akashic/akashic-engine", - "version": "3.16.3", + "version": "3.16.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@akashic/akashic-engine", - "version": "3.16.3", + "version": "3.16.4", "license": "MIT", "dependencies": { "@akashic/game-configuration": "~2.0.0", diff --git a/package.json b/package.json index 6154d6dbb..766e84f58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@akashic/akashic-engine", - "version": "3.16.3", + "version": "3.16.4", "description": "The core library of Akashic Engine", "main": "index.js", "dependencies": { diff --git a/src/ModuleManager.ts b/src/ModuleManager.ts index c94511c62..2e1cb0dbb 100644 --- a/src/ModuleManager.ts +++ b/src/ModuleManager.ts @@ -68,53 +68,21 @@ export class ModuleManager { } } - // 1. If X is a core module, - // (何もしない。コアモジュールには対応していない。ゲーム開発者は自分でコアモジュールへの依存を解決する必要がある) - - if (/^\.\/|^\.\.\/|^\//.test(path)) { - // 2. If X begins with './' or '/' or '../' - - if (currentModule) { - if (!currentModule._virtualDirname) - throw ExceptionFactory.createAssertionError("g._require: require from modules without virtualPath is not supported"); - resolvedPath = PathUtil.resolvePath(currentModule._virtualDirname, path); - } else { - if (!/^\.\//.test(path)) throw ExceptionFactory.createAssertionError("g._require: entry point path must start with './'"); - resolvedPath = path.substring(2); - } + if (!resolvedPath) { + resolvedPath = this._resolvePath(path, currentModule); + // 戻り値は先頭に "/" が付くので削除している。( moduleMainScripts を参照して返される値には先頭に "/" は付かない) + if (/^\//.test(resolvedPath)) resolvedPath = resolvedPath.slice(1); + } - if (this._scriptCaches.hasOwnProperty(resolvedPath)) { - return this._scriptCaches[resolvedPath]._cachedValue(); - } else if (this._scriptCaches.hasOwnProperty(resolvedPath + ".js")) { - return this._scriptCaches[resolvedPath + ".js"]._cachedValue(); - } + if (this._scriptCaches.hasOwnProperty(resolvedPath)) { + return this._scriptCaches[resolvedPath]._cachedValue(); + } - // 2.a. LOAD_AS_FILE(Y + X) - if (!targetScriptAsset) targetScriptAsset = this._findAssetByPathAsFile(resolvedPath, liveAssetVirtualPathTable); - // 2.b. LOAD_AS_DIRECTORY(Y + X) - if (!targetScriptAsset) targetScriptAsset = this._findAssetByPathAsDirectory(resolvedPath, liveAssetVirtualPathTable); + // akashic-engine独自仕様: 対象の `path` が `moduleMainScripts` に指定されていたらそちらを参照する + if (moduleMainScripts[path]) { + targetScriptAsset = liveAssetVirtualPathTable[resolvedPath]; } else { - // 3. LOAD_NODE_MODULES(X, dirname(Y)) - // `path` は node module の名前であると仮定して探す - - // akashic-engine独自仕様: 対象の `path` が `moduleMainScripts` に指定されていたらそちらを参照する - if (moduleMainScripts[path]) { - resolvedPath = moduleMainScripts[path]; - targetScriptAsset = liveAssetVirtualPathTable[resolvedPath]; - } - - if (!targetScriptAsset) { - const dirs = currentModule ? currentModule.paths : []; - dirs.push("node_modules"); - for (let i = 0; i < dirs.length; ++i) { - const dir = dirs[i]; - resolvedPath = PathUtil.resolvePath(dir, path); - targetScriptAsset = this._findAssetByPathAsFile(resolvedPath, liveAssetVirtualPathTable); - if (targetScriptAsset) break; - targetScriptAsset = this._findAssetByPathAsDirectory(resolvedPath, liveAssetVirtualPathTable); - if (targetScriptAsset) break; - } - } + targetScriptAsset = this._findAssetByPathAsFile(resolvedPath, liveAssetVirtualPathTable); } if (targetScriptAsset) { @@ -175,7 +143,10 @@ export class ModuleManager { } resolvedPath = PathUtil.resolvePath(currentModule._virtualDirname, path); } else { - throw ExceptionFactory.createAssertionError("g._require.resolve: couldn't resolve the moudle without currentModule"); + if (!/^\.\//.test(path)) { + throw ExceptionFactory.createAssertionError("g._require.resolve: entry point path must start with './'"); + } + resolvedPath = path.substring(2); } // 2.a. LOAD_AS_FILE(Y + X) @@ -234,34 +205,6 @@ export class ModuleManager { return undefined; } - /** - * 与えられたパス文字列がディレクトリパスであると仮定して、対応するアセットを探す。 - * 見つかった場合そのアセットを、そうでない場合 `undefined` を返す。 - * 通常、ゲーム開発者がファイルパスを扱うことはなく、このメソッドを呼び出す必要はない。 - * ディレクトリ内に package.json が存在する場合、package.json 自体もアセットとして - * `liveAssetPathTable` から参照可能でなければならないことに注意。 - * - * @ignore - * @param resolvedPath パス文字列 - * @param liveAssetPathTable パス文字列のプロパティに対応するアセットを格納したオブジェクト - */ - _findAssetByPathAsDirectory(resolvedPath: string, liveAssetPathTable: { [key: string]: OneOfAsset }): OneOfAsset | undefined { - let path: string; - path = resolvedPath + "/package.json"; - const pkgJsonAsset = liveAssetPathTable[path]; - // liveAssetPathTable[path] != null だけではpathと同名のprototypeプロパティがある場合trueになってしまうので hasOwnProperty() を利用 - if (liveAssetPathTable.hasOwnProperty(path) && pkgJsonAsset.type === "text") { - const pkg = JSON.parse(pkgJsonAsset.data); - if (pkg && typeof pkg.main === "string") { - const asset = this._findAssetByPathAsFile(PathUtil.resolvePath(resolvedPath, pkg.main), liveAssetPathTable); - if (asset) return asset; - } - } - path = resolvedPath + "/index.js"; - if (liveAssetPathTable.hasOwnProperty(path)) return liveAssetPathTable[path]; - return undefined; - } - /** * 与えられたパス文字列がファイルパスであると仮定して、対応するアセットの絶対パスを解決する。 * アセットが存在した場合はそのパスを、そうでない場合 `null` を返す。 @@ -297,7 +240,7 @@ export class ModuleManager { if (pkg && typeof pkg.main === "string") { const targetPath = this._resolveAbsolutePathAsFile(PathUtil.resolvePath(resolvedPath, pkg.main), liveAssetPathTable); if (targetPath) { - return "/" + targetPath; + return targetPath; } } } diff --git a/src/__tests__/ModuleSpec.ts b/src/__tests__/ModuleSpec.ts index 096f75879..ca75d7ba0 100644 --- a/src/__tests__/ModuleSpec.ts +++ b/src/__tests__/ModuleSpec.ts @@ -655,6 +655,16 @@ describe("test Module", () => { expect(libraryA.thisModule.loaded).toBe(true); expect(moduleUsesALibraryA).not.toBe(libraryA); + + const keys = Object.keys(manager._scriptCaches); + expect(keys.includes("script/useA.js")).toBeTruthy(); + expect(keys.includes("node_modules/moduleUsesA/index.js")).toBeTruthy(); + expect(keys.includes("node_modules/moduleUsesA/node_modules/libraryA/index.js")).toBeTruthy(); + expect(keys.includes("node_modules/moduleUsesA/node_modules/libraryA/lib/foo/foo.js")).toBeTruthy(); + expect(keys.includes("node_modules/libraryA/index.js")).toBeTruthy(); + // node_modules/libraryA が node_modules/libraryA/index.js として登録されていることを確認 + expect(keys.includes("node_modules/libraryA")).toBeFalsy(); + done(); }); game._startLoadingGlobalAssets(); @@ -906,13 +916,12 @@ describe("test Module", () => { expect(manager._findAssetByPathAsFile("zoo/roo.js", liveAssetPathTable)).toBe(undefined); }); - it("_findAssetByPathDirectory", done => { + it("_resolveAbsolutePathAsDirectory", done => { const game = new Game({ width: 320, height: 320, main: "", assets: {} }); const pkgJsonAsset = game.resourceFactory.createTextAsset("foopackagejson", "foo/package.json"); const liveAssetPathTable = { "foo/root.js": game.resourceFactory.createScriptAsset("root", "/foo/root.js"), "foo/package.json": pkgJsonAsset, - "foo/index.js": game.resourceFactory.createScriptAsset("fooindex", "/foo/index.js"), "bar/index.js": game.resourceFactory.createScriptAsset("barindex", "/bar/index.js"), "zoo/roo/notMain.js": game.resourceFactory.createScriptAsset("zooRooNotMain", "/zoo/roo/notMain.js") }; @@ -920,16 +929,17 @@ describe("test Module", () => { game.resourceFactory.scriptContents = { "foo/package.json": '{ "main": "root.js" }' }; + pkgJsonAsset._load({ _onAssetError: e => { throw e; }, _onAssetLoad: () => { try { - expect(manager._findAssetByPathAsDirectory("foo", liveAssetPathTable)).toBe(liveAssetPathTable["foo/root.js"]); - expect(manager._findAssetByPathAsDirectory("bar", liveAssetPathTable)).toBe(liveAssetPathTable["bar/index.js"]); - expect(manager._findAssetByPathAsDirectory("zoo/roo", liveAssetPathTable)).toBe(undefined); - expect(manager._findAssetByPathAsDirectory("tee", liveAssetPathTable)).toBe(undefined); + expect(manager._resolveAbsolutePathAsDirectory("foo", liveAssetPathTable)).toBe("/foo/root.js"); + expect(manager._resolveAbsolutePathAsDirectory("bar", liveAssetPathTable)).toBe("/bar/index.js"); + expect(manager._resolveAbsolutePathAsDirectory("zoo/roo", liveAssetPathTable)).toBeNull(); + expect(manager._resolveAbsolutePathAsDirectory("hoge", liveAssetPathTable)).toBeNull(); } finally { done(); }