Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: generate relative imports #538

Merged
merged 2 commits into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"typecheck": "tsc --noEmit && tsc -p test/tsconfig.json --noEmit",
"prepublishOnly": "pnpm clean && pnpm build && chmod +x ./dist/bin/cli.js && pnpm test:ci",
"tsx": "node -r @swc-node/register",
"ts-bunchee": "pnpm tsx ./src/bin/index.ts",
"build-dir": "pnpm ts-bunchee --cwd",
"build": "node -r @swc-node/register ./src/bin/index.ts --runtime node",
"format": "prettier --write .",
"prepare": "husky"
Expand Down
1 change: 0 additions & 1 deletion src/build-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ async function buildInputConfig(
const aliasPlugin = aliasEntries({
entry,
entries,
entriesAlias: pluginContext.entriesAlias,
format: aliasFormat,
dts,
cwd,
Expand Down
3 changes: 0 additions & 3 deletions src/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import fsp from 'fs/promises'
import fs from 'fs'
import { resolve } from 'path'
import { performance } from 'perf_hooks'
import { getReversedAlias } from './build-config'
import {
createOutputState,
logOutputState,
Expand Down Expand Up @@ -142,7 +141,6 @@ async function bundle(
}

const sizeCollector = createOutputState({ entries })
const entriesAlias = getReversedAlias({ entries, name: pkg.name })
const buildContext: BuildContext = {
entries,
pkg,
Expand All @@ -152,7 +150,6 @@ async function bundle(
pluginContext: {
outputState: sizeCollector,
moduleDirectiveLayerMap: new Map(),
entriesAlias,
},
}

Expand Down
139 changes: 91 additions & 48 deletions src/plugins/alias-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,91 @@ import { Entries } from '../types'
import path from 'path'
import { relativify } from '../lib/format'

// Alias entries to import path
// e.g.
// For a resolved file, if it's one of the entries,
// aliases it as export path, such as <absolute file> -> <pkg>/<export path>
function findJsBundlePathCallback({
format,
bundlePath,
conditionNames,
}: {
format: OutputOptions['format']
bundlePath: string
conditionNames: Set<string>
}) {
const hasCondition = bundlePath != null
const formatCond = format === 'cjs' ? 'require' : 'import'
const isTypesCondName = conditionNames.has('types')

const isMatchedFormat = conditionNames.has(formatCond)
return isMatchedFormat && !isTypesCondName && hasCondition
}

function findTypesFileCallback({
format,
bundlePath,
conditionNames,
}: {
format: OutputOptions['format'] | undefined
bundlePath: string
conditionNames: Set<string>
}) {
const hasCondition = bundlePath != null
const formatCond = format ? (format === 'cjs' ? 'require' : 'import') : null
const isTypesCondName = conditionNames.has('types')
return (
isTypesCondName &&
hasCondition &&
(formatCond ? conditionNames.has(formatCond) : true)
)
}

// Alias entry key to dist bundle path
export function aliasEntries({
entry,
entry: sourceFilePath,
entries,
entriesAlias,
format,
dts,
cwd,
}: {
entry: string
entries: Entries
entriesAlias: Record<string, string>
format: OutputOptions['format']
dts: boolean
cwd: string
}): Plugin {
const entryAliasWithoutSelf = {
...entriesAlias,
[entry]: null,
}
const pathToRelativeDistMap = new Map<string, string>()
// <imported source file path>: <relative path to source's bundle>
const sourceToRelativeBundleMap = new Map<string, string>()
for (const [, exportCondition] of Object.entries(entries)) {
const exportDistMaps = exportCondition.export
const exportMapEntries = Object.entries(exportDistMaps).map(
([composedKey, bundlePath]) => ({
conditionNames: new Set(composedKey.split('.')),
bundlePath,
format,
}),
)

let matchedBundlePath: string | undefined
if (dts) {
// Search types + [format] condition from entries map
// e.g. import.types, require.types
const typeCond = Object.entries(exportDistMaps).find(
([composedKey, cond]) => {
const typesSet = new Set(composedKey.split('.'))
const formatCond = format === 'cjs' ? 'require' : 'import'
const isMatchedCond =
typesSet.has(formatCond) ||
(!typesSet.has('import') && !typesSet.has('require'))

return typesSet.has('types') && isMatchedCond && cond != null
},
)?.[1]

if (typeCond) {
pathToRelativeDistMap.set(exportCondition.source, typeCond)
// Find the type with format condition first
matchedBundlePath = exportMapEntries.find(findTypesFileCallback)
?.bundlePath
// If theres no format specific types such as import.types or require.types,
// fallback to the general types file.
if (!matchedBundlePath) {
matchedBundlePath = exportMapEntries.find((item) => {
return findTypesFileCallback({
...item,
format: undefined,
})
})?.bundlePath
}
} else {
matchedBundlePath = exportMapEntries.find(findJsBundlePathCallback)
?.bundlePath
}

if (matchedBundlePath) {
if (!sourceToRelativeBundleMap.has(exportCondition.source))
sourceToRelativeBundleMap.set(exportCondition.source, matchedBundlePath)
}
}

Expand All @@ -58,28 +98,31 @@ export function aliasEntries({
const resolved = await this.resolve(source, importer, options)

if (resolved != null) {
if (dts) {
// For types, generate relative path to the other type files,
// this will be compatible for the node10 ts module resolution.
const resolvedDist = pathToRelativeDistMap.get(resolved.id)
const entryDist = pathToRelativeDistMap.get(entry)
if (resolved.id !== entry && entryDist && resolvedDist) {
const absoluteEntryDist = path.resolve(cwd, entryDist)
const absoluteResolvedDist = path.resolve(cwd, resolvedDist)
// For types, generate relative path to the other type files,
// this will be compatible for the node10 ts module resolution.
const srcBundle = sourceToRelativeBundleMap.get(sourceFilePath)
// Resolved module bundle path
const resolvedModuleBundle = sourceToRelativeBundleMap.get(
resolved.id,
)

const filePathBase = path.relative(
path.dirname(absoluteEntryDist),
absoluteResolvedDist,
)!
const relativePath = relativify(filePathBase)
return { id: relativePath, external: true }
}
} else {
const aliasedId = entryAliasWithoutSelf[resolved.id]
if (
resolved.id !== sourceFilePath &&
srcBundle &&
resolvedModuleBundle
) {
const absoluteBundlePath = path.resolve(cwd, srcBundle)
const absoluteImportBundlePath = path.resolve(
cwd,
resolvedModuleBundle,
)

if (aliasedId != null) {
return { id: aliasedId }
}
const filePathBase = path.relative(
path.dirname(absoluteBundlePath),
absoluteImportBundlePath,
)!
const relativePath = relativify(filePathBase)
return { id: relativePath, external: true }
}
}
return null
Expand Down
1 change: 0 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ type BuildContext = {
pluginContext: {
outputState: OutputState
moduleDirectiveLayerMap: Map<string, Set<[string, string]>>
entriesAlias: Record<string, string>
}
}

Expand Down
1 change: 1 addition & 0 deletions test/integration/multi-entries/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"import": "./dist/client/index.mjs"
},
"./shared": {
"types": "./dist/shared/index.d.ts",
"import": "./dist/shared/index.mjs",
"require": "./dist/shared/index.cjs",
"edge-light": "./dist/shared/edge-light.mjs"
Expand Down
4 changes: 2 additions & 2 deletions test/integration/multi-exports-ts/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ describe('integration multi-exports', () => {
})
it('should work with multi exports condition', async () => {
await assertFilesContent(dir, {
'./dist/cjs/index.js': `var foo = require('multi-exports-ts/foo')`,
'./dist/cjs/index.js': `= require('../../foo/cjs/index.js')`,
'./dist/cjs/index.d.cts': `import { Foo } from '../../foo/cjs/index.cjs'`,
'./dist/es/index.mjs': `import { foo } from 'multi-exports-ts/foo'`,
'./dist/es/index.mjs': `import { foo } from '../../foo/es/index.mjs'`,
'./dist/es/index.d.mts': `import { Foo } from '../../foo/es/index.mjs'`,
'./foo/cjs/index.js': `exports.foo = foo`,
'./foo/cjs/index.d.cts': `export { type Foo, foo }`,
Expand Down
2 changes: 1 addition & 1 deletion test/integration/pkg-exports-ts-rsc/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('integration pkg-exports-ts-rsc', () => {
'./react-server.mjs': /'react-server'/,
'./react-native.js': /'react-native'/,
'./index.d.ts': /declare const shared = true/,
'./api.mjs': /\'pkg-export-ts-rsc\'/,
'./api.mjs': `'./index.mjs'`,
})
},
)
Expand Down
4 changes: 2 additions & 2 deletions test/integration/shared-entry/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('integration shared-entry', () => {
join(dir, './dist/index.mjs'),
'utf-8',
)
expect(indexEsm).toContain('shared-entry/shared')
expect(indexEsm).toContain(`'./shared.mjs'`)
expect(indexEsm).toContain('index-export')
expect(indexEsm).not.toMatch(/['"]\.\/shared['"]/)
expect(indexEsm).not.toContain('shared-export')
Expand All @@ -32,7 +32,7 @@ describe('integration shared-entry', () => {
join(dir, './dist/index.js'),
'utf-8',
)
expect(indexCjs).toContain('shared-entry/shared')
expect(indexCjs).toContain(`require('./shared.js')`)
expect(indexCjs).toContain('index-export')
expect(indexCjs).not.toMatch(/['"]\.\/shared['"]/)

Expand Down
Loading