From dcf4dd9810223b98ff0cae9dc2be3de5a3a9257f Mon Sep 17 00:00:00 2001 From: Hiroki Osame Date: Sun, 17 Sep 2023 16:35:48 +0900 Subject: [PATCH] fix: support query in specifier (#76) --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- src/loaders-deprecated.ts | 5 +++-- src/loaders.ts | 15 +++++++++----- src/utils.ts | 4 +++- tests/specs/javascript/esm.ts | 13 ++++++++++++ tests/specs/json.ts | 21 +++++++++++++++---- tests/specs/typescript/mts.ts | 23 +++++++++++++++++++++ tests/specs/typescript/ts.ts | 36 +++++++++++++++++++++++++++++++++ tests/utils/node-with-loader.ts | 1 + tests/utils/query.ts | 1 + 11 files changed, 112 insertions(+), 17 deletions(-) create mode 100644 tests/utils/query.ts diff --git a/package.json b/package.json index 59dc9e2..1888706 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "prepack": "pnpm build && clean-pkg-json" }, "dependencies": { - "@esbuild-kit/core-utils": "^3.3.0", + "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f62389c..0e5db45 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@esbuild-kit/core-utils': - specifier: ^3.3.0 - version: 3.3.0 + specifier: ^3.3.2 + version: 3.3.2 get-tsconfig: specifier: ^4.7.0 version: 4.7.0 @@ -113,8 +113,8 @@ packages: js-tokens: 4.0.0 dev: true - /@esbuild-kit/core-utils@3.3.0: - resolution: {integrity: sha512-jTtSvVpr5ygNXyPXf9IBbCrKQ0uckq6vWkcGfy1fEr9KjkKdnFn4kFAGjpqZDZim1wsTwh/Be+lhAUemEmxEYA==} + /@esbuild-kit/core-utils@3.3.2: + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} dependencies: esbuild: 0.18.20 source-map-support: 0.5.21 diff --git a/src/loaders-deprecated.ts b/src/loaders-deprecated.ts index f8a2afd..e60d1e8 100644 --- a/src/loaders-deprecated.ts +++ b/src/loaders-deprecated.ts @@ -17,6 +17,7 @@ import { tsExtensionsPattern, getFormatFromFileUrl, fileProtocol, + isJsonPattern, type MaybePromise, type NodeError, } from './utils.js'; @@ -32,7 +33,7 @@ const _getFormat: getFormat = async function ( context, defaultGetFormat, ) { - if (url.endsWith('.json')) { + if (isJsonPattern.test(url)) { return { format: 'module' }; } @@ -80,7 +81,7 @@ const _transformSource: transformSource = async function ( } if ( - url.endsWith('.json') + isJsonPattern.test(url) || tsExtensionsPattern.test(url) ) { const transformed = await transform( diff --git a/src/loaders.ts b/src/loaders.ts index ad41edf..a587682 100644 --- a/src/loaders.ts +++ b/src/loaders.ts @@ -16,12 +16,15 @@ import { tsconfigPathsMatcher, fileMatcher, tsExtensionsPattern, + isJsonPattern, getFormatFromFileUrl, fileProtocol, type MaybePromise, type NodeError, } from './utils.js'; +const isDirectoryPattern = /\/(?:$|\?)/; + type NextResolve = ( specifier: string, context?: ResolveHookContext, @@ -75,11 +78,12 @@ async function tryExtensions( context: ResolveHookContext, defaultResolve: NextResolve, ) { + const [specifierWithoutQuery, query] = specifier.split('?'); let throwError: Error | undefined; for (const extension of extensions) { try { return await resolve( - specifier + extension, + specifierWithoutQuery + extension + (query ? `?${query}` : ''), context, defaultResolve, true, @@ -105,11 +109,12 @@ async function tryDirectory( context: ResolveHookContext, defaultResolve: NextResolve, ) { - const isExplicitDirectory = specifier.endsWith('/'); + const isExplicitDirectory = isDirectoryPattern.test(specifier); const appendIndex = isExplicitDirectory ? 'index' : '/index'; + const [specifierWithoutQuery, query] = specifier.split('?'); try { - return await tryExtensions(specifier + appendIndex, context, defaultResolve); + return await tryExtensions(specifierWithoutQuery + appendIndex + (query ? `?${query}` : ''), context, defaultResolve); } catch (_error) { if (!isExplicitDirectory) { try { @@ -145,7 +150,7 @@ export const resolve: resolve = async function ( } // If directory, can be index.js, index.ts, etc. - if (specifier.endsWith('/')) { + if (isDirectoryPattern.test(specifier)) { return await tryDirectory(specifier, context, defaultResolve); } @@ -243,7 +248,7 @@ export const load: LoadHook = async function ( }); } - if (url.endsWith('.json')) { + if (isJsonPattern.test(url)) { if (!context.importAssertions) { context.importAssertions = {}; } diff --git a/src/utils.ts b/src/utils.ts index 7b0e8be..d34f7d4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -25,7 +25,9 @@ export const tsconfigPathsMatcher = tsconfig && createPathsMatcher(tsconfig); export const fileProtocol = 'file://'; -export const tsExtensionsPattern = /\.([cm]?ts|[tj]sx)$/; +export const tsExtensionsPattern = /\.([cm]?ts|[tj]sx)($|\?)/; + +export const isJsonPattern = /\.json(?:$|\?)/; const getFormatFromExtension = (fileUrl: string): ModuleFormat | undefined => { const extension = path.extname(fileUrl); diff --git a/tests/specs/javascript/esm.ts b/tests/specs/javascript/esm.ts index a147b49..50ea299 100644 --- a/tests/specs/javascript/esm.ts +++ b/tests/specs/javascript/esm.ts @@ -3,6 +3,7 @@ import semver from 'semver'; import type { NodeApis } from '../../utils/node-with-loader.js'; import nodeSupports from '../../utils/node-supports.js'; import { assertNotFound } from '../../utils/assertions.js'; +import { query } from '../../utils/query.js'; export default testSuite(async ({ describe }, node: NodeApis) => { describe('Load ESM', ({ describe }) => { @@ -36,6 +37,12 @@ export default testSuite(async ({ describe }, node: NodeApis) => { expect(nodeProcess.stdout).toMatch('{"default":1234}'); }); + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query); + assertResults(nodeProcess); + expect(nodeProcess.stdout).toMatch('{"default":1234}'); + }); + test('TypeScript Import', async () => { const nodeProcess = await node.import(importPath, { typescript: true }); assertResults(nodeProcess); @@ -101,6 +108,12 @@ export default testSuite(async ({ describe }, node: NodeApis) => { assertResults(nodeProcess); expect(nodeProcess.stdout).toMatch('{"default":1234}'); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query); + assertResults(nodeProcess); + expect(nodeProcess.stdout).toMatch('{"default":1234}'); + }); }); describe('extensionless', ({ test }) => { diff --git a/tests/specs/json.ts b/tests/specs/json.ts index 4e30692..5f1eb48 100644 --- a/tests/specs/json.ts +++ b/tests/specs/json.ts @@ -1,6 +1,7 @@ import { testSuite, expect } from 'manten'; import { createFixture } from 'fs-fixture'; import type { NodeApis } from '../utils/node-with-loader.js'; +import { query } from '../utils/query.js'; const jsonFixture = { 'package.json': JSON.stringify({ @@ -28,6 +29,11 @@ export default testSuite(async ({ describe }, node: NodeApis) => { const nodeProcess = await node.importFile(fixture.path, './index.json'); expect(nodeProcess.stdout).toMatch('default: { loaded: \'json\' }'); }); + + test('Import with query', async () => { + const nodeProcess = await node.importFile(fixture.path, `./index.json${query}`); + expect(nodeProcess.stdout).toMatch('default: { loaded: \'json\' }'); + }); }); describe('extensionless', ({ test }) => { @@ -41,14 +47,16 @@ export default testSuite(async ({ describe }, node: NodeApis) => { const nodeProcess = await node.importFile(fixture.path, './index'); expect(nodeProcess.stdout).toMatch('default: { loaded: \'json\' }'); }); + + test('Import with query', async () => { + const nodeProcess = await node.importFile(fixture.path, `./index${query}`); + expect(nodeProcess.stdout).toMatch('default: { loaded: \'json\' }'); + }); }); describe('directory', ({ test }) => { - test('Load', async ({ onTestFail }) => { + test('Load', async () => { const nodeProcess = await node.loadFile(fixture.path, '.'); - onTestFail(() => { - console.log(nodeProcess); - }); expect(nodeProcess.exitCode).toBe(0); expect(nodeProcess.stdout).toBe(''); }); @@ -57,6 +65,11 @@ export default testSuite(async ({ describe }, node: NodeApis) => { const nodeProcess = await node.importFile(fixture.path, '.'); expect(nodeProcess.stdout).toMatch('default: { loaded: \'json\' }'); }); + + test('Import with query', async () => { + const nodeProcess = await node.importFile(fixture.path, `./${query}`); + expect(nodeProcess.stdout).toMatch('default: { loaded: \'json\' }'); + }); }); describe('ambiguous path', async ({ describe, onFinish }) => { diff --git a/tests/specs/typescript/mts.ts b/tests/specs/typescript/mts.ts index 95b91f4..f6339fd 100644 --- a/tests/specs/typescript/mts.ts +++ b/tests/specs/typescript/mts.ts @@ -3,6 +3,7 @@ import semver from 'semver'; import type { NodeApis } from '../../utils/node-with-loader.js'; import nodeSupports from '../../utils/node-supports.js'; import { assertNotFound } from '../../utils/assertions.js'; +import { query } from '../../utils/query.js'; export default testSuite(async ({ describe }, node: NodeApis) => { describe('.mts extension', ({ describe }) => { @@ -33,6 +34,12 @@ export default testSuite(async ({ describe }, node: NodeApis) => { assertResults(nodeProcess.stdout); expect(nodeProcess.stdout).toMatch('{"default":1234}'); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query); + assertResults(nodeProcess.stdout); + expect(nodeProcess.stdout).toMatch('{"default":1234}'); + }); }); describe('full path via .mjs', ({ test }) => { @@ -48,6 +55,12 @@ export default testSuite(async ({ describe }, node: NodeApis) => { assertResults(nodeProcess.stdout); expect(nodeProcess.stdout).toMatch('{"default":1234}'); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query, { typescript: true }); + assertResults(nodeProcess.stdout); + expect(nodeProcess.stdout).toMatch('{"default":1234}'); + }); }); describe('extensionless - should not work', ({ test }) => { @@ -62,6 +75,11 @@ export default testSuite(async ({ describe }, node: NodeApis) => { const nodeProcess = await node.import(importPath); assertNotFound(nodeProcess.stderr, importPath); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query); + assertNotFound(nodeProcess.stderr, importPath); + }); }); describe('directory - should not work', ({ test }) => { @@ -76,6 +94,11 @@ export default testSuite(async ({ describe }, node: NodeApis) => { const nodeProcess = await node.import(importPath); assertNotFound(nodeProcess.stderr, importPath); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query); + assertNotFound(nodeProcess.stderr, importPath); + }); }); }); }); diff --git a/tests/specs/typescript/ts.ts b/tests/specs/typescript/ts.ts index 7d9ba59..fed1a15 100644 --- a/tests/specs/typescript/ts.ts +++ b/tests/specs/typescript/ts.ts @@ -3,6 +3,7 @@ import semver from 'semver'; import type { NodeApis } from '../../utils/node-with-loader.js'; import nodeSupports from '../../utils/node-supports.js'; import { assertNotFound } from '../../utils/assertions.js'; +import { query } from '../../utils/query.js'; export default testSuite(async ({ describe }, node: NodeApis) => { describe('.ts extension', ({ describe }) => { @@ -58,6 +59,12 @@ export default testSuite(async ({ describe }, node: NodeApis) => { assertResults(nodeProcess.stdout); expect(nodeProcess.stdout).toMatch('{"default":1234}'); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query, { typescript: true }); + assertResults(nodeProcess.stdout); + expect(nodeProcess.stdout).toMatch('{"default":1234}'); + }); }); describe('extensionless', ({ test }) => { @@ -73,6 +80,12 @@ export default testSuite(async ({ describe }, node: NodeApis) => { assertResults(nodeProcess.stdout); expect(nodeProcess.stdout).toMatch('{"default":1234}'); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query, { typescript: true }); + assertResults(nodeProcess.stdout); + expect(nodeProcess.stdout).toMatch('{"default":1234}'); + }); }); describe('extensionless with subextension', ({ test }) => { @@ -88,6 +101,12 @@ export default testSuite(async ({ describe }, node: NodeApis) => { assertResults(nodeProcess.stdout, 'ts-ext-ts/index.tsx.ts'); expect(nodeProcess.stdout).toMatch('{"default":1234}'); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query); + assertResults(nodeProcess.stdout, 'ts-ext-ts/index.tsx.ts'); + expect(nodeProcess.stdout).toMatch('{"default":1234}'); + }); }); describe('directory', ({ test }) => { @@ -103,6 +122,12 @@ export default testSuite(async ({ describe }, node: NodeApis) => { assertResults(nodeProcess.stdout); expect(nodeProcess.stdout).toMatch('{"default":1234}'); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query); + assertResults(nodeProcess.stdout); + expect(nodeProcess.stdout).toMatch('{"default":1234}'); + }); }); describe('empty directory should fallback to file', ({ test }) => { @@ -118,6 +143,12 @@ export default testSuite(async ({ describe }, node: NodeApis) => { assertResults(nodeProcess.stdout); expect(nodeProcess.stdout).toMatch('{"default":1234}'); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query); + assertResults(nodeProcess.stdout); + expect(nodeProcess.stdout).toMatch('{"default":1234}'); + }); }); describe('empty but explicit directory should not fallback to file', ({ test }) => { @@ -127,6 +158,11 @@ export default testSuite(async ({ describe }, node: NodeApis) => { const nodeProcess = await node.import(importPath); assertNotFound(nodeProcess.stderr, importPath); }); + + test('Import with query', async () => { + const nodeProcess = await node.import(importPath + query); + assertNotFound(nodeProcess.stderr, importPath); + }); }); }); }); diff --git a/tests/utils/node-with-loader.ts b/tests/utils/node-with-loader.ts index b59985f..e321846 100644 --- a/tests/utils/node-with-loader.ts +++ b/tests/utils/node-with-loader.ts @@ -36,6 +36,7 @@ export const nodeWithLoader = ( nodePath: options.nodePath, cwd: options.cwd, reject: false, + all: true, }, ); diff --git a/tests/utils/query.ts b/tests/utils/query.ts new file mode 100644 index 0000000..cf4ef6b --- /dev/null +++ b/tests/utils/query.ts @@ -0,0 +1 @@ +export const query = '?query=123';