Skip to content

Commit 65a0fad

Browse files
authoredSep 30, 2022
feat(lib): allow multiple entries (#7047)
1 parent ee3231c commit 65a0fad

File tree

4 files changed

+194
-16
lines changed

4 files changed

+194
-16
lines changed
 

‎docs/config/build-options.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,10 @@ Options to pass on to [@rollup/plugin-dynamic-import-vars](https://github.com/ro
145145
146146
## build.lib
147147
148-
- **Type:** `{ entry: string, name?: string, formats?: ('es' | 'cjs' | 'umd' | 'iife')[], fileName?: string | ((format: ModuleFormat) => string) }`
148+
- **Type:** `{ entry: string | string[] | { [entryAlias: string]: string }, name?: string, formats?: ('es' | 'cjs' | 'umd' | 'iife')[], fileName?: string | ((format: ModuleFormat, entryName: string) => string) }`
149149
- **Related:** [Library Mode](/guide/build#library-mode)
150150
151-
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']`. `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` as an argument.
151+
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']`. `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.
152152
153153
## build.manifest
154154

‎docs/guide/build.md

+23
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ import { defineConfig } from 'vite'
128128
export default defineConfig({
129129
build: {
130130
lib: {
131+
// Could also be a dictionary or array of multiple entry points
131132
entry: resolve(__dirname, 'lib/main.js'),
132133
name: 'MyLib',
133134
// the proper extensions will be added
@@ -185,6 +186,28 @@ Recommended `package.json` for your lib:
185186
}
186187
```
187188

189+
Or, if exposing multiple entry points:
190+
191+
```json
192+
{
193+
"name": "my-lib",
194+
"type": "module",
195+
"files": ["dist"],
196+
"main": "./dist/my-lib.cjs",
197+
"module": "./dist/my-lib.mjs",
198+
"exports": {
199+
".": {
200+
"import": "./dist/my-lib.mjs",
201+
"require": "./dist/my-lib.cjs"
202+
},
203+
"./secondary": {
204+
"import": "./dist/secondary.mjs",
205+
"require": "./dist/secondary.cjs"
206+
}
207+
}
208+
}
209+
```
210+
188211
::: tip Note
189212
If the `package.json` does not contain `"type": "module"`, Vite will generate different file extensions for Node.js compatibility. `.js` will become `.mjs` and `.cjs` will become `.js`.
190213
:::

‎packages/vite/src/node/__tests__/build.spec.ts

+130
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('resolveLibFilename', () => {
2020
entry: 'mylib.js'
2121
},
2222
'es',
23+
'myLib',
2324
resolve(__dirname, 'packages/name')
2425
)
2526

@@ -33,6 +34,7 @@ describe('resolveLibFilename', () => {
3334
entry: 'mylib.js'
3435
},
3536
'es',
37+
'myLib',
3638
resolve(__dirname, 'packages/name')
3739
)
3840

@@ -45,6 +47,7 @@ describe('resolveLibFilename', () => {
4547
entry: 'mylib.js'
4648
},
4749
'es',
50+
'myLib',
4851
resolve(__dirname, 'packages/name')
4952
)
5053

@@ -58,6 +61,7 @@ describe('resolveLibFilename', () => {
5861
entry: 'mylib.js'
5962
},
6063
'es',
64+
'myLib',
6165
resolve(__dirname, 'packages/noname')
6266
)
6367

@@ -71,6 +75,7 @@ describe('resolveLibFilename', () => {
7175
entry: 'mylib.js'
7276
},
7377
'es',
78+
'myLib',
7479
resolve(__dirname, 'packages/noname')
7580
)
7681
}).toThrow()
@@ -88,6 +93,7 @@ describe('resolveLibFilename', () => {
8893
const filename = resolveLibFilename(
8994
baseLibOptions,
9095
format,
96+
'myLib',
9197
resolve(__dirname, 'packages/noname')
9298
)
9399

@@ -107,10 +113,134 @@ describe('resolveLibFilename', () => {
107113
const filename = resolveLibFilename(
108114
baseLibOptions,
109115
format,
116+
'myLib',
110117
resolve(__dirname, 'packages/module')
111118
)
112119

113120
expect(expectedFilename).toBe(filename)
114121
}
115122
})
123+
124+
test('multiple entries with aliases', () => {
125+
const libOptions: LibraryOptions = {
126+
entry: {
127+
entryA: 'entryA.js',
128+
entryB: 'entryB.js'
129+
}
130+
}
131+
132+
const [fileName1, fileName2] = ['entryA', 'entryB'].map((entryAlias) =>
133+
resolveLibFilename(
134+
libOptions,
135+
'es',
136+
entryAlias,
137+
resolve(__dirname, 'packages/name')
138+
)
139+
)
140+
141+
expect(fileName1).toBe('entryA.mjs')
142+
expect(fileName2).toBe('entryB.mjs')
143+
})
144+
145+
test('multiple entries with aliases: custom filename function', () => {
146+
const libOptions: LibraryOptions = {
147+
entry: {
148+
entryA: 'entryA.js',
149+
entryB: 'entryB.js'
150+
},
151+
fileName: (format, entryAlias) =>
152+
`custom-filename-function.${entryAlias}.${format}.js`
153+
}
154+
155+
const [fileName1, fileName2] = ['entryA', 'entryB'].map((entryAlias) =>
156+
resolveLibFilename(
157+
libOptions,
158+
'es',
159+
entryAlias,
160+
resolve(__dirname, 'packages/name')
161+
)
162+
)
163+
164+
expect(fileName1).toBe('custom-filename-function.entryA.es.js')
165+
expect(fileName2).toBe('custom-filename-function.entryB.es.js')
166+
})
167+
168+
test('multiple entries with aliases: custom filename string', () => {
169+
const libOptions: LibraryOptions = {
170+
entry: {
171+
entryA: 'entryA.js',
172+
entryB: 'entryB.js'
173+
},
174+
fileName: 'custom-filename'
175+
}
176+
177+
const [fileName1, fileName2] = ['entryA', 'entryB'].map((entryAlias) =>
178+
resolveLibFilename(
179+
libOptions,
180+
'es',
181+
entryAlias,
182+
resolve(__dirname, 'packages/name')
183+
)
184+
)
185+
186+
expect(fileName1).toBe('custom-filename.mjs')
187+
expect(fileName2).toBe('custom-filename.mjs')
188+
})
189+
190+
test('multiple entries as array', () => {
191+
const libOptions: LibraryOptions = {
192+
entry: ['entryA.js', 'entryB.js']
193+
}
194+
195+
const [fileName1, fileName2] = ['entryA', 'entryB'].map((entryAlias) =>
196+
resolveLibFilename(
197+
libOptions,
198+
'es',
199+
entryAlias,
200+
resolve(__dirname, 'packages/name')
201+
)
202+
)
203+
204+
expect(fileName1).toBe('entryA.mjs')
205+
expect(fileName2).toBe('entryB.mjs')
206+
})
207+
208+
test('multiple entries as array: custom filename function', () => {
209+
const libOptions: LibraryOptions = {
210+
entry: ['entryA.js', 'entryB.js'],
211+
fileName: (format, entryAlias) =>
212+
`custom-filename-function.${entryAlias}.${format}.js`
213+
}
214+
215+
const [fileName1, fileName2] = ['entryA', 'entryB'].map((entryAlias) =>
216+
resolveLibFilename(
217+
libOptions,
218+
'es',
219+
entryAlias,
220+
resolve(__dirname, 'packages/name')
221+
)
222+
)
223+
224+
expect(fileName1).toBe('custom-filename-function.entryA.es.js')
225+
expect(fileName2).toBe('custom-filename-function.entryB.es.js')
226+
})
227+
228+
test('multiple entries as array: custom filename string', () => {
229+
const libOptions: LibraryOptions = {
230+
entry: ['entryA.js', 'entryB.js'],
231+
fileName: 'custom-filename'
232+
}
233+
234+
const [fileName1, fileName2] = ['entryA', 'entryB'].map((entryAlias) =>
235+
resolveLibFilename(
236+
libOptions,
237+
'es',
238+
entryAlias,
239+
resolve(__dirname, 'packages/name')
240+
)
241+
)
242+
243+
expect(fileName1).toBe('custom-filename.mjs')
244+
expect(fileName2).toBe('custom-filename.mjs')
245+
})
116246
})

‎packages/vite/src/node/build.ts

+39-14
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import path from 'node:path'
33
import colors from 'picocolors'
44
import type {
55
ExternalOption,
6+
InputOption,
67
InternalModuleFormat,
78
ModuleFormat,
89
OutputOptions,
@@ -214,7 +215,7 @@ export interface LibraryOptions {
214215
/**
215216
* Path of library entry
216217
*/
217-
entry: string
218+
entry: InputOption
218219
/**
219220
* The name of the exposed global variable. Required when the `formats` option includes
220221
* `umd` or `iife`
@@ -230,7 +231,7 @@ export interface LibraryOptions {
230231
* of the project package.json. It can also be defined as a function taking the
231232
* format as an argument.
232233
*/
233-
fileName?: string | ((format: ModuleFormat) => string)
234+
fileName?: string | ((format: ModuleFormat, entryName: string) => string)
234235
}
235236

236237
export type LibraryFormats = 'es' | 'cjs' | 'umd' | 'iife'
@@ -439,7 +440,17 @@ async function doBuild(
439440

440441
const resolve = (p: string) => path.resolve(config.root, p)
441442
const input = libOptions
442-
? options.rollupOptions?.input || resolve(libOptions.entry)
443+
? options.rollupOptions?.input ||
444+
(typeof libOptions.entry === 'string'
445+
? resolve(libOptions.entry)
446+
: Array.isArray(libOptions.entry)
447+
? libOptions.entry.map(resolve)
448+
: Object.fromEntries(
449+
Object.entries(libOptions.entry).map(([alias, file]) => [
450+
alias,
451+
resolve(file)
452+
])
453+
))
443454
: typeof options.ssr === 'string'
444455
? resolve(options.ssr)
445456
: options.rollupOptions?.input || resolve('index.html')
@@ -536,7 +547,8 @@ async function doBuild(
536547
entryFileNames: ssr
537548
? `[name].${jsExt}`
538549
: libOptions
539-
? resolveLibFilename(libOptions, format, config.root, jsExt)
550+
? ({ name }) =>
551+
resolveLibFilename(libOptions, format, name, config.root, jsExt)
540552
: path.posix.join(options.assetsDir, `[name].[hash].${jsExt}`),
541553
chunkFileNames: libOptions
542554
? `[name].[hash].${jsExt}`
@@ -705,15 +717,20 @@ function resolveOutputJsExtension(
705717
export function resolveLibFilename(
706718
libOptions: LibraryOptions,
707719
format: ModuleFormat,
720+
entryName: string,
708721
root: string,
709722
extension?: JsExt
710723
): string {
711724
if (typeof libOptions.fileName === 'function') {
712-
return libOptions.fileName(format)
725+
return libOptions.fileName(format, entryName)
713726
}
714727

715728
const packageJson = getPkgJson(root)
716-
const name = libOptions.fileName || getPkgName(packageJson.name)
729+
const name =
730+
libOptions.fileName ||
731+
(typeof libOptions.entry === 'string'
732+
? getPkgName(packageJson.name)
733+
: entryName)
717734

718735
if (!name)
719736
throw new Error(
@@ -736,14 +753,22 @@ function resolveBuildOutputs(
736753
): OutputOptions | OutputOptions[] | undefined {
737754
if (libOptions) {
738755
const formats = libOptions.formats || ['es', 'umd']
739-
if (
740-
(formats.includes('umd') || formats.includes('iife')) &&
741-
!libOptions.name
742-
) {
743-
throw new Error(
744-
`Option "build.lib.name" is required when output formats ` +
745-
`include "umd" or "iife".`
746-
)
756+
if (formats.includes('umd') || formats.includes('iife')) {
757+
if (
758+
typeof libOptions.entry !== 'string' &&
759+
Object.values(libOptions.entry).length > 1
760+
) {
761+
throw new Error(
762+
`Multiple entry points are not supported when output formats include "umd" or "iife".`
763+
)
764+
}
765+
766+
if (!libOptions.name) {
767+
throw new Error(
768+
`Option "build.lib.name" is required when output formats ` +
769+
`include "umd" or "iife".`
770+
)
771+
}
747772
}
748773
if (!outputs) {
749774
return formats.map((format) => ({ format }))

0 commit comments

Comments
 (0)