From 954a3d965cbf5ff3d473e7e92ffbd2d3bd57d1a6 Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Tue, 27 Jul 2021 17:30:30 +0200 Subject: [PATCH 1/3] feat(modules): detect exports for moduleTypes --- src/@types/nice-package.ts | 1 + src/__tests__/formatPkg.test.ts | 62 ++++++++++++++++++++++++++++++++- src/formatPkg.ts | 60 +++++++++++++++++++++++-------- src/npm/types.ts | 10 ++++++ 4 files changed, 118 insertions(+), 15 deletions(-) diff --git a/src/@types/nice-package.ts b/src/@types/nice-package.ts index 2e6bde6cd..354d3b621 100644 --- a/src/@types/nice-package.ts +++ b/src/@types/nice-package.ts @@ -19,6 +19,7 @@ export interface NicePackageType { main?: string | string[]; modified: string; module?: string; + exports?: GetPackage['exports']; name: string; other: { _id?: string; diff --git a/src/__tests__/formatPkg.test.ts b/src/__tests__/formatPkg.test.ts index 68b450261..15be4d013 100644 --- a/src/__tests__/formatPkg.test.ts +++ b/src/__tests__/formatPkg.test.ts @@ -5,6 +5,7 @@ import formatPkg, { getRepositoryInfo, getMains, getVersions, + getExportKeys, } from '../formatPkg'; import type { GetPackage } from '../npm/types'; @@ -560,13 +561,14 @@ describe('moduleTypes', () => { formatPkg({ ...BASE, name: 'whoever', + // @ts-expect-error main: [{ personalMain: 'index.mjs' }], }).moduleTypes ).toEqual(['unknown']); }); }); -describe('getMain', () => { +describe('getMains', () => { test('main === string', () => { expect(getMains({ main: 'index.js' })).toEqual(['index.js']); }); @@ -583,10 +585,68 @@ describe('getMain', () => { }); test('nothing if object', () => { + // @ts-expect-error expect(getMains({ main: { something: 'cool.js' } })).toEqual([]); }); }); +describe('getExportKeys', () => { + test('exports is missing', () => { + expect(getExportKeys(undefined)).toEqual([]); + }); + + test('exports is one level', () => { + expect(getExportKeys({ import: './lol.js', require: './cjs.js' })).toEqual([ + 'import', + 'require', + ]); + }); + + test('exports is two levels', () => { + expect( + getExportKeys({ '.': { import: './lol.js', require: './cjs.js' } }) + ).toEqual(['.', 'import', 'require']); + }); + + test('exports is repeated', () => { + expect( + getExportKeys({ + something: { import: './lol.js', require: './cjs.js' }, + bazoo: { import: './bazoo.js', require: './cjs.js' }, + }) + ).toEqual(['something', 'bazoo', 'import', 'require', 'import', 'require']); + }); + + test('exports is many levels', () => { + expect( + getExportKeys({ + something: { import: './lol.js', require: './cjs.js' }, + bazoo: { + lol: { import: './bazoo.js', require: './cjs.js' }, + kol: 'test.js', + mol: { + bol: { + condition: 'test.js', + }, + }, + }, + }) + ).toEqual([ + 'something', + 'bazoo', + 'import', + 'require', + 'lol', + 'kol', + 'mol', + 'import', + 'require', + 'bol', + 'condition', + ]); + }); +}); + describe('getVersions', () => { test("renames 'time' to versions", () => { expect( diff --git a/src/formatPkg.ts b/src/formatPkg.ts index 78228d0e5..bb2a7ec18 100644 --- a/src/formatPkg.ts +++ b/src/formatPkg.ts @@ -575,26 +575,58 @@ export function getMains(pkg: Pick): string[] { return []; } +export function getExportKeys( + exp: NicePackageType['exports'] | string +): string[] { + if (typeof exp !== 'object') { + return []; + } + const keys = Object.keys(exp); + const nestedKeys = keys.flatMap((key) => getExportKeys(exp[key])); + return [...keys, ...nestedKeys]; +} + function getModuleTypes(pkg: NicePackageType): ModuleType[] { - const mains = getMains(pkg); - const moduleTypes: ModuleType[] = []; + const moduleTypes: Set = new Set(); - mains.forEach((main) => { - if ( - typeof pkg.module === 'string' || - pkg.type === 'module' || - main.endsWith('.mjs') - ) { - moduleTypes.push('esm'); + // type is declared + if (pkg.type) { + const mapping: Record['type'], ModuleType> = { + commonjs: 'cjs', + module: 'esm', + }; + moduleTypes.add(mapping[pkg.type]); + } + + // get all explicit exports (supporting cjs in esm or other way round) + // reference: https://nodejs.org/api/packages.html + const exportKeys = getExportKeys(pkg.exports); + if (exportKeys.includes('import')) { + moduleTypes.add('esm'); + } + if (exportKeys.includes('require')) { + moduleTypes.add('cjs'); + } + + // module (non-standard) is declared + if (typeof pkg.module === 'string') { + moduleTypes.add('esm'); + } + + // check the extension of each of the "main" values + getMains(pkg).forEach((main) => { + if (main.endsWith('.mjs')) { + moduleTypes.add('esm'); } - if (pkg.type === 'commonjs' || main.endsWith('.cjs')) { - moduleTypes.push('cjs'); + if (main.endsWith('.cjs')) { + moduleTypes.add('cjs'); } }); - if (moduleTypes.length === 0) { - moduleTypes.push('unknown'); + // add a default value to make filtering possible + if (moduleTypes.size === 0) { + moduleTypes.add('unknown'); } - return moduleTypes; + return [...moduleTypes]; } diff --git a/src/npm/types.ts b/src/npm/types.ts index f8eb7f720..4a460f512 100644 --- a/src/npm/types.ts +++ b/src/npm/types.ts @@ -45,6 +45,10 @@ export interface PackageRepo { directory?: string; } +export interface PackageExports { + [key: string]: string | PackageExports; +} + export interface GetPackage { _id: string; _rev: string; @@ -66,6 +70,12 @@ export interface GetPackage { contributors?: Array<{ name: string }>; repository?: PackageRepo; schematics?: string; + types?: string; + typings?: string; + type?: 'module' | 'commonjs'; + module?: string; + main?: string; + exports?: PackageExports; } export interface GetPackageLight { From 8578f0f1058482187c305ec9bd5523fe5f667856 Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Wed, 28 Jul 2021 11:24:23 +0200 Subject: [PATCH 2/3] type --- src/@types/nice-package.ts | 9 +++++++-- src/npm/types.ts | 15 ++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/@types/nice-package.ts b/src/@types/nice-package.ts index 354d3b621..95809b481 100644 --- a/src/@types/nice-package.ts +++ b/src/@types/nice-package.ts @@ -1,4 +1,9 @@ -import type { GetPackage, GetUser, PackageRepo } from '../npm/types'; +import type { + GetPackage, + GetUser, + GetVersion, + PackageRepo, +} from '../npm/types'; export interface NicePackageType { _hasShrinkwrap?: false; @@ -19,7 +24,7 @@ export interface NicePackageType { main?: string | string[]; modified: string; module?: string; - exports?: GetPackage['exports']; + exports?: GetVersion['exports']; name: string; other: { _id?: string; diff --git a/src/npm/types.ts b/src/npm/types.ts index 4a460f512..e16ea11f4 100644 --- a/src/npm/types.ts +++ b/src/npm/types.ts @@ -31,12 +31,20 @@ export interface GetVersion { tarball: string; }; license?: string; + + type?: 'module' | 'commonjs'; + module?: string; main?: string; + exports?: PackageExports; + maintainers: GetUser[]; name: string; scripts?: Record; version: string; deprecated?: string | boolean; + schematics?: string; + types?: string; + typings?: string; } export interface PackageRepo { @@ -69,13 +77,6 @@ export interface GetPackage { keywords?: string[] | string; contributors?: Array<{ name: string }>; repository?: PackageRepo; - schematics?: string; - types?: string; - typings?: string; - type?: 'module' | 'commonjs'; - module?: string; - main?: string; - exports?: PackageExports; } export interface GetPackageLight { From 14ac605f209c8a10c1475c5780eb4fc05e3f1cfd Mon Sep 17 00:00:00 2001 From: Haroen Viaene Date: Wed, 28 Jul 2021 11:25:50 +0200 Subject: [PATCH 3/3] staticify object --- src/formatPkg.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/formatPkg.ts b/src/formatPkg.ts index bb2a7ec18..8339e6bfd 100644 --- a/src/formatPkg.ts +++ b/src/formatPkg.ts @@ -586,16 +586,20 @@ export function getExportKeys( return [...keys, ...nestedKeys]; } +const typeToModuleTypeMapping: Record< + Required['type'], + ModuleType +> = { + commonjs: 'cjs', + module: 'esm', +}; + function getModuleTypes(pkg: NicePackageType): ModuleType[] { const moduleTypes: Set = new Set(); // type is declared if (pkg.type) { - const mapping: Record['type'], ModuleType> = { - commonjs: 'cjs', - module: 'esm', - }; - moduleTypes.add(mapping[pkg.type]); + moduleTypes.add(typeToModuleTypeMapping[pkg.type]); } // get all explicit exports (supporting cjs in esm or other way round)