1- import type { BuildContext , BuildResult } from "esbuild " ;
1+ import type { InputOptions , OutputChunk , OutputOptions , RolldownOutput } from "rolldown " ;
22import type { Context } from "../../types/index.js" ;
3- import { resolve } from "node:path" ;
4- import { context } from "esbuild " ;
3+ import { relative , resolve } from "node:path" ;
4+ import { cwd } from "node:process " ;
55import { copy , outputFile , outputJSON , pathExists } from "fs-extra/esm" ;
6+ import { rolldown } from "rolldown" ;
67import { glob } from "tinyglobby" ;
7- import { CACHE_DIR , TESTER_PLUGIN_DIR } from "../../constant.js" ;
8+ import { CACHE_DIR , TESTER_PLUGIN_DIR , TESTER_PLUGIN_TESTS_DIR } from "../../constant.js" ;
89import { saveResource } from "../../utils/file.js" ;
910import { logger } from "../../utils/logger.js" ;
10- import { toArray } from "../../utils/string.js" ;
11+ import { normalizePath , toArray } from "../../utils/string.js" ;
1112import { generateBootstrap , generateHtml , generateManifest , generateMochaSetup } from "./test-bundler-template/index.js" ;
1213
1314export class TestBundler {
14- private esbuildContext ?: BuildContext ;
15+ private rolldownOutput ?: RolldownOutput ;
1516 constructor (
1617 private ctx : Context ,
1718 private port : number ,
@@ -35,10 +36,11 @@ export class TestBundler {
3536
3637 async regenerate ( changedFile : string ) : Promise < void > {
3738 // re-bundle tests
38- const esbuildResult = await this . esbuildContext ?. rebuild ( ) ;
39+ await this . bundleTests ( ) ;
3940
40- // get affected tests
41- const tests = findImpactedTests ( changedFile , esbuildResult ?. metafile ) ;
41+ // get affected tests based on changed file
42+ const metadata = transformRolldownOutputToMetafile ( this . rolldownOutput ?. output ) ;
43+ const tests = findImpactedTests ( changedFile , metadata ) ;
4244
4345 // this.generateTestPage
4446 // mocha setup
@@ -110,21 +112,28 @@ export class TestBundler {
110112
111113 private async bundleTests ( ) {
112114 const testDirs = toArray ( this . ctx . test . entries ) ;
113- // Because esbuild only support `*` and `**`,
114- // so we need glob ourselves.
115- // https://esbuild.github.io/api/#glob-style-entry-points
115+ // Find all test files
116116 const entryPoints = ( await Promise . all ( testDirs . map ( dir => glob ( `${ dir } /**/*.{spec,test}.[jt]s` ) ) ) )
117117 . flat ( ) ;
118118
119- // Bundle all test files, including both JavaScript and TypeScript
120- this . esbuildContext = await context ( {
121- entryPoints,
122- outdir : `${ TESTER_PLUGIN_DIR } /content/units` ,
123- bundle : true ,
124- target : "firefox115" ,
125- metafile : true ,
126- } ) ;
127- await this . esbuildContext . rebuild ( ) ;
119+ // configure rolldown options
120+ const rolldownInputOptions : InputOptions = {
121+ input : entryPoints ,
122+ treeshake : false ,
123+ preserveEntrySignatures : "allow-extension" ,
124+ } ;
125+
126+ const outputOptions : OutputOptions = {
127+ dir : `${ TESTER_PLUGIN_DIR } /content/units` ,
128+ format : "esm" ,
129+ sourcemap : true ,
130+ codeSplitting : false ,
131+ } ;
132+
133+ const rolldownBuild = await rolldown ( rolldownInputOptions ) ;
134+ this . rolldownOutput = await rolldownBuild . write ( outputOptions ) ;
135+
136+ await rolldownBuild . close ( ) ;
128137 }
129138
130139 private async createTestHtml ( tests : string [ ] = [ ] ) {
@@ -139,38 +148,55 @@ export class TestBundler {
139148 // html
140149 let testFiles = tests ;
141150 if ( testFiles . length === 0 ) {
142- testFiles = ( await glob ( `**/*.{spec,test}.js` , { cwd : `${ TESTER_PLUGIN_DIR } /content ` } ) ) . sort ( ) ;
151+ testFiles = ( await glob ( `**/*.{spec,test}.js` , { cwd : `${ TESTER_PLUGIN_TESTS_DIR } ` } ) ) . sort ( ) ;
143152 }
144153 const html = generateHtml ( setupCode , testFiles ) ;
145154 await outputFile ( `${ TESTER_PLUGIN_DIR } /content/index.xhtml` , html ) ;
146155 }
147156}
148157
158+ interface _MetaData extends Pick < OutputChunk , "fileName" | "name" | "moduleIds" > { }
159+ export type MetaData = _MetaData [ ] ;
160+
161+ function transformRolldownOutputToMetafile ( output ?: RolldownOutput [ "output" ] ) : MetaData {
162+ if ( ! output )
163+ return [ ] ;
164+
165+ return output
166+ . flat ( )
167+ . filter ( r => r . type === "chunk" )
168+ . map ( r => ( {
169+ fileName : normalizePath ( r . fileName ) ,
170+ name : r . name ,
171+ moduleIds : r . moduleIds . map ( id => relative ( cwd ( ) , id ) ) . map ( normalizePath ) ,
172+ } ) ) ;
173+ }
174+
149175/**
150- * Determines which test files are impacted by a given changed file based on the esbuild metafile .
176+ * Determines which test files are impacted by a given changed file based on rolldown build output .
151177 *
152178 * This function analyzes the build metadata to find test files that depend on the changed file
153- * either directly as an entry point or indirectly as an input. It is useful in a watch mode setup
154- * to selectively rerun only the affected tests.
179+ * either directly as an entry point or indirectly as an input.
155180 *
156181 * @param {string } changedFilePath - The file path of the changed source file.
157- * @param {BuildResult["metafile"] } buildMetadata - The esbuild metafile containing dependency information .
158- * @returns {string[] } An array of impacted test file paths that need to be re-executed.
182+ * @param {MetaData } buildMetadata - The transfromed rolldown build outputs .
183+ * @returns {string[] } An array of impacted test file names that need to be re-executed.
159184 */
160- export function findImpactedTests ( changedFilePath : string , buildMetadata : BuildResult [ "metafile" ] ) : string [ ] {
161- if ( ! buildMetadata )
162- return [ ] ;
163-
164- const resolvedChangedFile = resolve ( changedFilePath ) ;
185+ export function findImpactedTests (
186+ changedFilePath : string ,
187+ buildMetadata : MetaData ,
188+ ) : string [ ] {
189+ const normalizedChangedFile = resolve ( changedFilePath ) ;
165190 const impactedTestFiles = new Set < string > ( ) ;
166191
167- for ( const [ outputFilePath , outputInfo ] of Object . entries ( buildMetadata . outputs ) ) {
168- const testFilePath = outputFilePath . replace ( ` ${ TESTER_PLUGIN_DIR } /content/` , "" ) ;
169- // const resolvedEntryPoint = outputInfo.entryPoint ? resolve(outputInfo.entryPoint) : null ;
170-
171- if ( Object . keys ( outputInfo . inputs ) . some ( inputPath => resolve ( inputPath ) === resolvedChangedFile ) ) {
172- impactedTestFiles . add ( testFilePath ) ;
192+ for ( const { fileName , moduleIds } of buildMetadata ) {
193+ for ( const moduleId of moduleIds ) {
194+ const normalizedModuleId = resolve ( moduleId ) ;
195+ if ( normalizedModuleId === normalizedChangedFile ) {
196+ impactedTestFiles . add ( fileName ) ;
197+ }
173198 }
174199 }
200+
175201 return Array . from ( impactedTestFiles ) ;
176202}
0 commit comments