diff --git a/packages/qwik/src/optimizer/src/plugins/rollup.ts b/packages/qwik/src/optimizer/src/plugins/rollup.ts index 9e136a7323f..acfc40fd685 100644 --- a/packages/qwik/src/optimizer/src/plugins/rollup.ts +++ b/packages/qwik/src/optimizer/src/plugins/rollup.ts @@ -69,7 +69,7 @@ export function qwikRollup(qwikRollupOpts: QwikRollupPluginOptions = {}): any { }, outputOptions(rollupOutputOpts) { - return normalizeRollupOutputOptions( + return normalizeRollupOutputOptionsObject( qwikPlugin.getPath(), qwikPlugin.getOptions(), rollupOutputOpts @@ -168,11 +168,30 @@ export function normalizeRollupOutputOptions( path: Path, opts: NormalizedQwikPluginOptions, rollupOutputOpts: Rollup.OutputOptions | Rollup.OutputOptions[] | undefined -) { - const outputOpts: Rollup.OutputOptions = {}; - if (rollupOutputOpts && !Array.isArray(rollupOutputOpts)) { - Object.assign(outputOpts, rollupOutputOpts); +): Rollup.OutputOptions[] { + const outputOpts: Rollup.OutputOptions[] = Array.isArray(rollupOutputOpts) + ? // fill the `outputOpts` array with all existing option entries + [...rollupOutputOpts] + : // use the existing rollupOutputOpts object or create a new one + [rollupOutputOpts || {}]; + + // make sure at least one output is present in every case + if (!outputOpts.length) { + outputOpts.push({}); } + + return outputOpts.map((outputOptsObj) => + normalizeRollupOutputOptionsObject(path, opts, outputOptsObj) + ); +} + +export function normalizeRollupOutputOptionsObject( + path: Path, + opts: NormalizedQwikPluginOptions, + rollupOutputOptsObj: Rollup.OutputOptions | undefined +): Rollup.OutputOptions { + const outputOpts: Rollup.OutputOptions = { ...rollupOutputOptsObj }; + if (!outputOpts.assetFileNames) { outputOpts.assetFileNames = 'build/q-[hash].[ext]'; } diff --git a/packages/qwik/src/optimizer/src/plugins/vite.ts b/packages/qwik/src/optimizer/src/plugins/vite.ts index c2727ce94dc..17bee0a4105 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.ts @@ -335,10 +335,14 @@ export function qwikVite(qwikViteOpts: QwikVitePluginOptions = {}): any { updatedViteConfig.build!.outDir = buildOutputDir; updatedViteConfig.build!.rollupOptions = { input: opts.input, - output: { - ...normalizeRollupOutputOptions(path, opts, viteConfig.build?.rollupOptions?.output), - dir: buildOutputDir, - }, + output: normalizeRollupOutputOptions( + path, + opts, + viteConfig.build?.rollupOptions?.output + ).map((outputOptsObj) => { + outputOptsObj.dir = buildOutputDir; + return outputOptsObj; + }), preserveEntrySignatures: 'exports-only', onwarn: (warning, warn) => { if (warning.plugin === 'typescript' && warning.message.includes('outputToFilesystem')) { diff --git a/packages/qwik/src/optimizer/src/plugins/vite.unit.ts b/packages/qwik/src/optimizer/src/plugins/vite.unit.ts index c2676d08457..52bec43cd98 100644 --- a/packages/qwik/src/optimizer/src/plugins/vite.unit.ts +++ b/packages/qwik/src/optimizer/src/plugins/vite.unit.ts @@ -52,7 +52,7 @@ test('command: serve, mode: development', async () => { const opts = await plugin.api?.getOptions(); const build = c.build!; const rollupOptions = build!.rollupOptions!; - const outputOptions = rollupOptions.output as Rollup.OutputOptions; + const outputOptions = rollupOptions.output as Rollup.OutputOptions[]; assert.deepEqual(opts.target, 'client'); assert.deepEqual(opts.buildMode, 'development'); @@ -61,10 +61,17 @@ test('command: serve, mode: development', async () => { assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'dist'))); assert.deepEqual(rollupOptions.input, normalizePath(resolve(cwd, 'src', 'entry.dev'))); - assert.deepEqual(outputOptions.assetFileNames, 'build/q-[hash].[ext]'); - assert.deepEqual(outputOptions.chunkFileNames, 'build/[name].js'); - assert.deepEqual(outputOptions.entryFileNames, 'build/[name].js'); - assert.deepEqual(outputOptions.format, 'es'); + + assert.ok(Array.isArray(outputOptions)); + assert.lengthOf(outputOptions, 1); + + outputOptions.forEach((outputOptionsObj) => { + assert.deepEqual(outputOptionsObj.assetFileNames, 'build/q-[hash].[ext]'); + assert.deepEqual(outputOptionsObj.chunkFileNames, 'build/[name].js'); + assert.deepEqual(outputOptionsObj.entryFileNames, 'build/[name].js'); + assert.deepEqual(outputOptionsObj.format, 'es'); + }); + assert.deepEqual(build.dynamicImportVarsOptions?.exclude, [/./]); assert.deepEqual(build.ssr, undefined); assert.deepEqual(c.optimizeDeps?.include, includeDeps); @@ -85,7 +92,7 @@ test('command: serve, mode: production', async () => { const opts = await plugin.api?.getOptions(); const build = c.build!; const rollupOptions = build!.rollupOptions!; - const outputOptions = rollupOptions.output as Rollup.OutputOptions; + const outputOptions = rollupOptions.output as Rollup.OutputOptions[]; assert.deepEqual(opts.target, 'client'); assert.deepEqual(opts.buildMode, 'production'); @@ -96,10 +103,17 @@ test('command: serve, mode: production', async () => { assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'dist'))); assert.deepEqual(build.emptyOutDir, undefined); assert.deepEqual(rollupOptions.input, normalizePath(resolve(cwd, 'src', 'entry.dev'))); - assert.deepEqual(outputOptions.assetFileNames, 'build/q-[hash].[ext]'); - assert.deepEqual(outputOptions.chunkFileNames, 'build/q-[hash].js'); - assert.deepEqual(outputOptions.entryFileNames, 'build/q-[hash].js'); - assert.deepEqual(outputOptions.format, 'es'); + + assert.ok(Array.isArray(outputOptions)); + assert.lengthOf(outputOptions, 1); + + outputOptions.forEach((outputOptionsObj) => { + assert.deepEqual(outputOptionsObj.assetFileNames, 'build/q-[hash].[ext]'); + assert.deepEqual(outputOptionsObj.chunkFileNames, 'build/q-[hash].js'); + assert.deepEqual(outputOptionsObj.entryFileNames, 'build/q-[hash].js'); + assert.deepEqual(outputOptionsObj.format, 'es'); + }); + assert.deepEqual(build.dynamicImportVarsOptions?.exclude, [/./]); assert.deepEqual(build.ssr, undefined); assert.deepEqual(c.optimizeDeps?.include, includeDeps); @@ -119,7 +133,7 @@ test('command: build, mode: development', async () => { const opts = await plugin.api?.getOptions(); const build = c.build!; const rollupOptions = build!.rollupOptions!; - const outputOptions = rollupOptions.output as Rollup.OutputOptions; + const outputOptions = rollupOptions.output as Rollup.OutputOptions[]; assert.deepEqual(opts.target, 'client'); assert.deepEqual(opts.buildMode, 'development'); @@ -131,9 +145,16 @@ test('command: build, mode: development', async () => { assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'dist'))); assert.deepEqual(build.emptyOutDir, undefined); assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'root'))]); - assert.deepEqual(outputOptions.assetFileNames, 'build/q-[hash].[ext]'); - assert.deepEqual(outputOptions.chunkFileNames, 'build/[name].js'); - assert.deepEqual(outputOptions.entryFileNames, 'build/[name].js'); + + assert.ok(Array.isArray(outputOptions)); + assert.lengthOf(outputOptions, 1); + + outputOptions.forEach((outputOptionsObj) => { + assert.deepEqual(outputOptionsObj.assetFileNames, 'build/q-[hash].[ext]'); + assert.deepEqual(outputOptionsObj.chunkFileNames, 'build/[name].js'); + assert.deepEqual(outputOptionsObj.entryFileNames, 'build/[name].js'); + }); + assert.deepEqual(build.dynamicImportVarsOptions?.exclude, [/./]); assert.deepEqual(build.ssr, undefined); assert.deepEqual(c.optimizeDeps?.include, includeDeps); @@ -156,7 +177,7 @@ test('command: build, mode: production', async () => { const opts = await plugin.api?.getOptions(); const build = c.build!; const rollupOptions = build!.rollupOptions!; - const outputOptions = rollupOptions.output as Rollup.OutputOptions; + const outputOptions = rollupOptions.output as Rollup.OutputOptions[]; assert.deepEqual(opts.target, 'client'); assert.deepEqual(opts.buildMode, 'production'); @@ -168,9 +189,16 @@ test('command: build, mode: production', async () => { assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'dist'))); assert.deepEqual(build.emptyOutDir, undefined); assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'root'))]); - assert.deepEqual(outputOptions.assetFileNames, 'build/q-[hash].[ext]'); - assert.deepEqual(outputOptions.chunkFileNames, 'build/q-[hash].js'); - assert.deepEqual(outputOptions.entryFileNames, 'build/q-[hash].js'); + + assert.ok(Array.isArray(outputOptions)); + assert.lengthOf(outputOptions, 1); + + outputOptions.forEach((outputOptionsObj) => { + assert.deepEqual(outputOptionsObj.assetFileNames, 'build/q-[hash].[ext]'); + assert.deepEqual(outputOptionsObj.chunkFileNames, 'build/q-[hash].js'); + assert.deepEqual(outputOptionsObj.entryFileNames, 'build/q-[hash].js'); + }); + assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'dist'))); assert.deepEqual(build.dynamicImportVarsOptions?.exclude, [/./]); assert.deepEqual(build.ssr, undefined); @@ -220,7 +248,7 @@ test('command: build, --ssr entry.server.tsx', async () => { const opts = await plugin.api?.getOptions(); const build = c.build!; const rollupOptions = build!.rollupOptions!; - const outputOptions = rollupOptions.output as Rollup.OutputOptions; + const outputOptions = rollupOptions.output as Rollup.OutputOptions[]; assert.deepEqual(opts.target, 'ssr'); assert.deepEqual(opts.buildMode, 'development'); @@ -232,9 +260,16 @@ test('command: build, --ssr entry.server.tsx', async () => { assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'server'))); assert.deepEqual(build.emptyOutDir, undefined); assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'entry.server.tsx'))]); - assert.deepEqual(outputOptions.assetFileNames, 'build/q-[hash].[ext]'); - assert.deepEqual(outputOptions.chunkFileNames, undefined); - assert.deepEqual(outputOptions.entryFileNames, undefined); + + assert.ok(Array.isArray(outputOptions)); + assert.lengthOf(outputOptions, 1); + + outputOptions.forEach((outputOptionsObj) => { + assert.deepEqual(outputOptionsObj.assetFileNames, 'build/q-[hash].[ext]'); + assert.deepEqual(outputOptionsObj.chunkFileNames, undefined); + assert.deepEqual(outputOptionsObj.entryFileNames, undefined); + }); + assert.deepEqual(build.outDir, normalizePath(resolve(cwd, 'server'))); assert.deepEqual(build.dynamicImportVarsOptions?.exclude, [/./]); assert.deepEqual(build.ssr, true); @@ -348,12 +383,81 @@ test('command: build, --mode lib', async () => { const opts = await plugin.api?.getOptions(); const build = c.build!; const rollupOptions = build!.rollupOptions!; + const outputOptions = rollupOptions.output as Rollup.OutputOptions[]; assert.deepEqual(opts.target, 'lib'); assert.deepEqual(opts.buildMode, 'development'); assert.deepEqual(build.minify, false); assert.deepEqual(build.ssr, undefined); assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'index.ts'))]); + + assert.ok(Array.isArray(outputOptions)); + assert.lengthOf(outputOptions, 1); + + outputOptions.forEach((outputOptionsObj) => { + assert.deepEqual(outputOptionsObj.assetFileNames, 'build/q-[hash].[ext]'); + assert.deepEqual(outputOptionsObj.chunkFileNames, undefined); + }); + + assert.deepEqual(c.build.outDir, normalizePath(resolve(cwd, 'lib'))); + assert.deepEqual(build.emptyOutDir, undefined); + assert.deepEqual(opts.resolveQwikBuild, true); +}); + +test('command: build, --mode lib with multiple outputs', async () => { + const initOpts = { + optimizerOptions: mockOptimizerOptions(), + }; + const plugin = qwikVite(initOpts)[0]; + const c: any = (await plugin.config( + { + build: { + lib: { + entry: './src/index.ts', + }, + rollupOptions: { + output: [ + { + format: 'es', + entryFileNames: 'index.esm.js', + }, + { + format: 'es', + entryFileNames: 'index.mjs', + }, + { + format: 'cjs', + entryFileNames: 'index.cjs.js', + }, + { + format: 'cjs', + entryFileNames: 'index.cjs', + }, + ], + }, + }, + }, + { command: 'build', mode: 'lib' } + ))!; + const opts = await plugin.api?.getOptions(); + const build = c.build!; + const rollupOptions = build!.rollupOptions!; + const outputOptions = rollupOptions.output as Rollup.OutputOptions[]; + + assert.deepEqual(opts.target, 'lib'); + assert.deepEqual(opts.buildMode, 'development'); + assert.deepEqual(build.minify, false); + assert.deepEqual(build.ssr, undefined); + assert.deepEqual(rollupOptions.input, [normalizePath(resolve(cwd, 'src', 'index.ts'))]); + + assert.ok(Array.isArray(outputOptions)); + assert.lengthOf(outputOptions, 4); + + outputOptions.forEach((outputOptionsObj) => { + assert.deepEqual(outputOptionsObj.assetFileNames, 'build/q-[hash].[ext]'); + assert.deepEqual(outputOptionsObj.chunkFileNames, undefined); + }); + assert.deepEqual(c.build.outDir, normalizePath(resolve(cwd, 'lib'))); assert.deepEqual(build.emptyOutDir, undefined); assert.deepEqual(opts.resolveQwikBuild, true);