1+ /*
2+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+ * SPDX-License-Identifier: AGPL-3.0-or-later
4+ */
5+
6+ const browserslistConfig = require ( '@nextcloud/browserslist-config' )
7+ const { RsdoctorRspackPlugin } = require ( '@rsdoctor/rspack-plugin' )
8+ const { defineConfig } = require ( '@rspack/cli' )
9+ const { CssExtractRspackPlugin, LightningCssMinimizerRspackPlugin, DefinePlugin, ProgressPlugin, SwcJsMinimizerRspackPlugin, IgnorePlugin } = require ( '@rspack/core' )
10+ const NodePolyfillPlugin = require ( '@rspack/plugin-node-polyfill' )
11+ const browserslist = require ( 'browserslist' )
12+ const path = require ( 'node:path' )
13+ const { VueLoaderPlugin } = require ( 'vue-loader' )
14+
15+ // browserslist-rs does not support baseline queries yet
16+ // Manually resolving the browserslist config to the list of browsers with minimal versions
17+ // See: https://github.com/browserslist/browserslist-rs/issues/40
18+ const browsers = browserslist ( browserslistConfig )
19+ const minBrowserVersion = browsers
20+ . map ( ( str ) => str . split ( ' ' ) )
21+ . reduce ( ( minVersion , [ browser , version ] ) => {
22+ minVersion [ browser ] = minVersion [ browser ] ? Math . min ( minVersion [ browser ] , parseFloat ( version ) ) : parseFloat ( version )
23+ return minVersion
24+ } , { } )
25+ const targets = Object . entries ( minBrowserVersion ) . map ( ( [ browser , version ] ) => `${ browser } >=${ version } ` ) . join ( ',' )
26+
27+ const transpilePackages = [
28+ 'js-base64' ,
29+ 'p-limit' ,
30+ 'p-defer' ,
31+ 'p-queue' ,
32+ 'p-try' ,
33+ 'parseley' ,
34+ 'selderee' ,
35+ 'yocto-queue' ,
36+ ]
37+
38+ const shouldExcludeFromJsTranspile = ( resourcePath ) => {
39+ if ( ! resourcePath . includes ( `${ path . sep } node_modules${ path . sep } ` ) ) {
40+ return false
41+ }
42+
43+ return ! transpilePackages . some ( ( moduleName ) => resourcePath . includes ( `${ path . sep } node_modules${ path . sep } ${ moduleName } ${ path . sep } ` ) )
44+ }
45+
46+ module . exports = defineConfig ( ( env ) => {
47+ const appName = process . env . npm_package_name
48+ const appVersion = process . env . npm_package_version
49+
50+ const mode = ( env . development && 'development' ) || ( env . production && 'production' ) || process . env . NODE_ENV || 'production'
51+ const isDev = mode === 'development'
52+ process . env . NODE_ENV = mode
53+
54+ console . info ( 'Building' , appName , appVersion , '\n' )
55+
56+ return {
57+ target : 'web' ,
58+ mode,
59+ devtool : isDev ? 'cheap-source-map' : 'source-map' ,
60+ stats : 'normal' ,
61+
62+ entry : {
63+ mail : path . join ( __dirname , 'src' , 'main.js' ) ,
64+ oauthpopup : path . join ( __dirname , 'src' , 'main-oauth-popup.js' ) ,
65+ settings : path . join ( __dirname , 'src' , 'main-settings.js' ) ,
66+ htmlresponse : path . join ( __dirname , 'src' , 'html-response.js' ) ,
67+ } ,
68+
69+ output : {
70+ path : path . resolve ( './js' ) ,
71+ filename : '[name].js' ,
72+ chunkFilename : `${ appName } -[name].js?v=[contenthash]` ,
73+ publicPath : 'auto' ,
74+ assetModuleFilename : '[name].[ext]?[contenthash]' ,
75+ clean : true ,
76+ devtoolNamespace : appName ,
77+ devtoolModuleFilenameTemplate ( info ) {
78+ const rootDir = process . cwd ( )
79+ const rel = path . relative ( rootDir , info . absoluteResourcePath )
80+ return `webpack:///${ appName } /${ rel } `
81+ } ,
82+ } ,
83+
84+ optimization : {
85+ chunkIds : 'named' ,
86+ splitChunks : {
87+ automaticNameDelimiter : '-' ,
88+ cacheGroups : {
89+ defaultVendors : {
90+ reuseExistingChunk : true ,
91+ } ,
92+ } ,
93+ } ,
94+ minimize : ! isDev ,
95+ minimizer : [
96+ new SwcJsMinimizerRspackPlugin ( {
97+ minimizerOptions : {
98+ targets,
99+ } ,
100+ } ) ,
101+ new LightningCssMinimizerRspackPlugin ( {
102+ minimizerOptions : {
103+ targets,
104+ } ,
105+ } ) ,
106+ ] ,
107+ } ,
108+
109+ module : {
110+ rules : [
111+ {
112+ test : / \. v u e $ / ,
113+ loader : 'vue-loader' ,
114+ options : {
115+ experimentalInlineMatchResource : true ,
116+ } ,
117+ } ,
118+ {
119+ test : / \. c s s $ / ,
120+ use : [
121+ 'vue-style-loader' ,
122+ 'css-loader' ,
123+ ] ,
124+ } ,
125+ {
126+ test : / \. s c s s $ / ,
127+ use : [
128+ 'vue-style-loader' ,
129+ 'css-loader' ,
130+ 'sass-loader' ,
131+ ] ,
132+ } ,
133+ {
134+ test : / \. [ c m ] ? j s $ / ,
135+ exclude : shouldExcludeFromJsTranspile ,
136+ loader : 'builtin:swc-loader' ,
137+ options : {
138+ jsc : {
139+ parser : {
140+ syntax : 'ecmascript' ,
141+ } ,
142+ } ,
143+ env : {
144+ targets,
145+ } ,
146+ } ,
147+ type : 'javascript/auto' ,
148+ } ,
149+ {
150+ test : / \. t s $ / ,
151+ exclude : [ / n o d e _ m o d u l e s / ] ,
152+ loader : 'builtin:swc-loader' ,
153+ options : {
154+ jsc : {
155+ parser : {
156+ syntax : 'typescript' ,
157+ } ,
158+ } ,
159+ env : {
160+ targets,
161+ } ,
162+ } ,
163+ type : 'javascript/auto' ,
164+ } ,
165+ {
166+ test : / \. ( p n g | j p e ? g | g i f | s v g | w e b p ) $ / i,
167+ type : 'asset' ,
168+ } ,
169+ {
170+ test : / \. ( w o f f 2 ? | e o t | t t f | o t f ) $ / i,
171+ type : 'asset/resource' ,
172+ } ,
173+ {
174+ test : / \. w a s m $ / i,
175+ type : 'asset/resource' ,
176+ } ,
177+ {
178+ test : / \. t f l i t e $ / i,
179+ type : 'asset/resource' ,
180+ } ,
181+ {
182+ resourceQuery : / r a w / ,
183+ type : 'asset/source' ,
184+ } ,
185+ {
186+ resourceQuery : / u r l $ / ,
187+ type : 'asset/resource' ,
188+ } ,
189+ {
190+ test : / c k e d i t o r 5 - [ ^ / \\ ] + [ / \\ ] t h e m e [ / \\ ] i c o n s [ / \\ ] [ ^ / \\ ] + \. s v g $ / ,
191+ type : 'asset/source' ,
192+ } ,
193+ ] ,
194+ } ,
195+
196+ plugins : [
197+ new ProgressPlugin ( ) ,
198+ new VueLoaderPlugin ( ) ,
199+ new NodePolyfillPlugin ( ) ,
200+ new DefinePlugin ( {
201+ appName : JSON . stringify ( appName ) ,
202+ appVersion : JSON . stringify ( appVersion ) ,
203+ // Vue compile time flags
204+ // See: https://vuejs.org/api/compile-time-flags.html#compile-time-flags
205+ // See: https://github.com/vuejs/core/blob/v3.5.24/packages/vue/README.md#bundler-build-feature-flags
206+ // > The build will work without configuring these flags,
207+ // > however it is strongly recommended to properly configure them in order to get proper tree-shaking in the final bundle
208+ // Unlike Vite plugin, vue-loader does not do this automatically for Webpack
209+ // Although documentation says, it is optional, sometimes it breaks with:
210+ // ReferenceError: __VUE_PROD_DEVTOOLS__ is not defined
211+ __VUE_OPTIONS_API__ : true ,
212+ __VUE_PROD_DEVTOOLS__ : false ,
213+ __VUE_PROD_HYDRATION_MISMATCH_DETAILS__ : false ,
214+ } ) ,
215+ new IgnorePlugin ( {
216+ resourceRegExp : / ^ \. \/ l o c a l e $ / ,
217+ contextRegExp : / m o m e n t $ / ,
218+ } ) ,
219+ process . env . RSDOCTOR && new RsdoctorRspackPlugin ( ) ,
220+ ] ,
221+
222+ resolve : {
223+ extensions : [ '*' , '.tsx' , '.ts' , '.js' , '.vue' , '.json' ] ,
224+ symlinks : false ,
225+ alias : {
226+ '@' : path . resolve ( __dirname , 'src' ) ,
227+ } ,
228+ fallback : {
229+ fs : false ,
230+ } ,
231+ } ,
232+
233+ cache : true ,
234+ }
235+ } )
0 commit comments