1 const path
= require('path');
2 const webpack
= require('webpack');
3 const ESLintPlugin
= require('eslint-webpack-plugin');
4 const CopyPlugin
= require('copy-webpack-plugin');
5 const MiniCssExtractPlugin
= require('mini-css-extract-plugin');
6 const CircularDependencyPlugin
= require('circular-dependency-plugin');
8 const getCssLoaders
= require('@proton/pack/webpack/css.loader');
9 const getAssetsLoaders
= require('@proton/pack/webpack/assets.loader');
10 const getOptimizations
= require('@proton/pack/webpack/optimization');
11 const ProtonIconsTreeShakePlugin
= require('@proton/pass/utils/webpack/icons/plugin');
12 const { excludeNodeModulesExcept
, excludeFiles
, createRegex
} = require('@proton/pack/webpack/helpers/regex');
13 const { BABEL_EXCLUDE_FILES
, BABEL_INCLUDE_NODE_MODULES
} = require('@proton/pack/webpack/constants');
14 const fs
= require('fs');
28 WEBPACK_CIRCULAR_DEPS
,
29 } = require('./tools/env');
31 const SUPPORTED_TARGETS
= ['chrome', 'firefox', 'safari'];
33 if (!SUPPORTED_TARGETS
.includes(BUILD_TARGET
)) {
34 throw new Error(`Build target "${BUILD_TARGET}" is not supported`);
37 const CONFIG
= fs
.readFileSync('./src/app/config.ts', 'utf-8').replaceAll(/(export const |;)/gm
, '');
38 const MANIFEST_KEYS
= JSON
.parse(fs
.readFileSync(path
.resolve(__dirname
, 'manifest-keys.json'), 'utf8'));
39 const PUBLIC_KEY
= BUILD_TARGET
=== 'chrome' ? MANIFEST_KEYS
?.[MANIFEST_KEY
] : null;
40 const ARGON2_CHUNK_NAME
= 'node_modules_openpgp_dist_lightweight_argon2id_min_mjs';
42 console
.log(`ENV = ${ENV}`);
43 console
.log(`RELEASE = ${RELEASE}`);
44 console
.log(`BUILD_TARGET = ${BUILD_TARGET}`);
45 console
.log(`MANIFEST_KEY = ${MANIFEST_KEY || 'none'}`);
46 console
.log(`PUBLIC_KEY = ${PUBLIC_KEY || 'none'}`);
47 console
.log(`CLEAN_MANIFEST = ${CLEAN_MANIFEST}`);
49 if (ENV
!== 'production') {
50 console
.log(`HOT_MANIFEST_UPDATE = ${HOT_MANIFEST_UPDATE}`);
51 console
.log(`REDUX_DEVTOOLS_PORT = ${REDUX_DEVTOOLS_PORT}`);
52 console
.log(`RESUME_FALLBACK = ${RESUME_FALLBACK}`);
53 console
.log(`RUNTIME_RELOAD = ${RUNTIME_RELOAD}`);
54 console
.log(`RUNTIME_RELOAD_PORT = ${RUNTIME_RELOAD_PORT}`);
55 console
.log(`WEBPACK_DEV_PORT = ${WEBPACK_DEV_PORT}`);
60 const production
= ENV
=== 'production';
61 const importScripts
= BUILD_TARGET
=== 'chrome' || BUILD_TARGET
=== 'safari';
62 const manifest
= `manifest-${BUILD_TARGET}.json`;
63 const manifestPath
= path
.resolve(__dirname
, manifest
);
65 const nonAccessibleWebResource
= (entry
) => [entry
, './src/lib/utils/web-accessible-resource.ts'];
66 const disableBrowserTrap
= (entry
) => [entry
, './src/lib/utils/disable-browser-trap.ts'];
67 const getManifestVersion
= () => JSON
.stringify(JSON
.parse(fs
.readFileSync(manifestPath
, 'utf8')).version
);
71 ? { mode
: 'production', devtool
: 'source-map' }
72 : { mode
: 'development', devtool
: 'inline-source-map' }),
75 import: './src/app/worker/index.ts',
76 filename
: 'background.js',
77 /* MV3 implementations of non-persistent background scripts change
78 * depending on the platform. On chromium based browsers, we'll be
79 * in a ServiceWorker environment whereas on FF it'll actually be a
80 * non-persistent event page. One of the quirks of chunk loading in
81 * a service-worker is that you have to register any imported script
82 * during the service worker's oninstall phase (`/worker/index.ts`)
83 * https://bugs.chromium.org/p/chromium/issues/detail?id=1198822#c10 */
84 chunkLoading: importScripts ? 'import-scripts' : 'jsonp',
86 client: './src/app/content/client.ts',
87 dropdown: nonAccessibleWebResource('./src/app/content/injections/apps/dropdown/index.tsx'),
88 elements: './src/app/content/elements.ts',
89 notification: nonAccessibleWebResource('./src/app/content/injections/apps/notification/index.tsx'),
90 onboarding: './src/app/pages/onboarding/index.tsx',
91 orchestrator: disableBrowserTrap('./src/app/content/orchestrator.ts'),
92 popup: './src/app/popup/index.tsx',
93 settings: './src/app/pages/settings/index.tsx',
94 /* Passkey handling not available in Safari */
95 ...(BUILD_TARGET
!== 'safari' ? { webauthn
: './src/app/content/webauthn.ts' } : {}),
96 /* FF account communication fallback */
97 ...(BUILD_TARGET
=== 'firefox' ? { account
: disableBrowserTrap('./src/app/content/firefox/index.ts') } : {}),
98 /* Safari fork fallback */
99 ...(BUILD_TARGET
=== 'safari' ? { fork
: disableBrowserTrap('./src/app/content/safari/index.ts') } : {}),
102 strictExportPresence
: true,
105 test
: /\.js$|\.tsx?$/,
106 exclude
: createRegex(
107 excludeNodeModulesExcept(BABEL_INCLUDE_NODE_MODULES
),
108 excludeFiles([...BABEL_EXCLUDE_FILES
, 'pre.ts', 'unsupported.ts'])
110 use: require
.resolve('babel-loader'),
112 ...getCssLoaders({ browserslist
: undefined, logical
: false }),
113 ...getAssetsLoaders({ inlineIcons
: true }),
117 ...getOptimizations({ isProduction
: production
}),
124 extensions
: ['.js', '.tsx', '.ts'],
133 modules
: [path
.resolve(__dirname
), 'node_modules'],
135 'proton-pass-extension': path
.resolve(__dirname
, 'src/'),
136 ...(BUILD_TARGET
=== 'safari'
137 ? /* exclude `webextension-polyfill` from safari build to avoid
138 * service-worker registration errors when worker fails. */
139 { 'webextension-polyfill': '@proton/pass/lib/globals/webextension-polyfill.stub.ts' }
141 /* friends don't let friends publish code with `eval` : but that didn't stop `ttag` */
142 ttag
$: path
.resolve(__dirname
, '../../node_modules/ttag/dist/ttag.min.js'),
147 cacheDirectory
: path
.resolve('./node_modules/.cache/webpack'),
149 defaultWebpack
: ['webpack/lib/'],
150 config
: [__filename
],
154 filename
: '[name].js',
156 /** Some chunks need to be predictable in order for
157 * importScripts to work properly in the context of
158 * chromium builds (eg crypto lazy loaded modules) */
159 chunkFilename
: ({ chunk
: { name
, id
} }) => {
161 if (id
=== ARGON2_CHUNK_NAME
) return 'chunk.crypto-argon2.js';
162 return 'chunk.[contenthash:8].js';
165 return 'chunk.[name].js';
167 path
: path
.resolve(__dirname
, 'dist'),
169 assetModuleFilename
: 'assets/[hash][ext][query]',
173 new webpack
.EnvironmentPlugin({ NODE_ENV
: ENV
}),
174 new webpack
.DefinePlugin({
175 BUILD_TARGET
: JSON
.stringify(BUILD_TARGET
),
176 DESKTOP_BUILD
: false,
177 ENV
: JSON
.stringify(ENV
),
178 EXTENSION_BUILD
: true,
179 OFFLINE_SUPPORTED
: false,
186 ? getManifestVersion()
187 : webpack
.DefinePlugin
.runtimeValue(getManifestVersion
, true),
190 extensions
: ['js', 'ts'],
191 overrideConfigFile
: path
.resolve(__dirname
, '.eslintrc.js'),
193 new MiniCssExtractPlugin({
194 filename
: 'styles/[name].css',
203 const data
= content
.toString('utf-8');
204 const manifest
= JSON
.parse(data
);
206 if (PUBLIC_KEY
) manifest
.key
= PUBLIC_KEY
;
208 /* add the appropriate CSP policies for the development build to connect
209 * to unsecure ws:// protocols without firefox trying to upgrade it to
210 * wss:// - currently the redux cli tools do not support https */
211 if (ENV
!== 'production' && BUILD_TARGET
=== 'firefox') {
212 manifest
.content_security_policy
= {
214 "connect-src 'self' https: ws:; script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; ",
218 /* sanitize manifest when building for production */
219 if (CLEAN_MANIFEST
) {
220 switch (BUILD_TARGET
) {
222 manifest
.content_scripts
[1].matches
= [
223 'https://account.proton.me/*',
224 'https://pass.proton.me/*',
228 manifest
.externally_connectable
.matches
= [
229 'https://account.proton.me/*',
230 'https://pass.proton.me/*',
234 manifest
.content_scripts
[1].matches
= ['https://account.proton.me/*'];
235 manifest
.externally_connectable
.matches
= [
236 'https://account.proton.me/*',
237 'https://pass.proton.me/*',
243 return Buffer
.from(JSON
.stringify(manifest
, null, 2), 'utf-8');
248 ...(WEBPACK_CIRCULAR_DEPS
250 new CircularDependencyPlugin({
251 exclude
: /node_modules/,
252 include
: /(packages\/pass|applications\/pass-extension)/,
254 allowAsyncCycles
: false,
261 new ProtonIconsTreeShakePlugin({
262 entries
: ['dropdown', 'notification', 'onboarding', 'popup', 'settings'],
263 excludeMimeIcons
: true,
269 asyncWebAssembly
: true,