From 3951d8938c4462d7ec80f5e004cdaf60a508e717 Mon Sep 17 00:00:00 2001 From: Marco Schumacher Date: Sun, 13 Nov 2022 21:33:04 +0100 Subject: [PATCH] feat(lib): multiple entries for umd/iife --- docs/config/build-options.md | 2 +- .../vite/src/node/__tests__/build.spec.ts | 200 +++++++++- packages/vite/src/node/build.ts | 345 ++++++++++++------ packages/vite/src/node/plugins/reporter.ts | 6 + playground/vue-lib/__tests__/vue-lib.spec.ts | 10 +- 5 files changed, 432 insertions(+), 131 deletions(-) diff --git a/docs/config/build-options.md b/docs/config/build-options.md index 2c60c838fa54ee..09b5880342ad89 100644 --- a/docs/config/build-options.md +++ b/docs/config/build-options.md @@ -148,7 +148,7 @@ Options to pass on to [@rollup/plugin-dynamic-import-vars](https://github.com/ro - **Type:** `{ entry: string | string[] | { [entryAlias: string]: string }, name?: string, formats?: ('es' | 'cjs' | 'umd' | 'iife')[], fileName?: string | ((format: ModuleFormat, entryName: string) => string) }` - **Related:** [Library Mode](/guide/build#library-mode) -Build as a library. `entry` is required since the library cannot use HTML as entry. `name` is the exposed global variable and is required when `formats` includes `'umd'` or `'iife'`. Default `formats` are `['es', 'umd']`, or `['es', 'cjs']`, if multiple entries are used. `fileName` is the name of the package file output, default `fileName` is the name option of package.json, it can also be defined as function taking the `format` and `entryAlias` as arguments. +Build as a library. `entry` is required since the library cannot use HTML as entry. `name` is the exposed global variable and is required when `formats` includes `'umd'` or `'iife'` and entry is not defined as object (in which case the object keys define names for each entry). Default `formats` are `['es', 'umd']`, or `['es', 'cjs']`, if multiple entries are used. `fileName` is the name of the package file output, default `fileName` is the name option of package.json, it can also be defined as function taking the `format` and `entryAlias` as arguments. ## build.manifest diff --git a/packages/vite/src/node/__tests__/build.spec.ts b/packages/vite/src/node/__tests__/build.spec.ts index ecd0b187b78a12..2434318ac0b996 100644 --- a/packages/vite/src/node/__tests__/build.spec.ts +++ b/packages/vite/src/node/__tests__/build.spec.ts @@ -4,7 +4,12 @@ import type { Logger } from 'vite' import { describe, expect, test, vi } from 'vitest' import type { OutputOptions } from 'rollup' import type { LibraryFormats, LibraryOptions } from '../build' -import { resolveBuildOutputs, resolveLibFilename } from '../build' +import { + resolveBuildOutputs, + resolveBuilds, + resolveLibFilename, + resolveLibName +} from '../build' import { createLogger } from '../logger' const __dirname = resolve(fileURLToPath(import.meta.url), '..') @@ -20,7 +25,12 @@ describe('resolveBuildOutputs', () => { const logger = createLogger() const libOptions: LibraryOptions = { ...baseLibOptions } const outputs: OutputOptions[] = [{ format: 'es' }] - const resolvedOutputs = resolveBuildOutputs(outputs, libOptions, logger) + const resolvedOutputs = resolveBuildOutputs( + libOptions.entry, + outputs, + libOptions, + logger + ) expect(resolvedOutputs).toEqual([ { @@ -32,7 +42,12 @@ describe('resolveBuildOutputs', () => { test('resolves outputs from lib options', () => { const logger = createLogger() const libOptions: LibraryOptions = { ...baseLibOptions, name: 'lib' } - const resolvedOutputs = resolveBuildOutputs(void 0, libOptions, logger) + const resolvedOutputs = resolveBuildOutputs( + libOptions.entry, + void 0, + libOptions, + logger + ) expect(resolvedOutputs).toEqual([ { @@ -47,7 +62,7 @@ describe('resolveBuildOutputs', () => { test('does not change outputs when lib options are missing', () => { const logger = createLogger() const outputs: OutputOptions[] = [{ format: 'es' }] - const resolvedOutputs = resolveBuildOutputs(outputs, false, logger) + const resolvedOutputs = resolveBuildOutputs('', outputs, false, logger) expect(resolvedOutputs).toEqual(outputs) }) @@ -61,7 +76,7 @@ describe('resolveBuildOutputs', () => { } const outputs: OutputOptions[] = [{ format: 'es' }] - resolveBuildOutputs(outputs, libOptions, logger) + resolveBuildOutputs(libOptions.entry, outputs, libOptions, logger) expect(loggerSpy).toHaveBeenCalledWith( expect.stringContaining('"build.lib.formats" will be ignored because') @@ -74,27 +89,37 @@ describe('resolveBuildOutputs', () => { ...baseLibOptions, formats: ['iife'] } - const resolveBuild = () => resolveBuildOutputs(void 0, libOptions, logger) + const resolveBuild = () => + resolveBuildOutputs(libOptions.entry, void 0, libOptions, logger) - expect(resolveBuild).toThrowError(/Option "build\.lib\.name" is required/) + expect(resolveBuild).toThrowError( + `"build.lib.entry"/"build.rollupOptions.input" must be defined as object or option ` + + `"build.lib.name"/"build.rollupOptions.output.name" must be provided when output formats include "umd" or "iife".` + ) }) 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) + const resolveBuild = () => + resolveBuildOutputs(libOptions.entry, void 0, libOptions, logger) - expect(resolveBuild).toThrowError(/Option "build\.lib\.name" is required/) + expect(resolveBuild).toThrowError( + `"build.lib.entry"/"build.rollupOptions.input" must be defined as object or option ` + + `"build.lib.name"/"build.rollupOptions.output.name" must be provided when output formats include "umd" or "iife".` + ) }) 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) + const resolveBuild = () => + resolveBuildOutputs(libOptions.entry, outputs, libOptions, logger) expect(resolveBuild).toThrowError( - /Entries in "build\.rollupOptions\.output" must specify "name"/ + `"build.lib.entry"/"build.rollupOptions.input" must be defined as object or option ` + + `"build.lib.name"/"build.rollupOptions.output.name" must be provided when output formats include "umd" or "iife".` ) }) @@ -102,10 +127,12 @@ describe('resolveBuildOutputs', () => { const logger = createLogger() const libOptions: LibraryOptions = { ...baseLibOptions } const outputs: OutputOptions[] = [{ format: 'umd' }] - const resolveBuild = () => resolveBuildOutputs(outputs, libOptions, logger) + const resolveBuild = () => + resolveBuildOutputs(libOptions.entry, outputs, libOptions, logger) expect(resolveBuild).toThrowError( - /Entries in "build\.rollupOptions\.output" must specify "name"/ + `"build.lib.entry"/"build.rollupOptions.input" must be defined as object or option ` + + `"build.lib.name"/"build.rollupOptions.output.name" must be provided when output formats include "umd" or "iife".` ) }) }) @@ -350,7 +377,12 @@ describe('resolveBuildOutputs', () => { name: 'entryA' } - const outputs = resolveBuildOutputs(undefined, libOptions, {} as Logger) + const outputs = resolveBuildOutputs( + libOptions.entry, + undefined, + libOptions, + {} as Logger + ) expect(outputs).toEqual([{ format: 'es' }, { format: 'umd' }]) }) @@ -360,8 +392,146 @@ describe('resolveBuildOutputs', () => { entry: ['entryA.js', 'entryB.js'] } - const outputs = resolveBuildOutputs(undefined, libOptions, {} as Logger) + const outputs = resolveBuildOutputs( + libOptions.entry, + undefined, + libOptions, + {} as Logger + ) expect(outputs).toEqual([{ format: 'es' }, { format: 'cjs' }]) }) + + test('error on missing names for multiple entries', () => { + const libOptions: LibraryOptions = { + entry: ['entryA.js', 'entryB.js'], + formats: ['umd'] + } + + expect(() => + resolveBuildOutputs(libOptions.entry, undefined, libOptions, {} as Logger) + ).toThrow( + `"build.lib.entry"/"build.rollupOptions.input" must be defined as object when there are multiple ` + + `inputs and output formats include "umd" or "iife".` + ) + }) +}) + +describe('resolveBuilds', () => { + test('one entry, one build', () => { + const libOptions: LibraryOptions = { + entry: 'entryA.js', + name: 'entryA', + formats: ['es', 'cjs', 'umd', 'iife'] + } + + const builds = resolveBuilds( + libOptions.entry, + undefined, + libOptions, + (_input) => (output) => output, + {} as Logger + ) + + expect(builds).toEqual([ + { + input: 'entryA.js', + output: [ + { format: 'es' }, + { format: 'cjs' }, + { format: 'umd' }, + { format: 'iife' } + ] + } + ]) + }) + + test('multiple entries, one build', () => { + const libOptions: LibraryOptions = { + entry: { + entryA: 'entryA.js', + entryB: 'entryB.js' + }, + formats: ['es', 'cjs'] + } + + const builds = resolveBuilds( + libOptions.entry, + undefined, + libOptions, + (_input) => (output) => output, + {} as Logger + ) + + expect(builds).toEqual([ + { + input: { + entryA: 'entryA.js', + entryB: 'entryB.js' + }, + output: [{ format: 'es' }, { format: 'cjs' }] + } + ]) + }) + + test('multiple builds', () => { + const libOptions: LibraryOptions = { + entry: { + entryA: 'entryA.js', + entryB: 'entryB.js' + }, + formats: ['es', 'cjs', 'umd', 'iife'] + } + + const builds = resolveBuilds( + libOptions.entry, + undefined, + libOptions, + (_input) => (output) => output, + {} as Logger + ) + + expect(builds).toEqual([ + { + input: { + entryA: 'entryA.js', + entryB: 'entryB.js' + }, + output: [{ format: 'es' }, { format: 'cjs' }] + }, + { + input: { entryA: 'entryA.js' }, + output: [{ format: 'umd' }, { format: 'iife' }], + label: 'Extra non code splitting build for entry: entryA' + }, + { + input: { entryB: 'entryB.js' }, + output: [{ format: 'umd' }, { format: 'iife' }], + label: 'Extra non code splitting build for entry: entryB' + } + ]) + }) +}) + +describe('resolveLibName', () => { + test('returns name if provided', () => { + const libOptions: LibraryOptions = { + entry: '', + name: 'lib' + } + + const name = resolveLibName(libOptions, '') + expect(name).toBe('lib') + }) + + test('returns name from entries object', () => { + const libOptions: LibraryOptions = { + entry: { + entryA: 'entryA.js' + } + } + + const name = resolveLibName(libOptions, libOptions.entry) + expect(name).toBe('entryA') + }) }) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 72b5948ccc04af..2a8ecaa1fa8168 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -37,6 +37,7 @@ import { } from './utils' import { manifestPlugin } from './plugins/manifest' import type { Logger } from './logger' +import { LogLevels } from './logger' import { dataURIPlugin } from './plugins/dataUri' import { buildImportAnalysisPlugin } from './plugins/importAnalysisBuild' import { @@ -518,7 +519,6 @@ async function doBuild( ? 'strict' : false, ...options.rollupOptions, - input, plugins, external, onwarn(warning, warn) { @@ -540,75 +540,74 @@ async function doBuild( } try { - const buildOutputOptions = (output: OutputOptions = {}): OutputOptions => { - // See https://github.com/vitejs/vite/issues/5812#issuecomment-984345618 - // @ts-ignore - if (output.output) { - config.logger.warn( - `You've set "rollupOptions.output.output" in your config. ` + - `This is deprecated and will override all Vite.js default output options. ` + - `Please use "rollupOptions.output" instead.` - ) - } + const buildOutputOptions = + (input: InputOption) => + (output: OutputOptions): OutputOptions => { + // See https://github.com/vitejs/vite/issues/5812#issuecomment-984345618 + // @ts-ignore + if (output.output) { + config.logger.warn( + `You've set "rollupOptions.output.output" in your config. ` + + `This is deprecated and will override all Vite.js default output options. ` + + `Please use "rollupOptions.output" instead.` + ) + } - const ssrNodeBuild = ssr && config.ssr.target === 'node' - const ssrWorkerBuild = ssr && config.ssr.target === 'webworker' - const cjsSsrBuild = ssr && config.ssr.format === 'cjs' - - const format = output.format || (cjsSsrBuild ? 'cjs' : 'es') - const jsExt = - ssrNodeBuild || libOptions - ? resolveOutputJsExtension(format, getPkgJson(config.root)?.type) - : 'js' - return { - dir: outDir, - // Default format is 'es' for regular and for SSR builds - format, - exports: cjsSsrBuild ? 'named' : 'auto', - sourcemap: options.sourcemap, - name: libOptions ? libOptions.name : undefined, - // es2015 enables `generatedCode.symbols` - // - #764 add `Symbol.toStringTag` when build es module into cjs chunk - // - #1048 add `Symbol.toStringTag` for module default export - generatedCode: 'es2015', - entryFileNames: ssr - ? `[name].${jsExt}` - : libOptions - ? ({ name }) => - resolveLibFilename(libOptions, format, name, config.root, jsExt) - : path.posix.join(options.assetsDir, `[name].[hash].${jsExt}`), - chunkFileNames: libOptions - ? `[name].[hash].${jsExt}` - : path.posix.join(options.assetsDir, `[name].[hash].${jsExt}`), - assetFileNames: libOptions - ? `[name].[ext]` - : path.posix.join(options.assetsDir, `[name].[hash].[ext]`), - inlineDynamicImports: - output.format === 'umd' || - output.format === 'iife' || - (ssrWorkerBuild && - (typeof input === 'string' || Object.keys(input).length === 1)), - ...output + const ssrNodeBuild = ssr && config.ssr.target === 'node' + const ssrWorkerBuild = ssr && config.ssr.target === 'webworker' + const cjsSsrBuild = ssr && config.ssr.format === 'cjs' + + const format = output.format || (cjsSsrBuild ? 'cjs' : 'es') + const jsExt = + ssrNodeBuild || libOptions + ? resolveOutputJsExtension(format, getPkgJson(config.root)?.type) + : 'js' + return { + dir: outDir, + // Default format is 'es' for regular and for SSR builds + format, + exports: cjsSsrBuild ? 'named' : 'auto', + sourcemap: options.sourcemap, + name: libOptions ? resolveLibName(libOptions, input) : undefined, + // es2015 enables `generatedCode.symbols` + // - #764 add `Symbol.toStringTag` when build es module into cjs chunk + // - #1048 add `Symbol.toStringTag` for module default export + generatedCode: 'es2015', + entryFileNames: ssr + ? `[name].${jsExt}` + : libOptions + ? ({ name }) => + resolveLibFilename(libOptions, format, name, config.root, jsExt) + : path.posix.join(options.assetsDir, `[name].[hash].${jsExt}`), + chunkFileNames: libOptions + ? `[name].[hash].${jsExt}` + : path.posix.join(options.assetsDir, `[name].[hash].${jsExt}`), + assetFileNames: libOptions + ? `[name].[ext]` + : path.posix.join(options.assetsDir, `[name].[hash].[ext]`), + inlineDynamicImports: + output.format === 'umd' || + output.format === 'iife' || + (ssrWorkerBuild && + (typeof input === 'string' || Object.keys(input).length === 1)), + ...output + } } - } // resolve lib mode outputs - const outputs = resolveBuildOutputs( - options.rollupOptions?.output, + const builds = resolveBuilds( + input, + options.rollupOptions.output, libOptions, + buildOutputOptions, config.logger ) - const normalizedOutputs: OutputOptions[] = [] - if (Array.isArray(outputs)) { - for (const resolvedOutput of outputs) { - normalizedOutputs.push(buildOutputOptions(resolvedOutput)) - } - } else { - normalizedOutputs.push(buildOutputOptions(outputs)) - } + const outDirs = builds.flatMap((build) => + build.output.map(({ dir }) => resolve(dir!)) + ) - const outDirs = normalizedOutputs.map(({ dir }) => resolve(dir!)) + const shouldLogInfo = LogLevels[config.logLevel || 'info'] >= LogLevels.info // watch file changes with rollup if (config.build.watch) { @@ -619,18 +618,28 @@ async function doBuild( ) const { watch } = await import('rollup') - const watcher = watch({ - ...rollupOptions, - output: normalizedOutputs, - watch: { - ...config.build.watch, - chokidar: resolvedChokidarOptions - } - }) + const watcher = watch( + builds.map(({ input, output }) => ({ + ...rollupOptions, + input, + output, + watch: { + ...config.build.watch, + chokidar: resolvedChokidarOptions + } + })) + ) watcher.on('event', (event) => { if (event.code === 'BUNDLE_START') { - config.logger.info(colors.cyan(`\nbuild started...`)) + const label = builds.find( + (build) => + JSON.stringify(build.input) === JSON.stringify(event.input) + )?.label + + config.logger.info( + colors.cyan(`\nbuild started${label ? ` (${label})` : ''}...`) + ) if (options.write) { prepareOutDir(outDirs, options.emptyOutDir, config) } @@ -647,22 +656,36 @@ async function doBuild( // write or generate files with rollup const { rollup } = await import('rollup') - const bundle = await rollup(rollupOptions) - parallelBuilds.push(bundle) - - const generate = (output: OutputOptions = {}) => { - return bundle[options.write ? 'write' : 'generate'](output) - } if (options.write) { prepareOutDir(outDirs, options.emptyOutDir, config) } const res = [] - for (const output of normalizedOutputs) { - res.push(await generate(output)) + + for (const { input, output, label } of builds) { + if (shouldLogInfo && label) { + config.logger.info(`\n${label}`) + } + + const bundle = await rollup({ + ...rollupOptions, + input, + output + }) + + parallelBuilds.push(bundle) + + const generate = (output: OutputOptions = {}) => { + return bundle[options.write ? 'write' : 'generate'](output) + } + + for (const part of output) { + res.push(await generate(part)) + } } - return Array.isArray(outputs) ? res : res[0] + + return res.length > 1 ? res : res[0] } catch (e) { outputBuildError(e) throw e @@ -745,6 +768,23 @@ function resolveOutputJsExtension( } } +export function resolveLibName( + libOptions: LibraryOptions, + input: InputOption +): string | undefined { + if (libOptions.name !== undefined) { + return libOptions.name + } + + if ( + typeof input !== 'string' && + !Array.isArray(input) && + Object.keys(input).length === 1 + ) { + return Object.keys(input)[0] + } +} + export function resolveLibFilename( libOptions: LibraryOptions, format: ModuleFormat, @@ -778,55 +818,142 @@ export function resolveLibFilename( } export function resolveBuildOutputs( + input: InputOption, outputs: OutputOptions | OutputOptions[] | undefined, libOptions: LibraryOptions | false, logger: Logger -): OutputOptions | OutputOptions[] | undefined { - if (libOptions) { - const libHasMultipleEntries = - typeof libOptions.entry !== 'string' && - Object.values(libOptions.entry).length > 1 - const libFormats = - libOptions.formats || - (libHasMultipleEntries ? ['es', 'cjs'] : ['es', 'umd']) +): OutputOptions[] { + const hasMultipleEntries = + typeof input !== 'string' && Object.values(input).length > 1 + if (libOptions) { 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 libFormats = + libOptions.formats || + (hasMultipleEntries ? ['es', 'cjs'] : ['es', 'umd']) - 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 })) - } - - // By this point, we know "outputs" is an Array. - if (libOptions.formats) { + outputs = libFormats.map((format) => ({ ...outputs, format })) + } else if (libOptions.formats) { logger.warn( colors.yellow( '"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) { + if (!Array.isArray(outputs)) { + outputs = [outputs ?? {}] + } + + outputs.forEach((output) => { + if (output.format === 'umd' || output.format === 'iife') { + if (hasMultipleEntries && Array.isArray(input)) { + throw new Error( + `"build.lib.entry"/"build.rollupOptions.input" must be defined as object when there are multiple ` + + `inputs and output formats include "umd" or "iife".` + ) + } + if ( + (typeof input === 'string' || Array.isArray(input)) && + !output.name && + !(libOptions && libOptions.name) + ) { throw new Error( - 'Entries in "build.rollupOptions.output" must specify "name" when the format is "umd" or "iife".' + `"build.lib.entry"/"build.rollupOptions.input" must be defined as object or option ` + + `"build.lib.name"/"build.rollupOptions.output.name" must be provided when output formats include "umd" or "iife".` ) } + } + }) + + return outputs +} + +export function resolveBuilds( + input: InputOption, + output: OutputOptions | OutputOptions[] | undefined, + libOptions: LibraryOptions | false, + buildOutputOptions: ( + input: InputOption + ) => (output: OutputOptions) => OutputOptions, + logger: Logger +): { + input: InputOption + output: OutputOptions[] + label?: string +}[] { + const resolvedOutput = resolveBuildOutputs(input, output, libOptions, logger) + + // For umd and iife formats, one separate build per entrypoint is needed + // because rollup doesn't support code splitting builds for them + // https://github.com/rollup/rollup/issues/2072 + // All other formats and entry points will be one build + + const hasMultipleEntries = + typeof input !== 'string' && Object.values(input).length > 1 + const canUseCodeSplitting = (output: OutputOptions) => + output.format !== 'umd' && output.format !== 'iife' + + const mainBuild = hasMultipleEntries + ? resolvedOutput.filter(canUseCodeSplitting) + : resolvedOutput + + const outputsWithoutCodeSplitting = hasMultipleEntries + ? resolvedOutput.filter((output) => !canUseCodeSplitting(output)) + : [] + + const builds: { + input: InputOption + output: OutputOptions[] + label?: string + }[] = [] + + // One build for all formats, with code splitting if needed + if (mainBuild.length > 0) { + builds.push({ + input, + output: mainBuild }) } - return outputs + const nonCodeSplittingLabel = (name: string) => + `Extra non code splitting build for entry: ${name}` + + // One build per entry point for formats without code splitting + if (outputsWithoutCodeSplitting.length > 0) { + // TODO first block needed? + if (typeof input === 'string' || !input) { + builds.push({ + input, + output: outputsWithoutCodeSplitting, + label: nonCodeSplittingLabel( + libOptions ? libOptions.name ?? input : input + ) + }) + } else if (Array.isArray(input)) { + for (const i of input) { + builds.push({ + input: i, + output: outputsWithoutCodeSplitting, + label: nonCodeSplittingLabel(libOptions ? libOptions.name ?? i : i) + }) + } + } else if (input) { + for (const [key, value] of Object.entries(input)) { + builds.push({ + input: { [key]: value }, + output: outputsWithoutCodeSplitting, + label: nonCodeSplittingLabel(key) + }) + } + } + } + + return builds.map((build) => ({ + ...build, + output: build.output.map(buildOutputOptions(build.input)) + })) } const warningIgnoreList = [`CIRCULAR_DEPENDENCY`, `THIS_IS_UNDEFINED`] diff --git a/packages/vite/src/node/plugins/reporter.ts b/packages/vite/src/node/plugins/reporter.ts index 79eb4c10975864..322d454ecb7130 100644 --- a/packages/vite/src/node/plugins/reporter.ts +++ b/packages/vite/src/node/plugins/reporter.ts @@ -81,6 +81,12 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:reporter', + buildStart() { + hasTransformed = false + hasRenderedChunk = false + transformedCount = 0 + }, + transform(_, id) { transformedCount++ if (shouldLogInfo) { diff --git a/playground/vue-lib/__tests__/vue-lib.spec.ts b/playground/vue-lib/__tests__/vue-lib.spec.ts index 50b554d63f9f76..d386de85f84b65 100644 --- a/playground/vue-lib/__tests__/vue-lib.spec.ts +++ b/playground/vue-lib/__tests__/vue-lib.spec.ts @@ -25,12 +25,10 @@ describe('vue component library', () => { test('should inject css when cssCodeSplit = true', async () => { // Build lib - const { output } = ( - await build({ - logLevel: 'silent', - configFile: path.resolve(__dirname, '../vite.config.lib-css.ts') - }) - )[0] + const { output } = (await build({ + logLevel: 'silent', + configFile: path.resolve(__dirname, '../vite.config.lib-css.ts') + })) as RollupOutput expect(output[0].code).toContain('.card{padding:4rem}') }) })