@vitus-labs/tools-rolldown 1.5.2-alpha.4 → 1.5.2-alpha.5

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.
@@ -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
+ })
@@ -0,0 +1,3 @@
1
+ import { createVitestConfig } from '../../vitest.shared.js'
2
+
3
+ export default createVitestConfig(['src/config/baseConfig.ts'])