grafana/scripts/webpack/webpack.dev.ts
Jack Westbrook 0a10a5d133
Build: Refactor Webpack config to TS (#121181)
* build(webpack): add tsconfig.json for Node strip-types compliance

* build(webpack): extract shared esbuild options to esbuild.ts

* build(webpack): add package.json to declare ESM module type

* build(webpack): convert sass.rule to TypeScript

* build(webpack): convert CorsWorkerPlugin to TypeScript

* build(webpack): convert FeatureFlaggedSriPlugin to TypeScript

* build(webpack): convert webpack.common to TypeScript, add theme entries

* build(webpack): convert webpack.dev to TypeScript, remove esbuild duplication

* build(webpack): convert webpack.prod to TypeScript, remove esbuild duplication

* build(webpack): fix TypeScript types in webpack.prod transform callback

* build(webpack): convert webpack.stats to TypeScript

* build(webpack): update scripts to use TypeScript webpack configs

* build(webpack): simplify env-util to use import.meta.dirname directly

* build(webpack): tidy up plugins

* build(webpack): move rules for ts and sass into single module

* build(webpack): consolidate shared config into common, move splitChunks to prod

- Move MiniCssExtractPlugin, esbuildRule and sassRule into common so both
  dev and prod configs share them without duplication
- Move splitChunks/runtimeChunk optimisation to webpack.prod only (not
  needed in dev)
- Use require() for SubresourceIntegrityPlugin to work around broken ESM
  build (waysact/webpack-subresource-integrity#236)
- Refactor conditional plugin logic in dev from ternary to if-blocks

* build(webpack): remove dead import and clarify webpack destructure pattern

- Remove unused MiniCssExtractPlugin import from webpack.prod (moved to common)
- Add comment explaining why DefinePlugin/EnvironmentPlugin are destructured
  from the default webpack import rather than using named ESM imports

* style(webpack): reorder consts

* chore(env-util): fix up env-util and webpack configs so tests continue to run

* refactor(env-util): accept grafanaRoot param instead of relying on __dirname

Removes the global.__dirname mutation hack in webpack.common.ts by making
the grafana root path an explicit argument to getEnvConfig. Each caller
resolves its own root and passes it in, removing the implicit path-depth
contract and the CJS/ESM compatibility workaround.

* build(webpack): remove unused angular chunk group
2026-04-28 12:45:31 +02:00

147 lines
4.3 KiB
TypeScript

import { getPackagesSync } from '@manypkg/get-packages';
import ESLintPlugin from 'eslint-webpack-plugin';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import fs from 'node:fs';
import path from 'node:path';
import webpack, { type Configuration } from 'webpack';
import WebpackAssetsManifest from 'webpack-assets-manifest';
import LiveReloadPlugin from 'webpack-livereload-plugin';
import { merge } from 'webpack-merge';
import WebpackBar from 'webpackbar';
import common, { type Env } from './webpack.common.ts';
// webpack does not correctly export named ESM bindings — destructure from the default import
const { DefinePlugin, EnvironmentPlugin } = webpack;
// To speed up webpack and prevent unnecessary rebuilds we ignore decoupled packages
function getDecoupledPlugins(): string[] {
const { packages } = getPackagesSync(process.cwd());
return packages.filter((pkg) => pkg.dir.includes('plugins/datasource')).map((pkg) => `${pkg.dir}/**`);
}
// When linking scenes for development, resolve the path to the src directory for sourcemaps
function scenesModule(): string {
const scenesPath = path.resolve('./node_modules/@grafana/scenes');
try {
const status = fs.lstatSync(scenesPath);
if (status.isSymbolicLink()) {
console.log(`scenes is linked to local scenes repo`);
return path.resolve(scenesPath + '/src');
}
} catch (error) {
console.error(`Error checking scenes path: ${error instanceof Error ? error.message : String(error)}`);
}
return scenesPath;
}
const decoupledPlugins = getDecoupledPlugins();
export default (env: Env = {}) => {
const devConfig: Configuration = {
devtool: 'source-map',
mode: 'development',
// If we enabled watch option via CLI
watchOptions: {
ignored: ['/node_modules/', ...decoupledPlugins],
},
resolve: {
alias: {
// Packages linked for development need react to be resolved from the same location
react: path.resolve('./node_modules/react'),
// This is required to correctly resolve react-router-dom when linking with
// local version of @grafana/scenes
'react-router-dom': path.resolve('./node_modules/react-router-dom'),
'@grafana/scenes': scenesModule(),
},
},
// https://webpack.js.org/guides/build-performance/#output-without-path-info
output: {
pathinfo: false,
},
// https://webpack.js.org/guides/build-performance/#avoid-extra-optimization-steps
optimization: {
moduleIds: 'named',
runtimeChunk: true,
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
},
// enable persistent cache for faster cold starts
cache: {
type: 'filesystem',
name: 'grafana-default-development',
buildDependencies: {
config: [import.meta.filename],
},
},
plugins: [
new DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('development'),
},
}),
new WebpackAssetsManifest({
entrypoints: true,
integrity: true,
integrityHashes: ['sha384', 'sha512'],
publicPath: true,
output: env.react19 ? 'assets-manifest-react19.json' : 'assets-manifest.json',
}),
new WebpackBar({
color: '#eb7b18',
name: 'Grafana',
}),
],
stats: 'minimal',
};
if (Number(env.liveReload)) {
devConfig.plugins?.push(
new LiveReloadPlugin({
appendScriptTag: true,
useSourceHash: true,
hostname: 'localhost',
protocol: 'http',
port: 35750,
})
);
}
if (!Number(env.noTsCheck)) {
devConfig.plugins?.push(
new ForkTsCheckerWebpackPlugin({
async: true, // don't block webpack emit
typescript: {
mode: 'write-references',
memoryLimit: 8192,
diagnosticOptions: {
semantic: true,
syntactic: true,
},
},
})
);
}
if (!Number(env.noLint)) {
devConfig.plugins?.push(
new ESLintPlugin({
cache: true,
lintDirtyModulesOnly: true, // don't lint on start, only lint changed files
extensions: ['.ts', '.tsx'],
configType: 'flat',
failOnError: false,
})
);
}
return merge(common(env), devConfig);
};