@meteorjs/rspack 1.1.0-beta.35 → 1.1.0-beta.36

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.
package/index.d.ts CHANGED
@@ -29,12 +29,14 @@ type MeteorEnv = Record<string, any> & {
29
29
  * A function that creates an instance of HtmlRspackPlugin with default options.
30
30
  * @param options - Optional configuration options that will be merged with defaults
31
31
  * @returns An instance of HtmlRspackPlugin
32
+ * @example Meteor.HtmlRspackPlugin({ title: 'My App' })
32
33
  */
33
34
  HtmlRspackPlugin: (options?: HtmlRspackPluginOptions) => HtmlRspackPlugin;
34
35
  /**
35
36
  * Wrap externals for Meteor runtime.
36
37
  * @param deps - Package names or module IDs
37
38
  * @returns A config object with externals configuration
39
+ * @example ...Meteor.compileWithMeteor(['sharp', 'thread-stream'])
38
40
  */
39
41
  compileWithMeteor: (deps: RuleSetConditions) => Record<string, object>;
40
42
  /**
@@ -42,6 +44,7 @@ type MeteorEnv = Record<string, any> & {
42
44
  * @param deps - Package names to include in SWC loader
43
45
  * @param options - Optional configuration options
44
46
  * @returns A config object with module rules configuration
47
+ * @example ...Meteor.compileWithRspack(['grubba-rpc', 'zod'])
45
48
  */
46
49
  compileWithRspack: (deps: RuleSetConditions, options?: SwcLoaderOptions) => Record<string, object>;
47
50
  /**
@@ -49,32 +52,35 @@ type MeteorEnv = Record<string, any> & {
49
52
  * @param enabled - Whether to enable caching
50
53
  * @param cacheConfig - Optional cache configuration
51
54
  * @returns A config object with cache configuration
55
+ * @example ...Meteor.setCache(false)
52
56
  */
53
57
  setCache: (enabled: boolean | 'memory') => Record<string, object>;
54
58
  /**
55
59
  * Enable Rspack split vendor chunk.
56
60
  * @returns A config object with optimization configuration
61
+ * @example ...Meteor.splitVendorChunk()
57
62
  */
58
63
  splitVendorChunk: () => Record<string, object>;
59
64
  /**
60
65
  * Extend the SWC loader config by smart-merging custom options on top of
61
- * Meteor's defaults. Only the properties you specify are overridden;
66
+ * Meteor's defaults. Only the properties you specify are overridden;
62
67
  * everything else is preserved.
63
68
  * @param swcConfig - SWC loader options to merge with defaults
64
69
  * @returns A config object with SWC loader config
70
+ * @example ...Meteor.extendSwcConfig({ jsc: { parser: { decorators: true } } })
65
71
  */
66
72
  extendSwcConfig: (swcConfig: SwcLoaderOptions) => Record<string, object>;
67
73
  /**
68
74
  * Replace the SWC loader config entirely, discarding Meteor's defaults.
69
- * Use this when you need full control over SWC options and don't want any
70
- * automatic merging with Meteor's built-in configuration.
71
75
  * @param swcConfig - Complete SWC loader options (replaces defaults)
72
76
  * @returns A config object with SWC loader config
77
+ * @example ...Meteor.replaceSwcConfig({ jsc: { target: 'es2020' } })
73
78
  */
74
79
  replaceSwcConfig: (swcConfig: SwcLoaderOptions) => Record<string, object>;
75
80
  /**
76
81
  * Extend Rspack configs.
77
82
  * @returns A config object with merged configs
83
+ * @example ...Meteor.extendConfig(configA, configB)
78
84
  */
79
85
  extendConfig: (...configs: Record<string, object>[]) => Record<string, object>;
80
86
 
@@ -82,16 +88,43 @@ type MeteorEnv = Record<string, any> & {
82
88
  * Remove plugins from a Rspack config by name, RegExp, predicate, or array of them.
83
89
  * @param matchers - String, RegExp, function, or array of them to match plugin names
84
90
  * @returns The modified config object
91
+ * @example ...Meteor.disablePlugins(['DefinePlugin', /HtmlRspack/])
85
92
  */
86
93
  disablePlugins: (
87
94
  matchers: string | RegExp | ((plugin: any, index: number) => boolean) | Array<string | RegExp | ((plugin: any, index: number) => boolean)>
88
95
  ) => Record<string, any>;
89
96
  /**
90
97
  * Omit `Meteor.isDevelopment` and `Meteor.isProduction` from the DefinePlugin so
91
- * the bundle is not tied to a specific Meteor environment (portable / isomorphic builds).
98
+ * the bundle is not tied to a specific Meteor environment (portable builds).
92
99
  * @returns A config fragment with `meteor.enablePortableBuild: true`
100
+ * @example ...Meteor.enablePortableBuild()
93
101
  */
94
102
  enablePortableBuild: () => Record<string, any>;
103
+ /**
104
+ * Persist build-output files to disk during development.
105
+ * HTML files are always persisted automatically.
106
+ *
107
+ * Matchers: `string` (endsWith), `RegExp`, or `(filePath) => boolean`.
108
+ * Array form defaults to `always`. Object form supports `once` and `always`.
109
+ * - `always` — written on every build (default)
110
+ * - `once` — first build only (e.g. service workers)
111
+ *
112
+ * @param matchers - Array or `{ once?, always? }` of matchers
113
+ * @returns A config fragment with `devServer.devMiddleware.writeToDisk`
114
+ *
115
+ * @example
116
+ * ...Meteor.persistDevFiles({ once: ['sw.js'], always: ['manifest.json'] })
117
+ */
118
+ persistDevFiles: (
119
+ matchers:
120
+ | (string | RegExp | ((filePath: string) => boolean))[]
121
+ | {
122
+ /** Files written on the first build only. */
123
+ once?: (string | RegExp | ((filePath: string) => boolean))[];
124
+ /** Files written on every build. */
125
+ always?: (string | RegExp | ((filePath: string) => boolean))[];
126
+ }
127
+ ) => Record<string, object>;
95
128
  }
96
129
 
97
130
  export type ConfigFactory = (
@@ -244,6 +244,92 @@ function disablePlugins(config, matchers) {
244
244
  return config;
245
245
  }
246
246
 
247
+ /**
248
+ * Create a `writeToDisk` callback that persists specific files to disk
249
+ * during development.
250
+ *
251
+ * Accepts an array (defaults to "always" strategy) or an object with
252
+ * `once` and/or `always` keys for mixed strategies.
253
+ *
254
+ * Matchers can be:
255
+ * - **string**: matched with `endsWith` (e.g. `'sw.js'`, `'.html'`)
256
+ * - **RegExp**: tested against the full file path
257
+ * - **function**: `(filePath: string) => boolean`
258
+ *
259
+ * Strategies:
260
+ * - `always`: Write on every build (default). Use for files that should
261
+ * always reflect the latest build output.
262
+ * - `once`: Write on the first build only. Skipped on HMR rebuilds to
263
+ * avoid triggering service worker re-registration or file
264
+ * watcher restarts.
265
+ *
266
+ * @example
267
+ * // Simple: array defaults to "always"
268
+ * ...Meteor.persistDevFiles(['manifest.json'])
269
+ *
270
+ * // Mixed strategies with strings, regex, and functions
271
+ * ...Meteor.persistDevFiles({
272
+ * once: ['sw.js', /\.worker\.js$/],
273
+ * always: ['manifest.json', (filePath) => filePath.includes('/custom/')],
274
+ * })
275
+ *
276
+ * @param {(string|RegExp|Function)[] | { once?: (string|RegExp|Function)[], always?: (string|RegExp|Function)[] }} matchers
277
+ * @returns {Record<string, object>} config fragment with devServer.devMiddleware.writeToDisk
278
+ */
279
+ /**
280
+ * Build the writeToDisk callback from matchers.
281
+ * Shared by persistDevFiles (fragment) and internal usage (direct).
282
+ * @private
283
+ */
284
+ function createPersistCallback(matchers) {
285
+ const once = [];
286
+ const always = [];
287
+
288
+ if (Array.isArray(matchers)) {
289
+ always.push(...matchers);
290
+ } else {
291
+ if (matchers.once) once.push(...matchers.once);
292
+ if (matchers.always) always.push(...matchers.always);
293
+ }
294
+
295
+ // HTML files are always persisted, Meteor's web server relies on them
296
+ if (!always.includes('.html')) {
297
+ always.push('.html');
298
+ }
299
+
300
+ const match = (filePath, pattern) => {
301
+ if (typeof pattern === 'function') return pattern(filePath);
302
+ if (typeof pattern === 'string') return filePath.endsWith(pattern);
303
+ return pattern.test(filePath);
304
+ };
305
+
306
+ const written = new Set();
307
+
308
+ return (filePath) => {
309
+ for (const pattern of always) {
310
+ if (match(filePath, pattern)) return true;
311
+ }
312
+ for (let i = 0; i < once.length; i++) {
313
+ if (match(filePath, once[i])) {
314
+ if (written.has(i)) return false;
315
+ written.add(i);
316
+ return true;
317
+ }
318
+ }
319
+ return false;
320
+ };
321
+ }
322
+
323
+ function persistDevFiles(matchers) {
324
+ return prepareMeteorRspackConfig({
325
+ devServer: {
326
+ devMiddleware: {
327
+ writeToDisk: createPersistCallback(matchers),
328
+ },
329
+ },
330
+ });
331
+ }
332
+
247
333
  function outputMeteorRspack(data) {
248
334
  const jsonString = JSON.stringify(data);
249
335
  const output = `[Meteor-Rspack]${jsonString}[/Meteor-Rspack]`;
@@ -261,4 +347,6 @@ module.exports = {
261
347
  disablePlugins,
262
348
  outputMeteorRspack,
263
349
  enablePortableBuild,
350
+ persistDevFiles,
351
+ createPersistCallback,
264
352
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meteorjs/rspack",
3
- "version": "1.1.0-beta.35",
3
+ "version": "1.1.0-beta.36",
4
4
  "description": "Configuration logic for using Rspack in Meteor projects",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
package/rspack.config.js CHANGED
@@ -24,6 +24,8 @@ const {
24
24
  disablePlugins,
25
25
  outputMeteorRspack,
26
26
  enablePortableBuild,
27
+ persistDevFiles,
28
+ createPersistCallback,
27
29
  } = require('./lib/meteorRspackHelpers.js');
28
30
  const { loadUserAndOverrideConfig } = require('./lib/meteorRspackConfigHelpers.js');
29
31
  const { prepareMeteorRspackConfig } = require("./lib/meteorRspackConfigFactory");
@@ -348,6 +350,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
348
350
  disablePlugins: matchers,
349
351
  });
350
352
  Meteor.enablePortableBuild = () => enablePortableBuild();
353
+ Meteor.persistDevFiles = (matchers) => persistDevFiles(matchers);
351
354
 
352
355
  // Add HtmlRspackPlugin function to Meteor
353
356
  Meteor.HtmlRspackPlugin = (options = {}) => {
@@ -416,6 +419,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
416
419
  const clientOutputDir = path.resolve(projectDir, "public");
417
420
  const serverOutputDir = path.resolve(projectDir, "private");
418
421
 
422
+
419
423
  // Get Meteor ignore entries
420
424
  const meteorIgnoreEntries = getMeteorIgnoreEntries(projectDir);
421
425
 
@@ -650,8 +654,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
650
654
  ...(Meteor.isBlazeEnabled && { hot: false }),
651
655
  port: devServerPort,
652
656
  devMiddleware: {
653
- writeToDisk: (filePath) =>
654
- /\.(html)$/.test(filePath) || filePath.endsWith('sw.js'),
657
+ writeToDisk: createPersistCallback({ once: ['sw.js'], always: ['.html'] }),
655
658
  },
656
659
  onListening(devServer) {
657
660
  if (!devServer) return;
@@ -713,7 +716,22 @@ module.exports = async function (inMeteor = {}, argv = {}) {
713
716
  runtimeChunk: false,
714
717
  },
715
718
  module: {
716
- rules: [swcConfigRule, ...extraRules],
719
+ rules: [
720
+ swcConfigRule,
721
+ // Mirror the client rule: ignore .html so rspack doesn't try to
722
+ // parse them as JavaScript. Meteor's template compiler handles
723
+ // .html files separately, and RequireExternalsPlugin below wires
724
+ // the imports to Meteor's module system.
725
+ ...(Meteor.isBlazeEnabled
726
+ ? [
727
+ {
728
+ test: /\.html$/i,
729
+ loader: 'ignore-loader',
730
+ },
731
+ ]
732
+ : []),
733
+ ...extraRules,
734
+ ],
717
735
  parser: {
718
736
  javascript: {
719
737
  // Dynamic imports on the server are treated as bundled in the same chunk