@meteorjs/rspack 0.0.59 → 0.0.61

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
@@ -5,7 +5,7 @@ import {
5
5
  defineConfig as _rspackDefineConfig,
6
6
  Configuration as _RspackConfig,
7
7
  } from '@rspack/cli';
8
- import { HtmlRspackPluginOptions } from '@rspack/core';
8
+ import { HtmlRspackPluginOptions, RuleSetConditions } from '@rspack/core';
9
9
 
10
10
  export interface MeteorRspackConfig extends _RspackConfig {
11
11
  meteor?: {
@@ -31,6 +31,31 @@ type MeteorEnv = Record<string, any> & {
31
31
  * @returns An instance of HtmlRspackPlugin
32
32
  */
33
33
  HtmlRspackPlugin: (options?: HtmlRspackPluginOptions) => HtmlRspackPlugin;
34
+ /**
35
+ * Wrap externals for Meteor runtime.
36
+ * @param deps - Package names or module IDs
37
+ * @returns A config object with externals configuration
38
+ */
39
+ compileWithMeteor: (deps: RuleSetConditions) => Record<string, object>;
40
+ /**
41
+ * Add SWC transpilation rules limited to specific deps (monorepo-friendly).
42
+ * @param deps - Package names to include in SWC loader
43
+ * @param options - Optional configuration options
44
+ * @returns A config object with module rules configuration
45
+ */
46
+ compileWithRspack: (deps: RuleSetConditions) => Record<string, object>;
47
+ /**
48
+ * Enable or disable Rspack cache config.
49
+ * @param enabled - Whether to enable caching
50
+ * @param cacheConfig - Optional cache configuration
51
+ * @returns A config object with cache configuration
52
+ */
53
+ setCache: (enabled: boolean | 'memory') => Record<string, object>;
54
+ /**
55
+ * Enable Rspack split vendor chunk.
56
+ * @returns A config object with optimization configuration
57
+ */
58
+ splitVendorChunk: () => Record<string, object>;
34
59
  }
35
60
 
36
61
  export type ConfigFactory = (
@@ -42,11 +42,17 @@ function prepareMeteorRspackConfig(customConfig, opts = {}) {
42
42
  * Merge all `{prefix}<n>` fragments into `config` using `mergeSplitOverlap`,
43
43
  * then remove those temporary keys. Mutates `config`.
44
44
  *
45
- * Order: fragments are applied in ascending numeric order (1, 2, 3, ...).
45
+ * Position-aware merge:
46
+ * Walk the config in insertion order and fold:
47
+ * - for a fragment key: out = mergeSplitOverlap(out, fragment)
48
+ * - for a normal key: out = mergeSplitOverlap(out, { [key]: value })
49
+ *
50
+ * Result: fragments behave like spreads at their exact position;
51
+ * later inline keys override earlier ones (including fragments).
46
52
  *
47
53
  * @param {object} config
48
54
  * @param {{ prefix?: string }} [opts]
49
- * @returns {object} The same (mutated) config instance.
55
+ * @returns {object} same (mutated) config
50
56
  */
51
57
  function mergeMeteorRspackFragments(config, opts = {}) {
52
58
  if (!config || typeof config !== "object" || Array.isArray(config)) {
@@ -54,39 +60,36 @@ function mergeMeteorRspackFragments(config, opts = {}) {
54
60
  }
55
61
  const prefix = opts.prefix || DEFAULT_PREFIX;
56
62
 
57
- // Collect fragment keys like "meteorRspackConfig12"
58
- const tempKeys = Object.keys(config)
59
- .filter((k) => k.startsWith(prefix) && /^\d+$/.test(k.slice(prefix.length)))
60
- .map((k) => [k, parseInt(k.slice(prefix.length), 10)])
61
- .sort((a, b) => a[1] - b[1])
62
- .map(([k]) => k);
63
+ let out = {};
64
+ for (const key of Object.keys(config)) {
65
+ const val = config[key];
63
66
 
64
- if (tempKeys.length === 0) return config;
67
+ const isFragment =
68
+ typeof key === "string" &&
69
+ key.startsWith(prefix) &&
70
+ /^\d+$/.test(key.slice(prefix.length));
65
71
 
66
- // Apply each fragment with your merge policy
67
- for (const k of tempKeys) {
68
- const fragment = config[k];
69
- if (!fragment || typeof fragment !== "object" || Array.isArray(fragment)) {
70
- throw new Error(`Fragment "${k}" must be a plain object`);
72
+ if (isFragment) {
73
+ if (!val || typeof val !== "object" || Array.isArray(val)) {
74
+ throw new Error(`Fragment "${key}" must be a plain object`);
75
+ }
76
+ out = mergeSplitOverlap(out, val);
77
+ } else {
78
+ out = mergeSplitOverlap(out, { [key]: val });
71
79
  }
72
- const merged = mergeSplitOverlap(config, fragment);
73
-
74
- // Keep object identity: replace contents of `config` with `merged`
75
- replaceObject(config, merged);
76
80
  }
77
81
 
78
- // Strip the temp keys at the end
79
- for (const k of tempKeys) delete config[k];
80
-
82
+ // keep object identity; fragments disappear because `out` doesn't include them
83
+ replaceObject(config, out);
81
84
  return config;
82
85
  }
83
86
 
84
87
  function replaceObject(target, source) {
85
- for (const key of Object.keys(target)) {
86
- if (!(key in source)) delete target[key];
88
+ for (const k of Object.keys(target)) {
89
+ if (!(k in source)) delete target[k];
87
90
  }
88
- for (const key of Object.keys(source)) {
89
- target[key] = source[key];
91
+ for (const k of Object.keys(source)) {
92
+ target[k] = source[k];
90
93
  }
91
94
  }
92
95
 
@@ -54,6 +54,30 @@ function compileWithRspack(deps, { options = {} } = {}) {
54
54
  });
55
55
  }
56
56
 
57
+ /**
58
+ * Enable or disable Rspack cache config
59
+ * Usage: setCache(false)
60
+ *
61
+ * @param {boolean} enabled
62
+ * @param {Record<string, object>} cacheConfig
63
+ * @returns {Record<string, object>} `{ meteorRspackConfigX: { cache: {} } }`
64
+ */
65
+ function setCache(
66
+ enabled,
67
+ cacheConfig = { cache: true, experiments: { cache: true } }
68
+ ) {
69
+ return prepareMeteorRspackConfig(
70
+ enabled
71
+ ? cacheConfig
72
+ : {
73
+ cache: false, // disable cache
74
+ experiments: {
75
+ cache: false, // disable persistent cache (experimental flag)
76
+ },
77
+ }
78
+ );
79
+ }
80
+
57
81
  /**
58
82
  * Build an alias map that disables ALL Node core modules in a web build.
59
83
  * - Includes both 'fs' and 'node:fs' keys
@@ -65,7 +89,7 @@ function makeWebNodeBuiltinsAlias(extras = []) {
65
89
 
66
90
  const names = new Set();
67
91
  for (const m of core) {
68
- names.add(m); // e.g. 'fs'
92
+ names.add(m); // e.g. 'fs'
69
93
  names.add(`node:${m}`); // e.g. 'node:fs'
70
94
  }
71
95
  for (const x of extras) names.add(x);
@@ -74,8 +98,35 @@ function makeWebNodeBuiltinsAlias(extras = []) {
74
98
  return Object.fromEntries([...names].map((m) => [m, false]));
75
99
  }
76
100
 
101
+ /**
102
+ * Enable Rspack split vendor chunk config
103
+ * Usage: splitVendorChunk()
104
+ *
105
+ * @returns {Record<string, object>} `{ meteorRspackConfigX: { optimization: { ... } } }`
106
+ */
107
+ function splitVendorChunk() {
108
+ return prepareMeteorRspackConfig({
109
+ optimization: {
110
+ splitChunks: {
111
+ chunks: "all", // split both sync and async imports
112
+ cacheGroups: {
113
+ vendor: {
114
+ test: /[\\/]node_modules[\\/]/,
115
+ name: "vendor",
116
+ enforce: true,
117
+ priority: 10,
118
+ chunks: "all",
119
+ },
120
+ },
121
+ },
122
+ },
123
+ });
124
+ }
125
+
77
126
  module.exports = {
78
127
  compileWithMeteor,
79
128
  compileWithRspack,
129
+ setCache,
130
+ splitVendorChunk,
80
131
  makeWebNodeBuiltinsAlias,
81
132
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meteorjs/rspack",
3
- "version": "0.0.59",
3
+ "version": "0.0.61",
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
@@ -14,6 +14,8 @@ const { mergeMeteorRspackFragments } = require("./lib/meteorRspackConfigFactory.
14
14
  const {
15
15
  compileWithMeteor,
16
16
  compileWithRspack,
17
+ setCache,
18
+ splitVendorChunk,
17
19
  makeWebNodeBuiltinsAlias,
18
20
  } = require('./lib/meteorRspackHelpers.js');
19
21
 
@@ -137,6 +139,43 @@ function createSwcConfig({
137
139
  };
138
140
  }
139
141
 
142
+ function createRemoteDevServerConfig() {
143
+ const rootUrl = process.env.ROOT_URL;
144
+ let hostname;
145
+ let protocol;
146
+ let port;
147
+
148
+ if (rootUrl) {
149
+ try {
150
+ const url = new URL(rootUrl);
151
+ // Detect if it's remote (not localhost or 127.x)
152
+ const isLocal =
153
+ url.hostname.includes('localhost') ||
154
+ url.hostname.startsWith('127.') ||
155
+ url.hostname.endsWith('.local');
156
+ if (!isLocal) {
157
+ hostname = url.hostname;
158
+ protocol = url.protocol === 'https:' ? 'wss' : 'ws';
159
+ port = url.port ? Number(url.port) : (url.protocol === 'https:' ? 443 : 80);
160
+
161
+ return {
162
+ client: {
163
+ webSocketURL: {
164
+ hostname,
165
+ port,
166
+ protocol,
167
+ },
168
+ },
169
+ };
170
+ }
171
+ } catch (err) {
172
+ console.warn(`Invalid ROOT_URL "${rootUrl}", falling back to localhost`);
173
+ }
174
+ }
175
+
176
+ // If local doesn't provide any extra config
177
+ return {};
178
+ }
140
179
 
141
180
  // Keep files outside of build folders
142
181
  function keepOutsideBuild() {
@@ -218,12 +257,24 @@ module.exports = async function (inMeteor = {}, argv = {}) {
218
257
  const buildOutputDir = path.resolve(projectDir, buildContext, outputDir);
219
258
  Meteor.buildOutputDir = buildOutputDir;
220
259
 
260
+ const cacheStrategy = createCacheStrategy(
261
+ mode,
262
+ (Meteor.isClient && 'client') || 'server',
263
+ { projectConfigPath, configPath }
264
+ );
265
+
221
266
  // Expose Meteor's helpers to expand Rspack configs
222
267
  Meteor.compileWithMeteor = deps => compileWithMeteor(deps);
223
268
  Meteor.compileWithRspack = deps =>
224
269
  compileWithRspack(deps, {
225
270
  options: Meteor.swcConfigOptions,
226
271
  });
272
+ Meteor.setCache = enabled =>
273
+ setCache(
274
+ !!enabled,
275
+ enabled === 'memory' ? undefined : cacheStrategy
276
+ );
277
+ Meteor.splitVendorChunk = () => splitVendorChunk();
227
278
 
228
279
  // Add HtmlRspackPlugin function to Meteor
229
280
  Meteor.HtmlRspackPlugin = (options = {}) => {
@@ -432,6 +483,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
432
483
  devtool: isDevEnvironment || isNative || isTest ? 'source-map' : 'hidden-source-map',
433
484
  ...(isDevEnvironment && {
434
485
  devServer: {
486
+ ...createRemoteDevServerConfig(),
435
487
  static: { directory: clientOutputDir, publicPath: '/__rspack__/' },
436
488
  hot: true,
437
489
  liveReload: true,
@@ -443,7 +495,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
443
495
  },
444
496
  },
445
497
  }),
446
- ...merge(createCacheStrategy(mode, "client", { projectConfigPath, configPath }), { experiments: { css: true } })
498
+ ...merge(cacheStrategy, { experiments: { css: true } })
447
499
  };
448
500
 
449
501
 
@@ -526,7 +578,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
526
578
  watchOptions,
527
579
  devtool: isDevEnvironment || isNative || isTest ? 'source-map' : 'hidden-source-map',
528
580
  ...((isDevEnvironment || (isTest && !isTestEager) || isNative) &&
529
- createCacheStrategy(mode, "server", { projectConfigPath, configPath })),
581
+ cacheStrategy),
530
582
  };
531
583
 
532
584
  // Load and apply project-level overrides for the selected build
@@ -561,10 +613,15 @@ module.exports = async function (inMeteor = {}, argv = {}) {
561
613
  throw error;
562
614
  }
563
615
 
564
- const userConfig =
616
+ const rawUserConfig =
565
617
  typeof projectConfig === 'function'
566
618
  ? projectConfig(Meteor, argv)
567
619
  : projectConfig;
620
+ const resolvedUserConfig = await Promise.resolve(rawUserConfig);
621
+ const userConfig =
622
+ resolvedUserConfig && '0' in resolvedUserConfig
623
+ ? resolvedUserConfig[0]
624
+ : resolvedUserConfig;
568
625
 
569
626
  const omitPaths = [
570
627
  "name",
@@ -583,22 +640,27 @@ module.exports = async function (inMeteor = {}, argv = {}) {
583
640
  );
584
641
  };
585
642
 
643
+ let nextUserConfig = cleanOmittedPaths(userConfig, {
644
+ omitPaths,
645
+ warningFn,
646
+ });
647
+ nextUserConfig = mergeMeteorRspackFragments(nextUserConfig);
648
+
586
649
  if (Meteor.isClient) {
587
650
  clientConfig = mergeSplitOverlap(
588
651
  clientConfig,
589
- cleanOmittedPaths(userConfig, { omitPaths, warningFn }),
652
+ nextUserConfig
590
653
  );
591
654
  }
592
655
  if (Meteor.isServer) {
593
656
  serverConfig = mergeSplitOverlap(
594
657
  serverConfig,
595
- cleanOmittedPaths(userConfig, { omitPaths, warningFn }),
658
+ nextUserConfig
596
659
  );
597
660
  }
598
661
  }
599
662
 
600
- const sideConfig = isClient ? clientConfig : serverConfig;
601
- const config = mergeMeteorRspackFragments(sideConfig);
663
+ const config = isClient ? clientConfig : serverConfig;
602
664
 
603
665
  if (Meteor.isDebug || Meteor.isVerbose) {
604
666
  console.log('Config:', inspect(config, { depth: null, colors: true }));