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