@@ -6,9 +6,8 @@ import * as fs from 'node:fs';
6
6
import * as path from 'node:path' ;
7
7
import { parseArgs } from 'node:util' ;
8
8
import * as LightningCSS from 'lightningcss' ;
9
- import * as rollup from 'rollup' ;
10
9
import { globSync } from 'tinyglobby' ;
11
- import { getRollupConfiguration } from './rollup.ts ' ;
10
+ import { build } from 'tsdown ' ;
12
11
13
12
const args = parseArgs ( {
14
13
allowPositionals : true ,
@@ -34,7 +33,7 @@ async function main() {
34
33
process . exit ( 1 ) ;
35
34
}
36
35
37
- const packageData = await import ( path . join ( packageRoot , 'package.json' ) , { with : { type : 'json' } } ) ;
36
+ const packageData = await import ( path . join ( packageRoot , 'package.json' ) , { with : { type : 'json' } } ) ;
38
37
const packageName = packageData . name ;
39
38
const srcDir = path . join ( packageRoot , 'src' ) ;
40
39
const distDir = path . join ( packageRoot , 'dist' ) ;
@@ -44,107 +43,115 @@ async function main() {
44
43
process . exit ( 1 ) ;
45
44
}
46
45
47
- if ( fs . existsSync ( distDir ) ) {
48
- console . log ( `Cleaning up the "${ distDir } " directory...` ) ;
49
- await fs . promises . rm ( distDir , { recursive : true } ) ;
50
- await fs . promises . mkdir ( distDir ) ;
51
- }
52
-
53
- const inputScriptFiles = [
46
+ const inputFiles = [
54
47
...globSync ( path . join ( srcDir , '*controller.ts' ) ) ,
55
48
...( [ '@symfony/ux-react' , '@symfony/ux-vue' , '@symfony/ux-svelte' ] . includes ( packageName )
56
49
? [ path . join ( srcDir , 'loader.ts' ) , path . join ( srcDir , 'components.ts' ) ]
57
50
: [ ] ) ,
58
51
...( packageName === '@symfony/stimulus-bundle'
59
52
? [ path . join ( srcDir , 'loader.ts' ) , path . join ( srcDir , 'controllers.ts' ) ]
60
53
: [ ] ) ,
54
+ ...( packageData ?. config ?. css_source ? [ packageData . config . css_source ] : [ ] ) ,
55
+ ] ;
56
+
57
+ const peerDependencies = [
58
+ '@hotwired/stimulus' ,
59
+ ...( packageData . peerDependencies ? Object . keys ( packageData . peerDependencies ) : [ ] ) ,
61
60
] ;
62
61
63
- const inputStyleFile = packageData . config ?. css_source ;
64
- const buildCss = async ( ) => {
65
- if ( ! inputStyleFile ) {
66
- return ;
62
+ inputFiles . forEach ( ( file ) => {
63
+ // custom handling for StimulusBundle
64
+ if ( file . includes ( 'StimulusBundle/assets/src/loader.ts' ) ) {
65
+ peerDependencies . push ( './controllers.js' ) ;
67
66
}
68
- const inputStyleFileDist = path . resolve ( distDir , `${ path . basename ( inputStyleFile , '.css' ) } .min.css` ) ;
69
-
70
- console . log ( 'Minifying CSS...' ) ;
71
- const css = await fs . promises . readFile ( inputStyleFile , 'utf-8' ) ;
72
- const { code : minified } = LightningCSS . transform ( {
73
- filename : path . basename ( inputStyleFile , '.css' ) ,
74
- code : Buffer . from ( css ) ,
75
- minify : true ,
76
- sourceMap : false , // TODO: Maybe we can add source maps later? :)
77
- } ) ;
78
- await fs . promises . writeFile ( inputStyleFileDist , minified ) ;
79
- } ;
80
-
81
- if ( inputScriptFiles . length === 0 ) {
82
- console . error (
83
- `No input files found for package "${ packageName } " (directory "${ packageRoot } ").\nEnsure you have at least a file matching the pattern "src/*_controller.ts", or manually specify input files in "${ import . meta. filename } " file.`
84
- ) ;
85
- process . exit ( 1 ) ;
86
- }
87
67
88
- const rollupConfig = getRollupConfiguration ( {
89
- packageRoot,
90
- inputFiles : inputScriptFiles ,
91
- isWatch,
92
- additionalPlugins : [
93
- ...( isWatch && inputStyleFile
94
- ? [
95
- {
96
- name : 'watcher' ,
97
- buildStart ( this : rollup . PluginContext ) {
98
- this . addWatchFile ( inputStyleFile ) ;
99
- } ,
100
- } ,
101
- ]
102
- : [ ] ) ,
103
- ] ,
68
+ // React, Vue
69
+ if ( file . includes ( 'assets/src/loader.ts' ) ) {
70
+ peerDependencies . push ( './components.js' ) ;
71
+ }
104
72
} ) ;
105
73
106
- if ( isWatch ) {
107
- console . log (
108
- `Watching for JavaScript${ inputStyleFile ? ' and CSS' : '' } files modifications in "${ srcDir } " directory...`
109
- ) ;
110
-
111
- const watcher = rollup . watch ( rollupConfig ) ;
112
- watcher . on ( 'event' , ( event ) => {
113
- if ( event . code === 'ERROR' ) {
114
- console . error ( 'Error during build:' , event . error ) ;
115
- }
116
-
117
- if ( ( event . code === 'BUNDLE_END' || event . code === 'ERROR' ) && event . result ) {
118
- event . result . close ( ) ;
119
- }
120
- } ) ;
121
- watcher . on ( 'change' , async ( id , { event } ) => {
122
- if ( event === 'update' ) {
123
- console . log ( 'Files were modified, rebuilding...' ) ;
124
- }
125
-
126
- if ( inputStyleFile && id === inputStyleFile ) {
127
- await buildCss ( ) ;
74
+ build ( {
75
+ entry : inputFiles ,
76
+ outDir : distDir ,
77
+ clean : true ,
78
+ outputOptions : {
79
+ cssEntryFileNames : '[name].min.css' ,
80
+ } ,
81
+ external : peerDependencies ,
82
+ format : 'esm' ,
83
+ platform : 'browser' ,
84
+ tsconfig : path . join ( import . meta. dirname , '../tsconfig.packages.json' ) ,
85
+ // The target should be kept in sync with `tsconfig.packages.json` file.
86
+ // In the future, I hope the target will be read from the `tsconfig.packages.json` file, but for now we need to specify it manually.
87
+ target : 'es2021' ,
88
+ watch : isWatch ,
89
+ plugins : [
90
+
91
+ /**
92
+ * Guarantees that any files imported from a peer dependency are treated as an external.
93
+ *
94
+ * For example, if we import `chart.js/auto`, that would not normally
95
+ * match the "chart.js" we pass to the "externals" config. This plugin
96
+ * catches that case and adds it as an external.
97
+ *
98
+ * Inspired by https://github.com/oat-sa/rollup-plugin-wildcard-external
99
+ */
100
+ {
101
+ name : 'wildcard-externals' ,
102
+ resolveId ( source : string , importer : string ) {
103
+ if ( ! importer ) {
104
+ return null ; // other ids should be handled as usually
105
+ }
106
+
107
+ const matchesExternal = peerDependencies . some ( ( peerDependency ) => {
108
+ return source . includes ( `/${ peerDependency } /` )
109
+ } ) ;
110
+
111
+ if ( matchesExternal ) {
112
+ return {
113
+ id : source ,
114
+ external : true ,
115
+ moduleSideEffects : true ,
116
+ } ;
117
+ }
118
+
119
+ return null ; // other ids should be handled as usually
120
+ } ,
121
+ } ,
122
+ // Since minifying files is not configurable per file, we need to use a custom plugin to handle CSS minification.
123
+ {
124
+ name : 'minimize-css' ,
125
+ transform : {
126
+ filter : {
127
+ id : / \. c s s $ / ,
128
+ } ,
129
+ handler ( code , id ) {
130
+ const { code : minifiedCode } = LightningCSS . transform ( {
131
+ filename : path . basename ( id ) ,
132
+ code : Buffer . from ( code ) ,
133
+ minify : true ,
134
+ sourceMap : false ,
135
+ } ) ;
136
+
137
+ return { code : minifiedCode . toString ( ) , map : null } ;
138
+ }
139
+ } ,
140
+ } ,
141
+ ] ,
142
+ hooks : {
143
+ async 'build:done' ( ) {
144
+ // TODO: Idk why, but when we build a CSS file (e.g. `style.css`), it also generate an empty JS file (e.g. `style.js`).
145
+ if ( packageData ?. config ?. css_source ) {
146
+ const unwantedJsFile = path . join ( distDir , path . basename ( packageData . config . css_source , '.css' ) + '.js' ) ;
147
+ await fs . promises . rm ( unwantedJsFile , { force : true } ) ;
148
+ }
128
149
}
129
- } ) ;
130
- } else {
131
- console . log ( `Building JavaScript files from ${ packageName } package...` ) ;
132
- const start = Date . now ( ) ;
133
-
134
- if ( typeof rollupConfig . output === 'undefined' || Array . isArray ( rollupConfig . output ) ) {
135
- console . error (
136
- `The rollup configuration for package "${ packageName } " does not contain a valid output configuration.`
137
- ) ;
138
- process . exit ( 1 ) ;
139
150
}
140
-
141
- const bundle = await rollup . rollup ( rollupConfig ) ;
142
- await bundle . write ( rollupConfig . output ) ;
143
-
144
- await buildCss ( ) ;
145
-
146
- console . log ( `Done in ${ ( ( Date . now ( ) - start ) / 1000 ) . toFixed ( 3 ) } seconds.` ) ;
147
- }
151
+ } ) . catch ( ( error ) => {
152
+ console . error ( 'Error during build:' , error ) ;
153
+ process . exit ( 1 ) ;
154
+ } ) ;
148
155
}
149
156
150
157
main ( ) ;
0 commit comments