sourcegraph/client/web/webpack.bazel.config.js
Jason Bedard 10aefc4bb7
bazel: add bazel build,tests for client/* (#46193)
Webpack bundles compile but need further testing. Jest + mocha tests
compile but are marked as `manual` until further work is done to get them
passing. The four jest tests are green and enabled now, though.

## Test plan

`bazel build //client/...` and `bazel test //client/...`
2023-02-28 20:46:03 -08:00

297 lines
10 KiB
JavaScript

// @ts-check
const path = require('path')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
const SentryWebpackPlugin = require('@sentry/webpack-plugin')
const CompressionPlugin = require('compression-webpack-plugin')
const CssMinimizerWebpackPlugin = require('css-minimizer-webpack-plugin')
const mapValues = require('lodash/mapValues')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const webpack = require('webpack')
const { WebpackManifestPlugin } = require('webpack-manifest-plugin')
const { StatsWriterPlugin } = require('webpack-stats-plugin')
const {
ROOT_PATH,
STATIC_ASSETS_PATH,
getBabelLoader,
getCacheConfig,
getMonacoWebpackPlugin,
getBazelCSSLoaders: getCSSLoaders,
getTerserPlugin,
getProvidePlugin,
getCSSModulesLoader,
getMonacoCSSRule,
getMonacoTTFRule,
getBasicCSSLoader,
getStatoscopePlugin,
} = require('@sourcegraph/build-config')
const { IS_PRODUCTION, IS_DEVELOPMENT, ENVIRONMENT_CONFIG, writeIndexHTMLPlugin } = require('./dev/utils')
// const { isHotReloadEnabled } = require('./src/integration/environment')
const {
NODE_ENV,
CI: IS_CI,
INTEGRATION_TESTS,
ENTERPRISE,
EMBED_DEVELOPMENT,
ENABLE_SENTRY,
ENABLE_OPEN_TELEMETRY,
SOURCEGRAPH_API_URL,
WEBPACK_BUNDLE_ANALYZER,
WEBPACK_EXPORT_STATS_FILENAME,
WEBPACK_SERVE_INDEX,
WEBPACK_STATS_NAME,
WEBPACK_USE_NAMED_CHUNKS,
WEBPACK_DEVELOPMENT_DEVTOOL,
SENTRY_UPLOAD_SOURCE_MAPS,
COMMIT_SHA,
VERSION,
SENTRY_DOT_COM_AUTH_TOKEN,
SENTRY_ORGANIZATION,
SENTRY_PROJECT,
} = ENVIRONMENT_CONFIG
const IS_PERSISTENT_CACHE_ENABLED = IS_DEVELOPMENT && !IS_CI
const IS_EMBED_ENTRY_POINT_ENABLED = ENTERPRISE && (IS_PRODUCTION || (IS_DEVELOPMENT && EMBED_DEVELOPMENT))
const RUNTIME_ENV_VARIABLES = {
NODE_ENV,
ENABLE_SENTRY,
ENABLE_OPEN_TELEMETRY,
INTEGRATION_TESTS,
COMMIT_SHA,
...(WEBPACK_SERVE_INDEX && { SOURCEGRAPH_API_URL }),
}
const hotLoadablePaths = ['branded', 'shared', 'web', 'wildcard'].map(workspace =>
path.resolve(ROOT_PATH, 'client', workspace, 'src')
)
// Third-party npm dependencies that contain jsx syntax
const NPM_JSX = ['react-visibility-sensor/visibility-sensor.js']
const enterpriseDirectory = path.resolve(__dirname, 'src', 'enterprise')
const styleLoader = IS_DEVELOPMENT ? 'style-loader' : MiniCssExtractPlugin.loader
// Used to ensure that we include all initial chunks into the Webpack manifest.
const initialChunkNames = {
react: 'react',
opentelemetry: 'opentelemetry',
}
/** @type {import('webpack').Configuration} */
const config = {
context: process.cwd(),
mode: IS_PRODUCTION ? 'production' : 'development',
stats: {
// Minimize logging in case if Webpack is used along with multiple other services.
// Use `normal` output preset in case of running standalone web server.
preset: WEBPACK_SERVE_INDEX || IS_PRODUCTION ? 'normal' : 'errors-warnings',
errorDetails: true,
timings: true,
},
infrastructureLogging: {
// Controls webpack-dev-server logging level.
level: 'warn',
},
target: 'browserslist',
// Use cache only in `development` mode to speed up production build.
cache: IS_PERSISTENT_CACHE_ENABLED && getCacheConfig({ invalidateCacheFiles: [] }),
optimization: {
minimize: IS_PRODUCTION,
minimizer: [getTerserPlugin(), new CssMinimizerWebpackPlugin()],
splitChunks: {
cacheGroups: {
[initialChunkNames.react]: {
test: /[/\\]node_modules.*[/\\](react|react-dom)[/\\]/,
name: initialChunkNames.react,
chunks: 'all',
},
[initialChunkNames.opentelemetry]: {
test: /[/\\]node_modules.*[/\\](@opentelemetry)[/\\]/,
name: initialChunkNames.opentelemetry,
chunks: 'all',
},
},
},
...(IS_DEVELOPMENT && {
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
}),
},
// entry: { ... SET BY BAZEL RULE ... }
// TODO(bazel): why is this sest by webpack_bundle() but not webpack_dev_server()?
entry: {
app: './client/web/src/main.js',
},
devServer: {
port: 8080,
},
output: {
// path: STATIC_ASSETS_PATH,
// Do not [hash] for development -- see https://github.com/webpack/webpack-dev-server/issues/377#issuecomment-241258405
// Note: [name] will vary depending on the Webpack chunk. If specified, it will use a provided chunk name, otherwise it will fallback to a deterministic id.
filename:
IS_PRODUCTION && !WEBPACK_USE_NAMED_CHUNKS
? 'scripts/[name].[contenthash].bundle.js'
: 'scripts/[name].bundle.js',
chunkFilename:
IS_PRODUCTION && !WEBPACK_USE_NAMED_CHUNKS ? 'scripts/[name]-[contenthash].chunk.js' : 'scripts/[name].chunk.js',
publicPath: '/.assets/',
globalObject: 'self',
pathinfo: false,
},
// Inline source maps for integration tests to preserve readable stack traces.
// See related issue here: https://github.com/puppeteer/puppeteer/issues/985
devtool: IS_PRODUCTION ? (INTEGRATION_TESTS ? 'inline-source-map' : 'source-map') : WEBPACK_DEVELOPMENT_DEVTOOL,
plugins: [
// Change scss imports to the pre-compiled css files
new webpack.NormalModuleReplacementPlugin(/.*\.scss$/, resource => {
resource.request = resource.request.replace(/\.scss$/, '.css')
}),
// TODO(bazel): implement *.worker.ts support
new webpack.IgnorePlugin({
resourceRegExp: /.*\.worker\.ts/,
}),
new webpack.DefinePlugin({
'process.env': mapValues(RUNTIME_ENV_VARIABLES, JSON.stringify),
}),
// TODO(bazel): why does the provide plugin crash?
// getProvidePlugin(),
new MiniCssExtractPlugin({
// Do not [hash] for development -- see https://github.com/webpack/webpack-dev-server/issues/377#issuecomment-241258405
filename:
IS_PRODUCTION && !WEBPACK_USE_NAMED_CHUNKS
? 'styles/[name].[contenthash].bundle.css'
: 'styles/[name].bundle.css',
}),
// getMonacoWebpackPlugin(),
new WebpackManifestPlugin({
writeToFileEmit: false,
fileName: 'webpack.manifest.json',
seed: {
environment: NODE_ENV,
},
// Only output files that are required to run the application.
filter: ({ isInitial, name }) =>
isInitial || Object.values(initialChunkNames).some(initialChunkName => name?.includes(initialChunkName)),
}),
...(WEBPACK_SERVE_INDEX && IS_PRODUCTION ? [writeIndexHTMLPlugin] : []),
WEBPACK_BUNDLE_ANALYZER && getStatoscopePlugin(WEBPACK_STATS_NAME),
// isHotReloadEnabled && new ReactRefreshWebpackPlugin({ overlay: false }),
IS_PRODUCTION &&
new CompressionPlugin({
filename: '[path][base].gz',
algorithm: 'gzip',
test: /\.(js|css|svg)$/,
compressionOptions: {
/** Maximum compression level for Gzip */
level: 9,
},
}),
IS_PRODUCTION &&
new CompressionPlugin({
filename: '[path][base].br',
algorithm: 'brotliCompress',
test: /\.(js|css|svg)$/,
compressionOptions: {
/** Maximum compression level for Brotli */
level: 11,
},
/**
* We get little/no benefits from compressing files that are already under this size.
* We can fall back to dynamic gzip for these.
*/
threshold: 10240,
}),
VERSION &&
SENTRY_UPLOAD_SOURCE_MAPS &&
new SentryWebpackPlugin({
silent: true,
org: SENTRY_ORGANIZATION,
project: SENTRY_PROJECT,
authToken: SENTRY_DOT_COM_AUTH_TOKEN,
release: `frontend@${VERSION}`,
include: path.join(STATIC_ASSETS_PATH, 'scripts', '*.map'),
}),
WEBPACK_EXPORT_STATS_FILENAME &&
new StatsWriterPlugin({
filename: WEBPACK_EXPORT_STATS_FILENAME,
stats: {
all: false, // disable all the stats
hash: true, // compilation hash
entrypoints: true,
chunks: true,
chunkModules: true, // modules
ids: true, // IDs of modules and chunks (webpack 5)
cachedAssets: true, // information about the cached assets (webpack 5)
nestedModules: true, // concatenated modules
usedExports: true,
assets: true,
chunkOrigins: true, // chunks origins stats (to find out which modules require a chunk)
timings: true, // modules timing information
performance: true, // info about oversized assets
},
}),
].filter(Boolean),
resolve: {
extensions: ['.mjs', '.jsx', '.js', '.json'],
mainFields: ['es2015', 'module', 'browser', 'main'],
fallback: {
path: require.resolve('path-browserify'),
punycode: require.resolve('punycode'),
util: require.resolve('util'),
},
alias: {
// react-visibility-sensor's main field points to a UMD bundle instead of ESM
// https://github.com/joshwnj/react-visibility-sensor/issues/148
'react-visibility-sensor': require.resolve('react-visibility-sensor').replace('/dist/', '/'),
},
},
module: {
rules: [
// Some third-party deps need jsx transpiled still
{
test: new RegExp('node_modules/' + NPM_JSX.join('|')),
use: [{ loader: require.resolve('babel-loader') }],
},
{
test: /\.m?js$/,
type: 'javascript/auto',
resolve: {
// Allow importing without file extensions
// https://webpack.js.org/configuration/module/#resolvefullyspecified
fullySpecified: false,
},
},
// TODO(bazel): why is this required when it is supposedly enabled by default?
{
test: /\.json$/,
type: 'json',
},
{
test: /\.css$/,
// CSS Modules loaders are only applied when the file is explicitly named as CSS module stylesheet using the extension `.module.scss`.
include: /\.module\.css$/,
use: getCSSLoaders(styleLoader, getCSSModulesLoader({ sourceMap: IS_DEVELOPMENT })),
},
{
test: /\.css$/,
exclude: /\.module\.css$/,
use: getCSSLoaders(styleLoader, getBasicCSSLoader()),
},
getMonacoTTFRule(),
{ test: /\.ya?ml$/, type: 'asset/source' },
{ test: /\.(png|woff2)$/, type: 'asset/resource' },
],
},
}
module.exports = config