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