| 1 | 'use strict'; |
| 2 | |
| 3 | const {resolve, isAbsolute, relative} = require('path'); |
| 4 | const Webpack = require('webpack'); |
| 5 | const TerserPlugin = require('terser-webpack-plugin'); |
| 6 | const {GITHUB_URL, getVersionString} = require('./utils'); |
| 7 | const {resolveFeatureFlags} = require('react-devtools-shared/buildUtils'); |
| 8 | const SourceMapIgnoreListPlugin = require('react-devtools-shared/SourceMapIgnoreListPlugin'); |
| 9 | const {StatsWriterPlugin} = require('webpack-stats-plugin'); |
| 10 | |
| 11 | const NODE_ENV = process.env.NODE_ENV; |
| 12 | if (!NODE_ENV) { |
| 13 | console.error('NODE_ENV not set'); |
| 14 | process.exit(1); |
| 15 | } |
| 16 | |
| 17 | const builtModulesDir = resolve( |
| 18 | __dirname, |
| 19 | '..', |
| 20 | '..', |
| 21 | 'build', |
| 22 | 'oss-experimental', |
| 23 | ); |
| 24 | |
| 25 | const __DEV__ = NODE_ENV === 'development'; |
| 26 | |
| 27 | const DEVTOOLS_VERSION = getVersionString(process.env.DEVTOOLS_VERSION); |
| 28 | |
| 29 | const EDITOR_URL = process.env.EDITOR_URL || null; |
| 30 | const LOGGING_URL = process.env.LOGGING_URL || null; |
| 31 | |
| 32 | const IS_CHROME = process.env.IS_CHROME === 'true'; |
| 33 | const IS_FIREFOX = process.env.IS_FIREFOX === 'true'; |
| 34 | const IS_EDGE = process.env.IS_EDGE === 'true'; |
| 35 | const IS_INTERNAL_VERSION = process.env.FEATURE_FLAG_TARGET === 'extension-fb'; |
| 36 | |
| 37 | const featureFlagTarget = process.env.FEATURE_FLAG_TARGET || 'extension-oss'; |
| 38 | |
| 39 | let statsFileName = `webpack-stats.${featureFlagTarget}.${__DEV__ ? 'development' : 'production'}`; |
| 40 | if (IS_CHROME) { |
| 41 | statsFileName += `.chrome`; |
| 42 | } |
| 43 | if (IS_FIREFOX) { |
| 44 | statsFileName += `.firefox`; |
| 45 | } |
| 46 | if (IS_EDGE) { |
| 47 | statsFileName += `.edge`; |
| 48 | } |
| 49 | statsFileName += '.json'; |
| 50 | |
| 51 | const babelOptions = { |
| 52 | configFile: resolve( |
| 53 | __dirname, |
| 54 | '..', |
| 55 | 'react-devtools-shared', |
| 56 | 'babel.config.js', |
| 57 | ), |
| 58 | }; |
| 59 | |
| 60 | module.exports = { |
| 61 | mode: __DEV__ ? 'development' : 'production', |
| 62 | devtool: false, |
| 63 | entry: { |
| 64 | backend: './src/backend.js', |
| 65 | background: './src/background/index.js', |
| 66 | backendManager: './src/contentScripts/backendManager.js', |
| 67 | fallbackEvalContext: './src/contentScripts/fallbackEvalContext.js', |
| 68 | fileFetcher: './src/contentScripts/fileFetcher.js', |
| 69 | main: './src/main/index.js', |
| 70 | panel: './src/panel.js', |
| 71 | proxy: './src/contentScripts/proxy.js', |
| 72 | prepareInjection: './src/contentScripts/prepareInjection.js', |
| 73 | installHook: './src/contentScripts/installHook.js', |
| 74 | hookSettingsInjector: './src/contentScripts/hookSettingsInjector.js', |
| 75 | }, |
| 76 | output: { |
| 77 | path: __dirname + '/build', |
| 78 | publicPath: '/build/', |
| 79 | filename: chunkData => { |
| 80 | switch (chunkData.chunk.name) { |
| 81 | case 'backend': |
| 82 | return 'react_devtools_backend_compact.js'; |
| 83 | default: |
| 84 | return '[name].js'; |
| 85 | } |
| 86 | }, |
| 87 | chunkFilename: '[name].chunk.js', |
| 88 | }, |
| 89 | node: { |
| 90 | global: false, |
| 91 | }, |
| 92 | resolve: { |
| 93 | alias: { |
| 94 | react: resolve(builtModulesDir, 'react'), |
| 95 | 'react-debug-tools': resolve(builtModulesDir, 'react-debug-tools'), |
| 96 | 'react-devtools-feature-flags': resolveFeatureFlags(featureFlagTarget), |
| 97 | 'react-dom/client': resolve(builtModulesDir, 'react-dom/client'), |
| 98 | 'react-dom': resolve(builtModulesDir, 'react-dom'), |
| 99 | 'react-is': resolve(builtModulesDir, 'react-is'), |
| 100 | scheduler: resolve(builtModulesDir, 'scheduler'), |
| 101 | }, |
| 102 | }, |
| 103 | optimization: { |
| 104 | minimize: !__DEV__, |
| 105 | minimizer: [ |
| 106 | new TerserPlugin({ |
| 107 | terserOptions: { |
| 108 | compress: { |
| 109 | unused: true, |
| 110 | dead_code: true, |
| 111 | }, |
| 112 | mangle: { |
| 113 | keep_fnames: true, |
| 114 | }, |
| 115 | format: { |
| 116 | comments: false, |
| 117 | }, |
| 118 | }, |
| 119 | extractComments: false, |
| 120 | }), |
| 121 | ], |
| 122 | }, |
| 123 | plugins: [ |
| 124 | new Webpack.ProvidePlugin({ |
| 125 | process: 'process/browser', |
| 126 | }), |
| 127 | new Webpack.DefinePlugin({ |
| 128 | __DEV__, |
| 129 | __EXPERIMENTAL__: true, |
| 130 | __EXTENSION__: true, |
| 131 | __PROFILE__: false, |
| 132 | __TEST__: NODE_ENV === 'test', |
| 133 | __IS_CHROME__: IS_CHROME, |
| 134 | __IS_FIREFOX__: IS_FIREFOX, |
| 135 | __IS_EDGE__: IS_EDGE, |
| 136 | __IS_NATIVE__: false, |
| 137 | __IS_INTERNAL_VERSION__: IS_INTERNAL_VERSION, |
| 138 | 'process.env.DEVTOOLS_PACKAGE': `"react-devtools-extensions"`, |
| 139 | 'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`, |
| 140 | 'process.env.EDITOR_URL': EDITOR_URL != null ? `"${EDITOR_URL}"` : null, |
| 141 | 'process.env.GITHUB_URL': `"${GITHUB_URL}"`, |
| 142 | 'process.env.LOGGING_URL': `"${LOGGING_URL}"`, |
| 143 | 'process.env.NODE_ENV': `"${NODE_ENV}"`, |
| 144 | }), |
| 145 | new Webpack.SourceMapDevToolPlugin({ |
| 146 | filename: '[file].map', |
| 147 | include: ['installHook.js', 'react_devtools_backend_compact.js'], |
| 148 | noSources: !__DEV__, |
| 149 | // https://github.com/webpack/webpack/issues/3603#issuecomment-1743147144 |
| 150 | moduleFilenameTemplate(info) { |
| 151 | const {absoluteResourcePath, namespace, resourcePath} = info; |
| 152 | |
| 153 | if (isAbsolute(absoluteResourcePath)) { |
| 154 | return relative(__dirname + '/build', absoluteResourcePath); |
| 155 | } |
| 156 | |
| 157 | // Mimic Webpack's default behavior: |
| 158 | return `webpack://${namespace}/${resourcePath}`; |
| 159 | }, |
| 160 | }), |
| 161 | new SourceMapIgnoreListPlugin({ |
| 162 | shouldIgnoreSource: (assetName, _source) => { |
| 163 | if (__DEV__) { |
| 164 | // Don't ignore list anything in DEV build for debugging purposes |
| 165 | return false; |
| 166 | } |
| 167 | |
| 168 | const contentScriptNamesToIgnoreList = [ |
| 169 | 'react_devtools_backend_compact', |
| 170 | // This is where we override console |
| 171 | 'installHook', |
| 172 | ]; |
| 173 | |
| 174 | return contentScriptNamesToIgnoreList.some(ignoreListName => |
| 175 | assetName.startsWith(ignoreListName), |
| 176 | ); |
| 177 | }, |
| 178 | }), |
| 179 | { |
| 180 | apply(compiler) { |
| 181 | if (__DEV__) { |
| 182 | return; |
| 183 | } |
| 184 | |
| 185 | const {RawSource} = compiler.webpack.sources; |
| 186 | compiler.hooks.compilation.tap( |
| 187 | 'CustomContentForHookScriptPlugin', |
| 188 | compilation => { |
| 189 | compilation.hooks.processAssets.tap( |
| 190 | { |
| 191 | name: 'CustomContentForHookScriptPlugin', |
| 192 | stage: Webpack.Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, |
| 193 | additionalAssets: true, |
| 194 | }, |
| 195 | assets => { |
| 196 | // eslint-disable-next-line no-for-of-loops/no-for-of-loops |
| 197 | for (const [name, asset] of Object.entries(assets)) { |
| 198 | if (name !== 'installHook.js.map') { |
| 199 | continue; |
| 200 | } |
| 201 | |
| 202 | const mapContent = asset.source().toString(); |
| 203 | if (!mapContent) { |
| 204 | continue; |
| 205 | } |
| 206 | |
| 207 | const map = JSON.parse(mapContent); |
| 208 | map.sourcesContent = map.sources.map(sourceName => { |
| 209 | if (!sourceName.endsWith('/hook.js')) { |
| 210 | return null; |
| 211 | } |
| 212 | |
| 213 | return ( |
| 214 | '/*\n' + |
| 215 | ' * This script is from React DevTools.\n' + |
| 216 | " * You're likely here because you thought it sent an error or warning to the console.\n" + |
| 217 | ' * React DevTools patches the console to support features like appending component stacks, \n' + |
| 218 | ' * so this file appears as a source. However, the console call actually came from another script.\n' + |
| 219 | " * To remove this script from stack traces, open your browser's DevTools (to enable source mapping) before these console calls happen.\n" + |
| 220 | ' */' |
| 221 | ); |
| 222 | }); |
| 223 | |
| 224 | compilation.updateAsset( |
| 225 | name, |
| 226 | new RawSource(JSON.stringify(map)), |
| 227 | ); |
| 228 | } |
| 229 | }, |
| 230 | ); |
| 231 | }, |
| 232 | ); |
| 233 | }, |
| 234 | }, |
| 235 | new StatsWriterPlugin({ |
| 236 | stats: 'verbose', |
| 237 | filename: statsFileName, |
| 238 | }), |
| 239 | ], |
| 240 | module: { |
| 241 | defaultRules: [ |
| 242 | { |
| 243 | type: 'javascript/auto', |
| 244 | resolve: {}, |
| 245 | }, |
| 246 | { |
| 247 | test: /\.json$/i, |
| 248 | type: 'json', |
| 249 | }, |
| 250 | ], |
| 251 | |
| 252 | rules: [ |
| 253 | { |
| 254 | test: /\.worker\.js$/, |
| 255 | use: [ |
| 256 | { |
| 257 | loader: 'workerize-loader', |
| 258 | options: { |
| 259 | inline: false, |
| 260 | name: '[name]', |
| 261 | }, |
| 262 | }, |
| 263 | { |
| 264 | loader: 'babel-loader', |
| 265 | options: babelOptions, |
| 266 | }, |
| 267 | ], |
| 268 | }, |
| 269 | { |
| 270 | test: /\.js$/, |
| 271 | loader: 'babel-loader', |
| 272 | options: babelOptions, |
| 273 | }, |
| 274 | { |
| 275 | test: /\.css$/, |
| 276 | use: [ |
| 277 | { |
| 278 | loader: 'style-loader', |
| 279 | }, |
| 280 | { |
| 281 | loader: 'css-loader', |
| 282 | options: { |
| 283 | sourceMap: __DEV__, |
| 284 | modules: true, |
| 285 | localIdentName: '[local]___[hash:base64:5]', |
| 286 | }, |
| 287 | }, |
| 288 | ], |
| 289 | }, |
| 290 | ], |
| 291 | }, |
| 292 | }; |