mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2026-07-22 02:56:57 -04:00
> [!IMPORTANT] > **Draft / do not merge yet.** Depends on [infra#2188](https://github.com/compiler-explorer/infra/pull/2188) being merged **and deployed** first. Until then, this `webpackJsHack` constant is still the only escape hatch for the source-map hash collisions, so it must stay. There's no deadline on this PR — it's pure cleanup that can wait indefinitely. ## What Removes the `webpackJsHack` `.vNN.` infix from the static asset filenames (`[name].vNN.[contenthash].js` → `[name].[contenthash].js`, and likewise for `.css` and worker files), along with its now-misleading "Hack alert" comment. ## Why This constant was a **manual escape hatch**. The CDN serves static files with a one-year immutable cache and verifies (in CI, `ce ... builds check_hashes`) that a given filename never changes content. Source maps are named after their *asset's* content hash (`<asset>.map`), so a toolchain bump that regenerated a map for byte-identical JS/CSS reused the same `.map` filename with different bytes → the check failed, and we manually bumped this constant to rename every asset and dodge the collision. [infra#2188](https://github.com/compiler-explorer/infra/pull/2188) fixes the root cause: source maps are debug-only metadata, not code, so they're now exempt from the immutable-hash check, overwritten on deploy, and served `no-cache` (so debugging always gets the current map). With that deployed, this constant can never need bumping again. ## The comment was true once — on webpack 4 — but has been obsolete since the webpack 5 upgrade The "Hack alert" comment claims the bumps are needed for *"webpack changes that affect how minification is done, even though that's supposed not to matter."* That was accurate when it was written, but stopped being true years ago: | When | webpack | What | Minification claim valid? | |------|---------|------|---------------------------| | 2020-04-18 (`67575197f`, *"Add a hacky fix for the cannot upload over identical file"*) | **4.42.1** | Comment + manual version introduced; template changed `[chunkhash]` → `v2.[contenthash]` | **Yes** | | 2021-12-21 (`7c20613c5`, *"Move to using webpack5 (#3197)"*) | **5.65.0** | Upgrade to webpack 5 | **No — obsolete from here on** | - **On webpack 4** there was no `realContentHash`; JS hashes were computed from the chunk's module content *before* Terser ran. So a webpack/minifier change that altered the *minified* output left the hash unchanged → same filename, different bytes → "cannot upload over identical file". The manual version was a genuine fix, and the comment correctly described pre-minification hashing. - **Since webpack 5** (`realContentHash: true`, default-on in production, landed in 5.8) every `[contenthash]` is recomputed from the **final, post-minification** bytes. A minifier change now produces a *new filename* on its own — it can no longer collide. So the minification rationale has been dead for ~3.5 years. That means essentially every version bump from the webpack-5 era onward (the `v2 → … → v69` march) was a **source-map** collision misattributed to minification — the source map being the one asset whose name still isn't a hash of its own post-processed content. That is exactly what infra#2188 addresses. ### Net - A real code change or a minifier change → different post-minification bytes → different `[contenthash]` → new filename. Handled correctly by content-addressing; no hack needed. - The only same-name-different-bytes case was source maps → fixed by infra#2188. - So removing `webpackJsHack` (and its misleading comment) is safe once infra#2188 is deployed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: mattgodbolt-molty <mattgodbolt-molty@users.noreply.github.com>
212 lines
6.9 KiB
TypeScript
212 lines
6.9 KiB
TypeScript
// Copyright (c) 2020, Compiler Explorer Authors
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import {fileURLToPath} from 'node:url';
|
|
|
|
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
|
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
|
|
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
|
import MonacoEditorWebpackPlugin from 'monaco-editor-webpack-plugin';
|
|
import TerserPlugin from 'terser-webpack-plugin';
|
|
import Webpack from 'webpack';
|
|
import {WebpackManifestPlugin} from 'webpack-manifest-plugin';
|
|
|
|
const __dirname = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
const isDev = process.env.NODE_ENV !== 'production';
|
|
|
|
function log(message: string) {
|
|
console.log('webpack: ', message);
|
|
}
|
|
|
|
log(`compiling for ${isDev ? 'development' : 'production'}.`);
|
|
// Memory limits us in most cases, so restrict parallelism to keep us in a sane amount of RAM
|
|
const parallelism = Math.floor(os.totalmem() / (4 * 1024 * 1024 * 1024)) + 1;
|
|
log(`Limiting parallelism to ${parallelism}`);
|
|
|
|
const manifestPath = path.resolve(__dirname, 'out', 'dist');
|
|
const staticPath = path.resolve(__dirname, 'out', 'webpack', 'static');
|
|
const hasGit = fs.existsSync(path.resolve(__dirname, '.git'));
|
|
|
|
const plugins: Webpack.WebpackPluginInstance[] = [
|
|
new MonacoEditorWebpackPlugin({
|
|
languages: [
|
|
'cpp',
|
|
'go',
|
|
'pascal',
|
|
'perl',
|
|
'python',
|
|
'rust',
|
|
'swift',
|
|
'java',
|
|
'julia',
|
|
'kotlin',
|
|
'scala',
|
|
'ruby',
|
|
'csharp',
|
|
'fsharp',
|
|
'vb',
|
|
'dart',
|
|
'typescript',
|
|
'solidity',
|
|
'scheme',
|
|
'objective-c',
|
|
'elixir',
|
|
'clojure',
|
|
],
|
|
filename: isDev ? '[name].worker.js' : '[name].worker.[contenthash].js',
|
|
}),
|
|
new MiniCssExtractPlugin({
|
|
filename: isDev ? '[name].css' : '[name].[contenthash].css',
|
|
}),
|
|
new WebpackManifestPlugin({
|
|
fileName: path.resolve(manifestPath, 'manifest.json'),
|
|
publicPath: '',
|
|
}),
|
|
new Webpack.DefinePlugin({
|
|
'window.PRODUCTION': JSON.stringify(!isDev),
|
|
}),
|
|
new CopyWebpackPlugin({
|
|
patterns: [{from: './public', to: staticPath}],
|
|
}),
|
|
];
|
|
|
|
if (isDev) {
|
|
plugins.push(new Webpack.HotModuleReplacementPlugin());
|
|
}
|
|
|
|
export default {
|
|
mode: isDev ? 'development' : 'production',
|
|
entry: {
|
|
main: './static/main.ts',
|
|
noscript: './static/noscript.ts',
|
|
},
|
|
output: {
|
|
filename: isDev ? '[name].js' : '[name].[contenthash].js',
|
|
path: staticPath,
|
|
},
|
|
cache: {
|
|
type: 'filesystem',
|
|
buildDependencies: {
|
|
config: [
|
|
fileURLToPath(import.meta.url),
|
|
// Depend on the package.json to force a recache if something changes:
|
|
// this is only because something in Monaco upsets the cache if its version changes
|
|
path.resolve(__dirname, 'package.json'),
|
|
],
|
|
},
|
|
},
|
|
resolve: {
|
|
alias: {
|
|
'monaco-editor$': 'monaco-editor/esm/vs/editor/editor.api',
|
|
},
|
|
fallback: {
|
|
path: 'path-browserify',
|
|
},
|
|
modules: ['./static', './node_modules'],
|
|
extensions: ['.ts', '.js'],
|
|
extensionAlias: {
|
|
'.js': ['.ts', '.js'],
|
|
'.mjs': ['.mts', '.mjs'],
|
|
},
|
|
},
|
|
stats: 'normal',
|
|
devtool: 'source-map',
|
|
optimization: {
|
|
runtimeChunk: 'single',
|
|
splitChunks: {
|
|
cacheGroups: {
|
|
vendors: {
|
|
test: /[/\\]node_modules[/\\]/,
|
|
name: 'vendor',
|
|
chunks: 'all',
|
|
priority: -10,
|
|
},
|
|
},
|
|
},
|
|
moduleIds: 'deterministic',
|
|
minimizer: [
|
|
new CssMinimizerPlugin(),
|
|
new TerserPlugin({
|
|
parallel: true,
|
|
terserOptions: {
|
|
ecma: 5,
|
|
sourceMap: true,
|
|
},
|
|
}),
|
|
],
|
|
},
|
|
parallelism: parallelism,
|
|
module: {
|
|
rules: [
|
|
{
|
|
test: /\.s?css$/,
|
|
use: [
|
|
{
|
|
loader: MiniCssExtractPlugin.loader,
|
|
options: {
|
|
publicPath: './',
|
|
},
|
|
},
|
|
'css-loader',
|
|
{
|
|
loader: 'sass-loader',
|
|
options: {
|
|
sassOptions: {
|
|
fatalDeprecations: ['import'],
|
|
},
|
|
},
|
|
},
|
|
{
|
|
loader: path.resolve(__dirname, 'etc/webpack/replace-golden-layout-imports.js'),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
|
|
type: 'asset',
|
|
parser: {dataUrlCondition: {maxSize: 8192}},
|
|
},
|
|
{
|
|
test: /\.pug$/,
|
|
loader: path.resolve(__dirname, 'etc/webpack/parsed-pug-loader.js'),
|
|
options: {
|
|
useGit: hasGit,
|
|
},
|
|
},
|
|
{
|
|
test: /\.ts$/,
|
|
loader: 'ts-loader',
|
|
},
|
|
{
|
|
test: /\.js$/,
|
|
loader: 'source-map-loader',
|
|
},
|
|
],
|
|
},
|
|
plugins: plugins,
|
|
} satisfies Webpack.Configuration;
|