Merge branch 'renovate/all-minor-patch' into 'main'
[ProtonMail-WebClient.git] / packages / pack / webpack.config.ts
blob9512c5f8751ace7add011c1295e2c9004900bcbd
1 import path from 'path';
2 import type { Configuration } from 'webpack';
3 import 'webpack-dev-server';
4 // @ts-ignore
5 import { parseResource } from 'webpack/lib/util/identifier';
7 import { getEntries } from './webpack/entries';
9 const getCssLoaders = require('./webpack/css.loader');
10 const getAssetsLoaders = require('./webpack/assets.loader');
11 const getPlugins = require('./webpack/plugins');
12 const getOptimizations = require('./webpack/optimization');
14 const getConfig = (env: any): Configuration => {
15     const isProduction = process.env.NODE_ENV === 'production';
16     const isRelease = !!process.env.CI_COMMIT_TAG;
18     // This folder is separate from the assets folder because they are special assets which get served through
19     // a long-term storage
20     const assetsFolder = 'assets/static';
22     const { getJsLoaders } = require(env.webpackOnCaffeine ? './webpack/js.loader.swc' : './webpack/js.loader');
24     const defaultBrowsersList = isProduction
25         ? `> 0.5%, not IE 11, Firefox ESR, Safari 14, iOS 14, Chrome 80`
26         : 'last 1 chrome version, last 1 firefox version, last 1 safari version';
28     const options = {
29         isProduction,
30         isRelease,
31         publicPath: env.publicPath || '/',
32         api: env.api,
33         appMode: env.appMode || 'standalone',
34         webpackOnCaffeine: env.webpackOnCaffeine,
35         featureFlags: env.featureFlags || '',
36         writeSRI: env.writeSri !== 'false',
37         inlineIcons: env.inlineIcons === 'true',
38         browserslist: env.browserslist ?? defaultBrowsersList,
39         buildData: {
40             version: env.version,
41             commit: env.commit,
42             branch: env.branch,
43             date: env.date,
44             mode: env.appMode,
45         },
46         warningLogs: env.warningLogs || false,
47         errorLogs: env.errorLogs || false,
48         overlayWarnings: env.overlayWarnings || false,
49         overlayErrors: env.overlayErrors || false,
50         overlayRuntimeErrors: env.overlayRuntimeErrors || false,
51         logical: env.logical || false,
52         analyze: env.analyze || false,
53     };
55     const version = options.buildData.version;
57     return {
58         target: `browserslist:${options.browserslist}`,
59         mode: isProduction ? 'production' : 'development',
60         bail: isProduction,
61         devtool: isProduction ? 'source-map' : 'cheap-module-source-map',
62         watchOptions: {
63             ignored: /dist|node_modules|locales|\.(gif|jpeg|jpg|ico|png|svg)/,
64             aggregateTimeout: 600,
65         },
66         resolve: {
67             extensions: ['.js', '.tsx', '.ts'],
68             fallback: {
69                 crypto: false,
70                 buffer: false,
71                 stream: false,
72                 iconv: false,
73                 path: false,
74                 punycode: false,
75             },
76         },
77         experiments: { asyncWebAssembly: true },
78         entry: getEntries(),
79         output: {
80             filename: isProduction
81                 ? `${assetsFolder}/[name].[contenthash:8].js?v=${version}`
82                 : `${assetsFolder}/[name].js?v=${version}`,
83             publicPath: options.publicPath,
84             chunkFilename: (pathData) => {
85                 const result = isProduction
86                     ? `${assetsFolder}/[name].[contenthash:8].chunk.js?v=${version}`
87                     : `${assetsFolder}/[name].chunk.js?v=${version}`;
88                 const chunkName = pathData?.chunk?.name;
89                 if (chunkName && (chunkName.startsWith('date-fns/') || chunkName.startsWith('locales/'))) {
90                     // @ts-ignore
91                     const strippedChunkName = chunkName.replaceAll(/-index-js|-json/g, '');
92                     return result.replace('[name]', strippedChunkName);
93                 }
94                 // Drive need static URL for transpiled SW
95                 if (chunkName && chunkName.startsWith('downloadSW')) {
96                     return `[name].js?v=${version}`;
97                 }
98                 return result;
99             },
100             assetModuleFilename: (data) => {
101                 const { path: file } = parseResource(data?.filename || '');
102                 const ext = path.extname(file);
103                 const base = path.basename(file);
104                 const name = base.slice(0, base.length - ext.length);
105                 if (name.includes('.var')) {
106                     const replacedNamed = name.replace('.var', '-var');
107                     return `${assetsFolder}/${replacedNamed}.[hash][ext]?v=${version}`;
108                 }
109                 return `${assetsFolder}/[name].[hash][ext]?v=${version}`;
110             },
111             crossOriginLoading: 'anonymous',
112         },
113         module: {
114             strictExportPresence: true, // Make missing exports an error instead of warning
115             rules: [...getJsLoaders(options), ...getCssLoaders(options), ...getAssetsLoaders(options)],
116         },
117         plugins: getPlugins({
118             ...options,
119             cssName: isProduction
120                 ? `${assetsFolder}/[name].[contenthash:8].css?v=${version}`
121                 : `${assetsFolder}/[name].css?v=${version}`,
122         }),
123         optimization: getOptimizations(options),
124         devServer: {
125             hot: !isProduction,
126             devMiddleware: {
127                 stats: 'minimal',
128                 publicPath: options.publicPath,
129             },
130             allowedHosts: 'all',
131             compress: true,
132             historyApiFallback: {
133                 index: options.publicPath,
134             },
135             client: {
136                 webSocketURL: 'auto://0.0.0.0:0/ws',
137                 overlay: {
138                     warnings: options.overlayWarnings,
139                     errors: options.overlayErrors,
140                     runtimeErrors: options.overlayRuntimeErrors,
141                 },
142             },
143             webSocketServer: 'ws',
144             ...(options.api && {
145                 proxy: [
146                     {
147                         context: ['/api', '/internal-api'],
148                         target: options.api,
149                         secure: false,
150                         changeOrigin: true,
151                         onProxyRes: (proxyRes) => {
152                             delete proxyRes.headers['content-security-policy'];
153                             delete proxyRes.headers['x-frame-options'];
154                             proxyRes.headers['set-cookie'] = proxyRes.headers['set-cookie']?.map((cookies) =>
155                                 cookies
156                                     .split('; ')
157                                     .filter((cookie) => {
158                                         return !/(secure$|samesite=|domain=)/i.test(cookie);
159                                     })
160                                     .join('; ')
161                             );
162                         },
163                     },
164                 ],
165             }),
166         },
167     };
170 export default getConfig;