@vitus-labs/tools-rolldown 1.5.2-alpha.4 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -7
- package/src/config/baseConfig.test.ts +50 -0
- package/src/rolldown/config.test.ts +348 -0
- package/src/rolldown/createBuildPipeline.test.ts +230 -0
- package/src/scripts/build.test.ts +246 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitus-labs/tools-rolldown",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,23 +30,24 @@
|
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"prepublish": "bun run build",
|
|
33
|
-
"build": "tsc"
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"typecheck": "tsc --noEmit"
|
|
34
35
|
},
|
|
35
36
|
"publishConfig": {
|
|
36
37
|
"access": "public"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@vitus-labs/tools-core": "1.
|
|
40
|
+
"@vitus-labs/tools-core": "1.6.0",
|
|
40
41
|
"chalk": "^5.6.2",
|
|
41
42
|
"rimraf": "^6.1.2",
|
|
42
43
|
"rolldown": "^1.0.0-rc.3",
|
|
43
|
-
"rolldown-plugin-dts": "^0.
|
|
44
|
+
"rolldown-plugin-dts": "^0.22.1",
|
|
44
45
|
"rollup-plugin-filesize": "^10.0.0",
|
|
45
|
-
"rollup-plugin-visualizer": "^
|
|
46
|
+
"rollup-plugin-visualizer": "^6.0.5"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
|
-
"@vitus-labs/tools-typescript": "1.
|
|
49
|
+
"@vitus-labs/tools-typescript": "1.6.0",
|
|
49
50
|
"typescript": "^5.9.3"
|
|
50
51
|
},
|
|
51
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "cb7b3bfa4e5730ecb95b3c886d23074a3ce9e85f"
|
|
52
53
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import config from './baseConfig.js'
|
|
3
|
+
|
|
4
|
+
describe('baseConfig', () => {
|
|
5
|
+
it('should have correct source and output directories', () => {
|
|
6
|
+
expect(config.sourceDir).toBe('src')
|
|
7
|
+
expect(config.outputDir).toBe('lib')
|
|
8
|
+
expect(config.typesDir).toBe('lib/types')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('should have typescript enabled by default', () => {
|
|
12
|
+
expect(config.typescript).toBe(true)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should have replaceGlobals enabled', () => {
|
|
16
|
+
expect(config.replaceGlobals).toBe(true)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should define file extensions', () => {
|
|
20
|
+
expect(config.extensions).toContain('.ts')
|
|
21
|
+
expect(config.extensions).toContain('.tsx')
|
|
22
|
+
expect(config.extensions).toContain('.js')
|
|
23
|
+
expect(config.extensions).toContain('.json')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should have visualise config', () => {
|
|
27
|
+
expect(config.visualise).toEqual({
|
|
28
|
+
template: 'network',
|
|
29
|
+
gzipSize: true,
|
|
30
|
+
outputDir: 'analysis',
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should have filesize enabled', () => {
|
|
35
|
+
expect(config.filesize).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should have default globals', () => {
|
|
39
|
+
expect(config.globals).toHaveProperty('react', 'React')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should have external dependencies', () => {
|
|
43
|
+
expect(config.external).toContain('react/jsx-runtime')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should have exclude patterns', () => {
|
|
47
|
+
expect(config.exclude).toContain('node_modules/**')
|
|
48
|
+
expect(config.exclude).toContain('**/__tests__/**')
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
vi.mock('rolldown-plugin-dts', () => ({
|
|
4
|
+
dts: vi.fn(() => [{ name: 'mock-dts' }]),
|
|
5
|
+
}))
|
|
6
|
+
vi.mock('rollup-plugin-filesize', () => ({
|
|
7
|
+
default: vi.fn(() => ({ name: 'mock-filesize' })),
|
|
8
|
+
}))
|
|
9
|
+
vi.mock('rollup-plugin-visualizer', () => ({
|
|
10
|
+
visualizer: vi.fn(() => ({ name: 'mock-visualizer' })),
|
|
11
|
+
}))
|
|
12
|
+
vi.mock('@vitus-labs/tools-core', () => ({
|
|
13
|
+
swapGlobals: (globals: Record<string, string>) =>
|
|
14
|
+
Object.fromEntries(Object.entries(globals).map(([k, v]) => [v, k])),
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
const { mockConfig, mockPKG } = vi.hoisted(() => ({
|
|
18
|
+
mockConfig: {
|
|
19
|
+
sourceDir: 'src',
|
|
20
|
+
outputDir: 'lib',
|
|
21
|
+
typesDir: 'lib/types',
|
|
22
|
+
extensions: ['.ts', '.tsx', '.js'],
|
|
23
|
+
typescript: true,
|
|
24
|
+
replaceGlobals: true,
|
|
25
|
+
visualise: { template: 'network', gzipSize: true, outputDir: 'analysis' },
|
|
26
|
+
filesize: true,
|
|
27
|
+
external: ['react/jsx-runtime'],
|
|
28
|
+
globals: { react: 'React' },
|
|
29
|
+
} as Record<string, any>,
|
|
30
|
+
mockPKG: {
|
|
31
|
+
name: '@test/pkg',
|
|
32
|
+
version: '1.0.0',
|
|
33
|
+
bundleName: 'testPkg',
|
|
34
|
+
externalDependencies: ['react'],
|
|
35
|
+
exports: { types: './lib/index.d.ts', import: './lib/index.js' },
|
|
36
|
+
} as Record<string, any>,
|
|
37
|
+
}))
|
|
38
|
+
|
|
39
|
+
vi.mock('../config/index.js', () => ({
|
|
40
|
+
CONFIG: mockConfig,
|
|
41
|
+
PKG: mockPKG,
|
|
42
|
+
PLATFORMS: ['browser', 'node', 'web', 'native'],
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
import rolldownConfig, { buildDts } from './config.js'
|
|
46
|
+
|
|
47
|
+
const defaultConfig = { ...mockConfig }
|
|
48
|
+
const defaultPKG = { ...mockPKG }
|
|
49
|
+
|
|
50
|
+
describe('rolldownConfig', () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
Object.assign(mockConfig, defaultConfig)
|
|
53
|
+
Object.assign(mockPKG, defaultPKG)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should create a valid ES module build config', () => {
|
|
57
|
+
const config = rolldownConfig({
|
|
58
|
+
file: 'lib/index.js',
|
|
59
|
+
format: 'es',
|
|
60
|
+
env: 'development',
|
|
61
|
+
platform: 'universal',
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
expect(config.input).toBe('src')
|
|
65
|
+
expect(config.output.format).toBe('es')
|
|
66
|
+
expect(config.output.sourcemap).toBe(true)
|
|
67
|
+
expect(config.output.esModule).toBe(true)
|
|
68
|
+
expect(config.external).toContain('react')
|
|
69
|
+
expect(config.external).toContain('react/jsx-runtime')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should set platform to node for node builds', () => {
|
|
73
|
+
const config = rolldownConfig({
|
|
74
|
+
file: 'lib/index.cjs',
|
|
75
|
+
format: 'cjs',
|
|
76
|
+
env: 'development',
|
|
77
|
+
platform: 'node',
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
expect(config.platform).toBe('node')
|
|
81
|
+
expect(config.output.exports).toBe('named')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should set platform to browser for browser builds', () => {
|
|
85
|
+
const config = rolldownConfig({
|
|
86
|
+
file: 'lib/index.js',
|
|
87
|
+
format: 'es',
|
|
88
|
+
env: 'development',
|
|
89
|
+
platform: 'browser',
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
expect(config.platform).toBe('browser')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should set platform to neutral for unknown platforms', () => {
|
|
96
|
+
const config = rolldownConfig({
|
|
97
|
+
file: 'lib/index.js',
|
|
98
|
+
format: 'es',
|
|
99
|
+
env: 'development',
|
|
100
|
+
platform: 'universal',
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
expect(config.platform).toBe('neutral')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should add platform-specific extensions for known platforms', () => {
|
|
107
|
+
const config = rolldownConfig({
|
|
108
|
+
file: 'lib/index.js',
|
|
109
|
+
format: 'es',
|
|
110
|
+
env: 'development',
|
|
111
|
+
platform: 'browser',
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
expect(config.resolve.extensions).toContain('.browser.ts')
|
|
115
|
+
expect(config.resolve.extensions).toContain('.ts')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('should not add platform extensions for unknown platforms', () => {
|
|
119
|
+
const config = rolldownConfig({
|
|
120
|
+
file: 'lib/index.js',
|
|
121
|
+
format: 'es',
|
|
122
|
+
env: 'development',
|
|
123
|
+
platform: 'universal',
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
expect(config.resolve.extensions).not.toContain('.universal.ts')
|
|
127
|
+
expect(config.resolve.extensions).toContain('.ts')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should set name for UMD format', () => {
|
|
131
|
+
const config = rolldownConfig({
|
|
132
|
+
file: 'lib/index.umd.js',
|
|
133
|
+
format: 'umd',
|
|
134
|
+
env: 'development',
|
|
135
|
+
platform: 'universal',
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
expect(config.output.name).toBe('testPkg')
|
|
139
|
+
expect(config.output.exports).toBe('named')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should add define options with replaceGlobals', () => {
|
|
143
|
+
const config = rolldownConfig({
|
|
144
|
+
file: 'lib/index.js',
|
|
145
|
+
format: 'es',
|
|
146
|
+
env: 'development',
|
|
147
|
+
platform: 'node',
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
expect(config.transform?.define?.__VERSION__).toBe('"1.0.0"')
|
|
151
|
+
expect(config.transform?.define?.__NODE__).toBe('true')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('should add process.env.NODE_ENV for production builds', () => {
|
|
155
|
+
const config = rolldownConfig({
|
|
156
|
+
file: 'lib/index.js',
|
|
157
|
+
format: 'es',
|
|
158
|
+
env: 'production',
|
|
159
|
+
platform: 'universal',
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
expect(config.transform?.define?.['process.env.NODE_ENV']).toBe(
|
|
163
|
+
'"production"',
|
|
164
|
+
)
|
|
165
|
+
expect(config.output.minify).toBe(true)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should not minify development builds', () => {
|
|
169
|
+
const config = rolldownConfig({
|
|
170
|
+
file: 'lib/index.js',
|
|
171
|
+
format: 'es',
|
|
172
|
+
env: 'development',
|
|
173
|
+
platform: 'universal',
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
expect(config.output.minify).toBe(false)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('should skip define options when replaceGlobals is false', () => {
|
|
180
|
+
mockConfig.replaceGlobals = false
|
|
181
|
+
|
|
182
|
+
const config = rolldownConfig({
|
|
183
|
+
file: 'lib/index.js',
|
|
184
|
+
format: 'es',
|
|
185
|
+
env: 'development',
|
|
186
|
+
platform: 'universal',
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
expect(config.transform).toBeUndefined()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('should skip visualizer when visualise is false', () => {
|
|
193
|
+
mockConfig.visualise = false
|
|
194
|
+
|
|
195
|
+
const config = rolldownConfig({
|
|
196
|
+
file: 'lib/index.js',
|
|
197
|
+
format: 'es',
|
|
198
|
+
env: 'development',
|
|
199
|
+
platform: 'universal',
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
const hasVisualizer = config.plugins.some(
|
|
203
|
+
(p: any) => p?.name === 'mock-visualizer',
|
|
204
|
+
)
|
|
205
|
+
expect(hasVisualizer).toBe(false)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('should skip filesize when filesize is false', () => {
|
|
209
|
+
mockConfig.filesize = false
|
|
210
|
+
|
|
211
|
+
const config = rolldownConfig({
|
|
212
|
+
file: 'lib/index.js',
|
|
213
|
+
format: 'es',
|
|
214
|
+
env: 'development',
|
|
215
|
+
platform: 'universal',
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
const hasFilesize = config.plugins.some(
|
|
219
|
+
(p: any) => p?.name === 'mock-filesize',
|
|
220
|
+
)
|
|
221
|
+
expect(hasFilesize).toBe(false)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('should set tsconfig when typescript is enabled', () => {
|
|
225
|
+
const config = rolldownConfig({
|
|
226
|
+
file: 'lib/index.js',
|
|
227
|
+
format: 'es',
|
|
228
|
+
env: 'development',
|
|
229
|
+
platform: 'universal',
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
expect(config.tsconfig).toBe('tsconfig.json')
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('should skip tsconfig when typescript is disabled', () => {
|
|
236
|
+
mockConfig.typescript = false
|
|
237
|
+
|
|
238
|
+
const config = rolldownConfig({
|
|
239
|
+
file: 'lib/index.js',
|
|
240
|
+
format: 'es',
|
|
241
|
+
env: 'development',
|
|
242
|
+
platform: 'universal',
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
expect(config.tsconfig).toBeUndefined()
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('should swap globals in output', () => {
|
|
249
|
+
const config = rolldownConfig({
|
|
250
|
+
file: 'lib/index.js',
|
|
251
|
+
format: 'es',
|
|
252
|
+
env: 'development',
|
|
253
|
+
platform: 'universal',
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
expect(config.output.globals).toEqual({ React: 'react' })
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should set correct dir and entryFileNames from file path', () => {
|
|
260
|
+
const config = rolldownConfig({
|
|
261
|
+
file: 'lib/esm/index.js',
|
|
262
|
+
format: 'es',
|
|
263
|
+
env: 'development',
|
|
264
|
+
platform: 'universal',
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
expect(config.output.dir).toBe('lib/esm')
|
|
268
|
+
expect(config.output.entryFileNames).toBe('index.js')
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('should handle file path without slash', () => {
|
|
272
|
+
const config = rolldownConfig({
|
|
273
|
+
file: 'bundle.js',
|
|
274
|
+
format: 'es',
|
|
275
|
+
env: 'development',
|
|
276
|
+
platform: 'universal',
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
expect(config.output.dir).toBe('.')
|
|
280
|
+
expect(config.output.entryFileNames).toBe('bundle.js')
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
describe('buildDts', () => {
|
|
285
|
+
beforeEach(() => {
|
|
286
|
+
Object.assign(mockConfig, defaultConfig)
|
|
287
|
+
Object.assign(mockPKG, defaultPKG)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('should return DTS config when typescript and types are available', () => {
|
|
291
|
+
const result = buildDts()
|
|
292
|
+
|
|
293
|
+
expect(result).not.toBeNull()
|
|
294
|
+
expect(result?.file).toBe('./lib/index.d.ts')
|
|
295
|
+
expect(result?.input).toBe('src/index.ts')
|
|
296
|
+
expect(result?.tsconfig).toBe('tsconfig.json')
|
|
297
|
+
expect(result?.output.format).toBe('es')
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('should return null when typescript is disabled', () => {
|
|
301
|
+
mockConfig.typescript = false
|
|
302
|
+
|
|
303
|
+
expect(buildDts()).toBeNull()
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('should return null when no types path exists', () => {
|
|
307
|
+
mockPKG.exports = { import: './lib/index.js' }
|
|
308
|
+
delete mockPKG.types
|
|
309
|
+
delete mockPKG.typings
|
|
310
|
+
|
|
311
|
+
expect(buildDts()).toBeNull()
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('should use PKG.types as fallback', () => {
|
|
315
|
+
mockPKG.exports = {}
|
|
316
|
+
mockPKG.types = './lib/types.d.ts'
|
|
317
|
+
|
|
318
|
+
const result = buildDts()
|
|
319
|
+
|
|
320
|
+
expect(result?.file).toBe('./lib/types.d.ts')
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it('should use PKG.typings as final fallback', () => {
|
|
324
|
+
mockPKG.exports = {}
|
|
325
|
+
delete mockPKG.types
|
|
326
|
+
mockPKG.typings = './lib/typings.d.ts'
|
|
327
|
+
|
|
328
|
+
const result = buildDts()
|
|
329
|
+
|
|
330
|
+
expect(result?.file).toBe('./lib/typings.d.ts')
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('should include external dependencies', () => {
|
|
334
|
+
const result = buildDts()
|
|
335
|
+
|
|
336
|
+
expect(result?.external).toContain('react')
|
|
337
|
+
expect(result?.external).toContain('react/jsx-runtime')
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('should handle types path without slash', () => {
|
|
341
|
+
mockPKG.exports = { types: 'index.d.ts' }
|
|
342
|
+
|
|
343
|
+
const result = buildDts()
|
|
344
|
+
|
|
345
|
+
expect(result?.output.dir).toBe('.')
|
|
346
|
+
expect(result?.output.entryFileNames).toBe('index.d.ts')
|
|
347
|
+
})
|
|
348
|
+
})
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const { mockPKG } = vi.hoisted(() => ({
|
|
4
|
+
mockPKG: {
|
|
5
|
+
type: 'commonjs',
|
|
6
|
+
name: '@test/pkg',
|
|
7
|
+
main: 'lib/index.cjs',
|
|
8
|
+
module: 'lib/index.js',
|
|
9
|
+
exports: { import: './lib/index.js', require: './lib/index.cjs' },
|
|
10
|
+
} as Record<string, any>,
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
vi.mock('../config/index.js', () => ({
|
|
14
|
+
PKG: mockPKG,
|
|
15
|
+
CONFIG: {},
|
|
16
|
+
PLATFORMS: ['browser', 'node', 'web', 'native'],
|
|
17
|
+
}))
|
|
18
|
+
|
|
19
|
+
describe('createBuildPipeline', () => {
|
|
20
|
+
const defaultPKG = { ...mockPKG }
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
Object.assign(mockPKG, defaultPKG)
|
|
24
|
+
// Remove any extra keys added by specific tests
|
|
25
|
+
for (const key of Object.keys(mockPKG)) {
|
|
26
|
+
if (!(key in defaultPKG)) delete mockPKG[key]
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('with CJS package', () => {
|
|
31
|
+
let createBuildPipeline: () => any[]
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
vi.resetModules()
|
|
35
|
+
mockPKG.type = 'commonjs'
|
|
36
|
+
const mod = await import('./createBuildPipeline.js')
|
|
37
|
+
createBuildPipeline = mod.default
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should create builds from package.json fields', () => {
|
|
41
|
+
const builds = createBuildPipeline()
|
|
42
|
+
|
|
43
|
+
expect(builds.length).toBeGreaterThan(0)
|
|
44
|
+
const mainBuild = builds.find((b) => b.file === 'lib/index.cjs')
|
|
45
|
+
expect(mainBuild).toBeDefined()
|
|
46
|
+
expect(mainBuild.format).toBe('cjs')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should include module build as ES format', () => {
|
|
50
|
+
const builds = createBuildPipeline()
|
|
51
|
+
|
|
52
|
+
const moduleBuild = builds.find((b) => b.file === 'lib/index.js')
|
|
53
|
+
expect(moduleBuild).toBeDefined()
|
|
54
|
+
expect(moduleBuild.format).toBe('es')
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('with ES module package', () => {
|
|
59
|
+
let createBuildPipeline: () => any[]
|
|
60
|
+
|
|
61
|
+
beforeEach(async () => {
|
|
62
|
+
vi.resetModules()
|
|
63
|
+
mockPKG.type = 'module'
|
|
64
|
+
const mod = await import('./createBuildPipeline.js')
|
|
65
|
+
createBuildPipeline = mod.default
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should use ES format for main build', () => {
|
|
69
|
+
const builds = createBuildPipeline()
|
|
70
|
+
|
|
71
|
+
const mainBuild = builds.find((b) => b.file === 'lib/index.cjs')
|
|
72
|
+
if (mainBuild) {
|
|
73
|
+
expect(mainBuild.format).toBe('es')
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should include exports options', () => {
|
|
78
|
+
const builds = createBuildPipeline()
|
|
79
|
+
|
|
80
|
+
expect(builds.length).toBeGreaterThan(0)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('with string exports', () => {
|
|
85
|
+
let createBuildPipeline: () => any[]
|
|
86
|
+
|
|
87
|
+
beforeEach(async () => {
|
|
88
|
+
vi.resetModules()
|
|
89
|
+
mockPKG.type = 'module'
|
|
90
|
+
mockPKG.exports = './lib/index.js'
|
|
91
|
+
const mod = await import('./createBuildPipeline.js')
|
|
92
|
+
createBuildPipeline = mod.default
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should handle string exports', () => {
|
|
96
|
+
const builds = createBuildPipeline()
|
|
97
|
+
|
|
98
|
+
const exportBuild = builds.find((b) => b.file === './lib/index.js')
|
|
99
|
+
expect(exportBuild).toBeDefined()
|
|
100
|
+
expect(exportBuild.format).toBe('es')
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('with object exports having node field', () => {
|
|
105
|
+
let createBuildPipeline: () => any[]
|
|
106
|
+
|
|
107
|
+
beforeEach(async () => {
|
|
108
|
+
vi.resetModules()
|
|
109
|
+
mockPKG.type = 'module'
|
|
110
|
+
mockPKG.exports = {
|
|
111
|
+
import: './lib/index.js',
|
|
112
|
+
require: './lib/index.cjs',
|
|
113
|
+
node: './lib/node.js',
|
|
114
|
+
default: './lib/default.js',
|
|
115
|
+
}
|
|
116
|
+
const mod = await import('./createBuildPipeline.js')
|
|
117
|
+
createBuildPipeline = mod.default
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should include node and default exports', () => {
|
|
121
|
+
const builds = createBuildPipeline()
|
|
122
|
+
|
|
123
|
+
const nodeBuild = builds.find((b) => b.file === './lib/node.js')
|
|
124
|
+
expect(nodeBuild).toBeDefined()
|
|
125
|
+
expect(nodeBuild.platform).toBe('node')
|
|
126
|
+
|
|
127
|
+
const defaultBuild = builds.find((b) => b.file === './lib/default.js')
|
|
128
|
+
expect(defaultBuild).toBeDefined()
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('with no exports', () => {
|
|
133
|
+
let createBuildPipeline: () => any[]
|
|
134
|
+
|
|
135
|
+
beforeEach(async () => {
|
|
136
|
+
vi.resetModules()
|
|
137
|
+
mockPKG.type = 'module'
|
|
138
|
+
delete mockPKG.exports
|
|
139
|
+
const mod = await import('./createBuildPipeline.js')
|
|
140
|
+
createBuildPipeline = mod.default
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('should return builds based on main/module fields only', () => {
|
|
144
|
+
const builds = createBuildPipeline()
|
|
145
|
+
|
|
146
|
+
expect(builds.length).toBeGreaterThan(0)
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
describe('with react-native build', () => {
|
|
151
|
+
let createBuildPipeline: () => any[]
|
|
152
|
+
|
|
153
|
+
beforeEach(async () => {
|
|
154
|
+
vi.resetModules()
|
|
155
|
+
mockPKG.type = 'commonjs'
|
|
156
|
+
mockPKG['react-native'] = 'lib/native.js'
|
|
157
|
+
mockPKG.module = 'lib/index.js'
|
|
158
|
+
const mod = await import('./createBuildPipeline.js')
|
|
159
|
+
createBuildPipeline = mod.default
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('should add native build when path differs from module', () => {
|
|
163
|
+
const builds = createBuildPipeline()
|
|
164
|
+
|
|
165
|
+
const nativeBuild = builds.find((b) => b.platform === 'native')
|
|
166
|
+
expect(nativeBuild).toBeDefined()
|
|
167
|
+
expect(nativeBuild.file).toBe('lib/native.js')
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
describe('with browser-specific builds', () => {
|
|
172
|
+
let createBuildPipeline: () => any[]
|
|
173
|
+
|
|
174
|
+
beforeEach(async () => {
|
|
175
|
+
vi.resetModules()
|
|
176
|
+
mockPKG.type = 'commonjs'
|
|
177
|
+
mockPKG.main = 'lib/index.cjs'
|
|
178
|
+
mockPKG.module = 'lib/index.js'
|
|
179
|
+
// browser remaps module output — hasDifferentBrowserBuild('main') returns true
|
|
180
|
+
// because source ('lib/index.js') !== PKG['main'] ('lib/index.cjs')
|
|
181
|
+
mockPKG.browser = {
|
|
182
|
+
'./lib/index.js': './lib/browser.js',
|
|
183
|
+
}
|
|
184
|
+
const mod = await import('./createBuildPipeline.js')
|
|
185
|
+
createBuildPipeline = mod.default
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('should create browser-specific build variants', () => {
|
|
189
|
+
const builds = createBuildPipeline()
|
|
190
|
+
|
|
191
|
+
const browserBuild = builds.find((b) => b.platform === 'browser')
|
|
192
|
+
expect(browserBuild).toBeDefined()
|
|
193
|
+
expect(browserBuild.file).toBe('lib/browser.js')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('should set node platform for main build when browser variant exists', () => {
|
|
197
|
+
const builds = createBuildPipeline()
|
|
198
|
+
|
|
199
|
+
const nodeBuild = builds.find(
|
|
200
|
+
(b) => b.file === 'lib/index.cjs' && b.platform === 'node',
|
|
201
|
+
)
|
|
202
|
+
expect(nodeBuild).toBeDefined()
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
describe('with UMD builds', () => {
|
|
207
|
+
let createBuildPipeline: () => any[]
|
|
208
|
+
|
|
209
|
+
beforeEach(async () => {
|
|
210
|
+
vi.resetModules()
|
|
211
|
+
mockPKG.type = 'commonjs'
|
|
212
|
+
mockPKG['umd:main'] = 'lib/index.umd.js'
|
|
213
|
+
mockPKG.unpkg = 'lib/index.umd.min.js'
|
|
214
|
+
const mod = await import('./createBuildPipeline.js')
|
|
215
|
+
createBuildPipeline = mod.default
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('should include UMD build variants', () => {
|
|
219
|
+
const builds = createBuildPipeline()
|
|
220
|
+
|
|
221
|
+
const umdDev = builds.find((b) => b.file === 'lib/index.umd.js')
|
|
222
|
+
expect(umdDev).toBeDefined()
|
|
223
|
+
expect(umdDev.format).toBe('umd')
|
|
224
|
+
|
|
225
|
+
const umdProd = builds.find((b) => b.file === 'lib/index.umd.min.js')
|
|
226
|
+
expect(umdProd).toBeDefined()
|
|
227
|
+
expect(umdProd.env).toBe('production')
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
})
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
mockRolldown,
|
|
5
|
+
mockBundleWrite,
|
|
6
|
+
mockBundleClose,
|
|
7
|
+
mockReaddirSync,
|
|
8
|
+
mockCreateBuildPipeline,
|
|
9
|
+
mockRolldownConfig,
|
|
10
|
+
mockBuildDts,
|
|
11
|
+
} = vi.hoisted(() => ({
|
|
12
|
+
mockRolldown: vi.fn(),
|
|
13
|
+
mockBundleWrite: vi.fn(),
|
|
14
|
+
mockBundleClose: vi.fn(),
|
|
15
|
+
mockReaddirSync: vi.fn(),
|
|
16
|
+
mockCreateBuildPipeline: vi.fn(),
|
|
17
|
+
mockRolldownConfig: vi.fn(),
|
|
18
|
+
mockBuildDts: vi.fn(),
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
vi.mock('rolldown', () => ({ rolldown: mockRolldown }))
|
|
22
|
+
vi.mock('rimraf', () => ({ rimraf: { sync: vi.fn() } }))
|
|
23
|
+
vi.mock('node:fs', () => ({
|
|
24
|
+
readdirSync: mockReaddirSync,
|
|
25
|
+
renameSync: vi.fn(),
|
|
26
|
+
statSync: vi.fn(),
|
|
27
|
+
unlinkSync: vi.fn(),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
vi.mock('../config/index.js', () => ({
|
|
31
|
+
CONFIG: { outputDir: 'lib' },
|
|
32
|
+
PKG: { name: '@test/pkg', version: '1.0.0' },
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
vi.mock('../rolldown/index.js', () => ({
|
|
36
|
+
createBuildPipeline: mockCreateBuildPipeline,
|
|
37
|
+
config: mockRolldownConfig,
|
|
38
|
+
buildDts: mockBuildDts,
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
describe('build', () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.clearAllMocks()
|
|
44
|
+
|
|
45
|
+
mockBundleWrite.mockResolvedValue(undefined)
|
|
46
|
+
mockBundleClose.mockResolvedValue(undefined)
|
|
47
|
+
mockRolldown.mockResolvedValue({
|
|
48
|
+
write: mockBundleWrite,
|
|
49
|
+
close: mockBundleClose,
|
|
50
|
+
})
|
|
51
|
+
mockReaddirSync.mockReturnValue([])
|
|
52
|
+
|
|
53
|
+
mockCreateBuildPipeline.mockReturnValue([
|
|
54
|
+
{
|
|
55
|
+
file: 'lib/index.js',
|
|
56
|
+
format: 'es',
|
|
57
|
+
env: 'development',
|
|
58
|
+
platform: 'universal',
|
|
59
|
+
},
|
|
60
|
+
])
|
|
61
|
+
mockRolldownConfig.mockImplementation((item: any) => ({
|
|
62
|
+
input: 'src',
|
|
63
|
+
output: {
|
|
64
|
+
dir: 'lib',
|
|
65
|
+
entryFileNames: 'index.js',
|
|
66
|
+
format: item.format,
|
|
67
|
+
},
|
|
68
|
+
}))
|
|
69
|
+
mockBuildDts.mockReturnValue(null)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should execute build pipeline successfully', async () => {
|
|
73
|
+
vi.resetModules()
|
|
74
|
+
const { runBuild } = await import('./build.js')
|
|
75
|
+
|
|
76
|
+
await runBuild()
|
|
77
|
+
|
|
78
|
+
expect(mockRolldown).toHaveBeenCalled()
|
|
79
|
+
expect(mockBundleWrite).toHaveBeenCalled()
|
|
80
|
+
expect(mockBundleClose).toHaveBeenCalled()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should generate DTS when buildDts returns config', async () => {
|
|
84
|
+
mockBuildDts.mockReturnValue({
|
|
85
|
+
file: './lib/index.d.ts',
|
|
86
|
+
input: 'src/index.ts',
|
|
87
|
+
output: {
|
|
88
|
+
dir: 'lib',
|
|
89
|
+
entryFileNames: 'index.d.ts',
|
|
90
|
+
format: 'es',
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
vi.resetModules()
|
|
95
|
+
const { runBuild } = await import('./build.js')
|
|
96
|
+
vi.clearAllMocks()
|
|
97
|
+
|
|
98
|
+
// Re-setup after clear
|
|
99
|
+
mockRolldown.mockResolvedValue({
|
|
100
|
+
write: mockBundleWrite,
|
|
101
|
+
close: mockBundleClose,
|
|
102
|
+
})
|
|
103
|
+
mockBundleWrite.mockResolvedValue(undefined)
|
|
104
|
+
mockBundleClose.mockResolvedValue(undefined)
|
|
105
|
+
mockReaddirSync.mockReturnValue([])
|
|
106
|
+
mockRolldownConfig.mockImplementation((item: any) => ({
|
|
107
|
+
input: 'src',
|
|
108
|
+
output: {
|
|
109
|
+
dir: 'lib',
|
|
110
|
+
entryFileNames: 'index.js',
|
|
111
|
+
format: item.format,
|
|
112
|
+
},
|
|
113
|
+
}))
|
|
114
|
+
mockBuildDts.mockReturnValue({
|
|
115
|
+
file: './lib/index.d.ts',
|
|
116
|
+
input: 'src/index.ts',
|
|
117
|
+
output: {
|
|
118
|
+
dir: 'lib',
|
|
119
|
+
entryFileNames: 'index.d.ts',
|
|
120
|
+
format: 'es',
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
await runBuild()
|
|
125
|
+
|
|
126
|
+
// main build + DTS build = 2 rolldown calls
|
|
127
|
+
expect(mockRolldown).toHaveBeenCalledTimes(2)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should handle DTS chunk consolidation', async () => {
|
|
131
|
+
mockBuildDts.mockReturnValue({
|
|
132
|
+
file: './lib/index.d.ts',
|
|
133
|
+
input: 'src/index.ts',
|
|
134
|
+
output: {
|
|
135
|
+
dir: 'lib',
|
|
136
|
+
entryFileNames: 'index.d.ts',
|
|
137
|
+
format: 'es',
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
vi.resetModules()
|
|
142
|
+
const { runBuild } = await import('./build.js')
|
|
143
|
+
const { statSync, unlinkSync, renameSync } = await import('node:fs')
|
|
144
|
+
vi.clearAllMocks()
|
|
145
|
+
|
|
146
|
+
// Re-setup
|
|
147
|
+
mockRolldown.mockResolvedValue({
|
|
148
|
+
write: mockBundleWrite,
|
|
149
|
+
close: mockBundleClose,
|
|
150
|
+
})
|
|
151
|
+
mockBundleWrite.mockResolvedValue(undefined)
|
|
152
|
+
mockBundleClose.mockResolvedValue(undefined)
|
|
153
|
+
mockRolldownConfig.mockImplementation((item: any) => ({
|
|
154
|
+
input: 'src',
|
|
155
|
+
output: {
|
|
156
|
+
dir: 'lib',
|
|
157
|
+
entryFileNames: 'index.js',
|
|
158
|
+
format: item.format,
|
|
159
|
+
},
|
|
160
|
+
}))
|
|
161
|
+
mockBuildDts.mockReturnValue({
|
|
162
|
+
file: './lib/index.d.ts',
|
|
163
|
+
input: 'src/index.ts',
|
|
164
|
+
output: {
|
|
165
|
+
dir: 'lib',
|
|
166
|
+
entryFileNames: 'index.d.ts',
|
|
167
|
+
format: 'es',
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
mockReaddirSync.mockReturnValue(['chunk-abc.d.ts'])
|
|
171
|
+
vi.mocked(statSync).mockImplementation((p: any) => {
|
|
172
|
+
if (String(p).includes('chunk'))
|
|
173
|
+
return { size: 1000 } as ReturnType<typeof statSync>
|
|
174
|
+
return { size: 10 } as ReturnType<typeof statSync>
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
await runBuild()
|
|
178
|
+
|
|
179
|
+
expect(unlinkSync).toHaveBeenCalled()
|
|
180
|
+
expect(renameSync).toHaveBeenCalled()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should handle build failure gracefully', async () => {
|
|
184
|
+
vi.resetModules()
|
|
185
|
+
const { runBuild } = await import('./build.js')
|
|
186
|
+
vi.clearAllMocks()
|
|
187
|
+
|
|
188
|
+
const buildError = new Error('rolldown failed')
|
|
189
|
+
mockRolldown.mockRejectedValue(buildError)
|
|
190
|
+
mockReaddirSync.mockReturnValue([])
|
|
191
|
+
mockRolldownConfig.mockImplementation((item: any) => ({
|
|
192
|
+
input: 'src',
|
|
193
|
+
output: {
|
|
194
|
+
dir: 'lib',
|
|
195
|
+
entryFileNames: 'index.js',
|
|
196
|
+
format: item.format,
|
|
197
|
+
},
|
|
198
|
+
}))
|
|
199
|
+
mockBuildDts.mockReturnValue(null)
|
|
200
|
+
|
|
201
|
+
await expect(runBuild()).rejects.toThrow('rolldown failed')
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('should handle multiple builds in sequence', async () => {
|
|
205
|
+
mockCreateBuildPipeline.mockReturnValue([
|
|
206
|
+
{
|
|
207
|
+
file: 'lib/index.js',
|
|
208
|
+
format: 'es',
|
|
209
|
+
env: 'development',
|
|
210
|
+
platform: 'universal',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
file: 'lib/index.cjs',
|
|
214
|
+
format: 'cjs',
|
|
215
|
+
env: 'development',
|
|
216
|
+
platform: 'universal',
|
|
217
|
+
},
|
|
218
|
+
])
|
|
219
|
+
|
|
220
|
+
vi.resetModules()
|
|
221
|
+
const { runBuild } = await import('./build.js')
|
|
222
|
+
vi.clearAllMocks()
|
|
223
|
+
|
|
224
|
+
mockRolldown.mockResolvedValue({
|
|
225
|
+
write: mockBundleWrite,
|
|
226
|
+
close: mockBundleClose,
|
|
227
|
+
})
|
|
228
|
+
mockBundleWrite.mockResolvedValue(undefined)
|
|
229
|
+
mockBundleClose.mockResolvedValue(undefined)
|
|
230
|
+
mockReaddirSync.mockReturnValue([])
|
|
231
|
+
mockRolldownConfig.mockImplementation((item: any) => ({
|
|
232
|
+
input: 'src',
|
|
233
|
+
output: {
|
|
234
|
+
dir: 'lib',
|
|
235
|
+
entryFileNames: 'index.js',
|
|
236
|
+
format: item.format,
|
|
237
|
+
},
|
|
238
|
+
}))
|
|
239
|
+
mockBuildDts.mockReturnValue(null)
|
|
240
|
+
|
|
241
|
+
await runBuild()
|
|
242
|
+
|
|
243
|
+
expect(mockRolldownConfig).toHaveBeenCalledTimes(2)
|
|
244
|
+
expect(mockRolldown).toHaveBeenCalledTimes(2)
|
|
245
|
+
})
|
|
246
|
+
})
|
package/tsconfig.json
CHANGED
package/vitest.config.ts
ADDED