@grafana/create-plugin 7.17.0 → 7.19.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 (44) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/codemods/additions/additions.js +7 -0
  3. package/dist/codemods/additions/prompts/rspack.md +120 -0
  4. package/dist/codemods/additions/scripts/docs.js +2 -2
  5. package/dist/codemods/additions/scripts/rspack.js +341 -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 +120 -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 +852 -0
  20. package/src/codemods/additions/scripts/rspack.ts +453 -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 +3 -3
  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/PluginCssRspackPlugin.ts +103 -0
  41. package/templates/common/.config/rspack/rspack.config.ts +79 -48
  42. package/templates/common/.cprc.json +1 -2
  43. package/templates/common/_package.json +22 -22
  44. package/templates/common/.config/rspack/liveReloadPlugin.ts +0 -133
@@ -5,24 +5,34 @@
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
+ LightningCssMinimizerRspackPlugin,
13
+ SubresourceIntegrityPlugin,
14
+ SwcJsMinimizerRspackPlugin,
15
+ type Configuration,
16
+ } from '@rspack/core';
10
17
  import { TsCheckerRspackPlugin } from 'ts-checker-rspack-plugin';
11
18
  import path from 'path';
12
19
  import ReplaceInFileWebpackPlugin from 'replace-in-file-webpack-plugin';
13
- import TerserPlugin from 'terser-webpack-plugin';
14
20
  import { RspackVirtualModulePlugin } from 'rspack-plugin-virtual-module';
15
21
 
16
- import RspackLiveReloadPlugin from './liveReloadPlugin.ts';
22
+ import RspackLiveReloadPlugin from './LiveReloadRspackPlugin.ts';
17
23
  import { BuildModeRspackPlugin } from './BuildModeRspackPlugin.ts';
24
+ import { LicenseRspackPlugin } from './LicenseRspackPlugin.ts';
25
+ import DiagnosticsRspackPlugin from './DiagnosticsRspackPlugin.ts';
26
+ import { PluginCssRspackPlugin } from './PluginCssRspackPlugin.ts';
18
27
  import { DIST_DIR, SOURCE_DIR } from '../bundler/constants.ts';
19
28
  import { getCPConfigVersion, getEntries, getPackageJson, getPluginJson, isWSL } from '../bundler/utils.ts';
20
29
  import { externals } from '../bundler/externals.ts';
21
30
  import { copyFilePatterns } from '../bundler/copyFiles.ts';
22
31
 
23
- const { SubresourceIntegrityPlugin } = rspack.experiments;
24
32
  const pluginJson = getPluginJson();
33
+ const pluginId = pluginJson.id;
25
34
  const cpVersion = getCPConfigVersion();
35
+ const pluginVersion = getPackageJson().version;
26
36
  const virtualPublicPath = new RspackVirtualModulePlugin({
27
37
  'grafana-public-path': `
28
38
  import amdMetaModule from 'amd-module';
@@ -30,19 +40,29 @@ import amdMetaModule from 'amd-module';
30
40
  __webpack_public_path__ =
31
41
  amdMetaModule && amdMetaModule.uri
32
42
  ? amdMetaModule.uri.slice(0, amdMetaModule.uri.lastIndexOf('/') + 1)
33
- : 'public/plugins/${pluginJson.id}/';
43
+ : 'public/plugins/${pluginId}/';
34
44
  `,
35
45
  });
36
46
 
37
- const config = async (env): Promise<Configuration> => {
47
+ export type Env = {
48
+ [key: string]: true | string | Env;
49
+ };
50
+
51
+ const config = async (env: Env): Promise<Configuration> => {
38
52
  const baseConfig: Configuration = {
39
53
  context: path.join(process.cwd(), SOURCE_DIR),
40
54
 
41
55
  devtool: env.production ? 'source-map' : 'eval-source-map',
42
56
 
43
- entry: await getEntries(),
57
+ // grafana-public-path sets the public path first. grafana-plugin-css adds ./module.css to the define() dependencies
58
+ entry: Object.fromEntries(
59
+ Object.entries(await getEntries()).map(([name, entryPath]) => [
60
+ name,
61
+ ['grafana-public-path', 'grafana-plugin-css', entryPath],
62
+ ])
63
+ ),
44
64
 
45
- externals,
65
+ externals: [...externals, { 'grafana-plugin-css': './module.css' }],
46
66
 
47
67
  // Support WebAssembly according to latest spec - makes WebAssembly module async
48
68
  experiments: {
@@ -52,19 +72,15 @@ const config = async (env): Promise<Configuration> => {
52
72
  mode: env.production ? 'production' : 'development',
53
73
 
54
74
  module: {
75
+ parser: {
76
+ // keeps default imports of CSS modules working: `import styles from './x.module.css'`
77
+ 'css/auto': { namedExports: false },
78
+ },
79
+ generator: {
80
+ // class names of CSS modules start with the plugin id
81
+ 'css/auto': { localIdentName: '[uniqueName]-[local]-[hash:6]' },
82
+ },
55
83
  rules: [
56
- // This must come first in the rules array otherwise it breaks sourcemaps.
57
- {
58
- test: /src\/(?:.*\/)?module\.tsx?$/,
59
- use: [
60
- {
61
- loader: 'imports-loader',
62
- options: {
63
- imports: `side-effects grafana-public-path`,
64
- },
65
- },
66
- ],
67
- },
68
84
  {
69
85
  exclude: /(node_modules)/,
70
86
  test: /\.[tj]sx?$/,
@@ -91,11 +107,12 @@ const config = async (env): Promise<Configuration> => {
91
107
  },
92
108
  {
93
109
  test: /\.css$/,
94
- use: ['style-loader', 'css-loader'],
110
+ type: 'css/auto',
95
111
  },
96
112
  {
97
113
  test: /\.s[ac]ss$/,
98
- use: ['style-loader', 'css-loader', 'sass-loader'],
114
+ use: ['sass-loader'],
115
+ type: 'css/auto',
99
116
  },
100
117
  {
101
118
  test: /\.(png|jpe?g|gif|svg)$/,
@@ -116,22 +133,27 @@ const config = async (env): Promise<Configuration> => {
116
133
 
117
134
  optimization: {
118
135
  minimize: Boolean(env.production),
119
- minimizer: [
120
- new TerserPlugin({
121
- // Emit a single LICENSE.txt file for all comments.
122
- extractComments: {
123
- banner: false,
124
- filename: 'LICENSE.txt',
136
+ splitChunks: {
137
+ cacheGroups: {
138
+ // CSS used by more than one lazy chunk goes into one shared file instead of a copy per chunk
139
+ sharedCss: {
140
+ type: /^css/,
141
+ chunks: 'async',
142
+ minChunks: 2,
143
+ minSize: 0,
144
+ priority: 10,
125
145
  },
126
- terserOptions: {
127
- format: {
128
- comments: (_, { type, value }) => type === 'comment2' && value.trim().startsWith('[create-plugin]'),
129
- },
130
- compress: {
131
- drop_console: ['log', 'info'],
132
- },
146
+ },
147
+ },
148
+ minimizer: [
149
+ new SwcJsMinimizerRspackPlugin({
150
+ extractComments: { banner: false },
151
+ minimizerOptions: {
152
+ format: { comments: false },
153
+ compress: { pure_funcs: ['console.log', 'console.info'] },
133
154
  },
134
155
  }),
156
+ new LightningCssMinimizerRspackPlugin(),
135
157
  ],
136
158
  },
137
159
 
@@ -141,36 +163,45 @@ const config = async (env): Promise<Configuration> => {
141
163
  },
142
164
  filename: '[name].js',
143
165
  chunkFilename: env.production ? '[name].js?_cache=[contenthash]' : '[name].js',
166
+ cssFilename: '[name].css',
167
+ cssChunkFilename: env.production ? '[name].css?_cache=[contenthash]' : '[name].css',
144
168
  library: {
145
169
  type: 'amd',
146
170
  },
147
171
  path: path.resolve(process.cwd(), DIST_DIR),
148
- publicPath: `public/plugins/${pluginJson.id}/`,
149
- uniqueName: pluginJson.id,
172
+ publicPath: `public/plugins/${pluginId}/`,
173
+ uniqueName: pluginId,
150
174
  crossOriginLoading: 'anonymous',
151
175
  },
152
176
 
153
177
  plugins: [
154
178
  new BuildModeRspackPlugin(),
179
+ new LicenseRspackPlugin(),
180
+ new PluginCssRspackPlugin({ pluginId, publicPath: `public/plugins/${pluginId}/` }),
155
181
  virtualPublicPath,
156
182
  // Insert create plugin version information into the bundle
157
- new rspack.BannerPlugin({
158
- banner: '/* [create-plugin] version: ' + cpVersion + ' */',
183
+ new BannerPlugin({
184
+ banner: [
185
+ `/* [create-plugin] version: ${cpVersion} */`,
186
+ `/* [create-plugin] plugin: ${pluginId}@${pluginVersion} */`,
187
+ ].join('\n'),
159
188
  raw: true,
160
189
  entryOnly: true,
190
+ test: /\.js$/,
191
+ // after minification to prevent banner removal and before source maps and hashes are generated so they include it.
192
+ stage: Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE + 1,
161
193
  }),
162
- new rspack.CopyRspackPlugin({
194
+ new CopyRspackPlugin({
163
195
  patterns: copyFilePatterns,
164
196
  }),
165
- // Replace certain template-variables in the README and plugin.json
166
197
  new ReplaceInFileWebpackPlugin([
167
198
  {
168
199
  dir: DIST_DIR,
169
- files: ['plugin.json', 'README.md'],
200
+ test: [/(^|\/)plugin\.json$/, /(^|\/)README\.md$/],
170
201
  rules: [
171
202
  {
172
203
  search: /\%VERSION\%/g,
173
- replace: getPackageJson().version,
204
+ replace: pluginVersion,
174
205
  },
175
206
  {
176
207
  search: /\%TODAY\%/g,
@@ -178,7 +209,7 @@ const config = async (env): Promise<Configuration> => {
178
209
  },
179
210
  {
180
211
  search: /\%PLUGIN_ID\%/g,
181
- replace: pluginJson.id,
212
+ replace: pluginId,
182
213
  },
183
214
  ],
184
215
  },
@@ -196,9 +227,9 @@ const config = async (env): Promise<Configuration> => {
196
227
  },
197
228
  typescript: { configFile: path.join(process.cwd(), 'tsconfig.json') },
198
229
  }),
199
- new ESLintPlugin({
200
- extensions: ['.ts', '.tsx'],
201
- lintDirtyModulesOnly: Boolean(env.development), // don't lint on start, only lint changed files
230
+ new DiagnosticsRspackPlugin({
231
+ lintOnStart: false, // don't lint on start, only lint changed files
232
+ checks: [{ use: 'eslint', extensions: ['ts', 'tsx'] }],
202
233
  }),
203
234
  ]
204
235
  : []),
@@ -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,47 +32,47 @@
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}}
39
- "copy-webpack-plugin": "^14.0.0",{{/unless}}
40
- "css-loader": "^7.1.0",{{#unless useExperimentalRspack}}
41
- "diagnostics-webpack-plugin": "^1.0.1",{{/unless}}
38
+ "@typescript-eslint/parser": "^8.38.0",{{#unless useRspack}}
39
+ "copy-webpack-plugin": "^14.0.0",
40
+ "css-loader": "^7.1.0",{{/unless}}
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
- "identity-obj-proxy": "^3.0.0",
50
- "imports-loader": "^5.0.0",
48
+ "identity-obj-proxy": "^3.0.0",{{#unless useRspack}}
49
+ "imports-loader": "^5.0.0",{{/unless}}
51
50
  "intersection-observer": "^0.12.2",
52
51
  "jest": "^30.5.0",
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
- "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}}
60
+ "semver": "^7.7.0",{{#unless useRspack}}
61
+ "style-loader": "^4.0.0",
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",
@@ -1,133 +0,0 @@
1
- import path from 'path';
2
- import { Compilation, type Compiler } from '@rspack/core';
3
- import { WebSocketServer } from 'ws';
4
- import { createServer } from 'http';
5
-
6
- interface RspackLiveReloadPluginOptions {
7
- port?: number;
8
- delay?: number;
9
- appendScriptTag?: boolean;
10
- protocol?: string;
11
- }
12
-
13
- class RspackLiveReloadPlugin {
14
- options: RspackLiveReloadPluginOptions;
15
- httpServer: ReturnType<typeof createServer> | null = null;
16
- server: WebSocketServer | null = null;
17
- constructor(options = {}) {
18
- this.options = Object.assign(
19
- {
20
- port: 35729,
21
- delay: 0,
22
- appendScriptTag: true,
23
- protocol: 'http',
24
- },
25
- options
26
- );
27
- }
28
-
29
- apply(compiler: Compiler) {
30
- const isRspack = compiler.rspack !== undefined;
31
- if (!isRspack) {
32
- throw new Error('This plugin is designed to work with Rspack.');
33
- }
34
-
35
- compiler.hooks.afterEmit.tap('RspackLiveReloadPlugin', (compilation) => {
36
- this._startServer();
37
- this._notifyClient();
38
- });
39
-
40
- compiler.hooks.done.tap('RspackLiveReloadPlugin', (stats) => {
41
- if (this.options.appendScriptTag) {
42
- this._injectLiveReloadScript(stats.compilation);
43
- }
44
- });
45
- }
46
-
47
- _startServer() {
48
- if (this.server) {
49
- return;
50
- }
51
-
52
- const port = this.options.port;
53
-
54
- this.httpServer = createServer((req, res) => {
55
- if (req.url === '/livereload.js') {
56
- res.writeHead(200, { 'Content-Type': 'application/javascript' });
57
- res.end(this._getLiveReloadScript());
58
- } else {
59
- res.writeHead(404);
60
- res.end('Not Found');
61
- }
62
- });
63
-
64
- this.server = new WebSocketServer({ server: this.httpServer });
65
- this.httpServer.listen(port, () => {
66
- console.log(`LiveReload server started on http://localhost:${port}`);
67
- });
68
- }
69
-
70
- _notifyClient() {
71
- if (!this.server) {
72
- return;
73
- }
74
-
75
- this.server.clients.forEach((client) => {
76
- if (client.readyState === WebSocket.OPEN) {
77
- client.send(JSON.stringify({ action: 'reload' }));
78
- }
79
- });
80
- }
81
-
82
- _injectLiveReloadScript(compilation: Compilation) {
83
- compilation.hooks.processAssets.tap(
84
- {
85
- name: 'RspackLiveReloadPlugin',
86
- stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
87
- },
88
- (assets) => {
89
- Object.keys(assets).forEach((filename) => {
90
- if (path.extname(filename) === '.html') {
91
- const assetSource = compilation.getAsset(filename)?.source;
92
- if (!assetSource) {
93
- return;
94
- }
95
-
96
- const updatedSource = assetSource
97
- .source()
98
- .toString()
99
- .replace('</body>', `<script src="http://localhost:${this.options.port}/livereload.js"></script></body>`);
100
-
101
- compilation.updateAsset(filename, {
102
- source: () => updatedSource,
103
- buffer: () => Buffer.from(updatedSource),
104
- size: () => updatedSource.length,
105
- map: () => null,
106
- sourceAndMap: () => ({ source: updatedSource, map: null }),
107
- updateHash: (hash) => {
108
- hash.update(updatedSource);
109
- },
110
- });
111
- }
112
- });
113
- }
114
- );
115
- }
116
-
117
- _getLiveReloadScript() {
118
- return `
119
- (function() {
120
- if (typeof WebSocket === 'undefined') return;
121
- const ws = new WebSocket('${this.options.protocol}://localhost:${this.options.port}');
122
- ws.onmessage = function(event) {
123
- const data = JSON.parse(event.data);
124
- if (data.action === 'reload') {
125
- window.location.reload();
126
- }
127
- };
128
- })();
129
- `;
130
- }
131
- }
132
-
133
- export default RspackLiveReloadPlugin;