diff --git a/lib/loader.js b/lib/loader.js index 51c8144..fc56041 100644 --- a/lib/loader.js +++ b/lib/loader.js @@ -1,4 +1,5 @@ const path = require('path'); +const url = require('url'); class Loader { constructor(options) { @@ -11,16 +12,7 @@ class Loader { load(modulePath) { if ((this.alwaysImport && !modulePath.endsWith('.json')) || modulePath.endsWith('.mjs')) { - let importSpecifier; - - if (modulePath.indexOf(path.sep) === -1 && modulePath.indexOf('/') === -1) { - importSpecifier = modulePath; - } else { - // The ES module spec requires import paths to be valid URLs. As of v14, - // Node enforces this on Windows but not on other OSes. On OS X, import - // paths that are URLs must not contain parent directory references. - importSpecifier = `file://${this.resolvePath_(modulePath)}`; - } + const importSpecifier = this.resolveImportSpecifier_(modulePath); return this.import_(importSpecifier) .then( @@ -47,6 +39,25 @@ class Loader { }); } } + + resolveImportSpecifier_(modulePath) { + const isNamespaced = modulePath.startsWith('@'); + const isRelative = modulePath.startsWith('.'); + const hasExtension = /\.[A-Za-z]+/.test(modulePath); + + const resolvedModulePath = hasExtension || isRelative + ? this.resolvePath_(modulePath) + : modulePath; + + if (isNamespaced || ! hasExtension) { + return resolvedModulePath; + } + + // The ES module spec requires import paths to be valid URLs. As of v14, + // Node enforces this on Windows but not on other OSes. On OS X, import + // paths that are URLs must not contain parent directory references. + return url.pathToFileURL(resolvedModulePath).toString(); + } } function requireShim(modulePath) { diff --git a/spec/loader_spec.js b/spec/loader_spec.js index 2164abd..9240860 100644 --- a/spec/loader_spec.js +++ b/spec/loader_spec.js @@ -1,4 +1,5 @@ const path = require('path'); +const url = require('url'); const Loader = require('../lib/loader'); describe('loader', function() { @@ -55,6 +56,21 @@ describe('loader', function() { expect(importShim).toHaveBeenCalledWith('some-module'); }); + it('imports namespaced modules', async function() { + const payload = {default: {}}; + const requireShim = jasmine.createSpy('requireShim'); + const importShim = jasmine.createSpy('importShim') + .and.returnValue(Promise.resolve(payload)); + const loader = new Loader({requireShim, importShim}); + loader.alwaysImport = true; + + const result = await loader.load('@namespace/some-module'); + + expect(result).toBe(payload.default); + expect(requireShim).not.toHaveBeenCalled(); + expect(importShim).toHaveBeenCalledWith('@namespace/some-module'); + }); + it('uses require to load JSON files', async function() { const requireShim = jasmine.createSpy('requireShim') .and.returnValue(Promise.resolve()); @@ -101,6 +117,19 @@ describe('loader', function() { expect(importShim).not.toHaveBeenCalled(); }); + it('loads namespaced commonjs module', async function () { + const requireShim = jasmine.createSpy('requireShim') + .and.returnValue(Promise.resolve()); + const importShim = jasmine.createSpy('importShim'); + const loader = new Loader({requireShim, importShim}); + loader.alwaysImport = false; + + await expectAsync(loader.load('@namespace/some-module')).toBeResolved(); + + expect(requireShim).toHaveBeenCalledWith('@namespace/some-module'); + expect(importShim).not.toHaveBeenCalled(); + }); + it('propagates the error when import fails', async function () { const underlyingError = new Error('nope'); const requireShim = jasmine.createSpy('requireShim') @@ -135,18 +164,18 @@ function esModuleSharedExamples(extension, alwaysImport) { expect(requireShim).not.toHaveBeenCalled(); expect(resolvePath).toHaveBeenCalledWith(requestedPath); - expect(importShim).toHaveBeenCalledWith('file:///the/path/to/the/module'); + expect(importShim).toHaveBeenCalledWith(url.pathToFileURL('/the/path/to/the/module').toString()); await expectAsync(loaderPromise).toBePending(); resolve({}); await expectAsync(loaderPromise).toBeResolved(); } - + it('loads the file as an es module', async function () { await testBasicEsModuleLoading(path.sep); }); - + it('supports /-separated paths', async function() { await testBasicEsModuleLoading('/'); });