@meteorjs/rspack 0.2.54 → 0.3.50

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
@@ -66,6 +66,15 @@ type MeteorEnv = Record<string, any> & {
66
66
  * @returns A config object with merged configs
67
67
  */
68
68
  extendConfig: (...configs: Record<string, object>[]) => Record<string, object>;
69
+
70
+ /**
71
+ * Remove plugins from a Rspack config by name, RegExp, predicate, or array of them.
72
+ * @param matchers - String, RegExp, function, or array of them to match plugin names
73
+ * @returns The modified config object
74
+ */
75
+ disablePlugins: (
76
+ matchers: string | RegExp | ((plugin: any, index: number) => boolean) | Array<string | RegExp | ((plugin: any, index: number) => boolean)>
77
+ ) => Record<string, any>;
69
78
  }
70
79
 
71
80
  export type ConfigFactory = (
@@ -152,6 +152,59 @@ function extendSwcConfig(swcConfig) {
152
152
  });
153
153
  }
154
154
 
155
+ /**
156
+ * Remove plugins from a Rspack config by name, RegExp, predicate, or array of them.
157
+ * When using a function predicate, it receives both the plugin and its index in the plugins array.
158
+ *
159
+ * @param {object} config Rspack config object
160
+ * @param {string | RegExp | ((plugin: any, index: number) => boolean) | Array<string|RegExp|Function>} matchers
161
+ * @returns {object} The modified config object
162
+ */
163
+ function disablePlugins(config, matchers) {
164
+ if (!config || typeof config !== "object") {
165
+ throw new TypeError("disablePlugins: `config` must be an object");
166
+ }
167
+
168
+ const plugins = Array.isArray(config.plugins) ? config.plugins : [];
169
+ const kept = [];
170
+
171
+ const list = Array.isArray(matchers) ? matchers : [matchers];
172
+
173
+ const getPluginName = (p) => {
174
+ if (!p) return "";
175
+ return (
176
+ (p.constructor && typeof p.constructor.name === "string" && p.constructor.name) ||
177
+ (typeof p.name === "string" && p.name) ||
178
+ (typeof p.pluginName === "string" && p.pluginName) ||
179
+ (typeof p.__pluginName === "string" && p.__pluginName) ||
180
+ ""
181
+ );
182
+ };
183
+
184
+ const predicates = list.map((m) => {
185
+ if (typeof m === "function") return m;
186
+ if (m instanceof RegExp) {
187
+ return (p) => m.test(getPluginName(p));
188
+ }
189
+ if (typeof m === "string") {
190
+ return (p) => getPluginName(p) === m;
191
+ }
192
+ throw new TypeError(
193
+ "disablePlugins: matchers must be string, RegExp, function, or array of them"
194
+ );
195
+ });
196
+
197
+ plugins.forEach((p, index) => {
198
+ const matches = predicates.some((fn) => fn(p, index));
199
+ if (!matches) {
200
+ kept.push(p);
201
+ }
202
+ });
203
+
204
+ config.plugins = kept;
205
+ return config;
206
+ }
207
+
155
208
  module.exports = {
156
209
  compileWithMeteor,
157
210
  compileWithRspack,
@@ -159,4 +212,5 @@ module.exports = {
159
212
  splitVendorChunk,
160
213
  extendSwcConfig,
161
214
  makeWebNodeBuiltinsAlias,
215
+ disablePlugins,
162
216
  };
package/lib/test.js CHANGED
@@ -13,13 +13,13 @@ const { createIgnoreRegex, createIgnoreGlobConfig } = require("./ignore.js");
13
13
  * @returns {string} The path to the generated file
14
14
  */
15
15
  const generateEagerTestFile = ({
16
- isAppTest,
17
- projectDir,
18
- buildContext,
19
- ignoreEntries: inIgnoreEntries = [],
20
- prefix: inPrefix = '',
21
- extraEntry,
22
- }) => {
16
+ isAppTest,
17
+ projectDir,
18
+ buildContext,
19
+ ignoreEntries: inIgnoreEntries = [],
20
+ prefix: inPrefix = '',
21
+ extraEntry,
22
+ }) => {
23
23
  const distDir = path.resolve(projectDir, ".meteor/local/test");
24
24
  if (!fs.existsSync(distDir)) {
25
25
  fs.mkdirSync(distDir, { recursive: true });
@@ -60,8 +60,8 @@ const generateEagerTestFile = ({
60
60
  ${
61
61
  extraEntry
62
62
  ? `const extra = import.meta.webpackContext('${path.dirname(
63
- extraEntry
64
- )}', {
63
+ extraEntry
64
+ )}', {
65
65
  recursive: false,
66
66
  regExp: ${new RegExp(`${path.basename(extraEntry)}$`).toString()},
67
67
  mode: 'eager',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meteorjs/rspack",
3
- "version": "0.2.54",
3
+ "version": "0.3.50",
4
4
  "description": "Configuration logic for using Rspack in Meteor projects",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -0,0 +1,40 @@
1
+ // AssetExternalsPlugin.js
2
+ //
3
+ // This plugin externalizes assets within CSS/SCSS and other files.
4
+ // It prevents Rspack from bundling assets referenced in CSS url() and similar contexts,
5
+ // allowing them to be served directly from the public directory.
6
+
7
+ // Regular expression to match CSS, SCSS, and other style files
8
+ const CSS_EXT_REGEX = /\.(css|scss|sass|less|styl)$/;
9
+
10
+ class AssetExternalsPlugin {
11
+ constructor(options = {}) {
12
+ this.pluginName = 'AssetExternalsPlugin';
13
+ this.options = options;
14
+ }
15
+
16
+ apply(compiler) {
17
+ // Add the externals function to handle asset URLs in CSS files
18
+ compiler.options.externals = [
19
+ ...compiler.options.externals || [],
20
+ (data, callback) => {
21
+ const req = data.request;
22
+
23
+ // Webpack provides dependencyType === "url" for CSS url() deps.
24
+ // Rspack is webpack-compatible here, but keep this tolerant.
25
+ const isUrlDep = data.dependencyType === 'url';
26
+ const issuer = data.contextInfo?.issuer || '';
27
+ const fromCss = CSS_EXT_REGEX.test(issuer);
28
+
29
+ if (req && req.startsWith('/') && (isUrlDep || fromCss)) {
30
+ // Keep the URL as-is (served by your server from /public)
31
+ return callback(null, `asset ${req}`);
32
+ }
33
+
34
+ callback();
35
+ }
36
+ ];
37
+ }
38
+ }
39
+
40
+ module.exports = { AssetExternalsPlugin };
package/rspack.config.js CHANGED
@@ -9,6 +9,7 @@ const { cleanOmittedPaths, mergeSplitOverlap } = require("./lib/mergeRulesSplitO
9
9
  const { getMeteorAppSwcConfig } = require('./lib/swc.js');
10
10
  const HtmlRspackPlugin = require('./plugins/HtmlRspackPlugin.js');
11
11
  const { RequireExternalsPlugin } = require('./plugins/RequireExtenalsPlugin.js');
12
+ const { AssetExternalsPlugin } = require('./plugins/AssetExternalsPlugin.js');
12
13
  const { generateEagerTestFile } = require("./lib/test.js");
13
14
  const { getMeteorIgnoreEntries, createIgnoreGlobConfig } = require("./lib/ignore");
14
15
  const { mergeMeteorRspackFragments } = require("./lib/meteorRspackConfigFactory.js");
@@ -19,7 +20,9 @@ const {
19
20
  splitVendorChunk,
20
21
  extendSwcConfig,
21
22
  makeWebNodeBuiltinsAlias,
23
+ disablePlugins,
22
24
  } = require('./lib/meteorRspackHelpers.js');
25
+ const { prepareMeteorRspackConfig } = require("./lib/meteorRspackConfigFactory");
23
26
 
24
27
  // Safe require that doesn't throw if the module isn't found
25
28
  function safeRequire(moduleName) {
@@ -286,6 +289,9 @@ module.exports = async function (inMeteor = {}, argv = {}) {
286
289
  Meteor.splitVendorChunk = () => splitVendorChunk();
287
290
  Meteor.extendSwcConfig = (customSwcConfig) => extendSwcConfig(customSwcConfig);
288
291
  Meteor.extendConfig = (...configs) => mergeSplitOverlap(...configs);
292
+ Meteor.disablePlugins = matchers => prepareMeteorRspackConfig({
293
+ disablePlugins: matchers,
294
+ });
289
295
 
290
296
  // Add HtmlRspackPlugin function to Meteor
291
297
  Meteor.HtmlRspackPlugin = (options = {}) => {
@@ -392,6 +398,15 @@ module.exports = async function (inMeteor = {}, argv = {}) {
392
398
  enableGlobalPolyfill: isDevEnvironment && !isServer,
393
399
  });
394
400
 
401
+ // Handle assets
402
+ const assetExternalsPlugin = new AssetExternalsPlugin();
403
+ const assetModuleFilename = _fileInfo => {
404
+ const filename = _fileInfo.filename;
405
+ const isPublic = filename.startsWith('/') || filename.startsWith('public');
406
+ if (isPublic) return `[name][ext][query]`;
407
+ return `${assetsContext}/[hash][ext][query]`;
408
+ };
409
+
395
410
  const rsdoctorModule = isBundleVisualizerEnabled
396
411
  ? safeRequire('@rsdoctor/rspack-plugin')
397
412
  : null;
@@ -462,7 +477,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
462
477
  libraryTarget: 'commonjs2',
463
478
  publicPath: '/',
464
479
  chunkFilename: `${chunksContext}/[id]${isProd ? '.[chunkhash]' : ''}.js`,
465
- assetModuleFilename: `${assetsContext}/[hash][ext][query]`,
480
+ assetModuleFilename,
466
481
  cssFilename: `${chunksContext}/[name]${
467
482
  isProd ? '.[contenthash]' : ''
468
483
  }.css`,
@@ -497,6 +512,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
497
512
  ? [new reactRefreshModule()]
498
513
  : []),
499
514
  requireExternalsPlugin,
515
+ assetExternalsPlugin,
500
516
  ].filter(Boolean),
501
517
  new DefinePlugin({
502
518
  'Meteor.isClient': JSON.stringify(true),
@@ -566,7 +582,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
566
582
  filename: () => `../${buildContext}/${outputPath}`,
567
583
  libraryTarget: 'commonjs2',
568
584
  chunkFilename: `${chunksContext}/[id]${isProd ? '.[chunkhash]' : ''}.js`,
569
- assetModuleFilename: `${assetsContext}/[hash][ext][query]`,
585
+ assetModuleFilename,
570
586
  ...(isProd && { clean: { keep: keepOutsideBuild() } }),
571
587
  },
572
588
  optimization: {
@@ -610,6 +626,7 @@ module.exports = async function (inMeteor = {}, argv = {}) {
610
626
  ),
611
627
  ...bannerPluginConfig,
612
628
  requireExternalsPlugin,
629
+ assetExternalsPlugin,
613
630
  ...doctorPluginConfig,
614
631
  ],
615
632
  watchOptions,
@@ -768,6 +785,12 @@ module.exports = async function (inMeteor = {}, argv = {}) {
768
785
  }
769
786
  }
770
787
 
788
+ const shouldDisablePlugins = config?.disablePlugins != null;
789
+ if (shouldDisablePlugins) {
790
+ config = disablePlugins(config, config.disablePlugins);
791
+ delete config.disablePlugins;
792
+ }
793
+
771
794
  if (Meteor.isDebug || Meteor.isVerbose) {
772
795
  console.log('Config:', inspect(config, { depth: null, colors: true }));
773
796
  }