@grafana/create-plugin 7.16.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 (50) hide show
  1. package/CHANGELOG.md +24 -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 +13 -1
  8. package/dist/codemods/migrations/scripts/019-remove-glob-dependency.js +93 -0
  9. package/dist/codemods/migrations/scripts/020-rename-rspack-feature-flag.js +8 -0
  10. package/dist/codemods/utils.ast.js +44 -1
  11. package/dist/codemods/utils.rspack.js +38 -0
  12. package/dist/commands/generate.command.js +1 -9
  13. package/dist/constants.js +0 -1
  14. package/dist/utils/utils.templates.js +4 -4
  15. package/package.json +1 -1
  16. package/src/codemods/additions/additions.test.ts +9 -0
  17. package/src/codemods/additions/additions.ts +7 -0
  18. package/src/codemods/additions/prompts/rspack.md +118 -0
  19. package/src/codemods/additions/scripts/docs.test.ts +4 -4
  20. package/src/codemods/additions/scripts/docs.ts +2 -2
  21. package/src/codemods/additions/scripts/rspack.test.ts +768 -0
  22. package/src/codemods/additions/scripts/rspack.ts +428 -0
  23. package/src/codemods/agentic/prompts.test.ts +11 -2
  24. package/src/codemods/agentic/prompts.ts +1 -1
  25. package/src/codemods/migrations/migrations.ts +14 -1
  26. package/src/codemods/migrations/scripts/019-remove-glob-dependency.test.ts +146 -0
  27. package/src/codemods/migrations/scripts/019-remove-glob-dependency.ts +121 -0
  28. package/src/codemods/migrations/scripts/020-rename-rspack-feature-flag.test.ts +94 -0
  29. package/src/codemods/migrations/scripts/020-rename-rspack-feature-flag.ts +8 -0
  30. package/src/codemods/utils.ast.test.ts +151 -0
  31. package/src/codemods/utils.ast.ts +60 -0
  32. package/src/codemods/utils.rspack.ts +58 -0
  33. package/src/codemods/utils.test.ts +5 -2
  34. package/src/commands/generate.command.ts +2 -10
  35. package/src/constants.ts +0 -1
  36. package/src/types.ts +1 -1
  37. package/src/utils/tests/utils.config.test.ts +10 -0
  38. package/src/utils/utils.config.ts +2 -1
  39. package/src/utils/utils.templates.ts +4 -4
  40. package/templates/common/.config/AGENTS/instructions.md +2 -2
  41. package/templates/common/.config/README.md +56 -0
  42. package/templates/common/.config/bundler/externals.ts +2 -2
  43. package/templates/common/.config/bundler/utils.ts +10 -9
  44. package/templates/common/.config/rspack/DiagnosticsRspackPlugin.ts +29 -0
  45. package/templates/common/.config/rspack/LicenseRspackPlugin.ts +43 -0
  46. package/templates/common/.config/rspack/LiveReloadRspackPlugin.ts +95 -0
  47. package/templates/common/.config/rspack/rspack.config.ts +41 -32
  48. package/templates/common/.cprc.json +1 -2
  49. package/templates/common/_package.json +19 -20
  50. package/templates/common/.config/rspack/liveReloadPlugin.ts +0 -133
@@ -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
@@ -2,7 +2,7 @@ import fs from 'fs';
2
2
  import process from 'process';
3
3
  import os from 'os';
4
4
  import path from 'path';
5
- import { glob } from 'glob';
5
+ import { glob } from 'node:fs/promises';
6
6
  import { SOURCE_DIR } from './constants.ts';
7
7
 
8
8
  export function isWSL() {
@@ -46,14 +46,15 @@ export function hasReadme() {
46
46
  // Support bundling nested plugins by finding all plugin.json files in src directory
47
47
  // then checking for a sibling module.[jt]sx? file.
48
48
  export async function getEntries() {
49
- const pluginsJson = await glob('**/src/**/plugin.json', { absolute: true });
50
-
51
- const plugins = await Promise.all(
52
- pluginsJson.map((pluginJson) => {
53
- const folder = path.dirname(pluginJson);
54
- return glob(`${folder}/module.{ts,tsx,js,jsx}`, { absolute: true });
55
- })
56
- );
49
+ const plugins: string[][] = [];
50
+ for await (const pluginJson of glob('**/src/**/plugin.json')) {
51
+ const folder = path.dirname(pluginJson);
52
+ const modules: string[] = [];
53
+ for await (const module of glob(`${folder}/module.{ts,tsx,js,jsx}`)) {
54
+ modules.push(path.resolve(module));
55
+ }
56
+ plugins.push(modules);
57
+ }
57
58
 
58
59
  return plugins.reduce<Record<string, string>>((result, modules) => {
59
60
  return modules.reduce((innerResult, module) => {
@@ -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
  }
@@ -17,12 +17,12 @@
17
17
  "license": "Apache-2.0",
18
18
  "devDependencies": {
19
19
  "@grafana/eslint-config": "^10.0.0",
20
- "@grafana/plugin-e2e": "^3.15.1",
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,21 +32,19 @@
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
- "glob": "^11.1.0",
50
48
  "identity-obj-proxy": "^3.0.0",
51
49
  "imports-loader": "^5.0.0",
52
50
  "intersection-observer": "^0.12.2",
@@ -54,26 +52,27 @@
54
52
  "jest-canvas-mock": "^2.5.2",
55
53
  "jest-environment-jsdom": "^30.5.0",
56
54
  "prettier": "^3.6.0",
57
- "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",
58
57
  "rspack-plugin-virtual-module": "^1.0.0",{{/if}}
59
58
  "sass": "^1.89.0",
60
59
  "sass-loader": "^16.0.0",
61
60
  "semver": "^7.7.0",
62
- "style-loader": "^4.0.0",{{#unless useExperimentalRspack}}
63
- "swc-loader": "^0.2.0",{{/unless}}
64
- "terser-webpack-plugin": "^5.3.0",
65
- "ts-node": "^10.9.0",{{#if useExperimentalRspack}}
66
- "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}}
67
66
  "typescript": "5.9.2",
68
- "webpack": "^5.106.0"{{#unless useExperimentalRspack}},
67
+ "webpack": "^5.106.0"{{#unless useRspack}},
69
68
  "webpack-cli": "^6.0.0",
70
69
  "webpack-livereload-plugin": "^3.0.2",
71
70
  "webpack-subresource-integrity": "^5.1.0",
72
- "webpack-virtual-modules": "^0.6.2"{{/unless}}{{#if useExperimentalRspack}},
71
+ "webpack-virtual-modules": "^0.6.2"{{/unless}}{{#if useRspack}},
73
72
  "ws": "^8.13.0"{{/if}}
74
73
  },
75
74
  "engines": {
76
- "node": ">=22"
75
+ "node": ">=22.23"
77
76
  },
78
77
  "dependencies": {
79
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;