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,105 @@ 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' ) ) ;
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 ) ) ;
41
39
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 ] : [ ] ) ,
61
46
] ;
62
47
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' ) ;
67
58
}
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
59
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
+ }
104
64
} ) ;
105
65
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
+ }
128
132
}
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
+ } ) ;
148
135
}
149
136
150
137
main ( ) ;
0 commit comments