diff --git a/packages/vite/src/node/__tests__/build.spec.ts b/packages/vite/src/node/__tests__/build.spec.ts index a692041b0ba04c..fea2e076c1b274 100644 --- a/packages/vite/src/node/__tests__/build.spec.ts +++ b/packages/vite/src/node/__tests__/build.spec.ts @@ -1,8 +1,10 @@ import { resolve } from 'node:path' import { fileURLToPath } from 'node:url' -import { describe, expect, test } from 'vitest' +import type { OutputOptions } from 'rollup' +import { describe, expect, test, vi } from 'vitest' import type { LibraryFormats, LibraryOptions } from '../build' -import { resolveLibFilename } from '../build' +import { resolveBuildOutputs, resolveLibFilename } from '../build' +import { createLogger } from '../logger' const __dirname = resolve(fileURLToPath(import.meta.url), '..') @@ -12,6 +14,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 21cdce31180e5d..3fbaf0124383c5 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -644,36 +644,45 @@ export function resolveLibFilename( return `${name}.${format}.${extension}` } -function resolveBuildOutputs( +export function resolveBuildOutputs( outputs: OutputOptions | OutputOptions[] | undefined, libOptions: LibraryOptions | false, logger: Logger ): OutputOptions | OutputOptions[] | undefined { if (libOptions) { - const formats = libOptions.formats || ['es', 'umd'] - if ( - (formats.includes('umd') || formats.includes('iife')) && - !libOptions.name - ) { - throw new Error( - `Option "build.lib.name" is required when output formats ` + - `include "umd" or "iife".` - ) + const libFormats = libOptions.formats || ['es', 'umd'] + + if (!Array.isArray(outputs)) { + const requiresName = + libFormats.includes('umd') || libFormats.includes('iife') + + if (requiresName && !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 }