diff --git a/packages/vite/src/node/__tests__/build.spec.ts b/packages/vite/src/node/__tests__/build.spec.ts index 27984280b4c38b..ecd0b187b78a12 100644 --- a/packages/vite/src/node/__tests__/build.spec.ts +++ b/packages/vite/src/node/__tests__/build.spec.ts @@ -1,9 +1,11 @@ import { resolve } from 'node:path' import { fileURLToPath } from 'node:url' import type { Logger } from 'vite' -import { describe, expect, test } from 'vitest' +import { describe, expect, test, vi } from 'vitest' +import type { OutputOptions } from 'rollup' import type { LibraryFormats, LibraryOptions } from '../build' import { resolveBuildOutputs, resolveLibFilename } from '../build' +import { createLogger } from '../logger' const __dirname = resolve(fileURLToPath(import.meta.url), '..') @@ -13,6 +15,101 @@ const baseLibOptions: LibraryOptions = { entry: 'mylib.js' } +describe('resolveBuildOutputs', () => { + test('resolves outputs correctly', () => { + const logger = createLogger() + const libOptions: LibraryOptions = { ...baseLibOptions } + const outputs: OutputOptions[] = [{ format: 'es' }] + const resolvedOutputs = resolveBuildOutputs(outputs, libOptions, logger) + + expect(resolvedOutputs).toEqual([ + { + format: 'es' + } + ]) + }) + + test('resolves outputs from lib options', () => { + const logger = createLogger() + const libOptions: LibraryOptions = { ...baseLibOptions, name: 'lib' } + const resolvedOutputs = resolveBuildOutputs(void 0, libOptions, logger) + + expect(resolvedOutputs).toEqual([ + { + format: 'es' + }, + { + format: 'umd' + } + ]) + }) + + test('does not change outputs when lib options are missing', () => { + const logger = createLogger() + const outputs: OutputOptions[] = [{ format: 'es' }] + const resolvedOutputs = resolveBuildOutputs(outputs, false, logger) + + expect(resolvedOutputs).toEqual(outputs) + }) + + test('logs a warning when outputs is an array and formats are specified', () => { + const logger = createLogger() + const loggerSpy = vi.spyOn(logger, 'warn').mockImplementation(() => {}) + const libOptions: LibraryOptions = { + ...baseLibOptions, + formats: ['iife'] + } + const outputs: OutputOptions[] = [{ format: 'es' }] + + resolveBuildOutputs(outputs, libOptions, logger) + + expect(loggerSpy).toHaveBeenCalledWith( + expect.stringContaining('"build.lib.formats" will be ignored because') + ) + }) + + test('throws an error when lib.name is missing on iife format', () => { + const logger = createLogger() + const libOptions: LibraryOptions = { + ...baseLibOptions, + formats: ['iife'] + } + const resolveBuild = () => resolveBuildOutputs(void 0, libOptions, logger) + + expect(resolveBuild).toThrowError(/Option "build\.lib\.name" is required/) + }) + + test('throws an error when lib.name is missing on umd format', () => { + const logger = createLogger() + const libOptions: LibraryOptions = { ...baseLibOptions, formats: ['umd'] } + const resolveBuild = () => resolveBuildOutputs(void 0, libOptions, logger) + + expect(resolveBuild).toThrowError(/Option "build\.lib\.name" is required/) + }) + + test('throws an error when output.name is missing on iife format', () => { + const logger = createLogger() + const libOptions: LibraryOptions = { ...baseLibOptions } + const outputs: OutputOptions[] = [{ format: 'iife' }] + const resolveBuild = () => resolveBuildOutputs(outputs, libOptions, logger) + + expect(resolveBuild).toThrowError( + /Entries in "build\.rollupOptions\.output" must specify "name"/ + ) + }) + + test('throws an error when output.name is missing on umd format', () => { + const logger = createLogger() + const libOptions: LibraryOptions = { ...baseLibOptions } + const outputs: OutputOptions[] = [{ format: 'umd' }] + const resolveBuild = () => resolveBuildOutputs(outputs, libOptions, logger) + + expect(resolveBuild).toThrowError( + /Entries in "build\.rollupOptions\.output" must specify "name"/ + ) + }) +}) + describe('resolveLibFilename', () => { test('custom filename function', () => { const filename = resolveLibFilename( diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 79fe78a7c12dd1..72b5948ccc04af 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -783,41 +783,49 @@ export function resolveBuildOutputs( logger: Logger ): OutputOptions | OutputOptions[] | undefined { if (libOptions) { - const hasMultipleEntries = + const libHasMultipleEntries = typeof libOptions.entry !== 'string' && Object.values(libOptions.entry).length > 1 + const libFormats = + libOptions.formats || + (libHasMultipleEntries ? ['es', 'cjs'] : ['es', 'umd']) + + if (!Array.isArray(outputs)) { + if (libFormats.includes('umd') || libFormats.includes('iife')) { + if (libHasMultipleEntries) { + throw new Error( + 'Multiple entry points are not supported when output formats include "umd" or "iife".' + ) + } - const formats = - libOptions.formats || (hasMultipleEntries ? ['es', 'cjs'] : ['es', 'umd']) - - if (formats.includes('umd') || formats.includes('iife')) { - if (hasMultipleEntries) { - throw new Error( - `Multiple entry points are not supported when output formats include "umd" or "iife".` - ) + if (!libOptions.name) { + throw new Error( + 'Option "build.lib.name" is required when output formats include "umd" or "iife".' + ) + } } - if (!libOptions.name) { - throw new Error( - `Option "build.lib.name" is required when output formats ` + - `include "umd" or "iife".` - ) - } + return libFormats.map((format) => ({ ...outputs, format })) } - if (!outputs) { - return formats.map((format) => ({ format })) - } else if (!Array.isArray(outputs)) { - return formats.map((format) => ({ ...outputs, format })) - } else if (libOptions.formats) { - // user explicitly specifying own output array + + // By this point, we know "outputs" is an Array. + if (libOptions.formats) { logger.warn( colors.yellow( - `"build.lib.formats" will be ignored because ` + - `"build.rollupOptions.output" is already an array format` + '"build.lib.formats" will be ignored because "build.rollupOptions.output" is already an array format.' ) ) } + + outputs.forEach((output) => { + if (['umd', 'iife'].includes(output.format!) && !output.name) { + throw new Error( + 'Entries in "build.rollupOptions.output" must specify "name" when the format is "umd" or "iife".' + ) + } + }) } + return outputs }