Skip to content
Draft
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
6 changes: 6 additions & 0 deletions .changeset/pr-1222.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!-- auto-generated -->
---
'@sanity/cli-build': minor
---

feat(cli-build): derive vendor import-map entry points from package.json exports
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import path from 'node:path'
import {fileURLToPath} from 'node:url'

import {describe, expect, test} from 'vitest'

import {getCjsNamedExports} from '../getCjsNamedExports.js'
import {resolveCjsNamedExportsSource} from '../resolveCjsNamedExportsSource.js'

const packageRoot = fileURLToPath(new URL('../../../../', import.meta.url))

describe('resolveCjsNamedExportsSource', () => {
test('follows production CJS re-export wrappers', async () => {
const reactDir = path.join(packageRoot, 'node_modules/react')
const source = await resolveCjsNamedExportsSource(reactDir, path.join(reactDir, 'index.js'))

expect(source).toContain('exports.createElement')
expect(source).not.toMatch(/module\.exports\s*=\s*require/)
})

test('returns wrapper source when it declares named exports directly', async () => {
const reactDomDir = path.join(packageRoot, 'node_modules/react-dom')
const entryPath = path.join(reactDomDir, 'server.browser.js')
const source = await resolveCjsNamedExportsSource(reactDomDir, entryPath)

expect(source).toContain('exports.renderToString')
expect(await getCjsNamedExports(source, 'react-dom/server')).toEqual(
expect.arrayContaining(['renderToString', 'renderToStaticMarkup']),
)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import path from 'node:path'
import {fileURLToPath} from 'node:url'

import {readPackageJson} from '@sanity/cli-core'
import {describe, expect, test} from 'vitest'

import {
listExportSubpaths,
resolveBrowserFieldEntryPoints,
resolveExportsEntryPoints,
resolveExportTarget,
resolveVendorEntryPoints,
} from '../resolveVendorEntryPoints.js'

const packageRoot = fileURLToPath(new URL('../../../../', import.meta.url))

describe('resolveExportTarget', () => {
test('returns string targets as-is', () => {
expect(resolveExportTarget('./index.js')).toBe('./index.js')
})

test('prefers browser over import and default', () => {
expect(
resolveExportTarget({
browser: './browser.js',
default: './default.js',
import: './import.mjs',
}),
).toBe('./browser.js')
})

test('falls back to import then default', () => {
expect(resolveExportTarget({default: './default.js', import: './import.mjs'})).toBe(
'./import.mjs',
)
expect(resolveExportTarget({default: './default.js'})).toBe('./default.js')
})

test('resolves nested conditions', () => {
expect(
resolveExportTarget({
default: {browser: './browser.js', default: './node.js'},
'react-server': './server.js',
}),
).toBe('./browser.js')
})
})

describe('listExportSubpaths', () => {
test('omits pattern exports', () => {
expect(
listExportSubpaths({
'.': './index.js',
'./features/*': './features/*.js',
'./package.json': './package.json',
}),
).toEqual(['.', './package.json'])
})
})

describe('resolveExportsEntryPoints', () => {
test('derives react entry points from installed package.json exports', async () => {
const manifest = await readPackageJson(
path.join(packageRoot, 'node_modules/react/package.json'),
{
skipSchemaValidation: true,
},
)

const entryPoints = resolveExportsEntryPoints(manifest.exports!)

expect(entryPoints).toEqual({
'.': './index.js',
'./compiler-runtime': './compiler-runtime.js',
'./jsx-dev-runtime': './jsx-dev-runtime.js',
'./jsx-runtime': './jsx-runtime.js',
'./package.json': './package.json',
})
})

test('derives react-dom entry points with browser conditions', async () => {
const manifest = await readPackageJson(
path.join(packageRoot, 'node_modules/react-dom/package.json'),
{skipSchemaValidation: true},
)

const entryPoints = resolveExportsEntryPoints(manifest.exports!)

expect(entryPoints['./server']).toBe('./server.browser.js')
expect(entryPoints['./static']).toBe('./static.browser.js')
expect(entryPoints['./client']).toBe('./client.js')
expect(entryPoints['./server.node']).toBe('./server.node.js')
})
})

describe('resolveBrowserFieldEntryPoints', () => {
test('derives styled-components browser ESM entry from module + browser field', async () => {
const manifest = await readPackageJson(
path.join(packageRoot, 'node_modules/styled-components/package.json'),
{skipSchemaValidation: true},
)

expect(resolveBrowserFieldEntryPoints(manifest)).toEqual({
'.': './dist/styled-components.browser.esm.js',
'./package.json': './package.json',
})
})
})

describe('resolveVendorEntryPoints', () => {
test('uses exports when present', async () => {
const manifest = await readPackageJson(
path.join(packageRoot, 'node_modules/react/package.json'),
{
skipSchemaValidation: true,
},
)

const entryPoints = resolveVendorEntryPoints({
fallback: 'exports',
manifest,
packageDir: path.join(packageRoot, 'node_modules/react'),
})

expect(entryPoints['./jsx-runtime']).toBe('./jsx-runtime.js')
})

test('falls back to browser-field strategy when exports are absent', async () => {
const manifest = await readPackageJson(
path.join(packageRoot, 'node_modules/styled-components/package.json'),
{skipSchemaValidation: true},
)

const entryPoints = resolveVendorEntryPoints({
fallback: 'browser-field',
manifest,
packageDir: path.join(packageRoot, 'node_modules/styled-components'),
})

expect(entryPoints['.']).toBe('./dist/styled-components.browser.esm.js')
})
})
Loading
Loading