11import path from 'node:path' ;
22import url from 'node:url' ;
3- import type { RollupTypescriptOptions } from '@rollup/plugin-typescript ' ;
3+ import type { Class , VariableDeclaration } from '@oxc-project/types ' ;
44import MagicString from 'magic-string' ;
5+ import { parseAst } from 'rolldown/parseAst' ;
56import type { OutputOptions } from 'rollup' ;
67import { defineConfig , type LibraryOptions , type Plugin } from 'vite' ;
78import { viteStaticCopy } from 'vite-plugin-static-copy' ;
8- import { defaultBuildUserConfig , umd , esm as defaultEsm , dtsPathsTransformer } from '../tooling/src/vite' ;
9- import { elementStyleUsingTransformer } from '../tooling/src/typescript' ;
9+ import { addDts , defaultBuildUserConfig , esm , umd } from '../tooling/src/vite' ;
1010
1111const __dirname = url . fileURLToPath ( new URL ( '.' , import . meta. url ) ) ;
1212
@@ -30,59 +30,105 @@ const adjustScriptPathsPlugin = (min: boolean) => {
3030 } satisfies Plugin ;
3131} ;
3232
33+ // Rolldown unconditionally lowers top-level `class X { ... }` declarations to
34+ // `var X = class { ... };` (documented as TDZ-hoisting behaviour, independent
35+ // of `output.topLevelVar`). Downstream tooling that pattern-matches the
36+ // published bundle on `class X` substrings — like the alphatab webpack plugin
37+ // — depends on the declaration form, so we restore it here.
38+ const preserveClassDeclarationsPlugin = ( ) : Plugin => ( {
39+ name : 'preserve-class-declarations' ,
40+ renderChunk ( code ) {
41+ const program = parseAst ( code , { lang : 'js' , sourceType : 'module' } ) ;
42+ const ms = new MagicString ( code ) ;
43+ let changed = false ;
44+
45+ for ( const stmt of program . body ) {
46+ if ( stmt . type !== 'VariableDeclaration' ) {
47+ continue ;
48+ }
49+ const varStmt = stmt as VariableDeclaration ;
50+ if ( varStmt . declarations . length !== 1 ) {
51+ continue ;
52+ }
53+ const decl = varStmt . declarations [ 0 ] ;
54+ if ( decl . id . type !== 'Identifier' || ! decl . init || decl . init . type !== 'ClassExpression' ) {
55+ continue ;
56+ }
57+ const classExpr = decl . init as Class ;
58+ // skip when the inner name differs from the outer binding — that
59+ // would change semantics if the body self-references the inner name.
60+ if ( classExpr . id && classExpr . id . name !== decl . id . name ) {
61+ continue ;
62+ }
63+
64+ // drop `var <id> = ` prefix, leaving the class expression
65+ ms . remove ( varStmt . start , classExpr . start ) ;
66+ // promote to class declaration: insert the binding name after `class`
67+ // if it isn't already present as an inner name.
68+ if ( ! classExpr . id ) {
69+ ms . appendLeft ( classExpr . start + 'class' . length , ` ${ decl . id . name } ` ) ;
70+ }
71+ // drop the trailing semicolon of the var statement
72+ if ( code [ varStmt . end - 1 ] === ';' ) {
73+ ms . remove ( varStmt . end - 1 , varStmt . end ) ;
74+ }
75+ changed = true ;
76+ }
77+
78+ if ( ! changed ) {
79+ return null ;
80+ }
81+ return { code : ms . toString ( ) , map : ms . generateMap ( { hires : 'boundary' } ) } ;
82+ }
83+ } ) ;
84+
3385export default defineConfig ( ( { mode } ) => {
34- const config = defaultBuildUserConfig ( ) ;
86+ const config = defaultBuildUserConfig ( __dirname ) ;
3587 config . plugins ! . push (
3688 viteStaticCopy ( {
89+ // `stripBase` flattens so files land directly under `font/` and
90+ // `soundfont/` instead of preserving the `font/bravura/` prefix.
3791 targets : [
38- { src : 'font/bravura/*' , dest : 'font/' } ,
39- { src : 'font/sonivox/*' , dest : 'soundfont/' }
92+ { src : 'font/bravura/*' , dest : 'font/' , rename : { stripBase : true } } ,
93+ { src : 'font/sonivox/*' , dest : 'soundfont/' , rename : { stripBase : true } }
4094 ]
4195 } )
4296 ) ;
4397
4498 const lib = config . build ! . lib ! as LibraryOptions ;
4599 lib . name = 'alphaTab' ;
46100
47- const typeScriptOptions = ( ) : Partial < RollupTypescriptOptions > => {
48- return {
49- transformers : {
50- before : [ elementStyleUsingTransformer ( ) ] ,
51- afterDeclarations : [ dtsPathsTransformer ( ) ]
52- }
53- } ;
54- } ;
55-
56- const esm = ( name : string , entry : string ) => {
57- defaultEsm (
58- config ,
59- __dirname ,
60- name ,
61- entry ,
62- typeScriptOptions ( ) ,
63- chunk => ! chunk . facadeModuleId ! . endsWith ( 'alphaTab.core.ts' )
64- ) ;
65-
66- ( config . build ! . rollupOptions ! . external as string [ ] ) . push ( '@coderline/alphatab/alphaTab.core' ) ;
67- } ;
68-
69101 switch ( mode ) {
70102 case 'umd' :
71- umd ( config , __dirname , 'alphaTab' , 'src/alphaTab.main.ts' , typeScriptOptions ( ) , true ) ;
103+ umd ( config , __dirname , 'alphaTab' , 'src/alphaTab.main.ts' , true ) ;
72104 break ;
73105 //case 'esm':
74- default :
75- esm ( 'alphaTab' , 'src/alphaTab.main.ts' ) ;
76- const entry = lib . entry ! as Record < string , string > ;
106+ default : {
107+ esm ( config , __dirname , 'alphaTab' , 'src/alphaTab.main.ts' ) ;
108+
109+ const entry = lib . entry as Record < string , string > ;
77110 entry [ 'alphaTab.core' ] = path . resolve ( __dirname , 'src/alphaTab.core.ts' ) ;
78111 entry [ 'alphaTab.worker' ] = path . resolve ( __dirname , 'src/alphaTab.worker.ts' ) ;
79112 entry [ 'alphaTab.worklet' ] = path . resolve ( __dirname , 'src/alphaTab.worklet.ts' ) ;
80113
114+ ( config . build ! . rollupOptions ! . external as string [ ] ) . push ( '@coderline/alphatab/alphaTab.core' ) ;
115+
81116 for ( const output of config . build ! . rollupOptions ! . output as OutputOptions [ ] ) {
82117 const isMin = ( output . entryFileNames as string ) . includes ( '.min' ) ;
83- ( output . plugins as Plugin [ ] ) . push ( adjustScriptPathsPlugin ( isMin ) ) ;
118+ ( output . plugins as Plugin [ ] ) . push (
119+ adjustScriptPathsPlugin ( isMin ) ,
120+ preserveClassDeclarationsPlugin ( )
121+ ) ;
84122 }
123+
124+ // alphaTab.core is an internal runtime-split JS chunk; its types
125+ // are already re-exported through alphaTab.main, so no separate
126+ // bundled `alphaTab.core.d.ts` is published.
127+ addDts ( config , __dirname , {
128+ shouldEmitForChunk : chunk => ! chunk . facadeModuleId ! . endsWith ( 'alphaTab.core.ts' )
129+ } ) ;
85130 break ;
131+ }
86132 }
87133
88134 return config ;
0 commit comments