From 32af00eff9eb8087b6cf2fe794f0a9a372d4e5f0 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Sat, 9 Nov 2024 17:59:24 +0100 Subject: [PATCH] feat(plugin-configs): replace webpack with rspack --- packages/grafana-plugin-configs/package.json | 9 +- .../grafana-plugin-configs/rspack.config.ts | 246 ++++++++++++++++++ 2 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 packages/grafana-plugin-configs/rspack.config.ts diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index 384e4c2d42c..9cd2097f6c4 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -8,19 +8,18 @@ }, "devDependencies": { "@grafana/tsconfig": "^2.0.0", + "@rspack/cli": "^1.0.0", + "@rspack/core": "^1.0.0", "@swc/core": "1.10.12", "@types/eslint": "9.6.1", - "@types/webpack-bundle-analyzer": "^4.7.0", - "copy-webpack-plugin": "12.0.2", "eslint": "9.19.0", "eslint-webpack-plugin": "4.2.0", "fork-ts-checker-webpack-plugin": "9.0.2", "glob": "11.0.1", "replace-in-file-webpack-plugin": "1.0.6", - "swc-loader": "0.2.6", + "rspack-plugin-virtual-module": "^0.1.13", "typescript": "5.7.3", - "webpack": "5.97.1", - "webpack-bundle-analyzer": "^4.10.2" + "webpack": "5.97.1" }, "packageManager": "yarn@4.6.0" } diff --git a/packages/grafana-plugin-configs/rspack.config.ts b/packages/grafana-plugin-configs/rspack.config.ts new file mode 100644 index 00000000000..cf49e542b6a --- /dev/null +++ b/packages/grafana-plugin-configs/rspack.config.ts @@ -0,0 +1,246 @@ +/* + * ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️ + * + * In order to extend the configuration follow the steps in + * https://grafana.com/developers/plugin-tools/get-started/set-up-development-environment#extend-the-webpack-config + */ + +import rspack, { type Configuration } from '@rspack/core'; +import ESLintPlugin from 'eslint-webpack-plugin'; +import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; +import path from 'path'; +// @ts-ignore +import ReplaceInFileWebpackPlugin from 'replace-in-file-webpack-plugin'; +import { RspackVirtualModulePlugin } from 'rspack-plugin-virtual-module'; + +import { DIST_DIR } from './constants'; +import { getEntries, getPackageJson, getPluginJson, hasLicense } from './utils'; + +const pluginJson = getPluginJson(); + +const virtualPublicPath = new RspackVirtualModulePlugin({ + 'grafana-public-path': ` +import amdMetaModule from 'amd-module'; + +__webpack_public_path__ = + amdMetaModule && amdMetaModule.uri + ? amdMetaModule.uri.slice(0, amdMetaModule.uri.lastIndexOf('/') + 1) + : 'public/plugins/${pluginJson.id}/'; +`, +}); + +const config = async (env: Record): Promise => { + const baseConfig: Configuration = { + context: process.cwd(), + + devtool: env.production ? 'source-map' : 'eval-source-map', + + entry: await getEntries(), + + externals: [ + // Required for dynamic publicPath resolution + { 'amd-module': 'module' }, + 'lodash', + 'jquery', + 'moment', + 'slate', + 'emotion', + '@emotion/react', + '@emotion/css', + 'prismjs', + 'slate-plain-serializer', + '@grafana/slate-react', + 'react', + 'react-dom', + 'react-redux', + 'redux', + 'rxjs', + 'react-router', + 'd3', + 'angular', + '@grafana/ui', + '@grafana/runtime', + '@grafana/data', + + // Mark legacy SDK imports as external if their name starts with the "grafana/" prefix + ({ request }, callback) => { + const prefix = 'grafana/'; + const hasPrefix = (request?: string) => request?.indexOf(prefix) === 0; + const stripPrefix = (request?: string) => request?.substr(prefix.length); + + if (hasPrefix(request)) { + return callback(undefined, stripPrefix(request)); + } + + callback(); + }, + ], + + // Support WebAssembly according to latest spec - makes WebAssembly module async + experiments: { + asyncWebAssembly: true, + }, + + mode: env.production ? 'production' : 'development', + + module: { + rules: [ + // This must come first in the rules array otherwise it breaks sourcemaps. + { + test: /src\/(?:.*\/)?module\.tsx?$/, + use: [ + { + loader: 'imports-loader', + options: { + imports: `side-effects grafana-public-path`, + }, + }, + ], + }, + { + exclude: /(node_modules)/, + test: /\.[tj]sx?$/, + use: { + loader: 'builtin:swc-loader', + options: { + jsc: { + externalHelpers: true, + loose: false, + parser: { + syntax: 'typescript', + tsx: true, + }, + transform: { + react: { + development: !env.production, + refresh: false, + runtime: 'automatic', + }, + }, + }, + env: { + target: 'es2020', + }, + }, + }, + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + }, + { + test: /\.s[ac]ss$/, + use: ['style-loader', 'css-loader', 'sass-loader'], + }, + { + test: /\.(png|jpe?g|gif|svg)$/, + type: 'asset/resource', + generator: { + filename: Boolean(env.production) ? '[hash][ext]' : '[file]', + }, + }, + { + test: /\.(woff|woff2|eot|ttf|otf)(\?v=\d+\.\d+\.\d+)?$/, + type: 'asset/resource', + generator: { + filename: Boolean(env.production) ? '[hash][ext]' : '[file]', + }, + }, + ], + }, + + output: { + filename: '[name].js', + library: { + type: 'amd', + }, + path: path.resolve(process.cwd(), DIST_DIR), + publicPath: `public/plugins/${pluginJson.id}/`, + uniqueName: pluginJson.id, + }, + + plugins: [ + virtualPublicPath, + new rspack.CopyRspackPlugin({ + patterns: [ + // If src/README.md exists use it; otherwise the root README + // To `compiler.options.output` + { from: 'README.md', to: '.', force: true }, + { from: 'plugin.json', to: '.' }, + { from: hasLicense() ? 'LICENSE' : '../../../../../LICENSE', to: '.' }, // Point to Grafana License by default + { from: 'CHANGELOG.md', to: '.', force: true }, + { + from: '**/*.json', + to: '.', + globOptions: { ignore: ['**/dist/**', '**/tsconfig.json', '**/package.json', '**/project.json'] }, + }, // Optional + { from: '**/*.svg', to: '.', noErrorOnMissing: true, globOptions: { ignore: ['**/dist/**'] } }, // Optional + { from: '**/*.png', to: '.', noErrorOnMissing: true, globOptions: { ignore: ['**/dist/**'] } }, // Optional + { from: '**/*.html', to: '.', noErrorOnMissing: true, globOptions: { ignore: ['**/dist/**'] } }, // Optional + { from: 'img/**/*', to: '.', noErrorOnMissing: true, globOptions: { ignore: ['**/dist/**'] } }, // Optional + { from: 'libs/**/*', to: '.', noErrorOnMissing: true, globOptions: { ignore: ['**/dist/**'] } }, // Optional + { from: 'static/**/*', to: '.', noErrorOnMissing: true, globOptions: { ignore: ['**/dist/**'] } }, // Optional + { from: '**/query_help.md', to: '.', noErrorOnMissing: true, globOptions: { ignore: ['**/dist/**'] } }, // Optional + ], + }), + // Replace certain template-variables in the README and plugin.json + new ReplaceInFileWebpackPlugin([ + { + dir: DIST_DIR, + files: ['plugin.json', 'README.md'], + rules: [ + { + search: /\%VERSION\%/g, + replace: getPackageJson().version, + }, + { + search: /\%TODAY\%/g, + replace: new Date().toISOString().substring(0, 10), + }, + { + search: /\%PLUGIN_ID\%/g, + replace: pluginJson.id, + }, + ], + }, + ]), + ...(env.development + ? [ + new ForkTsCheckerWebpackPlugin({ + async: Boolean(env.development), + issue: { + include: [{ file: '**/*.{ts,tsx}' }], + }, + typescript: { configFile: path.join(process.cwd(), 'tsconfig.json') }, + }), + new ESLintPlugin({ + extensions: ['.ts', '.tsx'], + lintDirtyModulesOnly: true, // don't lint on start, only lint changed files + cacheLocation: path.resolve( + __dirname, + '../../node_modules/.cache/eslint-webpack-plugin', + path.basename(process.cwd()), + '.eslintcache' + ), + }), + ] + : []), + ], + + resolve: { + extensions: ['.ts', '.tsx', '.js', '.jsx'], + // handle resolving "rootDir" paths + modules: [path.resolve(process.cwd(), 'src'), 'node_modules'], + }, + + stats: env.production ? 'normal' : 'minimal', + + watchOptions: { + ignored: ['**/node_modules', '**/dist', '**/.yarn'], + }, + }; + + return baseConfig; +}; + +export default config;