55import * as fs from 'node:fs' ;
66import * as path from 'node:path' ;
77import { parseArgs } from 'node:util' ;
8- import * as LightningCSS from 'lightningcss' ;
9- import * as rollup from 'rollup' ;
108import { globSync } from 'tinyglobby' ;
11- import { getRollupConfiguration } from './rollup.ts' ;
9+ import { build } from 'tsup' ;
10+ import { readPackageJSON } from "pkg-types" ;
1211
1312const args = parseArgs ( {
1413 allowPositionals : true ,
@@ -34,117 +33,105 @@ async function main() {
3433 process . exit ( 1 ) ;
3534 }
3635
37- const packageData = await import ( path . join ( packageRoot , 'package.json' ) , { with : { type : 'json' } } ) ;
38- const packageName = packageData . name ;
39- const srcDir = path . join ( packageRoot , 'src' ) ;
40- const distDir = path . join ( packageRoot , 'dist' ) ;
36+ const packageData = await readPackageJSON ( path . join ( packageRoot , 'package.json' ) ) ;
37+ const isStimulusBundle = '@symfony/stimulus-bundle' === packageData . name ;
38+ const isReactOrVueOrSvelte = [ '@symfony/ux-react' , '@symfony/ux-vue' , '@symfony/ux-svelte' ] . some ( name => packageData . name . startsWith ( name ) ) ;
4139
42- if ( ! fs . existsSync ( srcDir ) ) {
43- console . error ( `The package directory "${ packageRoot } " does not contain a "src" directory.` ) ;
44- process . exit ( 1 ) ;
45- }
46-
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 = [
54- ...globSync ( path . join ( srcDir , '*controller.ts' ) ) ,
55- ...( [ '@symfony/ux-react' , '@symfony/ux-vue' , '@symfony/ux-svelte' ] . includes ( packageName )
56- ? [ path . join ( srcDir , 'loader.ts' ) , path . join ( srcDir , 'components.ts' ) ]
57- : [ ] ) ,
58- ...( packageName === '@symfony/stimulus-bundle'
59- ? [ path . join ( srcDir , 'loader.ts' ) , path . join ( srcDir , 'controllers.ts' ) ]
60- : [ ] ) ,
40+ const inputCssFile = packageData ?. config ?. css_source ;
41+ const inputFiles = [
42+ ...globSync ( 'src/*controller.ts' ) ,
43+ ...( isStimulusBundle ? [ 'src/loader.ts' , 'src/controllers.ts' ] : [ ] ) ,
44+ ...( isReactOrVueOrSvelte ? [ 'src/loader.ts' , 'src/components.ts' ] : [ ] ) ,
45+ ...( inputCssFile ? [ inputCssFile ] : [ ] ) ,
6146 ] ;
6247
63- const inputStyleFile = packageData . config ?. css_source ;
64- const buildCss = async ( ) => {
65- if ( ! inputStyleFile ) {
66- return ;
48+ const external = new Set ( [
49+ // We force "dependencies" and "peerDependencies" to be external to avoid bundling them.
50+ ...Object . keys ( packageData . dependencies || { } ) ,
51+ ...Object . keys ( packageData . peerDependencies || { } ) ,
52+ ] ) ;
53+
54+ inputFiles . forEach ( ( file ) => {
55+ // custom handling for StimulusBundle
56+ if ( file . includes ( 'StimulusBundle/assets/src/loader.ts' ) ) {
57+ external . add ( './controllers.js' ) ;
6758 }
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- }
8759
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- ] ,
60+ // React, Vue, Svelte
61+ if ( file . includes ( 'assets/src/loader.ts' ) ) {
62+ external . add ( './components.js' ) ;
63+ }
10464 } ) ;
10565
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 ( ) ;
66+ await build ( {
67+ entry : inputFiles ,
68+ outDir : path . join ( packageRoot , 'dist' ) ,
69+ clean : true ,
70+ external : Array . from ( external ) ,
71+ format : 'esm' ,
72+ platform : 'browser' ,
73+ tsconfig : path . join ( import . meta. dirname , '../tsconfig.packages.json' ) ,
74+ dts : {
75+ entry : inputFiles . filter ( inputFile => ! inputFile . endsWith ( '.css' ) ) ,
76+ } ,
77+ watch : isWatch ,
78+ splitting : false ,
79+ esbuildOptions ( options ) {
80+ // Disabling `bundle` option prevent esbuild to inline relative (but external) imports (like "./components.js" for React, Vue, Svelte).
81+ options . bundle = ! ( isStimulusBundle || isReactOrVueOrSvelte ) ;
82+ } ,
83+ plugins : [
84+ {
85+ /**
86+ * This plugin is used to minify CSS files using LightningCSS.
87+ *
88+ * Even if tsup supports CSS minification through ESBuild by setting the `minify: true` option,
89+ * it also minifies JS files but we don't want that.
90+ */
91+ name : 'symfony-ux:minify-css' ,
92+ async renderChunk ( code , chunkInfo ) {
93+ if ( ! / \. c s s $ / . test ( chunkInfo . path ) ) {
94+ return null ;
95+ }
96+
97+ const { transform } = await import ( 'lightningcss' ) ;
98+ const result = transform ( {
99+ filename : chunkInfo . path ,
100+ code : Buffer . from ( code ) ,
101+ minify : true ,
102+ } ) ;
103+
104+ console . log ( `[Symfony UX] Minified CSS file: ${ chunkInfo . path } ` ) ;
105+
106+ return {
107+ code : result . code . toString ( ) ,
108+ map : result . map ? result . map . toString ( ) : null ,
109+ }
110+ } ,
111+ } ,
112+
113+ /**
114+ * Unlike tsdown/rolldown and the option "cssEntryFileNames", tsup does not support
115+ * customizing the output file names for CSS files.
116+ * A plugin is needed to rename the written CSS files to add the ".min" suffix.
117+ */
118+ {
119+ name : 'symfony-ux:append-min-to-css' ,
120+ async buildEnd ( { writtenFiles } ) {
121+ for ( const writtenFile of writtenFiles ) {
122+ if ( ! writtenFile . name . endsWith ( '.css' ) ) {
123+ continue ;
124+ }
125+
126+ const newName = writtenFile . name . replace ( / \. c s s $ / , '.min.css' ) ;
127+ await fs . promises . rename ( writtenFile . name , newName ) ;
128+
129+ console . info ( `[Symfony UX] Renamed ${ writtenFile . name } to ${ newName } ` ) ;
130+ }
131+ }
128132 }
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- }
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- }
133+ ] ,
134+ } ) ;
148135}
149136
150137main ( ) ;
0 commit comments