From e543adb1c72e03a066a9448ba80125c1bf85aed7 Mon Sep 17 00:00:00 2001 From: Blaster4385 Date: Sat, 1 Oct 2022 13:29:20 +0530 Subject: [PATCH 001/195] Add missing return statement to CollectionPrototype._validatedInsert - This fixes the issue where CollectionPrototype._validatedInsert doesn't return the insertedId for newly inserted documents. Changed Files: - packages/allow-deny/allow-deny.js --- packages/allow-deny/allow-deny.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/allow-deny/allow-deny.js b/packages/allow-deny/allow-deny.js index e1bff9f9ab..617c4a44f3 100644 --- a/packages/allow-deny/allow-deny.js +++ b/packages/allow-deny/allow-deny.js @@ -260,7 +260,7 @@ CollectionPrototype._validatedInsert = function (userId, doc, if (generatedId !== null) doc._id = generatedId; - self._collection.insert.call(self._collection, doc); + return self._collection.insert.call(self._collection, doc); }; // Simulate a mongo `update` operation while validating that the access From e027122820871f3097d6c20e34f27b5725b4cefd Mon Sep 17 00:00:00 2001 From: harryadel Date: Sun, 6 Jul 2025 13:07:33 +0300 Subject: [PATCH 002/195] Add CollectionExtensions features --- packages/mongo/collection/collection.js | 6 + .../mongo/collection/collection_extensions.js | 164 ++++++++++++ packages/mongo/package.js | 3 + .../tests/collection_extensions_tests.js | 248 ++++++++++++++++++ 4 files changed, 421 insertions(+) create mode 100644 packages/mongo/collection/collection_extensions.js create mode 100644 packages/mongo/tests/collection_extensions_tests.js diff --git a/packages/mongo/collection/collection.js b/packages/mongo/collection/collection.js index ec2e3323b7..2e13f3d54c 100644 --- a/packages/mongo/collection/collection.js +++ b/packages/mongo/collection/collection.js @@ -62,8 +62,14 @@ Mongo.Collection = function Collection(name, options) { setupAutopublish(this, name, options); Mongo._collections.set(name, this); + + // Apply collection extensions + CollectionExtensions._applyExtensions(this, name, options); }; +// Apply static methods to the Collection constructor +CollectionExtensions._applyStaticMethods(Mongo.Collection); + Object.assign(Mongo.Collection.prototype, { _getFindSelector(args) { if (args.length == 0) return {}; diff --git a/packages/mongo/collection/collection_extensions.js b/packages/mongo/collection/collection_extensions.js new file mode 100644 index 0000000000..b471f08446 --- /dev/null +++ b/packages/mongo/collection/collection_extensions.js @@ -0,0 +1,164 @@ +/** + * Collection Extensions System + * + * Provides a clean way to extend Mongo.Collection functionality + * without monkey patching. Supports constructor extensions, + * prototype methods, and static methods. + */ + +CollectionExtensions = { + _extensions: [], + _prototypeMethods: new Map(), + _staticMethods: new Map(), + + /** + * Add a constructor extension function + * Extension function is called with (name, options) and 'this' bound to collection instance + */ + addExtension(extension) { + if (typeof extension !== 'function') { + throw new Error('Extension must be a function'); + } + this._extensions.push(extension); + }, + + /** + * Add a prototype method to all collection instances + * Method is bound to the collection instance + */ + addPrototypeMethod(name, method) { + if (typeof name !== 'string' || !name) { + throw new Error('Prototype method name must be a non-empty string'); + } + if (typeof method !== 'function') { + throw new Error('Prototype method must be a function'); + } + + // Check for reserved names (reserved for future hook system) + const reservedNames = ['before', 'after', 'direct', 'hookOptions', '_hooks']; + if (reservedNames.includes(name)) { + throw new Error(`Method name '${name}' is reserved for future hook system`); + } + + this._prototypeMethods.set(name, method); + }, + + /** + * Backwards compatibility alias for lai:collection-extensions + * @deprecated Use addPrototypeMethod instead + */ + addPrototype(name, method) { + return this.addPrototypeMethod(name, method); + }, + + /** + * Add a static method to the Mongo.Collection constructor + */ + addStaticMethod(name, method) { + if (typeof name !== 'string' || !name) { + throw new Error('Static method name must be a non-empty string'); + } + if (typeof method !== 'function') { + throw new Error('Static method must be a function'); + } + + this._staticMethods.set(name, method); + }, + + /** + * Remove an extension (useful for testing) + */ + removeExtension(extension) { + const index = this._extensions.indexOf(extension); + if (index > -1) { + this._extensions.splice(index, 1); + } + }, + + /** + * Remove a prototype method + */ + removePrototypeMethod(name) { + this._prototypeMethods.delete(name); + }, + + /** + * Backwards compatibility alias for lai:collection-extensions + * @deprecated Use removePrototypeMethod instead + */ + removePrototype(name) { + return this.removePrototypeMethod(name); + }, + + /** + * Remove a static method + */ + removeStaticMethod(name) { + this._staticMethods.delete(name); + }, + + /** + * Clear all extensions (useful for testing) + */ + clearExtensions() { + this._extensions.length = 0; + this._prototypeMethods.clear(); + this._staticMethods.clear(); + }, + + /** + * Get all registered extensions (useful for debugging) + */ + getExtensions() { + return [...this._extensions]; + }, + + /** + * Get all registered prototype methods (useful for debugging) + */ + getPrototypeMethods() { + return new Map(this._prototypeMethods); + }, + + /** + * Get all registered static methods (useful for debugging) + */ + getStaticMethods() { + return new Map(this._staticMethods); + }, + + + + /** + * Apply all extensions to a collection instance + * Called during collection construction + */ + _applyExtensions(instance, name, options) { + // Apply constructor extensions + this._extensions.forEach(extension => { + try { + extension.call(instance, name, options); + } catch (error) { + // Provide helpful error context + throw new Error(`Extension failed for collection '${name}': ${error.message}`); + } + }); + + // Apply prototype methods + this._prototypeMethods.forEach((method, methodName) => { + instance[methodName] = method.bind(instance); + }); + }, + + /** + * Apply static methods to the Mongo.Collection constructor + * Called during package initialization + */ + _applyStaticMethods(CollectionConstructor) { + this._staticMethods.forEach((method, methodName) => { + CollectionConstructor[methodName] = method; + }); + }, + + +}; \ No newline at end of file diff --git a/packages/mongo/package.js b/packages/mongo/package.js index cefaf18449..d91ad2f78b 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -79,6 +79,7 @@ Package.onUse(function (api) { api.export("MongoInternals", "server"); api.export("Mongo"); + api.export("CollectionExtensions"); api.export("ObserveMultiplexer", "server", { testOnly: true }); api.addFiles( @@ -100,6 +101,7 @@ Package.onUse(function (api) { ); api.addFiles("local_collection_driver.js", ["client", "server"]); api.addFiles("remote_collection_driver.ts", "server"); + api.addFiles("collection/collection_extensions.js", ["client", "server"]); api.addFiles("collection/collection.js", ["client", "server"]); api.addFiles("connection_options.ts", "server"); // For zodern:types to pick up our published types. @@ -130,6 +132,7 @@ Package.onTest(function (api) { api.addFiles("tests/collection_tests.js", ["client", "server"]); api.addFiles("tests/collection_async_tests.js", ["client", "server"]); api.addFiles("tests/observe_changes_tests.js", ["client", "server"]); + api.addFiles("tests/collection_extensions_tests.js", ["client", "server"]); api.addFiles("tests/oplog_tests.js", "server"); api.addFiles("tests/oplog_v2_converter_tests.js", "server"); api.addFiles("tests/doc_fetcher_tests.js", "server"); diff --git a/packages/mongo/tests/collection_extensions_tests.js b/packages/mongo/tests/collection_extensions_tests.js new file mode 100644 index 0000000000..4a4d2cba0a --- /dev/null +++ b/packages/mongo/tests/collection_extensions_tests.js @@ -0,0 +1,248 @@ +import { Tinytest } from "meteor/tinytest"; +import { Mongo } from "meteor/mongo"; +import { CollectionExtensions } from "meteor/mongo"; +import { Random } from "meteor/random"; + +// Test setup and teardown +function setupTest() { + CollectionExtensions.clearExtensions(); +} + +function teardownTest() { + CollectionExtensions.clearExtensions(); +} + +Tinytest.add("CollectionExtensions - constructor extension", function (test) { + setupTest(); + + let extensionCallCount = 0; + let extensionData = null; + + CollectionExtensions.addExtension(function(name, options) { + extensionCallCount++; + extensionData = { name, options, instance: this }; + }); + + const testCollection = new Mongo.Collection(Random.id()); + + test.equal(extensionCallCount, 1); + test.equal(extensionData.name, testCollection._name); + test.equal(extensionData.instance, testCollection); + test.isTrue(extensionData.options && typeof extensionData.options === 'object'); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - multiple extensions", function (test) { + setupTest(); + + let callOrder = []; + + CollectionExtensions.addExtension(function(name, options) { + callOrder.push('extension1'); + }); + + CollectionExtensions.addExtension(function(name, options) { + callOrder.push('extension2'); + }); + + CollectionExtensions.addExtension(function(name, options) { + callOrder.push('extension3'); + }); + + const testCollection = new Mongo.Collection(Random.id()); + + test.equal(callOrder, ['extension1', 'extension2', 'extension3']); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - prototype methods", function (test) { + setupTest(); + + CollectionExtensions.addPrototypeMethod('testMethod', function() { + return 'testResult'; + }); + + const testCollection = new Mongo.Collection(Random.id()); + + test.isTrue(typeof testCollection.testMethod === 'function'); + test.equal(testCollection.testMethod(), 'testResult'); + + teardownTest(); +}); + +// Test prototype method with collection context +Tinytest.add("CollectionExtensions - prototype method context", function (test) { + setupTest(); + + // Add prototype method that uses collection context + CollectionExtensions.addPrototypeMethod('getCollectionName', function() { + return this._name; + }); + + // Create collection + const testCollection = new Mongo.Collection(Random.id()); + + // Verify method has correct context + test.equal(testCollection.getCollectionName(), testCollection._name); + + teardownTest(); +}); + +// Test static methods +Tinytest.add("CollectionExtensions - static methods", function (test) { + setupTest(); + + // Add static method + CollectionExtensions.addStaticMethod('testStaticMethod', function() { + return 'staticResult'; + }); + + // Apply static methods (this happens automatically in real usage) + CollectionExtensions._applyStaticMethods(Mongo.Collection); + + // Verify static method was added + test.isTrue(typeof Mongo.Collection.testStaticMethod === 'function'); + test.equal(Mongo.Collection.testStaticMethod(), 'staticResult'); + + // Clean up + delete Mongo.Collection.testStaticMethod; + teardownTest(); +}); + +// Test reserved names protection +Tinytest.add("CollectionExtensions - reserved names protection", function (test) { + setupTest(); + + const reservedNames = ['before', 'after', 'direct', 'hookOptions', '_hooks']; + + reservedNames.forEach(name => { + test.throws(() => { + CollectionExtensions.addPrototypeMethod(name, function() {}); + }, /reserved for future hook system/); + }); + + teardownTest(); +}); + +// Test error handling in extensions +Tinytest.add("CollectionExtensions - extension error handling", function (test) { + setupTest(); + + // Add extension that throws error + CollectionExtensions.addExtension(function(name, options) { + throw new Error('Test extension error'); + }); + + // Creating collection should throw with helpful error message + test.throws(() => { + new Mongo.Collection(Random.id()); + }, /Extension failed for collection/); + + teardownTest(); +}); + +// Test extension removal +Tinytest.add("CollectionExtensions - extension removal", function (test) { + setupTest(); + + let callCount = 0; + + const extension = function(name, options) { + callCount++; + }; + + CollectionExtensions.addExtension(extension); + + const testCollection1 = new Mongo.Collection(Random.id()); + test.equal(callCount, 1); + + CollectionExtensions.removeExtension(extension); + + // Create another collection - should not call extension + const testCollection2 = new Mongo.Collection(Random.id()); + test.equal(callCount, 1); // Still 1, not 2 + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - prototype method removal", function (test) { + setupTest(); + + CollectionExtensions.addPrototypeMethod('testMethod', function() { + return 'test'; + }); + + const testCollection1 = new Mongo.Collection(Random.id()); + test.isTrue(typeof testCollection1.testMethod === 'function'); + + CollectionExtensions.removePrototypeMethod('testMethod'); + + const testCollection2 = new Mongo.Collection(Random.id()); + test.isUndefined(testCollection2.testMethod); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - input validation", function (test) { + setupTest(); + + test.throws(() => { + CollectionExtensions.addExtension("not a function"); + }, /Extension must be a function/); + + test.throws(() => { + CollectionExtensions.addPrototypeMethod("", function() {}); + }, /Prototype method name must be a non-empty string/); + + test.throws(() => { + CollectionExtensions.addPrototypeMethod(123, function() {}); + }, /Prototype method name must be a non-empty string/); + + test.throws(() => { + CollectionExtensions.addPrototypeMethod("test", "not a function"); + }, /Prototype method must be a function/); + + test.throws(() => { + CollectionExtensions.addStaticMethod("", function() {}); + }, /Static method name must be a non-empty string/); + + test.throws(() => { + CollectionExtensions.addStaticMethod("test", "not a function"); + }, /Static method must be a function/); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - introspection", function (test) { + setupTest(); + + const extension1 = function() {}; + const extension2 = function() {}; + + test.equal(CollectionExtensions.getExtensions(), []); + test.equal(CollectionExtensions.getPrototypeMethods().size, 0); + test.equal(CollectionExtensions.getStaticMethods().size, 0); + + CollectionExtensions.addExtension(extension1); + CollectionExtensions.addExtension(extension2); + CollectionExtensions.addPrototypeMethod('test1', function() {}); + CollectionExtensions.addStaticMethod('test2', function() {}); + + // Test introspection + const extensions = CollectionExtensions.getExtensions(); + test.equal(extensions.length, 2); + test.equal(extensions[0], extension1); + test.equal(extensions[1], extension2); + + const prototypeMethods = CollectionExtensions.getPrototypeMethods(); + test.equal(prototypeMethods.size, 1); + test.isTrue(prototypeMethods.has('test1')); + + const staticMethods = CollectionExtensions.getStaticMethods(); + test.equal(staticMethods.size, 1); + test.isTrue(staticMethods.has('test2')); + + teardownTest(); +}); \ No newline at end of file From 42079393764b861f125d9e1f58ca977291665fa6 Mon Sep 17 00:00:00 2001 From: harryadel Date: Mon, 7 Jul 2025 10:51:31 +0300 Subject: [PATCH 003/195] Use for of loops instead of forEach --- packages/mongo/collection/collection_extensions.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/mongo/collection/collection_extensions.js b/packages/mongo/collection/collection_extensions.js index b471f08446..28db082d95 100644 --- a/packages/mongo/collection/collection_extensions.js +++ b/packages/mongo/collection/collection_extensions.js @@ -135,19 +135,19 @@ CollectionExtensions = { */ _applyExtensions(instance, name, options) { // Apply constructor extensions - this._extensions.forEach(extension => { + for (const extension of this._extensions) { try { extension.call(instance, name, options); } catch (error) { // Provide helpful error context throw new Error(`Extension failed for collection '${name}': ${error.message}`); } - }); + } // Apply prototype methods - this._prototypeMethods.forEach((method, methodName) => { + for (const [methodName, method] of this._prototypeMethods) { instance[methodName] = method.bind(instance); - }); + } }, /** @@ -155,9 +155,9 @@ CollectionExtensions = { * Called during package initialization */ _applyStaticMethods(CollectionConstructor) { - this._staticMethods.forEach((method, methodName) => { + for (const [methodName, method] of this._staticMethods) { CollectionConstructor[methodName] = method; - }); + } }, From 641f819eba2432f7f60a518a92636ac86f4f33ca Mon Sep 17 00:00:00 2001 From: harryadel Date: Mon, 7 Jul 2025 11:13:15 +0300 Subject: [PATCH 004/195] Attach CollectionExtensions methods to Mongo.Collection --- packages/mongo/collection/collection.js | 145 ++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/packages/mongo/collection/collection.js b/packages/mongo/collection/collection.js index 2e13f3d54c..fa0e2325b8 100644 --- a/packages/mongo/collection/collection.js +++ b/packages/mongo/collection/collection.js @@ -70,6 +70,7 @@ Mongo.Collection = function Collection(name, options) { // Apply static methods to the Collection constructor CollectionExtensions._applyStaticMethods(Mongo.Collection); + Object.assign(Mongo.Collection.prototype, { _getFindSelector(args) { if (args.length == 0) return {}; @@ -159,6 +160,143 @@ Object.assign(Mongo.Collection, { return selector; }, + + // Collection Extensions API - delegate to CollectionExtensions + /** + * @summary Add a constructor extension function that runs when collections are created. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {Function} extension Extension function called with (name, options) and 'this' bound to collection instance + */ + addExtension(extension) { + return CollectionExtensions.addExtension(extension); + }, + + /** + * @summary Add a prototype method to all collection instances. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the method to add + * @param {Function} method The method function, bound to the collection instance + */ + addPrototypeMethod(name, method) { + return CollectionExtensions.addPrototypeMethod(name, method); + }, + + /** + * @summary Backwards compatibility alias for lai:collection-extensions + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the method to add + * @param {Function} method The method function, bound to the collection instance + * @deprecated Use addPrototypeMethod instead + */ + addPrototype(name, method) { + return CollectionExtensions.addPrototype(name, method); + }, + + /** + * @summary Add a static method to the Mongo.Collection constructor. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the static method to add + * @param {Function} method The static method function + */ + addStaticMethod(name, method) { + return CollectionExtensions.addStaticMethod(name, method); + }, + + /** + * @summary Remove a constructor extension (useful for testing). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {Function} extension The extension function to remove + */ + removeExtension(extension) { + return CollectionExtensions.removeExtension(extension); + }, + + /** + * @summary Remove a prototype method from all collection instances. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the method to remove + */ + removePrototypeMethod(name) { + return CollectionExtensions.removePrototypeMethod(name); + }, + + /** + * @summary Backwards compatibility alias for lai:collection-extensions + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the method to remove + * @deprecated Use removePrototypeMethod instead + */ + removePrototype(name) { + return CollectionExtensions.removePrototype(name); + }, + + /** + * @summary Remove a static method from the Mongo.Collection constructor. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the static method to remove + */ + removeStaticMethod(name) { + return CollectionExtensions.removeStaticMethod(name); + }, + + /** + * @summary Clear all extensions, prototype methods, and static methods (useful for testing). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + */ + clearExtensions() { + return CollectionExtensions.clearExtensions(); + }, + + /** + * @summary Get all registered constructor extensions (useful for debugging). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @returns {Array} Array of registered extension functions + */ + getExtensions() { + return CollectionExtensions.getExtensions(); + }, + + /** + * @summary Get all registered prototype methods (useful for debugging). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @returns {Map} Map of method names to functions + */ + getPrototypeMethods() { + return CollectionExtensions.getPrototypeMethods(); + }, + + /** + * @summary Get all registered static methods (useful for debugging). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @returns {Map} Map of method names to functions + */ + getStaticMethods() { + return CollectionExtensions.getStaticMethods(); + } }); Object.assign(Mongo.Collection.prototype, ReplicationMethods, SyncMethods, AsyncMethods, IndexMethods); @@ -236,6 +374,13 @@ Object.assign(Mongo, { * @protected */ _collections: new Map(), + + /** + * @summary Collection Extensions API + * @memberof Mongo + * @static + */ + CollectionExtensions: CollectionExtensions }) From e60bab915672a1e272dfe8d76fd1f2c1939315b6 Mon Sep 17 00:00:00 2001 From: harryadel Date: Mon, 7 Jul 2025 11:23:48 +0300 Subject: [PATCH 005/195] Add documentation --- v3-docs/docs/api/collections.md | 83 +++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/v3-docs/docs/api/collections.md b/v3-docs/docs/api/collections.md index a466de49df..ec47aa71d7 100644 --- a/v3-docs/docs/api/collections.md +++ b/v3-docs/docs/api/collections.md @@ -669,6 +669,89 @@ The methods (like `update` or `insert`) you call on the resulting _raw_ collecti +## Collection Extensions + +Meteor provides a powerful Collection Extensions API that allows you to extend the functionality of all collection instances. These static methods on `Mongo.Collection` let you add constructor extensions, prototype methods, and static methods to customize collection behavior. These very same APIs are exported under `CollectionExtensions` for backwards compatibility with [lai:collection-extensions](https://github.com/Meteor-Community-Packages/meteor-collection-extensions). + + + +Add a constructor extension function that runs when collections are created. The extension function is called with `(name, options)` and `this` bound to the collection instance. + +Example: +```js +Mongo.Collection.addExtension(function(name, options) { + this._customProperty = 'value'; + console.log(`Collection ${name} was created`); +}); +``` + + + +Add a prototype method to all collection instances. The method is bound to the collection instance and available on all collections. + +Example: +```js +Mongo.Collection.addPrototypeMethod('customMethod', function() { + return `${this._name} is awesome`; +}); + +// Now available on all collections +const Users = new Mongo.Collection('users'); +console.log(Users.customMethod()); // "users is awesome" +``` + + + +Add a static method to the `Mongo.Collection` constructor itself. + +Example: +```js +Mongo.Collection.addStaticMethod('getAllCollections', function() { + return Array.from(Mongo._collections.values()); +}); + +// Now available as static method +const allCollections = Mongo.Collection.getAllCollections(); +``` + + + +Remove a constructor extension (useful for testing). + + + +Remove a prototype method from all collection instances. + + + +Remove a static method from the `Mongo.Collection` constructor. + + + +Clear all extensions, prototype methods, and static methods. This is useful for testing to ensure a clean state. + + + +Get all registered constructor extensions. Returns an array of extension functions. Useful for debugging. + + + +Get all registered prototype methods. Returns a Map of method names to functions. Useful for debugging. + + + +Get all registered static methods. Returns a Map of method names to functions. Useful for debugging. + +### Legacy Aliases + + + +Backwards compatibility alias for `addPrototypeMethod`. **Deprecated** - use `addPrototypeMethod` instead. + + + +Backwards compatibility alias for `removePrototypeMethod`. **Deprecated** - use `removePrototypeMethod` instead. + ## Cursors {#mongo_cursor} From e01852f3211ea20b24b8bdfbcaedb087b020c37e Mon Sep 17 00:00:00 2001 From: harryadel Date: Sun, 6 Jul 2025 13:07:33 +0300 Subject: [PATCH 006/195] Add CollectionExtensions features --- packages/mongo/collection/collection.js | 6 + .../mongo/collection/collection_extensions.js | 164 ++++++++++++ packages/mongo/package.js | 3 + .../tests/collection_extensions_tests.js | 248 ++++++++++++++++++ 4 files changed, 421 insertions(+) create mode 100644 packages/mongo/collection/collection_extensions.js create mode 100644 packages/mongo/tests/collection_extensions_tests.js diff --git a/packages/mongo/collection/collection.js b/packages/mongo/collection/collection.js index ec2e3323b7..2e13f3d54c 100644 --- a/packages/mongo/collection/collection.js +++ b/packages/mongo/collection/collection.js @@ -62,8 +62,14 @@ Mongo.Collection = function Collection(name, options) { setupAutopublish(this, name, options); Mongo._collections.set(name, this); + + // Apply collection extensions + CollectionExtensions._applyExtensions(this, name, options); }; +// Apply static methods to the Collection constructor +CollectionExtensions._applyStaticMethods(Mongo.Collection); + Object.assign(Mongo.Collection.prototype, { _getFindSelector(args) { if (args.length == 0) return {}; diff --git a/packages/mongo/collection/collection_extensions.js b/packages/mongo/collection/collection_extensions.js new file mode 100644 index 0000000000..b471f08446 --- /dev/null +++ b/packages/mongo/collection/collection_extensions.js @@ -0,0 +1,164 @@ +/** + * Collection Extensions System + * + * Provides a clean way to extend Mongo.Collection functionality + * without monkey patching. Supports constructor extensions, + * prototype methods, and static methods. + */ + +CollectionExtensions = { + _extensions: [], + _prototypeMethods: new Map(), + _staticMethods: new Map(), + + /** + * Add a constructor extension function + * Extension function is called with (name, options) and 'this' bound to collection instance + */ + addExtension(extension) { + if (typeof extension !== 'function') { + throw new Error('Extension must be a function'); + } + this._extensions.push(extension); + }, + + /** + * Add a prototype method to all collection instances + * Method is bound to the collection instance + */ + addPrototypeMethod(name, method) { + if (typeof name !== 'string' || !name) { + throw new Error('Prototype method name must be a non-empty string'); + } + if (typeof method !== 'function') { + throw new Error('Prototype method must be a function'); + } + + // Check for reserved names (reserved for future hook system) + const reservedNames = ['before', 'after', 'direct', 'hookOptions', '_hooks']; + if (reservedNames.includes(name)) { + throw new Error(`Method name '${name}' is reserved for future hook system`); + } + + this._prototypeMethods.set(name, method); + }, + + /** + * Backwards compatibility alias for lai:collection-extensions + * @deprecated Use addPrototypeMethod instead + */ + addPrototype(name, method) { + return this.addPrototypeMethod(name, method); + }, + + /** + * Add a static method to the Mongo.Collection constructor + */ + addStaticMethod(name, method) { + if (typeof name !== 'string' || !name) { + throw new Error('Static method name must be a non-empty string'); + } + if (typeof method !== 'function') { + throw new Error('Static method must be a function'); + } + + this._staticMethods.set(name, method); + }, + + /** + * Remove an extension (useful for testing) + */ + removeExtension(extension) { + const index = this._extensions.indexOf(extension); + if (index > -1) { + this._extensions.splice(index, 1); + } + }, + + /** + * Remove a prototype method + */ + removePrototypeMethod(name) { + this._prototypeMethods.delete(name); + }, + + /** + * Backwards compatibility alias for lai:collection-extensions + * @deprecated Use removePrototypeMethod instead + */ + removePrototype(name) { + return this.removePrototypeMethod(name); + }, + + /** + * Remove a static method + */ + removeStaticMethod(name) { + this._staticMethods.delete(name); + }, + + /** + * Clear all extensions (useful for testing) + */ + clearExtensions() { + this._extensions.length = 0; + this._prototypeMethods.clear(); + this._staticMethods.clear(); + }, + + /** + * Get all registered extensions (useful for debugging) + */ + getExtensions() { + return [...this._extensions]; + }, + + /** + * Get all registered prototype methods (useful for debugging) + */ + getPrototypeMethods() { + return new Map(this._prototypeMethods); + }, + + /** + * Get all registered static methods (useful for debugging) + */ + getStaticMethods() { + return new Map(this._staticMethods); + }, + + + + /** + * Apply all extensions to a collection instance + * Called during collection construction + */ + _applyExtensions(instance, name, options) { + // Apply constructor extensions + this._extensions.forEach(extension => { + try { + extension.call(instance, name, options); + } catch (error) { + // Provide helpful error context + throw new Error(`Extension failed for collection '${name}': ${error.message}`); + } + }); + + // Apply prototype methods + this._prototypeMethods.forEach((method, methodName) => { + instance[methodName] = method.bind(instance); + }); + }, + + /** + * Apply static methods to the Mongo.Collection constructor + * Called during package initialization + */ + _applyStaticMethods(CollectionConstructor) { + this._staticMethods.forEach((method, methodName) => { + CollectionConstructor[methodName] = method; + }); + }, + + +}; \ No newline at end of file diff --git a/packages/mongo/package.js b/packages/mongo/package.js index cefaf18449..d91ad2f78b 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -79,6 +79,7 @@ Package.onUse(function (api) { api.export("MongoInternals", "server"); api.export("Mongo"); + api.export("CollectionExtensions"); api.export("ObserveMultiplexer", "server", { testOnly: true }); api.addFiles( @@ -100,6 +101,7 @@ Package.onUse(function (api) { ); api.addFiles("local_collection_driver.js", ["client", "server"]); api.addFiles("remote_collection_driver.ts", "server"); + api.addFiles("collection/collection_extensions.js", ["client", "server"]); api.addFiles("collection/collection.js", ["client", "server"]); api.addFiles("connection_options.ts", "server"); // For zodern:types to pick up our published types. @@ -130,6 +132,7 @@ Package.onTest(function (api) { api.addFiles("tests/collection_tests.js", ["client", "server"]); api.addFiles("tests/collection_async_tests.js", ["client", "server"]); api.addFiles("tests/observe_changes_tests.js", ["client", "server"]); + api.addFiles("tests/collection_extensions_tests.js", ["client", "server"]); api.addFiles("tests/oplog_tests.js", "server"); api.addFiles("tests/oplog_v2_converter_tests.js", "server"); api.addFiles("tests/doc_fetcher_tests.js", "server"); diff --git a/packages/mongo/tests/collection_extensions_tests.js b/packages/mongo/tests/collection_extensions_tests.js new file mode 100644 index 0000000000..4a4d2cba0a --- /dev/null +++ b/packages/mongo/tests/collection_extensions_tests.js @@ -0,0 +1,248 @@ +import { Tinytest } from "meteor/tinytest"; +import { Mongo } from "meteor/mongo"; +import { CollectionExtensions } from "meteor/mongo"; +import { Random } from "meteor/random"; + +// Test setup and teardown +function setupTest() { + CollectionExtensions.clearExtensions(); +} + +function teardownTest() { + CollectionExtensions.clearExtensions(); +} + +Tinytest.add("CollectionExtensions - constructor extension", function (test) { + setupTest(); + + let extensionCallCount = 0; + let extensionData = null; + + CollectionExtensions.addExtension(function(name, options) { + extensionCallCount++; + extensionData = { name, options, instance: this }; + }); + + const testCollection = new Mongo.Collection(Random.id()); + + test.equal(extensionCallCount, 1); + test.equal(extensionData.name, testCollection._name); + test.equal(extensionData.instance, testCollection); + test.isTrue(extensionData.options && typeof extensionData.options === 'object'); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - multiple extensions", function (test) { + setupTest(); + + let callOrder = []; + + CollectionExtensions.addExtension(function(name, options) { + callOrder.push('extension1'); + }); + + CollectionExtensions.addExtension(function(name, options) { + callOrder.push('extension2'); + }); + + CollectionExtensions.addExtension(function(name, options) { + callOrder.push('extension3'); + }); + + const testCollection = new Mongo.Collection(Random.id()); + + test.equal(callOrder, ['extension1', 'extension2', 'extension3']); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - prototype methods", function (test) { + setupTest(); + + CollectionExtensions.addPrototypeMethod('testMethod', function() { + return 'testResult'; + }); + + const testCollection = new Mongo.Collection(Random.id()); + + test.isTrue(typeof testCollection.testMethod === 'function'); + test.equal(testCollection.testMethod(), 'testResult'); + + teardownTest(); +}); + +// Test prototype method with collection context +Tinytest.add("CollectionExtensions - prototype method context", function (test) { + setupTest(); + + // Add prototype method that uses collection context + CollectionExtensions.addPrototypeMethod('getCollectionName', function() { + return this._name; + }); + + // Create collection + const testCollection = new Mongo.Collection(Random.id()); + + // Verify method has correct context + test.equal(testCollection.getCollectionName(), testCollection._name); + + teardownTest(); +}); + +// Test static methods +Tinytest.add("CollectionExtensions - static methods", function (test) { + setupTest(); + + // Add static method + CollectionExtensions.addStaticMethod('testStaticMethod', function() { + return 'staticResult'; + }); + + // Apply static methods (this happens automatically in real usage) + CollectionExtensions._applyStaticMethods(Mongo.Collection); + + // Verify static method was added + test.isTrue(typeof Mongo.Collection.testStaticMethod === 'function'); + test.equal(Mongo.Collection.testStaticMethod(), 'staticResult'); + + // Clean up + delete Mongo.Collection.testStaticMethod; + teardownTest(); +}); + +// Test reserved names protection +Tinytest.add("CollectionExtensions - reserved names protection", function (test) { + setupTest(); + + const reservedNames = ['before', 'after', 'direct', 'hookOptions', '_hooks']; + + reservedNames.forEach(name => { + test.throws(() => { + CollectionExtensions.addPrototypeMethod(name, function() {}); + }, /reserved for future hook system/); + }); + + teardownTest(); +}); + +// Test error handling in extensions +Tinytest.add("CollectionExtensions - extension error handling", function (test) { + setupTest(); + + // Add extension that throws error + CollectionExtensions.addExtension(function(name, options) { + throw new Error('Test extension error'); + }); + + // Creating collection should throw with helpful error message + test.throws(() => { + new Mongo.Collection(Random.id()); + }, /Extension failed for collection/); + + teardownTest(); +}); + +// Test extension removal +Tinytest.add("CollectionExtensions - extension removal", function (test) { + setupTest(); + + let callCount = 0; + + const extension = function(name, options) { + callCount++; + }; + + CollectionExtensions.addExtension(extension); + + const testCollection1 = new Mongo.Collection(Random.id()); + test.equal(callCount, 1); + + CollectionExtensions.removeExtension(extension); + + // Create another collection - should not call extension + const testCollection2 = new Mongo.Collection(Random.id()); + test.equal(callCount, 1); // Still 1, not 2 + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - prototype method removal", function (test) { + setupTest(); + + CollectionExtensions.addPrototypeMethod('testMethod', function() { + return 'test'; + }); + + const testCollection1 = new Mongo.Collection(Random.id()); + test.isTrue(typeof testCollection1.testMethod === 'function'); + + CollectionExtensions.removePrototypeMethod('testMethod'); + + const testCollection2 = new Mongo.Collection(Random.id()); + test.isUndefined(testCollection2.testMethod); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - input validation", function (test) { + setupTest(); + + test.throws(() => { + CollectionExtensions.addExtension("not a function"); + }, /Extension must be a function/); + + test.throws(() => { + CollectionExtensions.addPrototypeMethod("", function() {}); + }, /Prototype method name must be a non-empty string/); + + test.throws(() => { + CollectionExtensions.addPrototypeMethod(123, function() {}); + }, /Prototype method name must be a non-empty string/); + + test.throws(() => { + CollectionExtensions.addPrototypeMethod("test", "not a function"); + }, /Prototype method must be a function/); + + test.throws(() => { + CollectionExtensions.addStaticMethod("", function() {}); + }, /Static method name must be a non-empty string/); + + test.throws(() => { + CollectionExtensions.addStaticMethod("test", "not a function"); + }, /Static method must be a function/); + + teardownTest(); +}); + +Tinytest.add("CollectionExtensions - introspection", function (test) { + setupTest(); + + const extension1 = function() {}; + const extension2 = function() {}; + + test.equal(CollectionExtensions.getExtensions(), []); + test.equal(CollectionExtensions.getPrototypeMethods().size, 0); + test.equal(CollectionExtensions.getStaticMethods().size, 0); + + CollectionExtensions.addExtension(extension1); + CollectionExtensions.addExtension(extension2); + CollectionExtensions.addPrototypeMethod('test1', function() {}); + CollectionExtensions.addStaticMethod('test2', function() {}); + + // Test introspection + const extensions = CollectionExtensions.getExtensions(); + test.equal(extensions.length, 2); + test.equal(extensions[0], extension1); + test.equal(extensions[1], extension2); + + const prototypeMethods = CollectionExtensions.getPrototypeMethods(); + test.equal(prototypeMethods.size, 1); + test.isTrue(prototypeMethods.has('test1')); + + const staticMethods = CollectionExtensions.getStaticMethods(); + test.equal(staticMethods.size, 1); + test.isTrue(staticMethods.has('test2')); + + teardownTest(); +}); \ No newline at end of file From 1e18ca011a98f671291facc82a18006b116f4738 Mon Sep 17 00:00:00 2001 From: harryadel Date: Mon, 7 Jul 2025 10:51:31 +0300 Subject: [PATCH 007/195] Use for of loops instead of forEach --- packages/mongo/collection/collection_extensions.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/mongo/collection/collection_extensions.js b/packages/mongo/collection/collection_extensions.js index b471f08446..28db082d95 100644 --- a/packages/mongo/collection/collection_extensions.js +++ b/packages/mongo/collection/collection_extensions.js @@ -135,19 +135,19 @@ CollectionExtensions = { */ _applyExtensions(instance, name, options) { // Apply constructor extensions - this._extensions.forEach(extension => { + for (const extension of this._extensions) { try { extension.call(instance, name, options); } catch (error) { // Provide helpful error context throw new Error(`Extension failed for collection '${name}': ${error.message}`); } - }); + } // Apply prototype methods - this._prototypeMethods.forEach((method, methodName) => { + for (const [methodName, method] of this._prototypeMethods) { instance[methodName] = method.bind(instance); - }); + } }, /** @@ -155,9 +155,9 @@ CollectionExtensions = { * Called during package initialization */ _applyStaticMethods(CollectionConstructor) { - this._staticMethods.forEach((method, methodName) => { + for (const [methodName, method] of this._staticMethods) { CollectionConstructor[methodName] = method; - }); + } }, From 363712d669f610ce25f15755f88244b4499384ba Mon Sep 17 00:00:00 2001 From: harryadel Date: Mon, 7 Jul 2025 11:13:15 +0300 Subject: [PATCH 008/195] Attach CollectionExtensions methods to Mongo.Collection --- packages/mongo/collection/collection.js | 145 ++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/packages/mongo/collection/collection.js b/packages/mongo/collection/collection.js index 2e13f3d54c..fa0e2325b8 100644 --- a/packages/mongo/collection/collection.js +++ b/packages/mongo/collection/collection.js @@ -70,6 +70,7 @@ Mongo.Collection = function Collection(name, options) { // Apply static methods to the Collection constructor CollectionExtensions._applyStaticMethods(Mongo.Collection); + Object.assign(Mongo.Collection.prototype, { _getFindSelector(args) { if (args.length == 0) return {}; @@ -159,6 +160,143 @@ Object.assign(Mongo.Collection, { return selector; }, + + // Collection Extensions API - delegate to CollectionExtensions + /** + * @summary Add a constructor extension function that runs when collections are created. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {Function} extension Extension function called with (name, options) and 'this' bound to collection instance + */ + addExtension(extension) { + return CollectionExtensions.addExtension(extension); + }, + + /** + * @summary Add a prototype method to all collection instances. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the method to add + * @param {Function} method The method function, bound to the collection instance + */ + addPrototypeMethod(name, method) { + return CollectionExtensions.addPrototypeMethod(name, method); + }, + + /** + * @summary Backwards compatibility alias for lai:collection-extensions + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the method to add + * @param {Function} method The method function, bound to the collection instance + * @deprecated Use addPrototypeMethod instead + */ + addPrototype(name, method) { + return CollectionExtensions.addPrototype(name, method); + }, + + /** + * @summary Add a static method to the Mongo.Collection constructor. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the static method to add + * @param {Function} method The static method function + */ + addStaticMethod(name, method) { + return CollectionExtensions.addStaticMethod(name, method); + }, + + /** + * @summary Remove a constructor extension (useful for testing). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {Function} extension The extension function to remove + */ + removeExtension(extension) { + return CollectionExtensions.removeExtension(extension); + }, + + /** + * @summary Remove a prototype method from all collection instances. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the method to remove + */ + removePrototypeMethod(name) { + return CollectionExtensions.removePrototypeMethod(name); + }, + + /** + * @summary Backwards compatibility alias for lai:collection-extensions + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the method to remove + * @deprecated Use removePrototypeMethod instead + */ + removePrototype(name) { + return CollectionExtensions.removePrototype(name); + }, + + /** + * @summary Remove a static method from the Mongo.Collection constructor. + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @param {String} name The name of the static method to remove + */ + removeStaticMethod(name) { + return CollectionExtensions.removeStaticMethod(name); + }, + + /** + * @summary Clear all extensions, prototype methods, and static methods (useful for testing). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + */ + clearExtensions() { + return CollectionExtensions.clearExtensions(); + }, + + /** + * @summary Get all registered constructor extensions (useful for debugging). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @returns {Array} Array of registered extension functions + */ + getExtensions() { + return CollectionExtensions.getExtensions(); + }, + + /** + * @summary Get all registered prototype methods (useful for debugging). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @returns {Map} Map of method names to functions + */ + getPrototypeMethods() { + return CollectionExtensions.getPrototypeMethods(); + }, + + /** + * @summary Get all registered static methods (useful for debugging). + * @locus Anywhere + * @memberof Mongo.Collection + * @static + * @returns {Map} Map of method names to functions + */ + getStaticMethods() { + return CollectionExtensions.getStaticMethods(); + } }); Object.assign(Mongo.Collection.prototype, ReplicationMethods, SyncMethods, AsyncMethods, IndexMethods); @@ -236,6 +374,13 @@ Object.assign(Mongo, { * @protected */ _collections: new Map(), + + /** + * @summary Collection Extensions API + * @memberof Mongo + * @static + */ + CollectionExtensions: CollectionExtensions }) From 8cee77867f5593165265012b492d9d60553fe2d0 Mon Sep 17 00:00:00 2001 From: harryadel Date: Mon, 7 Jul 2025 11:23:48 +0300 Subject: [PATCH 009/195] Add documentation --- v3-docs/docs/api/collections.md | 83 +++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/v3-docs/docs/api/collections.md b/v3-docs/docs/api/collections.md index 15e17dcd6d..b403eb9fdd 100644 --- a/v3-docs/docs/api/collections.md +++ b/v3-docs/docs/api/collections.md @@ -669,6 +669,89 @@ The methods (like `update` or `insert`) you call on the resulting _raw_ collecti +## Collection Extensions + +Meteor provides a powerful Collection Extensions API that allows you to extend the functionality of all collection instances. These static methods on `Mongo.Collection` let you add constructor extensions, prototype methods, and static methods to customize collection behavior. These very same APIs are exported under `CollectionExtensions` for backwards compatibility with [lai:collection-extensions](https://github.com/Meteor-Community-Packages/meteor-collection-extensions). + + + +Add a constructor extension function that runs when collections are created. The extension function is called with `(name, options)` and `this` bound to the collection instance. + +Example: +```js +Mongo.Collection.addExtension(function(name, options) { + this._customProperty = 'value'; + console.log(`Collection ${name} was created`); +}); +``` + + + +Add a prototype method to all collection instances. The method is bound to the collection instance and available on all collections. + +Example: +```js +Mongo.Collection.addPrototypeMethod('customMethod', function() { + return `${this._name} is awesome`; +}); + +// Now available on all collections +const Users = new Mongo.Collection('users'); +console.log(Users.customMethod()); // "users is awesome" +``` + + + +Add a static method to the `Mongo.Collection` constructor itself. + +Example: +```js +Mongo.Collection.addStaticMethod('getAllCollections', function() { + return Array.from(Mongo._collections.values()); +}); + +// Now available as static method +const allCollections = Mongo.Collection.getAllCollections(); +``` + + + +Remove a constructor extension (useful for testing). + + + +Remove a prototype method from all collection instances. + + + +Remove a static method from the `Mongo.Collection` constructor. + + + +Clear all extensions, prototype methods, and static methods. This is useful for testing to ensure a clean state. + + + +Get all registered constructor extensions. Returns an array of extension functions. Useful for debugging. + + + +Get all registered prototype methods. Returns a Map of method names to functions. Useful for debugging. + + + +Get all registered static methods. Returns a Map of method names to functions. Useful for debugging. + +### Legacy Aliases + + + +Backwards compatibility alias for `addPrototypeMethod`. **Deprecated** - use `addPrototypeMethod` instead. + + + +Backwards compatibility alias for `removePrototypeMethod`. **Deprecated** - use `removePrototypeMethod` instead. + ## Cursors {#mongo_cursor} From 400d50453f31a6d7630111ec0c8870e975286dcb Mon Sep 17 00:00:00 2001 From: harryadel Date: Tue, 8 Jul 2025 16:28:30 +0300 Subject: [PATCH 010/195] Remove collection-hooks reserved names --- .../mongo/collection/collection_extensions.js | 6 ------ .../mongo/tests/collection_extensions_tests.js | 15 --------------- 2 files changed, 21 deletions(-) diff --git a/packages/mongo/collection/collection_extensions.js b/packages/mongo/collection/collection_extensions.js index 28db082d95..ecf84607b5 100644 --- a/packages/mongo/collection/collection_extensions.js +++ b/packages/mongo/collection/collection_extensions.js @@ -34,12 +34,6 @@ CollectionExtensions = { throw new Error('Prototype method must be a function'); } - // Check for reserved names (reserved for future hook system) - const reservedNames = ['before', 'after', 'direct', 'hookOptions', '_hooks']; - if (reservedNames.includes(name)) { - throw new Error(`Method name '${name}' is reserved for future hook system`); - } - this._prototypeMethods.set(name, method); }, diff --git a/packages/mongo/tests/collection_extensions_tests.js b/packages/mongo/tests/collection_extensions_tests.js index 4a4d2cba0a..0a6e9dc3c5 100644 --- a/packages/mongo/tests/collection_extensions_tests.js +++ b/packages/mongo/tests/collection_extensions_tests.js @@ -111,21 +111,6 @@ Tinytest.add("CollectionExtensions - static methods", function (test) { teardownTest(); }); -// Test reserved names protection -Tinytest.add("CollectionExtensions - reserved names protection", function (test) { - setupTest(); - - const reservedNames = ['before', 'after', 'direct', 'hookOptions', '_hooks']; - - reservedNames.forEach(name => { - test.throws(() => { - CollectionExtensions.addPrototypeMethod(name, function() {}); - }, /reserved for future hook system/); - }); - - teardownTest(); -}); - // Test error handling in extensions Tinytest.add("CollectionExtensions - extension error handling", function (test) { setupTest(); From 2b5647e4fa99a800c3971dab11130b8a5a780802 Mon Sep 17 00:00:00 2001 From: harryadel Date: Fri, 18 Jul 2025 13:29:11 +0300 Subject: [PATCH 011/195] Add CollectionExtensions types --- packages/mongo/mongo.d.ts | 173 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/packages/mongo/mongo.d.ts b/packages/mongo/mongo.d.ts index 2f3c941732..e25beca488 100644 --- a/packages/mongo/mongo.d.ts +++ b/packages/mongo/mongo.d.ts @@ -92,6 +92,83 @@ export namespace Mongo { getCollection< TCollection extends Collection | undefined = Collection | undefined >(name: string): TCollection; + + // Collection Extensions API + /** + * Add a constructor extension function that runs when collections are created. + * @param extension Extension function called with (name, options) and 'this' bound to collection instance + */ + addExtension(extension: (this: Collection, name: string | null, options?: any) => void): void; + + /** + * Add a prototype method to all collection instances. + * @param name The name of the method to add + * @param method The method function, bound to the collection instance + */ + addPrototypeMethod(name: string, method: Function): void; + + /** + * Backwards compatibility alias for lai:collection-extensions + * @param name The name of the method to add + * @param method The method function, bound to the collection instance + * @deprecated Use addPrototypeMethod instead + */ + addPrototype(name: string, method: Function): void; + + /** + * Add a static method to the Mongo.Collection constructor. + * @param name The name of the static method to add + * @param method The static method function + */ + addStaticMethod(name: string, method: Function): void; + + /** + * Remove a constructor extension (useful for testing). + * @param extension The extension function to remove + */ + removeExtension(extension: Function): void; + + /** + * Remove a prototype method from all collection instances. + * @param name The name of the method to remove + */ + removePrototypeMethod(name: string): void; + + /** + * Backwards compatibility alias for lai:collection-extensions + * @param name The name of the method to remove + * @deprecated Use removePrototypeMethod instead + */ + removePrototype(name: string): void; + + /** + * Remove a static method from the Mongo.Collection constructor. + * @param name The name of the static method to remove + */ + removeStaticMethod(name: string): void; + + /** + * Clear all extensions, prototype methods, and static methods (useful for testing). + */ + clearExtensions(): void; + + /** + * Get all registered constructor extensions (useful for debugging). + * @returns Array of registered extension functions + */ + getExtensions(): Array; + + /** + * Get all registered prototype methods (useful for debugging). + * @returns Map of method names to functions + */ + getPrototypeMethods(): Map; + + /** + * Get all registered static methods (useful for debugging). + * @returns Map of method names to functions + */ + getStaticMethods(): Map; } interface Collection { allow = undefined>(options: { @@ -479,6 +556,102 @@ export namespace Mongo { equals(otherID: ObjectID): boolean; } + /** + * Collection Extensions API + */ + interface CollectionExtensions { + /** + * Add a constructor extension function that runs when collections are created. + * @param extension Extension function called with (name, options) and 'this' bound to collection instance + */ + addExtension(extension: (this: Collection, name: string | null, options?: any) => void): void; + + /** + * Add a prototype method to all collection instances. + * @param name The name of the method to add + * @param method The method function, bound to the collection instance + */ + addPrototypeMethod(name: string, method: Function): void; + + /** + * Backwards compatibility alias for lai:collection-extensions + * @param name The name of the method to add + * @param method The method function, bound to the collection instance + * @deprecated Use addPrototypeMethod instead + */ + addPrototype(name: string, method: Function): void; + + /** + * Add a static method to the Mongo.Collection constructor. + * @param name The name of the static method to add + * @param method The static method function + */ + addStaticMethod(name: string, method: Function): void; + + /** + * Remove a constructor extension (useful for testing). + * @param extension The extension function to remove + */ + removeExtension(extension: Function): void; + + /** + * Remove a prototype method from all collection instances. + * @param name The name of the method to remove + */ + removePrototypeMethod(name: string): void; + + /** + * Backwards compatibility alias for lai:collection-extensions + * @param name The name of the method to remove + * @deprecated Use removePrototypeMethod instead + */ + removePrototype(name: string): void; + + /** + * Remove a static method from the Mongo.Collection constructor. + * @param name The name of the static method to remove + */ + removeStaticMethod(name: string): void; + + /** + * Clear all extensions, prototype methods, and static methods (useful for testing). + */ + clearExtensions(): void; + + /** + * Get all registered constructor extensions (useful for debugging). + * @returns Array of registered extension functions + */ + getExtensions(): Array; + + /** + * Get all registered prototype methods (useful for debugging). + * @returns Map of method names to functions + */ + getPrototypeMethods(): Map; + + /** + * Get all registered static methods (useful for debugging). + * @returns Map of method names to functions + */ + getStaticMethods(): Map; + } + + var CollectionExtensions: CollectionExtensions; + + /** + * Retrieve a Meteor collection instance by name. Only collections defined with `new Mongo.Collection(...)` are available with this method. + * @param name Name of your collection as it was defined with `new Mongo.Collection()`. + * @returns The collection instance or undefined if not found + */ + function getCollection | undefined = Collection | undefined>(name: string): T; + + /** + * A record of all defined Mongo.Collection instances, indexed by collection name. + * @internal + */ + var _collections: Map>; + function setConnectionOptions(options: any): void; } From 007f0da2f48b55dfd3f32f5ffacb5a61c9257695 Mon Sep 17 00:00:00 2001 From: harryadel Date: Mon, 28 Jul 2025 11:04:43 +0300 Subject: [PATCH 012/195] [mongo] Add warning when using lai:collection-extensions --- packages/mongo/collection/collection_extensions.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/mongo/collection/collection_extensions.js b/packages/mongo/collection/collection_extensions.js index ecf84607b5..b7b8f881af 100644 --- a/packages/mongo/collection/collection_extensions.js +++ b/packages/mongo/collection/collection_extensions.js @@ -6,6 +6,10 @@ * prototype methods, and static methods. */ +if (Package['lai:collection-extensions']) { + console.warn('lai:collection-extensions is not deprecated. Use Mongo.Collection.addExtension instead.'); +} + CollectionExtensions = { _extensions: [], _prototypeMethods: new Map(), From 8ca07cc0d3d3051a6da9b4b432096ec1a039e3ee Mon Sep 17 00:00:00 2001 From: harryadel Date: Mon, 28 Jul 2025 11:24:42 +0300 Subject: [PATCH 013/195] [mongo] Add CollectionOptions interface --- packages/mongo/mongo.d.ts | 72 ++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/packages/mongo/mongo.d.ts b/packages/mongo/mongo.d.ts index e25beca488..547de5ca9a 100644 --- a/packages/mongo/mongo.d.ts +++ b/packages/mongo/mongo.d.ts @@ -53,6 +53,50 @@ export namespace Mongo { ? T : U; + /** + * Configuration options for Mongo Collection constructor + */ + interface CollectionOptions { + /** + * The server connection that will manage this collection. Uses the default connection if not specified. + * Pass the return value of calling `DDP.connect` to specify a different server. Pass `null` to specify + * no connection. Unmanaged (`name` is null) collections cannot specify a connection. + */ + connection?: DDP.DDPStatic | null | undefined; + + /** + * The method of generating the `_id` fields of new documents in this collection. Possible values: + * - **`'STRING'`**: random strings + * - **`'MONGO'`**: random [`Mongo.ObjectID`](#mongo_object_id) values + * + * The default id generation technique is `'STRING'`. + */ + idGeneration?: string | undefined; + + /** + * An optional transformation function. Documents will be passed through this function before being + * returned from `fetch` or `findOne`, and before being passed to callbacks of `observe`, `map`, + * `forEach`, `allow`, and `deny`. Transforms are *not* applied for the callbacks of `observeChanges` + * or to cursors returned from publish functions. + */ + transform?: (doc: T) => U; + + /** + * Set to `false` to skip setting up the mutation methods that enable insert/update/remove from client code. + * Default `true`. + */ + defineMutationMethods?: boolean | undefined; + + // Internal options (from normalizeOptions function) + /** @internal */ + _driver?: any; + /** @internal */ + _preventAutopublish?: boolean; + + // Allow additional properties for extensibility + [key: string]: any; + } + var Collection: CollectionStatic; interface CollectionStatic { /** @@ -61,27 +105,7 @@ export namespace Mongo { */ new ( name: string | null, - options?: { - /** - * The server connection that will manage this collection. Uses the default connection if not specified. Pass the return value of calling `DDP.connect` to specify a different - * server. Pass `null` to specify no connection. Unmanaged (`name` is null) collections cannot specify a connection. - */ - connection?: DDP.DDPStatic | null | undefined; - /** The method of generating the `_id` fields of new documents in this collection. Possible values: - * - **`'STRING'`**: random strings - * - **`'MONGO'`**: random [`Mongo.ObjectID`](#mongo_object_id) values - * - * The default id generation technique is `'STRING'`. - */ - idGeneration?: string | undefined; - /** - * An optional transformation function. Documents will be passed through this function before being returned from `fetch` or `findOne`, and before being passed to callbacks of - * `observe`, `map`, `forEach`, `allow`, and `deny`. Transforms are *not* applied for the callbacks of `observeChanges` or to cursors returned from publish functions. - */ - transform?: (doc: T) => U; - /** Set to `false` to skip setting up the mutation methods that enable insert/update/remove from client code. Default `true`. */ - defineMutationMethods?: boolean | undefined; - } + options?: CollectionOptions ): Collection; /** @@ -98,7 +122,7 @@ export namespace Mongo { * Add a constructor extension function that runs when collections are created. * @param extension Extension function called with (name, options) and 'this' bound to collection instance */ - addExtension(extension: (this: Collection, name: string | null, options?: any) => void): void; + addExtension(extension: (this: Collection, name: string | null, options?: CollectionOptions) => void): void; /** * Add a prototype method to all collection instances. @@ -564,8 +588,8 @@ export namespace Mongo { * Add a constructor extension function that runs when collections are created. * @param extension Extension function called with (name, options) and 'this' bound to collection instance */ - addExtension(extension: (this: Collection, name: string | null, options?: any) => void): void; - + addExtension(extension: (this: Collection, name: string | null, options?: CollectionOptions) => void): void; + /** * Add a prototype method to all collection instances. * @param name The name of the method to add From d66cbbb5ffb4ad667faae1a2824db16b058c6a50 Mon Sep 17 00:00:00 2001 From: harryadel Date: Mon, 28 Jul 2025 11:36:16 +0300 Subject: [PATCH 014/195] [mongo] Update addPrototype and addPrototypeMethod signatures to use generics --- packages/mongo/mongo.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/mongo/mongo.d.ts b/packages/mongo/mongo.d.ts index 547de5ca9a..c97f5bb043 100644 --- a/packages/mongo/mongo.d.ts +++ b/packages/mongo/mongo.d.ts @@ -129,7 +129,7 @@ export namespace Mongo { * @param name The name of the method to add * @param method The method function, bound to the collection instance */ - addPrototypeMethod(name: string, method: Function): void; + addPrototypeMethod(name: string, method: (this: Collection, ...args: any[]) => any): void; /** * Backwards compatibility alias for lai:collection-extensions @@ -137,7 +137,7 @@ export namespace Mongo { * @param method The method function, bound to the collection instance * @deprecated Use addPrototypeMethod instead */ - addPrototype(name: string, method: Function): void; + addPrototype(name: string, method: (this: Collection, ...args: any[]) => any): void; /** * Add a static method to the Mongo.Collection constructor. @@ -595,7 +595,7 @@ export namespace Mongo { * @param name The name of the method to add * @param method The method function, bound to the collection instance */ - addPrototypeMethod(name: string, method: Function): void; + addPrototypeMethod(name: string, method: (this: Collection, ...args: any[]) => any): void; /** * Backwards compatibility alias for lai:collection-extensions @@ -603,7 +603,7 @@ export namespace Mongo { * @param method The method function, bound to the collection instance * @deprecated Use addPrototypeMethod instead */ - addPrototype(name: string, method: Function): void; + addPrototype(name: string, method: (this: Collection, ...args: any[]) => any): void; /** * Add a static method to the Mongo.Collection constructor. From b8b43f57cc96eb9e1675c96901bb99a2911282f5 Mon Sep 17 00:00:00 2001 From: harryadel Date: Sat, 9 Aug 2025 10:30:15 +0300 Subject: [PATCH 015/195] [mongo] Remove deprecated addPrototype and removePrototype methods for cleaner API --- packages/mongo/collection/collection.js | 25 ---------------- .../mongo/collection/collection_extensions.js | 16 ---------- packages/mongo/mongo.d.ts | 30 ------------------- 3 files changed, 71 deletions(-) diff --git a/packages/mongo/collection/collection.js b/packages/mongo/collection/collection.js index fa0e2325b8..adfb6ce731 100644 --- a/packages/mongo/collection/collection.js +++ b/packages/mongo/collection/collection.js @@ -185,19 +185,6 @@ Object.assign(Mongo.Collection, { return CollectionExtensions.addPrototypeMethod(name, method); }, - /** - * @summary Backwards compatibility alias for lai:collection-extensions - * @locus Anywhere - * @memberof Mongo.Collection - * @static - * @param {String} name The name of the method to add - * @param {Function} method The method function, bound to the collection instance - * @deprecated Use addPrototypeMethod instead - */ - addPrototype(name, method) { - return CollectionExtensions.addPrototype(name, method); - }, - /** * @summary Add a static method to the Mongo.Collection constructor. * @locus Anywhere @@ -232,18 +219,6 @@ Object.assign(Mongo.Collection, { return CollectionExtensions.removePrototypeMethod(name); }, - /** - * @summary Backwards compatibility alias for lai:collection-extensions - * @locus Anywhere - * @memberof Mongo.Collection - * @static - * @param {String} name The name of the method to remove - * @deprecated Use removePrototypeMethod instead - */ - removePrototype(name) { - return CollectionExtensions.removePrototype(name); - }, - /** * @summary Remove a static method from the Mongo.Collection constructor. * @locus Anywhere diff --git a/packages/mongo/collection/collection_extensions.js b/packages/mongo/collection/collection_extensions.js index b7b8f881af..c259f9a4f2 100644 --- a/packages/mongo/collection/collection_extensions.js +++ b/packages/mongo/collection/collection_extensions.js @@ -40,14 +40,6 @@ CollectionExtensions = { this._prototypeMethods.set(name, method); }, - - /** - * Backwards compatibility alias for lai:collection-extensions - * @deprecated Use addPrototypeMethod instead - */ - addPrototype(name, method) { - return this.addPrototypeMethod(name, method); - }, /** * Add a static method to the Mongo.Collection constructor @@ -79,14 +71,6 @@ CollectionExtensions = { removePrototypeMethod(name) { this._prototypeMethods.delete(name); }, - - /** - * Backwards compatibility alias for lai:collection-extensions - * @deprecated Use removePrototypeMethod instead - */ - removePrototype(name) { - return this.removePrototypeMethod(name); - }, /** * Remove a static method diff --git a/packages/mongo/mongo.d.ts b/packages/mongo/mongo.d.ts index c97f5bb043..0d499ee588 100644 --- a/packages/mongo/mongo.d.ts +++ b/packages/mongo/mongo.d.ts @@ -131,14 +131,6 @@ export namespace Mongo { */ addPrototypeMethod(name: string, method: (this: Collection, ...args: any[]) => any): void; - /** - * Backwards compatibility alias for lai:collection-extensions - * @param name The name of the method to add - * @param method The method function, bound to the collection instance - * @deprecated Use addPrototypeMethod instead - */ - addPrototype(name: string, method: (this: Collection, ...args: any[]) => any): void; - /** * Add a static method to the Mongo.Collection constructor. * @param name The name of the static method to add @@ -158,13 +150,6 @@ export namespace Mongo { */ removePrototypeMethod(name: string): void; - /** - * Backwards compatibility alias for lai:collection-extensions - * @param name The name of the method to remove - * @deprecated Use removePrototypeMethod instead - */ - removePrototype(name: string): void; - /** * Remove a static method from the Mongo.Collection constructor. * @param name The name of the static method to remove @@ -597,14 +582,6 @@ export namespace Mongo { */ addPrototypeMethod(name: string, method: (this: Collection, ...args: any[]) => any): void; - /** - * Backwards compatibility alias for lai:collection-extensions - * @param name The name of the method to add - * @param method The method function, bound to the collection instance - * @deprecated Use addPrototypeMethod instead - */ - addPrototype(name: string, method: (this: Collection, ...args: any[]) => any): void; - /** * Add a static method to the Mongo.Collection constructor. * @param name The name of the static method to add @@ -624,13 +601,6 @@ export namespace Mongo { */ removePrototypeMethod(name: string): void; - /** - * Backwards compatibility alias for lai:collection-extensions - * @param name The name of the method to remove - * @deprecated Use removePrototypeMethod instead - */ - removePrototype(name: string): void; - /** * Remove a static method from the Mongo.Collection constructor. * @param name The name of the static method to remove From 60dfd25e84595d80795b3053cf8c46cddf4117b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 3 Sep 2025 09:33:21 +0200 Subject: [PATCH 016/195] add documentation for HtmlRspackPlugin configuration and others --- .../rspack-bundler-integration.md | 113 ++++++++++++++++-- 1 file changed, 103 insertions(+), 10 deletions(-) diff --git a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md index 7cf2f2d8a3..efcbf77643 100644 --- a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md +++ b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md @@ -75,6 +75,22 @@ Attempts were made to reuse the existing `.meteor/local` cache context instead o Use `.meteor/local` or folders that suggest internals or hidden content (e.g., starting with a dot). These affect debug visibility, file watching, final compilation, and inclusion in the Cordova bundle. ::: +### Replace build plugins + +Meteor build plugins extend the Meteor bundler by letting you handle new file types and process them for the final app bundle. They’ve commonly handled HTML templating, style files for Less or SCSS, CoffeeScript, and more, since the system allows third-party customization. + +However, Meteor’s build system solves the same problems as other bundlers, including Rspack. Build plugins are largely deprecated in favor of Rspack alternatives. Some plugins may still be useful if they don’t act directly on app files and do something Meteor-specific that can be preserved. + +Among the compatible plugins: +- zodern:types. Still compatible, automatically providing Meteor types for core and community packages. + +For others, please refer to the migration topics. +- [CSS, Less, and SCSS](#css-less-and-scss) (when using less, fourseven:scss) +- [Coffeescript](#coffeescript) (when using zodern:melte) +- [Svelte](#svelte) (when using zodern:melte) + +Please report your plugin usage as [GitHub issues](https://github.com/meteor/meteor/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) or [forum posts](https://forums.meteor.com/), so we can suggest an Rspack alternative or assess compatibility. + ## Custom `rspack.config.js` Meteor-Rspack projects can be customized using the `rspack.config.js` file, which is automatically available when installing the `rspack` package. @@ -107,16 +123,18 @@ export default defineConfig(Meteor => { You can use flags to control the final configuration based on the environment. The available flags are passed in the `Meteor` parameter. -| Flag | Type | Description | -| --------------- | ------- | -------------------------------------------------- | -| `isDevelopment` | boolean | True when running in development mode | -| `isProduction` | boolean | True when running in production mode | -| `isClient` | boolean | True when building or running client code | -| `isServer` | boolean | True when building or running server code | -| `isTest` | boolean | True when running in test mode | -| `isDebug` | boolean | True when debug mode is enabled | -| `isRun` | boolean | True when running the project with `meteor run` | -| `isBuild` | boolean | True when building the project with `meteor build` | +| Flag | Type | Description | +| ------------------ | -------- |-----------------------------------------------------------| +| `isDevelopment` | boolean | True when running in development mode | +| `isProduction` | boolean | True when running in production mode | +| `isClient` | boolean | True when building or running client code | +| `isServer` | boolean | True when building or running server code | +| `isTest` | boolean | True when running in test mode | +| `isDebug` | boolean | True when debug mode is enabled | +| `isRun` | boolean | True when running the project with `meteor run` | +| `isBuild` | boolean | True when building the project with `meteor build` | +| `swcConfigOptions` | object | Project-level SWC config available for reusing | +| `HtmlRspackPlugin` | function | Custom HtmlRspackPlugin function for extending the config | Some configurations in the Rspack config are reserved for the Meteor-Rspack setup to work, such as Rspack options inside the `entry` and `output` objects. These will trigger warnings if modified. All other settings can be overridden, giving you the flexibility to make any setup compatible with the modern bundler. @@ -286,6 +304,47 @@ Previous official support in the Meteor bundler was through [jorgenvatle:vite](h With Meteor-Rspack integration, you no longer need vite-related packages, so you should remove them from your project. ::: +### Coffeescript + +Meteor-Rspack supports CoffeeScript projects out of the box. To enable it, install the needed dependencies and add the configuration to Meteor’s rspack.config.js. + +[See the official Webpack and CoffeeScript integration guide](https://webpack.js.org/loaders/coffee-loader/#getting-started). Since Rspack is based on Webpack, the same setup applies. + +If you want to use SWC with CoffeeScript, combine `swc-loader` with `coffee-loader`. + +```bash +npm install --save-dev coffeescript swc-loader coffee-loader +``` + +In your `rspack.config.js` you would add something like: + +``` javascript +export default defineConfig(Meteor => { + return { + module: { + rules: [ + { + test: /\.coffee$/i, + use: [ + { + loader: 'swc-loader', + // perserve SWC config in the Meteor project level + options: Meteor.swcConfigOptions, + }, + { + loader: 'coffee-loader', + }, + ], + }, + ], + }, + resolve: { + extensions: ['.coffee'], + }, + }; +}); +``` + ### Svelte Meteor-Rspack supports Svelte projects out of the box. To enable it, install the required dependencies and add the new configuration to Meteor’s `rspack.config.js` file. @@ -306,6 +365,40 @@ Meteor-Rspack supports Tailwind projects out of the box. For details, check [the > Use `meteor create --tailwind` to start with a preconfigured Rspack Tailwind app. +### HtmlRspackPlugin + +Meteor-Rspack includes its own HtmlRspackPlugin, enabled by default to attach chunks and assets to the HTML skeleton. Meteor then uses this HTML to generate the final index file. + +If you want to customize HtmlRspackPlugin, add it to your `rspack.config.js` file: + +```javascript +export default defineConfig(Meteor => { + return { + plugins: [ + Meteor.HtmlRspackPlugin({ + meta: { + // Will generate: + viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no', + // Will generate: + 'theme-color': '#4285f4', + // Will generate: + 'Content-Security-Policy': { + 'http-equiv': 'Content-Security-Policy', + content: 'default-src https:', + }, + }, + }), + ], + }; +}); +``` + +This example adds meta tags to the HTML. For more options, see the [official Rspack and HTML integration guide](https://rspack.rs/plugins/rspack/html-rspack-plugin). + +:::warning +You can still use HTML files near your Meteor client entry point to define customizations (for example, `./client/main.html` will generate correctly and apply the contents you add). +::: + ## Benefits Meteor–Rspack integration sends your app code to Rspack to use modern bundler features. Meteor then uses Rspack’s output to handle Meteor-specific tasks (like Atmosphere package compilation) and create the final bundle. From c95bed0103b2165104edc4d959e466758e1ea9bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 3 Sep 2025 09:38:59 +0200 Subject: [PATCH 017/195] add package links and clarify plugin usage in Rspack integration docs --- .../modern-build-stack/rspack-bundler-integration.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md index efcbf77643..056ce5cc93 100644 --- a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md +++ b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md @@ -82,12 +82,14 @@ Meteor build plugins extend the Meteor bundler by letting you handle new file ty However, Meteor’s build system solves the same problems as other bundlers, including Rspack. Build plugins are largely deprecated in favor of Rspack alternatives. Some plugins may still be useful if they don’t act directly on app files and do something Meteor-specific that can be preserved. Among the compatible plugins: -- zodern:types. Still compatible, automatically providing Meteor types for core and community packages. +- [`zodern:types`](https://packosphere.com/zodern/types). Still compatible, automatically providing Meteor types for core and community packages. For others, please refer to the migration topics. -- [CSS, Less, and SCSS](#css-less-and-scss) (when using less, fourseven:scss) -- [Coffeescript](#coffeescript) (when using zodern:melte) -- [Svelte](#svelte) (when using zodern:melte) +- [CSS, Less, and SCSS](#css-less-and-scss) (when using [`less`](https://packosphere.com/meteor/less), [`fourseven:scss`](https://packosphere.com/fourseven/scss)) +- [Coffeescript](#coffeescript) (when using [`coffeescript`](https://packosphere.com/meteor/coffeescript)) +- [Svelte](#svelte) (when using [`zodern:melte`](https://packosphere.com/zodern/melte)) + +You can still use these plugins to handle files inside Meteor atmosphere packages. You only need consider Rspack alternative when it’s required for your app code, which will usually be the case. Please report your plugin usage as [GitHub issues](https://github.com/meteor/meteor/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) or [forum posts](https://forums.meteor.com/), so we can suggest an Rspack alternative or assess compatibility. From 3e3d4664d11ed51b63209874cd68bdf5782ced73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 3 Sep 2025 17:18:34 +0200 Subject: [PATCH 018/195] bump bundle_version --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index 756a5f9de5..b430d8af3c 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=22.18.0.22 +BUNDLE_VERSION=22.18.0.23 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From f8f780b51622f1695c3897f6a6cc03f877f7cd7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 3 Sep 2025 17:27:00 +0200 Subject: [PATCH 019/195] re-run checks From 4de7b01e35aaed4b08278e815beeb371d39d2ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 3 Sep 2025 18:12:55 +0200 Subject: [PATCH 020/195] =?UTF-8?q?Meteor=20version=20to=203.4-beta.0?= =?UTF-8?q?=C2=A0:comet:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/accounts-base/package.js | 2 +- packages/babel-compiler/package.js | 2 +- packages/boilerplate-generator/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minifier-js/package.js | 2 +- packages/shell-server/package.js | 2 +- packages/typescript/package.js | 2 +- .../admin/meteor-release-experimental.json | 2 +- .../generators/changelog/versions/3.4.0.md | 107 ++++++++++++++++++ 9 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 v3-docs/docs/generators/changelog/versions/3.4.0.md diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index 4190011694..11e14cc95b 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "A user account system", - version: "3.1.2", + version: "3.2.0-beta340.0", }); Package.onUse((api) => { diff --git a/packages/babel-compiler/package.js b/packages/babel-compiler/package.js index 2e69cdc028..7b544cf868 100644 --- a/packages/babel-compiler/package.js +++ b/packages/babel-compiler/package.js @@ -1,7 +1,7 @@ Package.describe({ name: "babel-compiler", summary: "Parser/transpiler for ECMAScript 2015+ syntax", - version: '7.12.2', + version: '7.13.0-beta340.0', }); Npm.depends({ diff --git a/packages/boilerplate-generator/package.js b/packages/boilerplate-generator/package.js index de1d8f8e40..4116aeebde 100644 --- a/packages/boilerplate-generator/package.js +++ b/packages/boilerplate-generator/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Generates the boilerplate html from program's manifest", - version: '2.0.2', + version: '2.1.0-beta340.0', }); Npm.depends({ diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 00beae7bc0..7f50d22e40 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "The Meteor command-line tool", - version: "3.3.1", + version: "3.4.0-beta.0", }); Package.includeTool(); diff --git a/packages/minifier-js/package.js b/packages/minifier-js/package.js index 788cf783b6..5c0911df1f 100644 --- a/packages/minifier-js/package.js +++ b/packages/minifier-js/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "JavaScript minifier", - version: '3.0.4', + version: '3.1.0-beta340.0', }); Npm.depends({ diff --git a/packages/shell-server/package.js b/packages/shell-server/package.js index 1dd7551714..96de08f4f3 100644 --- a/packages/shell-server/package.js +++ b/packages/shell-server/package.js @@ -1,6 +1,6 @@ Package.describe({ name: "shell-server", - version: '0.6.2', + version: '0.7.0-beta340.0', summary: "Server-side component of the `meteor shell` command.", documentation: "README.md" }); diff --git a/packages/typescript/package.js b/packages/typescript/package.js index 2a4372edf4..af3be4e71d 100644 --- a/packages/typescript/package.js +++ b/packages/typescript/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'typescript', - version: '5.6.6', + version: '5.7.0-beta340.0', summary: 'Compiler plugin that compiles TypeScript and ECMAScript in .ts and .tsx files', documentation: 'README.md', diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 2432e8857d..e87387c821 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "3.3.1-rc.2", + "version": "3.4-beta.0", "recommended": false, "official": false, "description": "Meteor experimental release" diff --git a/v3-docs/docs/generators/changelog/versions/3.4.0.md b/v3-docs/docs/generators/changelog/versions/3.4.0.md new file mode 100644 index 0000000000..9bbe3ffd0d --- /dev/null +++ b/v3-docs/docs/generators/changelog/versions/3.4.0.md @@ -0,0 +1,107 @@ +## v3.4.0, not yet + +### Highlights + +- **Meteor-Rspack Integration** + + - ⚡ New `rspack` package + + Orchestrates the full Rspack setup, including the development server and production builds. + + - 📦 New `@meteorjs/rspack` npm package + + Provides a default rspack.config.js. Applications can extend or override this configuration with their own. + + - 🛠️ New `tools-core` package + + Supplies runtime utilities for Meteor, designed to support this integration and future tool integrations. + + - 🔑 Core updates + + Enhanced Meteor’s core to support the Rspack integration. + + - ✅ Test suite additions + + Introduced tests for app skeletons and Meteor-Rspack features to ensure quality and reliability. + + - Adopting Rspack gives you a faster build experience + + - Adopting Rspack produces smaller bundle sizes through advanced tree shaking + + - Adopting Rspack lets you extend your app with modern setups and tooling + +All Merged PRs@[GitHub PRs 3.4](https://github.com/meteor/meteor/pulls?q=is%3Apr+is%3Amerged+base%3Arelease-3.4) + +#### Breaking Changes + +N/A + +#### Internal API changes + +N/A + +#### Migration Steps + +Please run the following command to update your project: + +```bash +meteor update --release 3.4-beta.0 +``` + +--- + +**Add this to your `package.json` to enable the new modern build stack:** + +```json +"meteor": { + "modern": true +} +``` + +Check out [the requirements for Meteor Bundler optimizations](https://deploy-preview-13910.docs.meteor.com/about/modern-build-stack/meteor-bundler-optimizations.html#requirements) on existing apps. + +**Add `rspack` package to enable the Rspack Bundler integration:** + +```bash +meteor add rspack-beta.x +``` + +> This package is added by default for new apps. + +Check out [the requirements for Rspack Bundler integration](https://deploy-preview-13910.docs.meteor.com/about/modern-build-stack/rspack-bundler-integration.html#requirements) on existing apps. + +### [📃 Modern Build Stack docs](https://deploy-preview-13910.docs.meteor.com/about/modern-build-stack.html) + +### [☄️ Meteor Bundler optimizations docs](https://deploy-preview-13910.docs.meteor.com/about/modern-build-stack/meteor-bundler-optimizations.html) + +### [⚡ Rspack Bundler integration docs](https://deploy-preview-13910.docs.meteor.com/about/modern-build-stack/rspack-bundler-integration.html) + +[Modern Build Stack docs](https://deploy-preview-13910.docs.meteor.com/about/modern-build-stack.html) + +If you find any issues, please report them to the [Meteor issues tracker](https://github.com/meteor/meteor). + +#### Bumped Meteor Packages + +- accounts-base@3.2.0-beta340.0 +- babel-compiler@7.13.0-beta340.0 +- boilerplate-generator@2.1.0-beta340.0 +- meteor-tool@3.4.0-beta.0 +- minifier-js@3.1.0-beta340.0 +- rspack@1.0.0-beta340.0 +- shell-server@0.7.0-beta340.0 +- tools-core@1.0.0-beta340.0 +- typescript@5.7.0-beta340.0 + +#### Bumped NPM Packages + +- @meteorjs/rspack@0.0.36 + +#### Special thanks to + +✨✨✨ + +- [@nachocodoner](https://github.com/nachocodoner) +- [@italojs](https://github.com/italojs) +- [@Grubba27](https://github.com/Grubba27) + +✨✨✨ From b7e707ef66e6c86bff75ba9e49d7426d4ba0d672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Wed, 3 Sep 2025 19:29:32 +0200 Subject: [PATCH 021/195] Update 3.4.0.md --- v3-docs/docs/generators/changelog/versions/3.4.0.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/v3-docs/docs/generators/changelog/versions/3.4.0.md b/v3-docs/docs/generators/changelog/versions/3.4.0.md index 9bbe3ffd0d..5abfb9f0bd 100644 --- a/v3-docs/docs/generators/changelog/versions/3.4.0.md +++ b/v3-docs/docs/generators/changelog/versions/3.4.0.md @@ -76,8 +76,6 @@ Check out [the requirements for Rspack Bundler integration](https://deploy-previ ### [⚡ Rspack Bundler integration docs](https://deploy-preview-13910.docs.meteor.com/about/modern-build-stack/rspack-bundler-integration.html) -[Modern Build Stack docs](https://deploy-preview-13910.docs.meteor.com/about/modern-build-stack.html) - If you find any issues, please report them to the [Meteor issues tracker](https://github.com/meteor/meteor). #### Bumped Meteor Packages From fdc0cf1ddba5c187e82364c6d1f488c031cd831c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 11:11:47 +0200 Subject: [PATCH 022/195] dont use @swc/helpers in the server context as node support already modern syntax --- npm-packages/meteor-rspack/rspack.config.js | 4 +++- packages/babel-compiler/babel-compiler.js | 4 +++- tools/modern-tests/apps/react-router/package.json | 1 + tools/modern-tests/apps/react-router/server/main.js | 2 ++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/npm-packages/meteor-rspack/rspack.config.js b/npm-packages/meteor-rspack/rspack.config.js index 6353ea1c74..da4a06c491 100644 --- a/npm-packages/meteor-rspack/rspack.config.js +++ b/npm-packages/meteor-rspack/rspack.config.js @@ -118,6 +118,7 @@ export default function (inMeteor = {}, argv = {}) { const isDev = !!Meteor.isDevelopment || !isProd; const isTest = !!Meteor.isTest; const isClient = !!Meteor.isClient; + const isServer = !!Meteor.isServer; const isRun = !!Meteor.isRun; const isReactEnabled = !!Meteor.isReactEnabled; const isTestModule = !!Meteor.isTestModule; @@ -203,12 +204,13 @@ export default function (inMeteor = {}, argv = {}) { console.log('[i] Meteor flags:', Meteor); } + const enableSwcExternalHelpers = !isServer && swcExternalHelpers; const isDevEnvironment = isRun && isDev && !isTest && !isNative; const swcConfigRule = createSwcConfig({ isTypescriptEnabled, isJsxEnabled, isTsxEnabled, - externalHelpers: swcExternalHelpers, + externalHelpers: enableSwcExternalHelpers, isDevEnvironment, }); // Expose swc config to use in custom configs diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index 860061818a..2cf561970d 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -281,7 +281,8 @@ BCp.processOneFileForTarget = function (inputFile, source) { const features = Object.assign({}, this.extraFeatures); const arch = inputFile.getArch(); - if (arch.startsWith("os.")) { + const isNodeTarget = arch.startsWith("os."); + if (isNodeTarget) { // Start with a much simpler set of Babel presets and plugins if // we're compiling for Node 8. features.nodeMajorVersion = parseInt(process.versions.node, 10); @@ -355,6 +356,7 @@ BCp.processOneFileForTarget = function (inputFile, source) { tsx: hasTSXSupport, }, ...(hasSwcHelpersAvailable && + !isNodeTarget && (packageName == null || !['modules-runtime'].includes(packageName)) && { externalHelpers: true, diff --git a/tools/modern-tests/apps/react-router/package.json b/tools/modern-tests/apps/react-router/package.json index dbbf46f446..8d19762364 100644 --- a/tools/modern-tests/apps/react-router/package.json +++ b/tools/modern-tests/apps/react-router/package.json @@ -8,6 +8,7 @@ "visualize": "meteor --production --extra-packages bundle-visualizer" }, "dependencies": { + "@aws-sdk/client-s3": "^3.879.0", "@babel/runtime": "^7.23.5", "@modelcontextprotocol/sdk": "^1.17.3", "@swc/helpers": "^0.5.17", diff --git a/tools/modern-tests/apps/react-router/server/main.js b/tools/modern-tests/apps/react-router/server/main.js index 6beb7c30b7..813507d906 100644 --- a/tools/modern-tests/apps/react-router/server/main.js +++ b/tools/modern-tests/apps/react-router/server/main.js @@ -4,8 +4,10 @@ import { LinksCollection } from '/imports/api/links'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import '@helper/alias'; import ReactAlias from '@react/alias'; +import { S3Client } from '@aws-sdk/client-s3'; console.log('@react/alias loaded', ReactAlias.version); +console.log('S3client loaded', !!S3Client); async function insertLink({ title, url }) { await LinksCollection.insertAsync({ title, url, createdAt: new Date() }); From 039d0eaf9535b7f80c79f7ee2fe6a98894fb7732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 11:18:14 +0200 Subject: [PATCH 023/195] require externals to ensure proper imports of some edge cases --- npm-packages/meteor-rspack/rspack.config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/npm-packages/meteor-rspack/rspack.config.js b/npm-packages/meteor-rspack/rspack.config.js index da4a06c491..8af1bc5f67 100644 --- a/npm-packages/meteor-rspack/rspack.config.js +++ b/npm-packages/meteor-rspack/rspack.config.js @@ -250,7 +250,7 @@ export default function (inMeteor = {}, argv = {}) { lastImports: [`./${outputFilename}`], }), }), - enableGlobalPolyfill: isDevEnvironment, + enableGlobalPolyfill: isDevEnvironment && !isServer, }); const rsdoctorModule = isBundleVisualizerEnabled @@ -390,6 +390,7 @@ export default function (inMeteor = {}, argv = {}) { }, externals, plugins: [ + requireExternalsPlugin, new DefinePlugin( isTest && (isTestModule || isTestEager) ? { From d919bd1de51bc7d9587cfb87a7e8bd20d2fd3203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 11:33:43 +0200 Subject: [PATCH 024/195] bump @meteorjs/rspack to version 0.0.37 --- npm-packages/meteor-rspack/package-lock.json | 4 ++-- npm-packages/meteor-rspack/package.json | 2 +- packages/rspack/lib/constants.js | 2 +- tools/modern-tests/apps/vue/package.json | 2 +- tools/static-assets/skel-apollo/package.json | 2 +- tools/static-assets/skel-blaze/package.json | 2 +- tools/static-assets/skel-chakra-ui/package.json | 2 +- tools/static-assets/skel-full/package.json | 2 +- tools/static-assets/skel-react/package.json | 2 +- tools/static-assets/skel-solid/package.json | 2 +- tools/static-assets/skel-svelte/package.json | 2 +- tools/static-assets/skel-tailwind/package.json | 2 +- tools/static-assets/skel-typescript/package.json | 2 +- tools/static-assets/skel-vue/package.json | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/npm-packages/meteor-rspack/package-lock.json b/npm-packages/meteor-rspack/package-lock.json index 4212d497e0..dc8ad16ba2 100644 --- a/npm-packages/meteor-rspack/package-lock.json +++ b/npm-packages/meteor-rspack/package-lock.json @@ -1,12 +1,12 @@ { "name": "@meteorjs/rspack", - "version": "0.0.36", + "version": "0.0.37", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@meteorjs/rspack", - "version": "0.0.36", + "version": "0.0.37", "license": "ISC", "dependencies": { "ignore-loader": "^0.1.2", diff --git a/npm-packages/meteor-rspack/package.json b/npm-packages/meteor-rspack/package.json index ae82b79cbe..54d284ce2d 100644 --- a/npm-packages/meteor-rspack/package.json +++ b/npm-packages/meteor-rspack/package.json @@ -1,6 +1,6 @@ { "name": "@meteorjs/rspack", - "version": "0.0.36", + "version": "0.0.37", "description": "Configuration logic for using Rspack in Meteor projects", "main": "index.js", "type": "module", diff --git a/packages/rspack/lib/constants.js b/packages/rspack/lib/constants.js index be6612be89..38feda447c 100644 --- a/packages/rspack/lib/constants.js +++ b/packages/rspack/lib/constants.js @@ -5,7 +5,7 @@ export const DEFAULT_RSPACK_VERSION = '1.5.0'; -export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.36'; +export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.37'; export const DEFAULT_METEOR_RSPACK_REACT_HMR_VERSION = '1.4.3'; diff --git a/tools/modern-tests/apps/vue/package.json b/tools/modern-tests/apps/vue/package.json index 8bc5421981..183e38bb61 100644 --- a/tools/modern-tests/apps/vue/package.json +++ b/tools/modern-tests/apps/vue/package.json @@ -17,7 +17,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rspack/cli": "^1.4.8", "@rspack/core": "^1.4.8", "@tailwindcss/postcss": "^4.1.12", diff --git a/tools/static-assets/skel-apollo/package.json b/tools/static-assets/skel-apollo/package.json index 8b34c505a0..064371700a 100644 --- a/tools/static-assets/skel-apollo/package.json +++ b/tools/static-assets/skel-apollo/package.json @@ -20,7 +20,7 @@ "devDependencies": { "@graphql-tools/webpack-loader": "^7.0.0", "@rsdoctor/rspack-plugin": "^1.2.3", - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", "@rspack/plugin-react-refresh": "^1.4.3", diff --git a/tools/static-assets/skel-blaze/package.json b/tools/static-assets/skel-blaze/package.json index 7ea28f6ab9..c850328fab 100644 --- a/tools/static-assets/skel-blaze/package.json +++ b/tools/static-assets/skel-blaze/package.json @@ -14,7 +14,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-chakra-ui/package.json b/tools/static-assets/skel-chakra-ui/package.json index d0fd93c897..c6c94b3467 100644 --- a/tools/static-assets/skel-chakra-ui/package.json +++ b/tools/static-assets/skel-chakra-ui/package.json @@ -21,7 +21,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-full/package.json b/tools/static-assets/skel-full/package.json index 99b2e31efa..28c4f074c6 100644 --- a/tools/static-assets/skel-full/package.json +++ b/tools/static-assets/skel-full/package.json @@ -12,7 +12,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-react/package.json b/tools/static-assets/skel-react/package.json index faa5d657c6..95d8231697 100644 --- a/tools/static-assets/skel-react/package.json +++ b/tools/static-assets/skel-react/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-solid/package.json b/tools/static-assets/skel-solid/package.json index a71f81a14d..265ab4de32 100644 --- a/tools/static-assets/skel-solid/package.json +++ b/tools/static-assets/skel-solid/package.json @@ -14,7 +14,7 @@ "picocolors": "^1.1.1" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-svelte/package.json b/tools/static-assets/skel-svelte/package.json index ac079c09c7..d441d9aed9 100644 --- a/tools/static-assets/skel-svelte/package.json +++ b/tools/static-assets/skel-svelte/package.json @@ -13,7 +13,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-tailwind/package.json b/tools/static-assets/skel-tailwind/package.json index 10b1b30628..d8a5ebc3dd 100644 --- a/tools/static-assets/skel-tailwind/package.json +++ b/tools/static-assets/skel-tailwind/package.json @@ -16,7 +16,7 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-typescript/package.json b/tools/static-assets/skel-typescript/package.json index b765302749..3d6d79f1b6 100644 --- a/tools/static-assets/skel-typescript/package.json +++ b/tools/static-assets/skel-typescript/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-vue/package.json b/tools/static-assets/skel-vue/package.json index b1a8dd36ad..4b663ce882 100644 --- a/tools/static-assets/skel-vue/package.json +++ b/tools/static-assets/skel-vue/package.json @@ -17,7 +17,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.36", + "@meteorjs/rspack": "^0.0.37", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", From c575cd7843ac8e6d705227e83fa51b8e49fd8a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 11:56:44 +0200 Subject: [PATCH 025/195] bumps @meteorjs/rspack to version 0.0.38 --- npm-packages/meteor-rspack/package-lock.json | 4 ++-- npm-packages/meteor-rspack/package.json | 2 +- npm-packages/meteor-rspack/rspack.config.js | 22 ++++++++++--------- packages/rspack/lib/constants.js | 2 +- tools/modern-tests/apps/vue/package.json | 2 +- tools/static-assets/skel-apollo/package.json | 2 +- tools/static-assets/skel-blaze/package.json | 2 +- .../static-assets/skel-chakra-ui/package.json | 2 +- tools/static-assets/skel-full/package.json | 2 +- tools/static-assets/skel-react/package.json | 2 +- tools/static-assets/skel-solid/package.json | 2 +- tools/static-assets/skel-svelte/package.json | 2 +- .../static-assets/skel-tailwind/package.json | 2 +- .../skel-typescript/package.json | 2 +- tools/static-assets/skel-vue/package.json | 2 +- 15 files changed, 27 insertions(+), 25 deletions(-) diff --git a/npm-packages/meteor-rspack/package-lock.json b/npm-packages/meteor-rspack/package-lock.json index dc8ad16ba2..9f2d8d1853 100644 --- a/npm-packages/meteor-rspack/package-lock.json +++ b/npm-packages/meteor-rspack/package-lock.json @@ -1,12 +1,12 @@ { "name": "@meteorjs/rspack", - "version": "0.0.37", + "version": "0.0.38", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@meteorjs/rspack", - "version": "0.0.37", + "version": "0.0.38", "license": "ISC", "dependencies": { "ignore-loader": "^0.1.2", diff --git a/npm-packages/meteor-rspack/package.json b/npm-packages/meteor-rspack/package.json index 54d284ce2d..72b10be36a 100644 --- a/npm-packages/meteor-rspack/package.json +++ b/npm-packages/meteor-rspack/package.json @@ -1,6 +1,6 @@ { "name": "@meteorjs/rspack", - "version": "0.0.37", + "version": "0.0.38", "description": "Configuration logic for using Rspack in Meteor projects", "main": "index.js", "type": "module", diff --git a/npm-packages/meteor-rspack/rspack.config.js b/npm-packages/meteor-rspack/rspack.config.js index 8af1bc5f67..8f5bbb55e6 100644 --- a/npm-packages/meteor-rspack/rspack.config.js +++ b/npm-packages/meteor-rspack/rspack.config.js @@ -120,6 +120,7 @@ export default function (inMeteor = {}, argv = {}) { const isClient = !!Meteor.isClient; const isServer = !!Meteor.isServer; const isRun = !!Meteor.isRun; + const isBuild = !!Meteor.isBuild; const isReactEnabled = !!Meteor.isReactEnabled; const isTestModule = !!Meteor.isTestModule; const isTestEager = !!Meteor.isTestEager; @@ -263,6 +264,14 @@ export default function (inMeteor = {}, argv = {}) { }), ] : []; + const bannerPluginConfig = !isBuild + ? [ + new BannerPlugin({ + banner: bannerOutput, + entryOnly: true, + }), + ] + : []; const clientNameConfig = `[${(isTest && 'test-') || ''}${ (isTestModule && 'module') || 'client' @@ -324,10 +333,7 @@ export default function (inMeteor = {}, argv = {}) { 'Meteor.isDevelopment': JSON.stringify(isDev), 'Meteor.isProduction': JSON.stringify(isProd), }), - new BannerPlugin({ - banner: bannerOutput, - entryOnly: true, - }), + ...bannerPluginConfig, Meteor.HtmlRspackPlugin(), ...doctorPluginConfig, ], @@ -390,7 +396,6 @@ export default function (inMeteor = {}, argv = {}) { }, externals, plugins: [ - requireExternalsPlugin, new DefinePlugin( isTest && (isTestModule || isTestEager) ? { @@ -407,11 +412,8 @@ export default function (inMeteor = {}, argv = {}) { 'Meteor.isProduction': JSON.stringify(isProd), }, ), - new BannerPlugin({ - banner: bannerOutput, - entryOnly: true, - }), - isTestModule && requireExternalsPlugin, + ...bannerPluginConfig, + requireExternalsPlugin, ...doctorPluginConfig, ], watchOptions, diff --git a/packages/rspack/lib/constants.js b/packages/rspack/lib/constants.js index 38feda447c..a98ad7f845 100644 --- a/packages/rspack/lib/constants.js +++ b/packages/rspack/lib/constants.js @@ -5,7 +5,7 @@ export const DEFAULT_RSPACK_VERSION = '1.5.0'; -export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.37'; +export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.38'; export const DEFAULT_METEOR_RSPACK_REACT_HMR_VERSION = '1.4.3'; diff --git a/tools/modern-tests/apps/vue/package.json b/tools/modern-tests/apps/vue/package.json index 183e38bb61..22ab54e0e9 100644 --- a/tools/modern-tests/apps/vue/package.json +++ b/tools/modern-tests/apps/vue/package.json @@ -17,7 +17,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rspack/cli": "^1.4.8", "@rspack/core": "^1.4.8", "@tailwindcss/postcss": "^4.1.12", diff --git a/tools/static-assets/skel-apollo/package.json b/tools/static-assets/skel-apollo/package.json index 064371700a..a264c46be1 100644 --- a/tools/static-assets/skel-apollo/package.json +++ b/tools/static-assets/skel-apollo/package.json @@ -20,7 +20,7 @@ "devDependencies": { "@graphql-tools/webpack-loader": "^7.0.0", "@rsdoctor/rspack-plugin": "^1.2.3", - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", "@rspack/plugin-react-refresh": "^1.4.3", diff --git a/tools/static-assets/skel-blaze/package.json b/tools/static-assets/skel-blaze/package.json index c850328fab..12f44bedab 100644 --- a/tools/static-assets/skel-blaze/package.json +++ b/tools/static-assets/skel-blaze/package.json @@ -14,7 +14,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-chakra-ui/package.json b/tools/static-assets/skel-chakra-ui/package.json index c6c94b3467..0d345e9740 100644 --- a/tools/static-assets/skel-chakra-ui/package.json +++ b/tools/static-assets/skel-chakra-ui/package.json @@ -21,7 +21,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-full/package.json b/tools/static-assets/skel-full/package.json index 28c4f074c6..db8bd19db8 100644 --- a/tools/static-assets/skel-full/package.json +++ b/tools/static-assets/skel-full/package.json @@ -12,7 +12,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-react/package.json b/tools/static-assets/skel-react/package.json index 95d8231697..08fa6c4961 100644 --- a/tools/static-assets/skel-react/package.json +++ b/tools/static-assets/skel-react/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-solid/package.json b/tools/static-assets/skel-solid/package.json index 265ab4de32..1881464082 100644 --- a/tools/static-assets/skel-solid/package.json +++ b/tools/static-assets/skel-solid/package.json @@ -14,7 +14,7 @@ "picocolors": "^1.1.1" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-svelte/package.json b/tools/static-assets/skel-svelte/package.json index d441d9aed9..245502a7d5 100644 --- a/tools/static-assets/skel-svelte/package.json +++ b/tools/static-assets/skel-svelte/package.json @@ -13,7 +13,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-tailwind/package.json b/tools/static-assets/skel-tailwind/package.json index d8a5ebc3dd..ad5faad51e 100644 --- a/tools/static-assets/skel-tailwind/package.json +++ b/tools/static-assets/skel-tailwind/package.json @@ -16,7 +16,7 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-typescript/package.json b/tools/static-assets/skel-typescript/package.json index 3d6d79f1b6..fae28d890b 100644 --- a/tools/static-assets/skel-typescript/package.json +++ b/tools/static-assets/skel-typescript/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-vue/package.json b/tools/static-assets/skel-vue/package.json index 4b663ce882..5947ac91db 100644 --- a/tools/static-assets/skel-vue/package.json +++ b/tools/static-assets/skel-vue/package.json @@ -17,7 +17,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.37", + "@meteorjs/rspack": "^0.0.38", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", From 7a09a37e0495d66be21b2f3169219636d9783e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 12:43:30 +0200 Subject: [PATCH 026/195] disable test case temporary --- tools/modern-tests/apps/react-router/package.json | 1 - tools/modern-tests/apps/react-router/server/main.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/tools/modern-tests/apps/react-router/package.json b/tools/modern-tests/apps/react-router/package.json index 8d19762364..dbbf46f446 100644 --- a/tools/modern-tests/apps/react-router/package.json +++ b/tools/modern-tests/apps/react-router/package.json @@ -8,7 +8,6 @@ "visualize": "meteor --production --extra-packages bundle-visualizer" }, "dependencies": { - "@aws-sdk/client-s3": "^3.879.0", "@babel/runtime": "^7.23.5", "@modelcontextprotocol/sdk": "^1.17.3", "@swc/helpers": "^0.5.17", diff --git a/tools/modern-tests/apps/react-router/server/main.js b/tools/modern-tests/apps/react-router/server/main.js index 813507d906..6beb7c30b7 100644 --- a/tools/modern-tests/apps/react-router/server/main.js +++ b/tools/modern-tests/apps/react-router/server/main.js @@ -4,10 +4,8 @@ import { LinksCollection } from '/imports/api/links'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import '@helper/alias'; import ReactAlias from '@react/alias'; -import { S3Client } from '@aws-sdk/client-s3'; console.log('@react/alias loaded', ReactAlias.version); -console.log('S3client loaded', !!S3Client); async function insertLink({ title, url }) { await LinksCollection.insertAsync({ title, url, createdAt: new Date() }); From aac0307d5b8e4ea106cbe969cafabcb6a5d71b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 14:05:53 +0200 Subject: [PATCH 027/195] support resolve.extensions to get first the customized values --- npm-packages/meteor-rspack/lib/mergeRulesSplitOverlap.js | 8 ++++++++ npm-packages/meteor-rspack/package-lock.json | 4 ++-- npm-packages/meteor-rspack/package.json | 2 +- packages/rspack/lib/constants.js | 2 +- tools/modern-tests/apps/react-router/rspack.config.js | 1 + tools/modern-tests/apps/react-router/server/main.js | 1 + .../apps/react-router/server/resolve-extensions/first.jsx | 1 + .../apps/react-router/server/resolve-extensions/first.tsx | 1 + tools/modern-tests/apps/vue/package.json | 2 +- tools/modern-tests/react-router.test.js | 2 ++ tools/static-assets/skel-apollo/package.json | 2 +- tools/static-assets/skel-blaze/package.json | 2 +- tools/static-assets/skel-chakra-ui/package.json | 2 +- tools/static-assets/skel-full/package.json | 2 +- tools/static-assets/skel-react/package.json | 2 +- tools/static-assets/skel-solid/package.json | 2 +- tools/static-assets/skel-svelte/package.json | 2 +- tools/static-assets/skel-tailwind/package.json | 2 +- tools/static-assets/skel-typescript/package.json | 2 +- tools/static-assets/skel-vue/package.json | 2 +- 20 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 tools/modern-tests/apps/react-router/server/resolve-extensions/first.jsx create mode 100644 tools/modern-tests/apps/react-router/server/resolve-extensions/first.tsx diff --git a/npm-packages/meteor-rspack/lib/mergeRulesSplitOverlap.js b/npm-packages/meteor-rspack/lib/mergeRulesSplitOverlap.js index 2b8e946939..5fd2e03dfb 100644 --- a/npm-packages/meteor-rspack/lib/mergeRulesSplitOverlap.js +++ b/npm-packages/meteor-rspack/lib/mergeRulesSplitOverlap.js @@ -267,6 +267,14 @@ export function mergeSplitOverlap(...configs) { return splitOverlapRulesMerge(aRules, bRules); } + // Ensure custom extensions first + if (key === 'resolve.extensions') { + const aRules = Array.isArray(a) ? a : []; + const bRules = Array.isArray(b) ? b : []; + const merged = [...bRules, ...aRules]; + return [...new Set(merged)]; + } + // Handle plugins uniqueness if (key === 'plugins') { return unique( diff --git a/npm-packages/meteor-rspack/package-lock.json b/npm-packages/meteor-rspack/package-lock.json index 9f2d8d1853..fdfd91b2bd 100644 --- a/npm-packages/meteor-rspack/package-lock.json +++ b/npm-packages/meteor-rspack/package-lock.json @@ -1,12 +1,12 @@ { "name": "@meteorjs/rspack", - "version": "0.0.38", + "version": "0.0.39", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@meteorjs/rspack", - "version": "0.0.38", + "version": "0.0.39", "license": "ISC", "dependencies": { "ignore-loader": "^0.1.2", diff --git a/npm-packages/meteor-rspack/package.json b/npm-packages/meteor-rspack/package.json index 72b10be36a..7c180deb22 100644 --- a/npm-packages/meteor-rspack/package.json +++ b/npm-packages/meteor-rspack/package.json @@ -1,6 +1,6 @@ { "name": "@meteorjs/rspack", - "version": "0.0.38", + "version": "0.0.39", "description": "Configuration logic for using Rspack in Meteor projects", "main": "index.js", "type": "module", diff --git a/packages/rspack/lib/constants.js b/packages/rspack/lib/constants.js index a98ad7f845..dc031f6113 100644 --- a/packages/rspack/lib/constants.js +++ b/packages/rspack/lib/constants.js @@ -5,7 +5,7 @@ export const DEFAULT_RSPACK_VERSION = '1.5.0'; -export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.38'; +export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.39'; export const DEFAULT_METEOR_RSPACK_REACT_HMR_VERSION = '1.4.3'; diff --git a/tools/modern-tests/apps/react-router/rspack.config.js b/tools/modern-tests/apps/react-router/rspack.config.js index 330dc69908..cd2548629d 100644 --- a/tools/modern-tests/apps/react-router/rspack.config.js +++ b/tools/modern-tests/apps/react-router/rspack.config.js @@ -17,6 +17,7 @@ export default defineConfig(Meteor => { '@helper/alias': '/imports/helpers/alias.js', '@react/alias': '/node_modules/react', }, + extensions: ['.jsx'], }, module: { rules: [ diff --git a/tools/modern-tests/apps/react-router/server/main.js b/tools/modern-tests/apps/react-router/server/main.js index 6beb7c30b7..cdb3e6ccfd 100644 --- a/tools/modern-tests/apps/react-router/server/main.js +++ b/tools/modern-tests/apps/react-router/server/main.js @@ -4,6 +4,7 @@ import { LinksCollection } from '/imports/api/links'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import '@helper/alias'; import ReactAlias from '@react/alias'; +import './resolve-extensions/first'; console.log('@react/alias loaded', ReactAlias.version); diff --git a/tools/modern-tests/apps/react-router/server/resolve-extensions/first.jsx b/tools/modern-tests/apps/react-router/server/resolve-extensions/first.jsx new file mode 100644 index 0000000000..f4a34cd479 --- /dev/null +++ b/tools/modern-tests/apps/react-router/server/resolve-extensions/first.jsx @@ -0,0 +1 @@ +console.log('first.jsx loaded'); diff --git a/tools/modern-tests/apps/react-router/server/resolve-extensions/first.tsx b/tools/modern-tests/apps/react-router/server/resolve-extensions/first.tsx new file mode 100644 index 0000000000..fafcfe4cf4 --- /dev/null +++ b/tools/modern-tests/apps/react-router/server/resolve-extensions/first.tsx @@ -0,0 +1 @@ +console.log('first.tsx loaded'); diff --git a/tools/modern-tests/apps/vue/package.json b/tools/modern-tests/apps/vue/package.json index 22ab54e0e9..3c0005cfac 100644 --- a/tools/modern-tests/apps/vue/package.json +++ b/tools/modern-tests/apps/vue/package.json @@ -17,7 +17,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rspack/cli": "^1.4.8", "@rspack/core": "^1.4.8", "@tailwindcss/postcss": "^4.1.12", diff --git a/tools/modern-tests/react-router.test.js b/tools/modern-tests/react-router.test.js index 47c42fcf08..0c811ae9a0 100644 --- a/tools/modern-tests/react-router.test.js +++ b/tools/modern-tests/react-router.test.js @@ -35,6 +35,8 @@ describe('ReactRouter App Bundling /', () => { await waitForMeteorOutput(result.outputLines, /.*default-package loaded.*/); // custom-package loading await waitForMeteorOutput(result.outputLines, /.*custom-package loaded.*/); + // resolve.extensions loading + await waitForMeteorOutput(result.outputLines, /.*first\.jsx loaded.*/); }, afterRunRebuildClient: async ({ allConsoleLogs }) => { // Check for HMR output as enabled by default diff --git a/tools/static-assets/skel-apollo/package.json b/tools/static-assets/skel-apollo/package.json index a264c46be1..a8de3e24ed 100644 --- a/tools/static-assets/skel-apollo/package.json +++ b/tools/static-assets/skel-apollo/package.json @@ -20,7 +20,7 @@ "devDependencies": { "@graphql-tools/webpack-loader": "^7.0.0", "@rsdoctor/rspack-plugin": "^1.2.3", - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", "@rspack/plugin-react-refresh": "^1.4.3", diff --git a/tools/static-assets/skel-blaze/package.json b/tools/static-assets/skel-blaze/package.json index 12f44bedab..66c3df2b4e 100644 --- a/tools/static-assets/skel-blaze/package.json +++ b/tools/static-assets/skel-blaze/package.json @@ -14,7 +14,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-chakra-ui/package.json b/tools/static-assets/skel-chakra-ui/package.json index 0d345e9740..78e265cca8 100644 --- a/tools/static-assets/skel-chakra-ui/package.json +++ b/tools/static-assets/skel-chakra-ui/package.json @@ -21,7 +21,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-full/package.json b/tools/static-assets/skel-full/package.json index db8bd19db8..18430645b1 100644 --- a/tools/static-assets/skel-full/package.json +++ b/tools/static-assets/skel-full/package.json @@ -12,7 +12,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-react/package.json b/tools/static-assets/skel-react/package.json index 08fa6c4961..6a9ef2e03b 100644 --- a/tools/static-assets/skel-react/package.json +++ b/tools/static-assets/skel-react/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-solid/package.json b/tools/static-assets/skel-solid/package.json index 1881464082..4541d9e598 100644 --- a/tools/static-assets/skel-solid/package.json +++ b/tools/static-assets/skel-solid/package.json @@ -14,7 +14,7 @@ "picocolors": "^1.1.1" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-svelte/package.json b/tools/static-assets/skel-svelte/package.json index 245502a7d5..bea3aa7ac4 100644 --- a/tools/static-assets/skel-svelte/package.json +++ b/tools/static-assets/skel-svelte/package.json @@ -13,7 +13,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-tailwind/package.json b/tools/static-assets/skel-tailwind/package.json index ad5faad51e..83074c7738 100644 --- a/tools/static-assets/skel-tailwind/package.json +++ b/tools/static-assets/skel-tailwind/package.json @@ -16,7 +16,7 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-typescript/package.json b/tools/static-assets/skel-typescript/package.json index fae28d890b..7d55e4e5bf 100644 --- a/tools/static-assets/skel-typescript/package.json +++ b/tools/static-assets/skel-typescript/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-vue/package.json b/tools/static-assets/skel-vue/package.json index 5947ac91db..c8517749e3 100644 --- a/tools/static-assets/skel-vue/package.json +++ b/tools/static-assets/skel-vue/package.json @@ -17,7 +17,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.38", + "@meteorjs/rspack": "^0.0.39", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", From da31ef676c87f022fb8ab002ea96891557c2ba72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 14:59:22 +0200 Subject: [PATCH 028/195] enforce proper mode for production --- npm-packages/meteor-rspack/rspack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm-packages/meteor-rspack/rspack.config.js b/npm-packages/meteor-rspack/rspack.config.js index 8f5bbb55e6..e8934cec5c 100644 --- a/npm-packages/meteor-rspack/rspack.config.js +++ b/npm-packages/meteor-rspack/rspack.config.js @@ -280,7 +280,7 @@ export default function (inMeteor = {}, argv = {}) { let clientConfig = { name: clientNameConfig, target: 'web', - mode: 'development', + mode, entry: path.resolve(process.cwd(), buildContext, entryPath), output: { path: clientOutputDir, From 043ddd8536cfa48e559d6d9b3882f162b75cb98a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 15:01:41 +0200 Subject: [PATCH 029/195] bump @meteorjs/rspack version 0.0.40 --- npm-packages/meteor-rspack/package-lock.json | 4 ++-- npm-packages/meteor-rspack/package.json | 2 +- packages/rspack/lib/constants.js | 2 +- tools/modern-tests/apps/vue/package.json | 2 +- tools/static-assets/skel-apollo/package.json | 2 +- tools/static-assets/skel-blaze/package.json | 2 +- tools/static-assets/skel-chakra-ui/package.json | 2 +- tools/static-assets/skel-full/package.json | 2 +- tools/static-assets/skel-react/package.json | 2 +- tools/static-assets/skel-solid/package.json | 2 +- tools/static-assets/skel-svelte/package.json | 2 +- tools/static-assets/skel-tailwind/package.json | 2 +- tools/static-assets/skel-typescript/package.json | 2 +- tools/static-assets/skel-vue/package.json | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) diff --git a/npm-packages/meteor-rspack/package-lock.json b/npm-packages/meteor-rspack/package-lock.json index fdfd91b2bd..14092166c2 100644 --- a/npm-packages/meteor-rspack/package-lock.json +++ b/npm-packages/meteor-rspack/package-lock.json @@ -1,12 +1,12 @@ { "name": "@meteorjs/rspack", - "version": "0.0.39", + "version": "0.0.40", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@meteorjs/rspack", - "version": "0.0.39", + "version": "0.0.40", "license": "ISC", "dependencies": { "ignore-loader": "^0.1.2", diff --git a/npm-packages/meteor-rspack/package.json b/npm-packages/meteor-rspack/package.json index 7c180deb22..5aaf6296b3 100644 --- a/npm-packages/meteor-rspack/package.json +++ b/npm-packages/meteor-rspack/package.json @@ -1,6 +1,6 @@ { "name": "@meteorjs/rspack", - "version": "0.0.39", + "version": "0.0.40", "description": "Configuration logic for using Rspack in Meteor projects", "main": "index.js", "type": "module", diff --git a/packages/rspack/lib/constants.js b/packages/rspack/lib/constants.js index dc031f6113..7d63269724 100644 --- a/packages/rspack/lib/constants.js +++ b/packages/rspack/lib/constants.js @@ -5,7 +5,7 @@ export const DEFAULT_RSPACK_VERSION = '1.5.0'; -export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.39'; +export const DEFAULT_METEOR_RSPACK_VERSION = '0.0.40'; export const DEFAULT_METEOR_RSPACK_REACT_HMR_VERSION = '1.4.3'; diff --git a/tools/modern-tests/apps/vue/package.json b/tools/modern-tests/apps/vue/package.json index 3c0005cfac..1d34694d27 100644 --- a/tools/modern-tests/apps/vue/package.json +++ b/tools/modern-tests/apps/vue/package.json @@ -17,7 +17,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rspack/cli": "^1.4.8", "@rspack/core": "^1.4.8", "@tailwindcss/postcss": "^4.1.12", diff --git a/tools/static-assets/skel-apollo/package.json b/tools/static-assets/skel-apollo/package.json index a8de3e24ed..c3f8a702f3 100644 --- a/tools/static-assets/skel-apollo/package.json +++ b/tools/static-assets/skel-apollo/package.json @@ -20,7 +20,7 @@ "devDependencies": { "@graphql-tools/webpack-loader": "^7.0.0", "@rsdoctor/rspack-plugin": "^1.2.3", - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", "@rspack/plugin-react-refresh": "^1.4.3", diff --git a/tools/static-assets/skel-blaze/package.json b/tools/static-assets/skel-blaze/package.json index 66c3df2b4e..ae64482e78 100644 --- a/tools/static-assets/skel-blaze/package.json +++ b/tools/static-assets/skel-blaze/package.json @@ -14,7 +14,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-chakra-ui/package.json b/tools/static-assets/skel-chakra-ui/package.json index 78e265cca8..d1b00f4b37 100644 --- a/tools/static-assets/skel-chakra-ui/package.json +++ b/tools/static-assets/skel-chakra-ui/package.json @@ -21,7 +21,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-full/package.json b/tools/static-assets/skel-full/package.json index 18430645b1..52bc60066d 100644 --- a/tools/static-assets/skel-full/package.json +++ b/tools/static-assets/skel-full/package.json @@ -12,7 +12,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-react/package.json b/tools/static-assets/skel-react/package.json index 6a9ef2e03b..806556ff16 100644 --- a/tools/static-assets/skel-react/package.json +++ b/tools/static-assets/skel-react/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-solid/package.json b/tools/static-assets/skel-solid/package.json index 4541d9e598..0ecd1f51ef 100644 --- a/tools/static-assets/skel-solid/package.json +++ b/tools/static-assets/skel-solid/package.json @@ -14,7 +14,7 @@ "picocolors": "^1.1.1" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-svelte/package.json b/tools/static-assets/skel-svelte/package.json index bea3aa7ac4..2f38169f3e 100644 --- a/tools/static-assets/skel-svelte/package.json +++ b/tools/static-assets/skel-svelte/package.json @@ -13,7 +13,7 @@ "meteor-node-stubs": "^1.2.12" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-tailwind/package.json b/tools/static-assets/skel-tailwind/package.json index 83074c7738..2df7afc9c1 100644 --- a/tools/static-assets/skel-tailwind/package.json +++ b/tools/static-assets/skel-tailwind/package.json @@ -16,7 +16,7 @@ "react-dom": "^17.0.2" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-typescript/package.json b/tools/static-assets/skel-typescript/package.json index 7d55e4e5bf..2a2542758f 100644 --- a/tools/static-assets/skel-typescript/package.json +++ b/tools/static-assets/skel-typescript/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", diff --git a/tools/static-assets/skel-vue/package.json b/tools/static-assets/skel-vue/package.json index c8517749e3..dccb833cd5 100644 --- a/tools/static-assets/skel-vue/package.json +++ b/tools/static-assets/skel-vue/package.json @@ -17,7 +17,7 @@ "vue-router": "^4.2.5" }, "devDependencies": { - "@meteorjs/rspack": "^0.0.39", + "@meteorjs/rspack": "^0.0.40", "@rsdoctor/rspack-plugin": "^1.2.3", "@rspack/cli": "^1.5.0", "@rspack/core": "^1.5.0", From 8635fcf2eb708b6610e01062db24bdb2be05c57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 17:24:15 +0200 Subject: [PATCH 030/195] support modules config to allow load in Meteor specific files or folders for plugins to act --- packages/rspack/lib/config.js | 9 +- packages/tools-core/lib/ignore.js | 87 +++++++++++++++++++ packages/tools-core/tools-core_server.js | 1 + .../apps/react-router/package.json | 1 + .../apps/react-router/styles/module.css | 3 + tools/modern-tests/assertions.js | 1 + tools/modern-tests/react-router.test.js | 8 ++ .../rspack-bundler-integration.md | 14 +++ 8 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 packages/tools-core/lib/ignore.js create mode 100644 tools/modern-tests/apps/react-router/styles/module.css diff --git a/packages/rspack/lib/config.js b/packages/rspack/lib/config.js index 24fc73a6d9..6855a291cf 100644 --- a/packages/rspack/lib/config.js +++ b/packages/rspack/lib/config.js @@ -21,7 +21,9 @@ const { isMeteorLessProject, isMeteorScssProject, getMeteorEnvPackageDirs, + getMeteorAppConfig, } = require('meteor/tools-core/lib/meteor'); +const { buildUnignorePatterns } = require('meteor/tools-core/lib/ignore'); import { getInitialEntrypoints } from './build-context'; @@ -92,6 +94,7 @@ function getFileExtensionsToIgnore() { * @returns {void} */ export function configureMeteorForRspack() { + const meteorAppConfig = getMeteorAppConfig(); const initialEntrypoints = getInitialEntrypoints(); // Ignore node_modules to prevent Meteor from processing them @@ -180,9 +183,13 @@ export function configureMeteorForRspack() { ), ]; const filesToIgnore = [...rootFilesToIgnore, ...extraFilesToIgnore]; + const unignoredFilesAndFolders = buildUnignorePatterns( + meteorAppConfig?.modules || [], + { skipLevel: 1 }, + ); const meteorAppIgnores = `${foldersToIgnore.join(' ')} ${filesToIgnore.join( ' ', - )}`; + )} ${unignoredFilesAndFolders.join(' ')}`.trim(); setMeteorAppIgnore(meteorAppIgnores); if (isMeteorAppDebug() || isMeteorAppConfigModernVerbose()) { diff --git a/packages/tools-core/lib/ignore.js b/packages/tools-core/lib/ignore.js new file mode 100644 index 0000000000..611cb3344b --- /dev/null +++ b/packages/tools-core/lib/ignore.js @@ -0,0 +1,87 @@ +/** + * Build gitignore-style "unignore" patterns for specific files/folders. + * + * Rules: + * - Files: !a/ !a/b/ !a/b/c.txt + * - Folders (must end with '/'): + * !a/ !a/b/ !a/b/c/ !a/b/c/** + * + * @param {string[]} inputPaths Paths to keep. Use '/' for dirs (e.g. 'assets/public/'). + * @param {Object} [options] + * @param {boolean} [options.includeAllAncestors=true] If false, only include the immediate parent dir. + * @param {boolean} [options.includeGlobForDirs=true] Emit '**' for directories. + * @param {number} [options.skipLevel=0] Skip this many levels from the beginning. + * @returns {string[]} Negation patterns, in correct order. + */ +export function buildUnignorePatterns(inputPaths, { + includeAllAncestors = true, + includeGlobForDirs = true, + skipLevel = 0, +} = {}) { + const out = []; + const seen = new Set(); + + const push = (p) => { + if (!seen.has(p)) { + seen.add(p); + out.push(p); + } + }; + + for (let raw of inputPaths) { + if (!raw || typeof raw !== 'string') continue; + + // Normalize: forward slashes, drop leading './', collapse double slashes + let anchored = raw.startsWith('/'); + let p = raw.replace(/\\/g, '/') + .replace(/^\.\/+/, '') + .replace(/\/{2,}/g, '/'); + + // detect dir by trailing slash + const isDir = p.endsWith('/'); + // strip leading + trailing slashes for splitting, but remember anchoring + const core = p.replace(/^\/+/, '').replace(/\/+$/, ''); + if (!core) continue; + + const parts = core.split('/'); + + // Process based on skipLevel + if (skipLevel >= parts.length) { + // Skip everything if skipLevel is greater than or equal to the number of parts + continue; + } + + // Ancestors (top-down) + if (includeAllAncestors) { + // Start from skipLevel + 1 to skip the specified number of levels + const startLevel = Math.max(1, skipLevel + 1); + for (let i = startLevel; i <= parts.length - 1; i++) { + const anc = (anchored ? '/' : '') + parts.slice(0, i).join('/') + '/'; + push('!' + anc); + } + } else if (parts.length > 1) { + // Only immediate parent + // For minimal mode with skipLevel, we need to check if the parent is at a level we should skip + if (skipLevel < parts.length - 1) { + // Check if the parent's level is greater than skipLevel + const parentLevel = parts.length - 1; + if (parentLevel > skipLevel) { + const parent = (anchored ? '/' : '') + parts.slice(0, parts.length - 1).join('/') + '/'; + push('!' + parent); + } + } + } + + // Add the file/directory pattern + if (isDir) { + const dir = (anchored ? '/' : '') + parts.join('/') + '/'; + push('!' + dir); + if (includeGlobForDirs) push('!' + dir + '**'); + } else { + const file = (anchored ? '/' : '') + parts.join('/'); + push('!' + file); + } + } + + return out; +} diff --git a/packages/tools-core/tools-core_server.js b/packages/tools-core/tools-core_server.js index 313cc1674c..108d4e35c5 100644 --- a/packages/tools-core/tools-core_server.js +++ b/packages/tools-core/tools-core_server.js @@ -5,3 +5,4 @@ export * from './lib/process'; export * from './lib/global-state'; export * from './lib/git'; export * from './lib/string'; +export * from './lib/ignore'; diff --git a/tools/modern-tests/apps/react-router/package.json b/tools/modern-tests/apps/react-router/package.json index dbbf46f446..9e3c78d80b 100644 --- a/tools/modern-tests/apps/react-router/package.json +++ b/tools/modern-tests/apps/react-router/package.json @@ -33,6 +33,7 @@ "client": "client/main.jsx", "server": "server/main.js" }, + "modules": ["styles/module.css"], "modern": true } } diff --git a/tools/modern-tests/apps/react-router/styles/module.css b/tools/modern-tests/apps/react-router/styles/module.css new file mode 100644 index 0000000000..2b1f0dafba --- /dev/null +++ b/tools/modern-tests/apps/react-router/styles/module.css @@ -0,0 +1,3 @@ +body { + align-content: center; +} diff --git a/tools/modern-tests/assertions.js b/tools/modern-tests/assertions.js index cf31f15aaa..5fe8813962 100644 --- a/tools/modern-tests/assertions.js +++ b/tools/modern-tests/assertions.js @@ -167,6 +167,7 @@ export async function assertConsoleEval(code, expectedResult, options = {}) { try { // Evaluate the code in the browser context const result = await page.evaluate(code); + console.log("--> (assertions.js-Line: 170)\n result: ", result); if (exactMatch) { // Check for exact match diff --git a/tools/modern-tests/react-router.test.js b/tools/modern-tests/react-router.test.js index 0c811ae9a0..1941fa7b85 100644 --- a/tools/modern-tests/react-router.test.js +++ b/tools/modern-tests/react-router.test.js @@ -27,6 +27,10 @@ describe('ReactRouter App Bundling /', () => { await assertBodyStyles({ 'white-space': 'break-spaces', }); + // Meteor modules config + await assertBodyStyles({ + 'align-content': 'center', + }); // Custom html rspack plugin options await assertMetaTags({ 'theme-color': '#4285f4', @@ -50,6 +54,10 @@ describe('ReactRouter App Bundling /', () => { await assertBodyStyles({ 'white-space': 'break-spaces', }); + // Meteor modules config + await assertBodyStyles({ + 'align-content': 'center', + }); // Custom html rspack plugin options await assertMetaTags({ 'theme-color': '#4285f4', diff --git a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md index 056ce5cc93..434cd777eb 100644 --- a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md +++ b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md @@ -174,6 +174,20 @@ Ensure your app defines these entry files with the correct paths where each modu Defining entry points improves performance even with the Meteor bundler, as Meteor stops scanning and eagerly loading unnecessary files. For Meteor-Rspack integration, this is required, since it does not support automatic code discovery for efficiency. +In Meteor-Rspack integration, all app code is ignored by Meteor and handled by Rspack. By default, Meteor still processes eagerly CSS and HTML files in the entry folder (e.g. `client/`). + +If you need Meteor to handle CSS or HTML files outside the main entry folder, add them to the `modules` field. This field accepts an array of strings, each pointing to a file or folder, except those inside the reserved `imports` folder for scripts. + +``` json +{ + "meteor": { + "modules": ["styles/main.css"] + } +} +``` + +With this, Meteor will process these files, merge stylesheets, generate the final HTML, and support files a Meteor plugin may use, except for JS or script code now handled by Rspack. You can also process CSS and HTML files directly with Rspack using loaders from imports in your app code, as mentioned in ["CSS, Less and SCSS"](#css-less-and-scss) or ["HtmlRspackPlugin"](#htmlrspackplugin). If you prefer Meteor's loading approach, you can still rely on it. + ### Nested Imports Nested imports are a feature of Meteor’s bundler, not supported in standard bundlers. Meteor introduced them during a time when bundling standards were still evolving and experimented with its own approach. This feature comes from the [`reify` module](https://github.com/benjamn/reify/tree/main) and works with Babel transpilation. SWC doesn't support them since they were never standardized. From 790803763256c39481454098504ce82677cac3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 17:24:15 +0200 Subject: [PATCH 031/195] support modules config to allow load in Meteor specific files or folders for plugins to act --- packages/rspack/lib/config.js | 9 +- packages/tools-core/lib/ignore.js | 87 +++++++++++++++++++ packages/tools-core/tools-core_server.js | 1 + .../apps/react-router/package.json | 1 + .../apps/react-router/styles/module.css | 3 + tools/modern-tests/assertions.js | 1 + tools/modern-tests/react-router.test.js | 8 ++ .../rspack-bundler-integration.md | 14 +++ 8 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 packages/tools-core/lib/ignore.js create mode 100644 tools/modern-tests/apps/react-router/styles/module.css diff --git a/packages/rspack/lib/config.js b/packages/rspack/lib/config.js index 24fc73a6d9..6855a291cf 100644 --- a/packages/rspack/lib/config.js +++ b/packages/rspack/lib/config.js @@ -21,7 +21,9 @@ const { isMeteorLessProject, isMeteorScssProject, getMeteorEnvPackageDirs, + getMeteorAppConfig, } = require('meteor/tools-core/lib/meteor'); +const { buildUnignorePatterns } = require('meteor/tools-core/lib/ignore'); import { getInitialEntrypoints } from './build-context'; @@ -92,6 +94,7 @@ function getFileExtensionsToIgnore() { * @returns {void} */ export function configureMeteorForRspack() { + const meteorAppConfig = getMeteorAppConfig(); const initialEntrypoints = getInitialEntrypoints(); // Ignore node_modules to prevent Meteor from processing them @@ -180,9 +183,13 @@ export function configureMeteorForRspack() { ), ]; const filesToIgnore = [...rootFilesToIgnore, ...extraFilesToIgnore]; + const unignoredFilesAndFolders = buildUnignorePatterns( + meteorAppConfig?.modules || [], + { skipLevel: 1 }, + ); const meteorAppIgnores = `${foldersToIgnore.join(' ')} ${filesToIgnore.join( ' ', - )}`; + )} ${unignoredFilesAndFolders.join(' ')}`.trim(); setMeteorAppIgnore(meteorAppIgnores); if (isMeteorAppDebug() || isMeteorAppConfigModernVerbose()) { diff --git a/packages/tools-core/lib/ignore.js b/packages/tools-core/lib/ignore.js new file mode 100644 index 0000000000..611cb3344b --- /dev/null +++ b/packages/tools-core/lib/ignore.js @@ -0,0 +1,87 @@ +/** + * Build gitignore-style "unignore" patterns for specific files/folders. + * + * Rules: + * - Files: !a/ !a/b/ !a/b/c.txt + * - Folders (must end with '/'): + * !a/ !a/b/ !a/b/c/ !a/b/c/** + * + * @param {string[]} inputPaths Paths to keep. Use '/' for dirs (e.g. 'assets/public/'). + * @param {Object} [options] + * @param {boolean} [options.includeAllAncestors=true] If false, only include the immediate parent dir. + * @param {boolean} [options.includeGlobForDirs=true] Emit '**' for directories. + * @param {number} [options.skipLevel=0] Skip this many levels from the beginning. + * @returns {string[]} Negation patterns, in correct order. + */ +export function buildUnignorePatterns(inputPaths, { + includeAllAncestors = true, + includeGlobForDirs = true, + skipLevel = 0, +} = {}) { + const out = []; + const seen = new Set(); + + const push = (p) => { + if (!seen.has(p)) { + seen.add(p); + out.push(p); + } + }; + + for (let raw of inputPaths) { + if (!raw || typeof raw !== 'string') continue; + + // Normalize: forward slashes, drop leading './', collapse double slashes + let anchored = raw.startsWith('/'); + let p = raw.replace(/\\/g, '/') + .replace(/^\.\/+/, '') + .replace(/\/{2,}/g, '/'); + + // detect dir by trailing slash + const isDir = p.endsWith('/'); + // strip leading + trailing slashes for splitting, but remember anchoring + const core = p.replace(/^\/+/, '').replace(/\/+$/, ''); + if (!core) continue; + + const parts = core.split('/'); + + // Process based on skipLevel + if (skipLevel >= parts.length) { + // Skip everything if skipLevel is greater than or equal to the number of parts + continue; + } + + // Ancestors (top-down) + if (includeAllAncestors) { + // Start from skipLevel + 1 to skip the specified number of levels + const startLevel = Math.max(1, skipLevel + 1); + for (let i = startLevel; i <= parts.length - 1; i++) { + const anc = (anchored ? '/' : '') + parts.slice(0, i).join('/') + '/'; + push('!' + anc); + } + } else if (parts.length > 1) { + // Only immediate parent + // For minimal mode with skipLevel, we need to check if the parent is at a level we should skip + if (skipLevel < parts.length - 1) { + // Check if the parent's level is greater than skipLevel + const parentLevel = parts.length - 1; + if (parentLevel > skipLevel) { + const parent = (anchored ? '/' : '') + parts.slice(0, parts.length - 1).join('/') + '/'; + push('!' + parent); + } + } + } + + // Add the file/directory pattern + if (isDir) { + const dir = (anchored ? '/' : '') + parts.join('/') + '/'; + push('!' + dir); + if (includeGlobForDirs) push('!' + dir + '**'); + } else { + const file = (anchored ? '/' : '') + parts.join('/'); + push('!' + file); + } + } + + return out; +} diff --git a/packages/tools-core/tools-core_server.js b/packages/tools-core/tools-core_server.js index 313cc1674c..108d4e35c5 100644 --- a/packages/tools-core/tools-core_server.js +++ b/packages/tools-core/tools-core_server.js @@ -5,3 +5,4 @@ export * from './lib/process'; export * from './lib/global-state'; export * from './lib/git'; export * from './lib/string'; +export * from './lib/ignore'; diff --git a/tools/modern-tests/apps/react-router/package.json b/tools/modern-tests/apps/react-router/package.json index dbbf46f446..9e3c78d80b 100644 --- a/tools/modern-tests/apps/react-router/package.json +++ b/tools/modern-tests/apps/react-router/package.json @@ -33,6 +33,7 @@ "client": "client/main.jsx", "server": "server/main.js" }, + "modules": ["styles/module.css"], "modern": true } } diff --git a/tools/modern-tests/apps/react-router/styles/module.css b/tools/modern-tests/apps/react-router/styles/module.css new file mode 100644 index 0000000000..2b1f0dafba --- /dev/null +++ b/tools/modern-tests/apps/react-router/styles/module.css @@ -0,0 +1,3 @@ +body { + align-content: center; +} diff --git a/tools/modern-tests/assertions.js b/tools/modern-tests/assertions.js index cf31f15aaa..5fe8813962 100644 --- a/tools/modern-tests/assertions.js +++ b/tools/modern-tests/assertions.js @@ -167,6 +167,7 @@ export async function assertConsoleEval(code, expectedResult, options = {}) { try { // Evaluate the code in the browser context const result = await page.evaluate(code); + console.log("--> (assertions.js-Line: 170)\n result: ", result); if (exactMatch) { // Check for exact match diff --git a/tools/modern-tests/react-router.test.js b/tools/modern-tests/react-router.test.js index 0c811ae9a0..1941fa7b85 100644 --- a/tools/modern-tests/react-router.test.js +++ b/tools/modern-tests/react-router.test.js @@ -27,6 +27,10 @@ describe('ReactRouter App Bundling /', () => { await assertBodyStyles({ 'white-space': 'break-spaces', }); + // Meteor modules config + await assertBodyStyles({ + 'align-content': 'center', + }); // Custom html rspack plugin options await assertMetaTags({ 'theme-color': '#4285f4', @@ -50,6 +54,10 @@ describe('ReactRouter App Bundling /', () => { await assertBodyStyles({ 'white-space': 'break-spaces', }); + // Meteor modules config + await assertBodyStyles({ + 'align-content': 'center', + }); // Custom html rspack plugin options await assertMetaTags({ 'theme-color': '#4285f4', diff --git a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md index 056ce5cc93..434cd777eb 100644 --- a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md +++ b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md @@ -174,6 +174,20 @@ Ensure your app defines these entry files with the correct paths where each modu Defining entry points improves performance even with the Meteor bundler, as Meteor stops scanning and eagerly loading unnecessary files. For Meteor-Rspack integration, this is required, since it does not support automatic code discovery for efficiency. +In Meteor-Rspack integration, all app code is ignored by Meteor and handled by Rspack. By default, Meteor still processes eagerly CSS and HTML files in the entry folder (e.g. `client/`). + +If you need Meteor to handle CSS or HTML files outside the main entry folder, add them to the `modules` field. This field accepts an array of strings, each pointing to a file or folder, except those inside the reserved `imports` folder for scripts. + +``` json +{ + "meteor": { + "modules": ["styles/main.css"] + } +} +``` + +With this, Meteor will process these files, merge stylesheets, generate the final HTML, and support files a Meteor plugin may use, except for JS or script code now handled by Rspack. You can also process CSS and HTML files directly with Rspack using loaders from imports in your app code, as mentioned in ["CSS, Less and SCSS"](#css-less-and-scss) or ["HtmlRspackPlugin"](#htmlrspackplugin). If you prefer Meteor's loading approach, you can still rely on it. + ### Nested Imports Nested imports are a feature of Meteor’s bundler, not supported in standard bundlers. Meteor introduced them during a time when bundling standards were still evolving and experimented with its own approach. This feature comes from the [`reify` module](https://github.com/benjamn/reify/tree/main) and works with Babel transpilation. SWC doesn't support them since they were never standardized. From 2f423772c752a929837182b3a304d352f2c6c104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nacho=20Codo=C3=B1er?= Date: Thu, 4 Sep 2025 18:00:58 +0200 Subject: [PATCH 032/195] fix docs --- .../docs/about/modern-build-stack/rspack-bundler-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md index 434cd777eb..b7ea3cc355 100644 --- a/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md +++ b/v3-docs/docs/about/modern-build-stack/rspack-bundler-integration.md @@ -176,7 +176,7 @@ Defining entry points improves performance even with the Meteor bundler, as Mete In Meteor-Rspack integration, all app code is ignored by Meteor and handled by Rspack. By default, Meteor still processes eagerly CSS and HTML files in the entry folder (e.g. `client/`). -If you need Meteor to handle CSS or HTML files outside the main entry folder, add them to the `modules` field. This field accepts an array of strings, each pointing to a file or folder, except those inside the reserved `imports` folder for scripts. +If you need Meteor to handle CSS or HTML files outside the main entry folder, add them to the `modules` field. This field accepts an array of strings, each pointing to a file or folder. ``` json { From 7fbc13020559a72636d12fb0c0a61209835da547 Mon Sep 17 00:00:00 2001 From: harryadel Date: Wed, 9 Jul 2025 09:43:07 +0300 Subject: [PATCH 033/195] [test-in-browser] Select and re-run tests by clicking them --- packages/test-in-browser/driver.html | 5 +- packages/test-in-browser/driver.js | 122 ++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/packages/test-in-browser/driver.html b/packages/test-in-browser/driver.html index 52bd3b8aad..a8f976998b 100644 --- a/packages/test-in-browser/driver.html +++ b/packages/test-in-browser/driver.html @@ -44,12 +44,15 @@