diff --git a/src/koa-node-resolve.ts b/src/koa-node-resolve.ts
index 26540de..5496ad5 100644
--- a/src/koa-node-resolve.ts
+++ b/src/koa-node-resolve.ts
@@ -18,7 +18,7 @@ import {parse as parseURL} from 'url';
import {moduleSpecifierTransform, ModuleSpecifierTransformOptions} from './koa-module-specifier-transform';
import {prefixedLogger} from './support/logger';
import {Logger} from './support/logger';
-import {noLeadingSlashInURL} from './support/path-utils';
+import {noLeadingSlashInURL, resolvePathPreserveTrailingSlash} from './support/path-utils';
import {resolveNodeSpecifier} from './support/resolve-node-specifier';
export {Logger} from './support/logger';
@@ -40,7 +40,7 @@ export const nodeResolve =
return moduleSpecifierTransform(
(baseURL: string, specifier: string, logger: Logger) =>
resolveNodeSpecifier(
- resolvePath(
+ resolvePathPreserveTrailingSlash(
resolvePath(options.root || '.'),
noLeadingSlashInURL(parseURL(baseURL).pathname || '/')),
specifier,
diff --git a/src/support/path-utils.ts b/src/support/path-utils.ts
index d2b661d..f039887 100644
--- a/src/support/path-utils.ts
+++ b/src/support/path-utils.ts
@@ -11,7 +11,7 @@
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
-import {posix, sep as pathSeparator} from 'path';
+import {posix, resolve as resolvePath, sep as pathSeparator} from 'path';
const filenameRegex = process.platform === 'win32' ? /[^\\]+$/ : /[^\/]+$/;
@@ -35,6 +35,9 @@ export const forwardSlashesOnlyPlease = (path: string): string =>
export const getBaseURL = (href: string): string => href.replace(/[^\/]+$/, '');
+export const noTrailingSlashInPath = (path: string): string =>
+ path.replace(/\/$/, '');
+
export const noLeadingSlashInURL = (href: string): string =>
href.replace(/^\//, '');
@@ -42,3 +45,12 @@ export const relativePathToURL = (from: string, to: string): string =>
ensureLeadingDotInURL(posix.relative(
getBaseURL(forwardSlashesOnlyPlease(from)),
forwardSlashesOnlyPlease(to)));
+
+export const resolvePathPreserveTrailingSlash =
+ (from: string, to: string): string => {
+ const resolvedPath = resolvePath(from, to);
+ return isDirectorySpecifier(to) ? `${resolvedPath}/` : resolvedPath;
+ };
+
+const isDirectorySpecifier = (specifier: string) => ['', '.', '..'].includes(
+ specifier.match(/([^\/]*$)/)![0]);
\ No newline at end of file
diff --git a/src/test/koa-node-resolve.test.ts b/src/test/koa-node-resolve.test.ts
index 12be27f..ad9317d 100644
--- a/src/test/koa-node-resolve.test.ts
+++ b/src/test/koa-node-resolve.test.ts
@@ -11,15 +11,16 @@
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
-import {resolve as resolvePath} from 'path';
import request from 'supertest';
import test from 'tape';
import {nodeResolve} from '../koa-node-resolve';
+import {resolvePathPreserveTrailingSlash} from '../support/path-utils';
import {createAndServe, squeeze, testLogger} from './test-utils';
-const fixturesPath = resolvePath(__dirname, '../../test/fixtures/');
+const fixturesPath =
+ resolvePathPreserveTrailingSlash(__dirname, '../../test/fixtures/');
test('nodeResolve middleware transforms resolvable specifiers', async (t) => {
t.plan(4);
@@ -63,6 +64,48 @@ test('nodeResolve middleware transforms resolvable specifiers', async (t) => {
});
});
+test('nodeResolve middleware works even if baseURL has no pathname', async (t) => {
+ t.plan(4);
+ const logger = testLogger();
+ createAndServe(
+ {
+ middleware:
+ [nodeResolve({root: fixturesPath, logger, logLevel: 'debug'})],
+ routes: {
+ '/my-module.js': `import * as x from 'x';`,
+ '/': `
+
+ `,
+ },
+ },
+ async (server) => {
+ t.equal(
+ squeeze((await request(server).get('/my-module.js')).text),
+ squeeze(`
+ import * as x from './node_modules/x/main.js';
+ `),
+ 'should transform specifiers in JavaScript module');
+ t.equal(
+ squeeze((await request(server).get('/')).text),
+ squeeze(`
+
+ `),
+ 'should transform specifiers in inline module script');
+ t.deepEqual(logger.debugs.map((args) => args.join(' ')), [
+ '[koa-node-resolve] Resolved Node module specifier "x" to "./node_modules/x/main.js"',
+ '[koa-node-resolve] Resolved Node module specifier "x" to "./node_modules/x/main.js"',
+ ]);
+ t.deepEqual(logger.infos.map((args) => args.join(' ')), [
+ '[koa-node-resolve] Transformed 1 module specifier(s) in "/my-module.js"',
+ '[koa-node-resolve] Transformed 1 module specifier(s) in "/"',
+ ]);
+ });
+});
+
test('nodeResolve middleware ignores unresolvable specifiers', async (t) => {
t.plan(2);
const logger = testLogger();
diff --git a/src/test/path-utils.test.ts b/src/test/path-utils.test.ts
new file mode 100644
index 0000000..ffa442c
--- /dev/null
+++ b/src/test/path-utils.test.ts
@@ -0,0 +1,44 @@
+/**
+ * @license
+ * Copyright (c) 2020 The Polymer Project Authors. All rights reserved.
+ * This code may only be used under the BSD style license found at
+ * http://polymer.github.io/LICENSE.txt
+ * The complete set of authors may be found at
+ * http://polymer.github.io/AUTHORS.txt
+ * The complete set of contributors may be found at
+ * http://polymer.github.io/CONTRIBUTORS.txt
+ * Code distributed by Google as part of the polymer project is also
+ * subject to an additional IP rights grant found at
+ * http://polymer.github.io/PATENTS.txt
+ */
+import test from 'tape';
+
+import {dirname, resolvePathPreserveTrailingSlash} from '../support/path-utils';
+
+test('dirname returns portion of path representing directory', (t) => {
+ t.plan(2);
+ t.equal(
+ dirname('/a/b/c'),
+ '/a/b/',
+ 'should treat lack of trailing slash as file');
+ t.equal(
+ dirname('/a/b/c/'),
+ '/a/b/c/',
+ 'should treat segment before trailing slash as directory name');
+});
+
+test('resolvePathPreserveTrailingSlash may return trailing slash', (t) => {
+ t.plan(3);
+ t.equal(
+ resolvePathPreserveTrailingSlash('/a/b', 'c/'),
+ '/a/b/c/',
+ 'should contain trailing slash when destination has trailing slash');
+ t.equal(
+ resolvePathPreserveTrailingSlash('/a/b', 'c'),
+ '/a/b/c',
+ 'should not contain trailing slash if destination does not have trailing slash');
+ t.equal(
+ resolvePathPreserveTrailingSlash('/a/b', ''),
+ '/a/b/',
+ 'should contain trailing slash if destination is current directory');
+});
\ No newline at end of file
diff --git a/src/test/resolve-node-specifier.test.ts b/src/test/resolve-node-specifier.test.ts
index 9c68e44..b3b3b5b 100644
--- a/src/test/resolve-node-specifier.test.ts
+++ b/src/test/resolve-node-specifier.test.ts
@@ -11,16 +11,15 @@
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
-import {resolve as resolvePath} from 'path';
import test from 'tape';
-import {ensureTrailingSlashInPath} from '../support/path-utils';
+import {resolvePathPreserveTrailingSlash} from '../support/path-utils';
import {resolveNodeSpecifier} from '../support/resolve-node-specifier';
import {testLogger} from './test-utils';
const logger = testLogger();
const fixturesPath =
- ensureTrailingSlashInPath(resolvePath(__dirname, '../../test/fixtures/'));
+ resolvePathPreserveTrailingSlash(__dirname, '../../test/fixtures/');
const resolve = (specifier: string): string =>
resolveNodeSpecifier(fixturesPath, specifier, logger);
diff --git a/src/test/test-utils.ts b/src/test/test-utils.ts
index f452e46..0e826f3 100644
--- a/src/test/test-utils.ts
+++ b/src/test/test-utils.ts
@@ -37,8 +37,8 @@ export const createApp = (options: AppOptions): Koa => {
app.use(route.get(key, (ctx) => {
if (key.endsWith('.js')) {
ctx.type = 'js';
- }
- if (key.endsWith('.html')) {
+ } else {
+ // Assume everything else is html.
ctx.type = 'html';
}
// Make our body a stream, like koa static would do, to make sure we