Skip to content

Commit

Permalink
feat: allow CSS exports (#1247)
Browse files Browse the repository at this point in the history
  • Loading branch information
colepeters authored Dec 10, 2024
1 parent de491a4 commit 4540e5b
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 6 deletions.
27 changes: 27 additions & 0 deletions playground/css-export/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "css-export",
"version": "1.0.0",
"private": true,
"license": "MIT",
"sideEffects": false,
"type": "commonjs",
"exports": {
".": {
"source": "./src/index.js",
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"default": "./dist/index.js"
},
"./css/styles.css": "./src/css/styles.css",
"./package.json": "./package.json"
},
"main": "./dist/index.js",
"files": [
"dist",
"src"
],
"scripts": {
"build": "pkg build --strict --check --clean",
"clean": "rimraf dist"
}
}
3 changes: 3 additions & 0 deletions playground/css-export/src/css/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background-color: palevioletred;
}
1 change: 1 addition & 0 deletions playground/css-export/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => {}
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 16 additions & 2 deletions src/node/core/pkg/parseExports.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import {existsSync} from 'node:fs'
import {resolve as resolvePath} from 'node:path'

import type {Logger} from '../../logger'
import type {InferredStrictOptions} from '../../strict'
import {defaultEnding, fileEnding, legacyEnding} from '../../tasks/dts/getTargetPaths'
Expand All @@ -9,13 +12,14 @@ import {validateExports} from './validateExports'

/** @internal */
export function parseExports(options: {
cwd: string
pkg: PackageJSON
strict: boolean
strictOptions: InferredStrictOptions
legacyExports: boolean
logger: Logger
}): (PkgExport & {_path: string})[] {
const {pkg, strict, strictOptions, legacyExports, logger} = options
const {cwd, pkg, strict, strictOptions, legacyExports, logger} = options
const type = pkg.type || 'commonjs'
const errors: string[] = []

Expand Down Expand Up @@ -169,9 +173,19 @@ export function parseExports(options: {
) {
if (exportPath === './package.json') {
if (exportEntry !== './package.json') {
errors.push('package.json: `exports["./package.json"] must be "./package.json".')
errors.push('package.json: `exports["./package.json"]` must be "./package.json".')
}
}
} else if (exportPath.endsWith('.css')) {
if (typeof exportEntry === 'string' && !existsSync(resolvePath(cwd, exportEntry))) {
errors.push(
`package.json: \`exports[${JSON.stringify(exportPath)}]\`: file does not exist.`,
)
} else if (typeof exportEntry !== 'string') {
errors.push(
`package.json: \`exports[${JSON.stringify(exportPath)}]\`: export conditions not supported for CSS files.`,
)
}
} else if (isRecord(exportEntry) && 'svelte' in exportEntry) {
// @TODO should we report a warning or a debug message here about a detected svelte export that is ignored?
} else if (isPkgExport(exportEntry)) {
Expand Down
1 change: 1 addition & 0 deletions src/node/core/pkg/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface PackageJSON {
exports?: Record<
string,
| `./${string}.json`
| `./${string}.css`
| {
source?: string
types?: string
Expand Down
1 change: 1 addition & 0 deletions src/node/core/pkg/validatePkg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const pkgSchema = z.object({
z.record(
z.union([
z.custom<`./${string}.json`>((val) => /^\.\/.*\.json$/.test(val as string)),
z.custom<`./${string}.css`>((val) => /^\.\/.*\.css$/.test(val as string)),
z.object({
'types': z.optional(z.string()),
'source': z.optional(z.string()),
Expand Down
1 change: 1 addition & 0 deletions src/node/resolveBuildContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export async function resolveBuildContext(options: {
}

const parsedExports = parseExports({
cwd,
pkg,
strict,
legacyExports: config?.legacyExports ?? false,
Expand Down
13 changes: 13 additions & 0 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,19 @@ test.skipIf(isWindows)('should build `ts-bundler` package', async () => {
await project.remove()
})

test.skipIf(isWindows)('should build `css-export` package', async () => {
const project = await spawnProject('css-export')

await project.install()

const {stdout} = await project.run('build')

expect(stdout).toContain('./src/index.js → ./dist/index.js')
// @TODO test that styles.css is available as an export from the package

await project.remove()
})

describe.skip('runtime: webpack v3', () => {
test('import `dist/*.browser.js` from package', async () => {
const exportsDummy = await spawnProject('dummy-module')
Expand Down
73 changes: 71 additions & 2 deletions test/parseExports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const defaults = {
const files = ['dist']
const strictOptions = parseStrictOptions({})
const logger = createLogger()
const cwd = process.cwd()

describe.each([
{type: 'commonjs' as const, legacyExports: false},
Expand All @@ -28,9 +29,9 @@ describe.each([
const testParseExports = (
options: Omit<
Parameters<typeof parseExports>[0],
'strict' | 'strictOptions' | 'legacyExports' | 'logger'
'strict' | 'strictOptions' | 'legacyExports' | 'logger' | 'cwd'
>,
) => parseExports({strict: true, legacyExports, logger, strictOptions, ...options})
) => parseExports({strict: true, legacyExports, logger, strictOptions, cwd, ...options})
const reference = {
'.': {
source: defaults['.'].source,
Expand Down Expand Up @@ -464,6 +465,74 @@ describe.each([
expect(() => testParseExports({pkg})).toThrowErrorMatchingSnapshot()
})

test('css exports must be strings (paths)', () => {
const pkg = {
type,
name,
version,
files,
main: './lib/index.js',
types: './lib/index.d.ts',
exports: {
'.': {
source: './src/index.ts',
default: './lib/index.js',
},
'./style.css': {source: './src/style.css', default: './lib/style.css'},
'./package.json': './package.json',
},
} satisfies PackageJSON

expect(() => testParseExports({pkg})).toThrowError(
'package.json: `exports["./style.css"]`: export conditions not supported for CSS files.',
)
})

test('css exports must exist on file system', () => {
const pkg = {
type,
name,
version,
files,
main: './lib/index.js',
types: './lib/index.d.ts',
exports: {
'.': {
source: './src/index.ts',
default: './lib/index.js',
},
'./style.css': './src/style.css',
'./package.json': './package.json',
},
} satisfies PackageJSON

expect(() => testParseExports({pkg})).toThrowError(
'package.json: `exports["./style.css"]`: file does not exist.',
)
})

test('the "package.json" field should be set to "./package.json" (if set)', () => {
const pkg = {
type,
name,
version,
files,
main: './lib/index.js',
types: './lib/index.d.ts',
exports: {
'.': {
source: './src/index.ts',
default: './lib/index.js',
},
'./package.json': './other.json',
},
} satisfies PackageJSON

expect(() => testParseExports({pkg})).toThrowError(
'package.json: `exports["./package.json"]` must be "./package.json"',
)
})

describe.runIf(legacyExports)('legacyExports: true', () => {
test('it handles "browsers" if it only redirects "source"', () => {
const pkg = {
Expand Down
21 changes: 19 additions & 2 deletions test/parseTasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {parseStrictOptions} from '../src/node/strict'

const strictOptions = parseStrictOptions({})
const logger = createLogger()
const cwd = process.cwd()

test('should parse tasks (type: module)', () => {
const pkg: PackageJSON = {
Expand Down Expand Up @@ -41,9 +42,17 @@ test('should parse tasks (type: module)', () => {
},
}

const exports = parseExports({pkg, strict: true, strictOptions, logger, legacyExports: false})
const exports = parseExports({
cwd,
pkg,
strict: true,
strictOptions,
logger,
legacyExports: false,
})

const ctx: BuildContext = {
bundledPackages: [],
cwd: '/test',
distPath: '/test/dist',
emitDeclarationOnly: false,
Expand Down Expand Up @@ -177,9 +186,17 @@ test('should parse tasks (type: commonjs, legacyExports: true)', () => {
},
}

const exports = parseExports({pkg, strict: true, logger, strictOptions, legacyExports: true})
const exports = parseExports({
cwd,
pkg,
strict: true,
logger,
strictOptions,
legacyExports: true,
})

const ctx: BuildContext = {
bundledPackages: [],
config: {legacyExports: true},
cwd: '/test',
distPath: '/test/dist',
Expand Down

0 comments on commit 4540e5b

Please sign in to comment.