@grafana/create-plugin 7.17.0 → 7.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/codemods/additions/additions.js +7 -0
  3. package/dist/codemods/additions/prompts/rspack.md +118 -0
  4. package/dist/codemods/additions/scripts/docs.js +2 -2
  5. package/dist/codemods/additions/scripts/rspack.js +322 -0
  6. package/dist/codemods/agentic/prompts.js +1 -1
  7. package/dist/codemods/migrations/migrations.js +6 -0
  8. package/dist/codemods/migrations/scripts/020-rename-rspack-feature-flag.js +8 -0
  9. package/dist/codemods/utils.rspack.js +38 -0
  10. package/dist/commands/generate.command.js +1 -9
  11. package/dist/constants.js +0 -1
  12. package/dist/utils/utils.templates.js +4 -4
  13. package/package.json +1 -1
  14. package/src/codemods/additions/additions.test.ts +9 -0
  15. package/src/codemods/additions/additions.ts +7 -0
  16. package/src/codemods/additions/prompts/rspack.md +118 -0
  17. package/src/codemods/additions/scripts/docs.test.ts +4 -4
  18. package/src/codemods/additions/scripts/docs.ts +2 -2
  19. package/src/codemods/additions/scripts/rspack.test.ts +768 -0
  20. package/src/codemods/additions/scripts/rspack.ts +428 -0
  21. package/src/codemods/agentic/prompts.test.ts +11 -2
  22. package/src/codemods/agentic/prompts.ts +1 -1
  23. package/src/codemods/migrations/migrations.ts +7 -0
  24. package/src/codemods/migrations/scripts/020-rename-rspack-feature-flag.test.ts +94 -0
  25. package/src/codemods/migrations/scripts/020-rename-rspack-feature-flag.ts +8 -0
  26. package/src/codemods/utils.rspack.ts +58 -0
  27. package/src/codemods/utils.test.ts +5 -2
  28. package/src/commands/generate.command.ts +2 -10
  29. package/src/constants.ts +0 -1
  30. package/src/types.ts +1 -1
  31. package/src/utils/tests/utils.config.test.ts +10 -0
  32. package/src/utils/utils.config.ts +2 -1
  33. package/src/utils/utils.templates.ts +4 -4
  34. package/templates/common/.config/AGENTS/instructions.md +2 -2
  35. package/templates/common/.config/README.md +56 -0
  36. package/templates/common/.config/bundler/externals.ts +2 -2
  37. package/templates/common/.config/rspack/DiagnosticsRspackPlugin.ts +29 -0
  38. package/templates/common/.config/rspack/LicenseRspackPlugin.ts +43 -0
  39. package/templates/common/.config/rspack/LiveReloadRspackPlugin.ts +95 -0
  40. package/templates/common/.config/rspack/rspack.config.ts +41 -32
  41. package/templates/common/.cprc.json +1 -2
  42. package/templates/common/_package.json +18 -18
  43. package/templates/common/.config/rspack/liveReloadPlugin.ts +0 -133
package/src/types.ts CHANGED
@@ -23,7 +23,7 @@ export type TemplateData = {
23
23
  version: string;
24
24
  bundleGrafanaUI: boolean;
25
25
  scenesVersion: string;
26
- useExperimentalRspack: boolean;
26
+ useRspack: boolean;
27
27
  pluginExecutable?: string;
28
28
  frontendBundler: 'webpack' | 'rspack';
29
29
  };
@@ -44,6 +44,16 @@ describe('getConfig', () => {
44
44
  });
45
45
  });
46
46
 
47
+ it('should not let cli args switch new plugins to rspack', async () => {
48
+ mocks.argv = {
49
+ 'feature-flags': 'useRspack',
50
+ };
51
+ const config = getConfig(tmpDir);
52
+
53
+ expect(config.features).toEqual(DEFAULT_FEATURE_FLAGS);
54
+ expect(config.features).not.toHaveProperty('useRspack');
55
+ });
56
+
47
57
  it('should override default feature flags via cli args', async () => {
48
58
  mocks.argv = {
49
59
  'feature-flags': 'bundleGrafanaUI',
@@ -11,7 +11,8 @@ import { EOL } from 'node:os';
11
11
 
12
12
  export type FeatureFlags = {
13
13
  bundleGrafanaUI?: boolean;
14
- useExperimentalRspack?: boolean;
14
+ // set by `add rspack` in .config/.cprc.json. not a default flag, so --feature-flags cannot set it
15
+ useRspack?: boolean;
15
16
  useExperimentalUpdates?: boolean;
16
17
  };
17
18
 
@@ -123,8 +123,8 @@ export function buildTemplateData({
123
123
  bundleGrafanaUI: features.bundleGrafanaUI ?? DEFAULT_FEATURE_FLAGS.bundleGrafanaUI,
124
124
  scenesVersion: '^8.17.0',
125
125
  pluginExecutable: pluginJson.executable,
126
- useExperimentalRspack: Boolean(features.useExperimentalRspack),
127
- frontendBundler: features.useExperimentalRspack ? 'rspack' : 'webpack',
126
+ useRspack: Boolean(features.useRspack),
127
+ frontendBundler: features.useRspack ? 'rspack' : 'webpack',
128
128
  };
129
129
  }
130
130
 
@@ -140,7 +140,7 @@ export function getTemplateData(cliArgs?: GenerateCliArgs): TemplateData {
140
140
  const { features } = getConfig();
141
141
  const currentVersion = CURRENT_APP_VERSION;
142
142
  const bundleGrafanaUI = features.bundleGrafanaUI ?? DEFAULT_FEATURE_FLAGS.bundleGrafanaUI;
143
- const frontendBundler = features.useExperimentalRspack ? 'rspack' : 'webpack';
143
+ const frontendBundler = features.useRspack ? 'rspack' : 'webpack';
144
144
 
145
145
  let templateData: TemplateData;
146
146
 
@@ -164,7 +164,7 @@ export function getTemplateData(cliArgs?: GenerateCliArgs): TemplateData {
164
164
  version: currentVersion,
165
165
  bundleGrafanaUI,
166
166
  scenesVersion: '^8.17.0',
167
- useExperimentalRspack: Boolean(features.useExperimentalRspack),
167
+ useRspack: Boolean(features.useRspack),
168
168
  frontendBundler,
169
169
  };
170
170
  // Updating or migrating a plugin
@@ -28,7 +28,7 @@ Your training data about the Grafana API is out of date. Use the official docume
28
28
  - **Do not change plugin ID or plugin type** in `plugin.json`.
29
29
  - Any modifications to `plugin.json` require a **restart of the Grafana server**. Remind the user of this.
30
30
  - Use `secureJsonData` for credentials and secrets; use `jsonData` only for non-sensitive configuration.
31
- - **You must use webpack** with the configuration provided in `.config/` for frontend builds.
31
+ - **You must use {{frontendBundler}}** with the configuration provided in `.config/` for frontend builds.
32
32
  - **You must use mage** with the build targets provided by the Grafana plugin Go SDK for backend builds.
33
- - To extend webpack, prettier, eslint or other tools, use the existing configuration as a base. Follow the guide: https://grafana.com/developers/plugin-tools/how-to-guides/extend-configurations.md
33
+ - To extend {{frontendBundler}}, prettier, eslint or other tools, use the existing configuration as a base. Follow the guide: https://grafana.com/developers/plugin-tools/how-to-guides/extend-configurations.md
34
34
  - Use **`@grafana/plugin-e2e`** for end-to-end testing. Read @./.config/AGENTS/e2e-testing.md before writing or modifying e2e tests.
@@ -99,6 +99,61 @@ Edit the `tsconfig.json` file in the project root in order to extend the TypeScr
99
99
 
100
100
  ---
101
101
 
102
+ {{#if useRspack}}
103
+ ### Extending the Rspack config
104
+
105
+ Follow these steps to extend the basic Rspack configuration that lives under `.config/`:
106
+
107
+ #### 1. Create a new Rspack configuration file
108
+
109
+ Create a new config file that is going to extend the basic one provided by Grafana.
110
+ It can live in the project root, e.g. `rspack.config.ts`.
111
+
112
+ #### 2. Merge the basic config provided by Grafana and your custom setup
113
+
114
+ We are going to use [`rspack-merge`](https://github.com/rstackjs/rspack-merge) for this.
115
+
116
+ ```typescript
117
+ // rspack.config.ts
118
+ import type { Configuration } from '@rspack/core';
119
+ import { merge } from 'rspack-merge';
120
+ import grafanaConfig, { type Env } from './.config/rspack/rspack.config.ts';
121
+
122
+ const config = async (env: Env): Promise<Configuration> => {
123
+ const baseConfig = await grafanaConfig(env);
124
+
125
+ return merge(baseConfig, {
126
+ // Add custom config here...
127
+ output: {
128
+ asyncChunks: true,
129
+ },
130
+ });
131
+ };
132
+
133
+ export default config;
134
+ ```
135
+
136
+ The Rspack CLI loads this file as native ESM, so use `import.meta.dirname` instead of `__dirname` and give relative imports a `.ts` extension.
137
+
138
+ #### 3. Update the `package.json` to use the new Rspack config
139
+
140
+ We need to update the `scripts` in the `package.json` to use the extended Rspack configuration.
141
+
142
+ **Update for `build`:**
143
+
144
+ ```diff
145
+ -"build": "rspack -c ./.config/rspack/rspack.config.ts --env production",
146
+ +"build": "rspack -c ./rspack.config.ts --env production",
147
+ ```
148
+
149
+ **Update for `dev`:**
150
+
151
+ ```diff
152
+ -"dev": "rspack -w -c ./.config/rspack/rspack.config.ts --env development",
153
+ +"dev": "rspack -w -c ./rspack.config.ts --env development",
154
+ ```
155
+
156
+ {{else}}
102
157
  ### Extending the Webpack config
103
158
 
104
159
  Follow these steps to extend the basic Webpack configuration that lives under `.config/`:
@@ -150,6 +205,7 @@ We need to update the `scripts` in the `package.json` to use the extended Webpac
150
205
  +"dev": "webpack -w -c ./webpack.config.ts --env development",
151
206
  ```
152
207
 
208
+ {{/if}}
153
209
  ### Configure grafana image to use when running docker
154
210
 
155
211
  By default, `grafana-enterprise` will be used as the docker image for all docker related commands. If you want to override this behavior, simply alter the `docker-compose.yaml` by adding the following build arg `grafana_image`.
@@ -1,7 +1,7 @@
1
- {{#unless useExperimentalRspack}}import type { Configuration, ExternalItemFunctionData } from 'webpack';{{/unless}}{{#if useExperimentalRspack}}
1
+ {{#unless useRspack}}import type { Configuration, ExternalItemFunctionData } from 'webpack';{{/unless}}{{#if useRspack}}
2
2
  import type { RspackOptions, ExternalItemFunctionData } from '@rspack/core';{{/if}}
3
3
 
4
- {{#unless useExperimentalRspack}} type ExternalsType = Configuration['externals'];{{/unless}}{{#if useExperimentalRspack}}type ExternalsType = RspackOptions['externals'];{{/if}}
4
+ {{#unless useRspack}} type ExternalsType = Configuration['externals'];{{/unless}}{{#if useRspack}}type ExternalsType = RspackOptions['externals'];{{/if}}
5
5
 
6
6
  export const externals: ExternalsType = [
7
7
  // Required for dynamic publicPath resolution
@@ -0,0 +1,29 @@
1
+ import { type Compiler } from '@rspack/core';
2
+ import DiagnosticsWebpackPlugin from 'diagnostics-webpack-plugin';
3
+
4
+ type DiagnosticsOptions = ConstructorParameters<typeof DiagnosticsWebpackPlugin>[0];
5
+
6
+ // diagnostics-webpack-plugin validates its options through compiler.hooks.validate, which webpack added
7
+ // in 5.106 and rspack doesn't have yet. a no-op hook lets the plugin run, without its options check.
8
+ // once rspack adds the hook, delete this wrapper and use diagnostics-webpack-plugin directly
9
+ class DiagnosticsRspackPlugin {
10
+ plugin: DiagnosticsWebpackPlugin;
11
+
12
+ constructor(options: DiagnosticsOptions) {
13
+ this.plugin = new DiagnosticsWebpackPlugin(options);
14
+ }
15
+
16
+ apply(compiler: Compiler) {
17
+ const validate = { tap: () => {} };
18
+ const hooks = new Proxy(compiler.hooks, {
19
+ get: (target, prop) => (prop === 'validate' ? validate : Reflect.get(target, prop, target)),
20
+ });
21
+ const compilerWithValidate = new Proxy(compiler, {
22
+ get: (target, prop) => (prop === 'hooks' ? hooks : Reflect.get(target, prop, target)),
23
+ });
24
+ // @ts-expect-error -- the plugin is typed against webpack's Compiler
25
+ this.plugin.apply(compilerWithValidate);
26
+ }
27
+ }
28
+
29
+ export default DiagnosticsRspackPlugin;
@@ -0,0 +1,43 @@
1
+ import { Compilation, type Compiler } from '@rspack/core';
2
+
3
+ const PLUGIN_NAME = 'LicenseRspackPlugin';
4
+ const CHUNK_LICENSE_PATTERN = /\.LICENSE\.txt$/;
5
+
6
+ // This Rspack plugin works alongside SwcJsMinimizerRspackPlugin by taking all license file comments,
7
+ // deduping them and then writing them to a single file.
8
+ export class LicenseRspackPlugin {
9
+ apply(compiler: Compiler) {
10
+ compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
11
+ compilation.hooks.processAssets.tap(
12
+ {
13
+ name: PLUGIN_NAME,
14
+ // after the minimizer (PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE) has extracted the comments
15
+ stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE + 1,
16
+ },
17
+ (assets) => {
18
+ const chunkLicenseFiles = Object.keys(assets)
19
+ .filter((assetName) => CHUNK_LICENSE_PATTERN.test(assetName))
20
+ .sort();
21
+
22
+ if (chunkLicenseFiles.length === 0) {
23
+ return;
24
+ }
25
+
26
+ const comments = new Set<string>();
27
+ for (const assetName of chunkLicenseFiles) {
28
+ const content = assets[assetName].source().toString();
29
+ content
30
+ .split(/\n{2,}/)
31
+ .map((comment) => comment.trim())
32
+ .filter(Boolean)
33
+ .forEach((comment) => comments.add(comment));
34
+ compilation.deleteAsset(assetName);
35
+ }
36
+
37
+ const { RawSource } = compiler.rspack.sources;
38
+ compilation.emitAsset('LICENSE.txt', new RawSource([...comments].join('\n\n') + '\n'));
39
+ }
40
+ );
41
+ });
42
+ }
43
+ }
@@ -0,0 +1,95 @@
1
+ import { createServer } from 'http';
2
+ import { type Compiler } from '@rspack/core';
3
+ import { WebSocketServer } from 'ws';
4
+
5
+ interface RspackLiveReloadPluginOptions {
6
+ port?: number;
7
+ }
8
+
9
+ // Serves /livereload.js and tells connected browsers to reload after every rebuild. The scaffolded
10
+ // Grafana docker image adds <script src="http://localhost:35729/livereload.js"> to its index.html.
11
+ class RspackLiveReloadPlugin {
12
+ port: number;
13
+ httpServer: ReturnType<typeof createServer> | null = null;
14
+ server: WebSocketServer | null = null;
15
+ // each rebuild retries the port, so live reload starts by itself once the port is free again
16
+ hasWarnedDisabled = false;
17
+
18
+ constructor(options: RspackLiveReloadPluginOptions = {}) {
19
+ this.port = options.port ?? 35729;
20
+ }
21
+
22
+ apply(compiler: Compiler) {
23
+ compiler.hooks.afterEmit.tap('RspackLiveReloadPlugin', () => {
24
+ this._startServer();
25
+ this._notifyClients();
26
+ });
27
+ }
28
+
29
+ _startServer() {
30
+ if (this.server) {
31
+ return;
32
+ }
33
+
34
+ this.httpServer = createServer((req, res) => {
35
+ if (req.url === '/livereload.js') {
36
+ res.writeHead(200, { 'Content-Type': 'application/javascript' });
37
+ res.end(this._getLiveReloadScript());
38
+ } else {
39
+ res.writeHead(404);
40
+ res.end('Not Found');
41
+ }
42
+ });
43
+
44
+ this.server = new WebSocketServer({ server: this.httpServer });
45
+ // live reload is a convenience, so a port that is already taken must not take the watch build down.
46
+ // ws re-emits the http server's errors on the WebSocketServer, so both need a listener
47
+ const disableLiveReload = (error: NodeJS.ErrnoException) => {
48
+ if (!this.httpServer) {
49
+ return;
50
+ }
51
+ if (!this.hasWarnedDisabled) {
52
+ this.hasWarnedDisabled = true;
53
+ const reason = error.code === 'EADDRINUSE' ? `port ${this.port} is already in use` : error.message;
54
+ console.warn(`LiveReload disabled: ${reason}. The build keeps watching without reloading the browser.`);
55
+ }
56
+ this.server?.close();
57
+ this.server = null;
58
+ this.httpServer = null;
59
+ };
60
+ this.httpServer.on('error', disableLiveReload);
61
+ this.server.on('error', disableLiveReload);
62
+ this.httpServer.listen(this.port, () => {
63
+ console.log(`LiveReload server started on http://localhost:${this.port}`);
64
+ });
65
+ }
66
+
67
+ _notifyClients() {
68
+ if (!this.server) {
69
+ return;
70
+ }
71
+
72
+ this.server.clients.forEach((client) => {
73
+ if (client.readyState === WebSocket.OPEN) {
74
+ client.send(JSON.stringify({ action: 'reload' }));
75
+ }
76
+ });
77
+ }
78
+
79
+ _getLiveReloadScript() {
80
+ return `
81
+ (function() {
82
+ if (typeof WebSocket === 'undefined') return;
83
+ const ws = new WebSocket('ws://localhost:${this.port}');
84
+ ws.onmessage = function(event) {
85
+ const data = JSON.parse(event.data);
86
+ if (data.action === 'reload') {
87
+ window.location.reload();
88
+ }
89
+ };
90
+ })();
91
+ `;
92
+ }
93
+ }
94
+
95
+ export default RspackLiveReloadPlugin;
@@ -5,24 +5,32 @@
5
5
  * https://grafana.com/developers/plugin-tools/how-to-guides/extend-configurations#extend-the-webpack-config
6
6
  */
7
7
 
8
- import rspack, { type Configuration } from '@rspack/core';
9
- import ESLintPlugin from 'eslint-webpack-plugin';
8
+ import {
9
+ BannerPlugin,
10
+ Compilation,
11
+ CopyRspackPlugin,
12
+ SubresourceIntegrityPlugin,
13
+ SwcJsMinimizerRspackPlugin,
14
+ type Configuration,
15
+ } from '@rspack/core';
10
16
  import { TsCheckerRspackPlugin } from 'ts-checker-rspack-plugin';
11
17
  import path from 'path';
12
18
  import ReplaceInFileWebpackPlugin from 'replace-in-file-webpack-plugin';
13
- import TerserPlugin from 'terser-webpack-plugin';
14
19
  import { RspackVirtualModulePlugin } from 'rspack-plugin-virtual-module';
15
20
 
16
- import RspackLiveReloadPlugin from './liveReloadPlugin.ts';
21
+ import RspackLiveReloadPlugin from './LiveReloadRspackPlugin.ts';
17
22
  import { BuildModeRspackPlugin } from './BuildModeRspackPlugin.ts';
23
+ import { LicenseRspackPlugin } from './LicenseRspackPlugin.ts';
24
+ import DiagnosticsRspackPlugin from './DiagnosticsRspackPlugin.ts';
18
25
  import { DIST_DIR, SOURCE_DIR } from '../bundler/constants.ts';
19
26
  import { getCPConfigVersion, getEntries, getPackageJson, getPluginJson, isWSL } from '../bundler/utils.ts';
20
27
  import { externals } from '../bundler/externals.ts';
21
28
  import { copyFilePatterns } from '../bundler/copyFiles.ts';
22
29
 
23
- const { SubresourceIntegrityPlugin } = rspack.experiments;
24
30
  const pluginJson = getPluginJson();
31
+ const pluginId = pluginJson.id;
25
32
  const cpVersion = getCPConfigVersion();
33
+ const pluginVersion = getPackageJson().version;
26
34
  const virtualPublicPath = new RspackVirtualModulePlugin({
27
35
  'grafana-public-path': `
28
36
  import amdMetaModule from 'amd-module';
@@ -30,11 +38,15 @@ import amdMetaModule from 'amd-module';
30
38
  __webpack_public_path__ =
31
39
  amdMetaModule && amdMetaModule.uri
32
40
  ? amdMetaModule.uri.slice(0, amdMetaModule.uri.lastIndexOf('/') + 1)
33
- : 'public/plugins/${pluginJson.id}/';
41
+ : 'public/plugins/${pluginId}/';
34
42
  `,
35
43
  });
36
44
 
37
- const config = async (env): Promise<Configuration> => {
45
+ export type Env = {
46
+ [key: string]: true | string | Env;
47
+ };
48
+
49
+ const config = async (env: Env): Promise<Configuration> => {
38
50
  const baseConfig: Configuration = {
39
51
  context: path.join(process.cwd(), SOURCE_DIR),
40
52
 
@@ -117,19 +129,11 @@ const config = async (env): Promise<Configuration> => {
117
129
  optimization: {
118
130
  minimize: Boolean(env.production),
119
131
  minimizer: [
120
- new TerserPlugin({
121
- // Emit a single LICENSE.txt file for all comments.
122
- extractComments: {
123
- banner: false,
124
- filename: 'LICENSE.txt',
125
- },
126
- terserOptions: {
127
- format: {
128
- comments: (_, { type, value }) => type === 'comment2' && value.trim().startsWith('[create-plugin]'),
129
- },
130
- compress: {
131
- drop_console: ['log', 'info'],
132
- },
132
+ new SwcJsMinimizerRspackPlugin({
133
+ extractComments: { banner: false },
134
+ minimizerOptions: {
135
+ format: { comments: false },
136
+ compress: { pure_funcs: ['console.log', 'console.info'] },
133
137
  },
134
138
  }),
135
139
  ],
@@ -145,32 +149,37 @@ const config = async (env): Promise<Configuration> => {
145
149
  type: 'amd',
146
150
  },
147
151
  path: path.resolve(process.cwd(), DIST_DIR),
148
- publicPath: `public/plugins/${pluginJson.id}/`,
149
- uniqueName: pluginJson.id,
152
+ publicPath: `public/plugins/${pluginId}/`,
153
+ uniqueName: pluginId,
150
154
  crossOriginLoading: 'anonymous',
151
155
  },
152
156
 
153
157
  plugins: [
154
158
  new BuildModeRspackPlugin(),
159
+ new LicenseRspackPlugin(),
155
160
  virtualPublicPath,
156
161
  // Insert create plugin version information into the bundle
157
- new rspack.BannerPlugin({
158
- banner: '/* [create-plugin] version: ' + cpVersion + ' */',
162
+ new BannerPlugin({
163
+ banner: [
164
+ `/* [create-plugin] version: ${cpVersion} */`,
165
+ `/* [create-plugin] plugin: ${pluginId}@${pluginVersion} */`,
166
+ ].join('\n'),
159
167
  raw: true,
160
168
  entryOnly: true,
169
+ // after minification to prevent banner removal and before source maps and hashes are generated so they include it.
170
+ stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE + 1,
161
171
  }),
162
- new rspack.CopyRspackPlugin({
172
+ new CopyRspackPlugin({
163
173
  patterns: copyFilePatterns,
164
174
  }),
165
- // Replace certain template-variables in the README and plugin.json
166
175
  new ReplaceInFileWebpackPlugin([
167
176
  {
168
177
  dir: DIST_DIR,
169
- files: ['plugin.json', 'README.md'],
178
+ test: [/(^|\/)plugin\.json$/, /(^|\/)README\.md$/],
170
179
  rules: [
171
180
  {
172
181
  search: /\%VERSION\%/g,
173
- replace: getPackageJson().version,
182
+ replace: pluginVersion,
174
183
  },
175
184
  {
176
185
  search: /\%TODAY\%/g,
@@ -178,7 +187,7 @@ const config = async (env): Promise<Configuration> => {
178
187
  },
179
188
  {
180
189
  search: /\%PLUGIN_ID\%/g,
181
- replace: pluginJson.id,
190
+ replace: pluginId,
182
191
  },
183
192
  ],
184
193
  },
@@ -196,9 +205,9 @@ const config = async (env): Promise<Configuration> => {
196
205
  },
197
206
  typescript: { configFile: path.join(process.cwd(), 'tsconfig.json') },
198
207
  }),
199
- new ESLintPlugin({
200
- extensions: ['.ts', '.tsx'],
201
- lintDirtyModulesOnly: Boolean(env.development), // don't lint on start, only lint changed files
208
+ new DiagnosticsRspackPlugin({
209
+ lintOnStart: false, // don't lint on start, only lint changed files
210
+ checks: [{ use: 'eslint', extensions: ['ts', 'tsx'] }],
202
211
  }),
203
212
  ]
204
213
  : []),
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "features": {
3
- "bundleGrafanaUI": {{ bundleGrafanaUI }},
4
- "useExperimentalRspack": {{ useExperimentalRspack }}
3
+ "bundleGrafanaUI": {{ bundleGrafanaUI }}
5
4
  }
6
5
  }
@@ -20,9 +20,9 @@
20
20
  "@grafana/plugin-e2e": "^3.15.2",
21
21
  "@grafana/sign-plugin": "^3.3.3",
22
22
  "@grafana/tsconfig": "^2.2.0",
23
- "@playwright/test": "^1.62.1",{{#if useExperimentalRspack}}
24
- "@rspack/core": "^1.6.0",
25
- "@rspack/cli": "^1.6.0",{{/if}}
23
+ "@playwright/test": "^1.62.1",{{#if useRspack}}
24
+ "@rspack/core": "^2.2.0",
25
+ "@rspack/cli": "^2.2.0",{{/if}}
26
26
  "@stylistic/eslint-plugin": "^5.10.0",
27
27
  "@swc/core": "^1.15.0",
28
28
  "@swc/helpers": "^0.5.0",
@@ -32,19 +32,18 @@
32
32
  "@types/jest": "^30.0.0",
33
33
  "@types/node": "^24.0.0",
34
34
  "@types/react": "^19.0.0",
35
- "@types/react-dom": "^19.0.0",{{#if useExperimentalRspack}}
35
+ "@types/react-dom": "^19.0.0",{{#if useRspack}}
36
36
  "@types/ws": "^8.18.1",{{/if}}
37
37
  "@typescript-eslint/eslint-plugin": "^8.38.0",
38
- "@typescript-eslint/parser": "^8.38.0",{{#unless useExperimentalRspack}}
38
+ "@typescript-eslint/parser": "^8.38.0",{{#unless useRspack}}
39
39
  "copy-webpack-plugin": "^14.0.0",{{/unless}}
40
- "css-loader": "^7.1.0",{{#unless useExperimentalRspack}}
41
- "diagnostics-webpack-plugin": "^1.0.1",{{/unless}}
40
+ "css-loader": "^7.1.0",
41
+ "diagnostics-webpack-plugin": "^1.0.1",
42
42
  "eslint": "^9.32.0",
43
43
  "eslint-config-prettier": "^10.1.0",
44
44
  "eslint-plugin-jsdoc": "^52.0.0",
45
45
  "eslint-plugin-react": "^7.37.0",
46
- "eslint-plugin-react-hooks": "^7.1.0",{{#if useExperimentalRspack}}
47
- "eslint-webpack-plugin": "^5.0.0",{{/if}}{{#unless useExperimentalRspack}}
46
+ "eslint-plugin-react-hooks": "^7.1.0",{{#unless useRspack}}
48
47
  "fork-ts-checker-webpack-plugin": "^9.1.0",{{/unless}}
49
48
  "identity-obj-proxy": "^3.0.0",
50
49
  "imports-loader": "^5.0.0",
@@ -53,26 +52,27 @@
53
52
  "jest-canvas-mock": "^2.5.2",
54
53
  "jest-environment-jsdom": "^30.5.0",
55
54
  "prettier": "^3.6.0",
56
- "replace-in-file-webpack-plugin": "^1.0.0",{{#if useExperimentalRspack}}
55
+ "replace-in-file-webpack-plugin": "^1.0.0",{{#if useRspack}}
56
+ "rspack-merge": "^1.0.1",
57
57
  "rspack-plugin-virtual-module": "^1.0.0",{{/if}}
58
58
  "sass": "^1.89.0",
59
59
  "sass-loader": "^16.0.0",
60
60
  "semver": "^7.7.0",
61
- "style-loader": "^4.0.0",{{#unless useExperimentalRspack}}
62
- "swc-loader": "^0.2.0",{{/unless}}
63
- "terser-webpack-plugin": "^5.3.0",
64
- "ts-node": "^10.9.0",{{#if useExperimentalRspack}}
65
- "ts-checker-rspack-plugin": "^1.2.0",{{/if}}
61
+ "style-loader": "^4.0.0",{{#unless useRspack}}
62
+ "swc-loader": "^0.2.0",
63
+ "terser-webpack-plugin": "^5.3.0",{{/unless}}
64
+ "ts-node": "^10.9.0",{{#if useRspack}}
65
+ "ts-checker-rspack-plugin": "^1.7.0",{{/if}}
66
66
  "typescript": "5.9.2",
67
- "webpack": "^5.106.0"{{#unless useExperimentalRspack}},
67
+ "webpack": "^5.106.0"{{#unless useRspack}},
68
68
  "webpack-cli": "^6.0.0",
69
69
  "webpack-livereload-plugin": "^3.0.2",
70
70
  "webpack-subresource-integrity": "^5.1.0",
71
- "webpack-virtual-modules": "^0.6.2"{{/unless}}{{#if useExperimentalRspack}},
71
+ "webpack-virtual-modules": "^0.6.2"{{/unless}}{{#if useRspack}},
72
72
  "ws": "^8.13.0"{{/if}}
73
73
  },
74
74
  "engines": {
75
- "node": ">=22"
75
+ "node": ">=22.23"
76
76
  },
77
77
  "dependencies": {
78
78
  "@emotion/css": "11.10.6",